1
0

Compare commits

..

856 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
5dcdfe83f9 Clean up 2024-09-05 13:17:52 -04:00
9349724bac Fix doc tests; Clean up 2024-09-05 13:16:26 -04:00
c42fca496b Track down tricky context bug 2024-09-05 13:10:38 -04:00
5c9a8dab31 Clean up 2024-09-05 11:36:55 -04:00
72a019cfe5 Clean up and refactor slightly 2024-09-05 11:32:31 -04:00
7b25b593ef Expand errors 2024-09-03 08:29:31 -04:00
bd76622543 Refactor context method 2024-09-03 06:08:34 -04:00
d6ab891d7f Refactor context so that parents are a Weak reference 2024-09-03 00:42:39 -04:00
0aebd81665 Fix context bug 2024-09-03 00:09:32 -04:00
0edb42836d Fix context scope bug 2024-09-02 12:57:27 -04:00
3c2a70803e Refactor error reports 2024-09-02 11:04:08 -04:00
4433c587f5 Clean up context 2024-09-02 05:53:09 -04:00
d32061ebba Clean up project imports 2024-09-02 04:27:11 -04:00
a5a35b6d8c Fix and improve docs 2024-09-02 04:21:29 -04:00
d0feac667f Remove old docs 2024-09-02 04:00:42 -04:00
2a0e4c9b78 Clean up context garbage collection 2024-09-02 03:58:53 -04:00
14aa7c242a Ignore context in Debug format for AST 2024-09-02 03:51:19 -04:00
e643ebe114 Fix parsing bug 2024-08-31 07:24:45 -04:00
f2e5b8d499 Clean up; Fix tests 2024-08-30 19:58:07 -04:00
012728b00a Try embedding contexts in the AST 2024-08-30 18:06:58 -04:00
d032334635 Clean up 2024-08-28 12:51:55 -04:00
5f186ade41 Add VM test 2024-08-28 12:18:27 -04:00
8043e24637 Use variable dereferencing to improve language 2024-08-28 12:15:14 -04:00
0c3e8d5edf Clean up 2024-08-28 12:06:25 -04:00
b02e5244f2 Add defereference expressions 2024-08-28 11:31:47 -04:00
5f733ba1dd Add Vm tests and refine how mutability and assignment works 2024-08-24 14:17:38 -04:00
561a290b16 Implement block scopes; Refactor async block execution 2024-08-24 10:13:16 -04:00
511cc10e98 Pass all tests 2024-08-23 17:27:11 -04:00
e45ac042b9 Pass some docs tests 2024-08-23 16:47:57 -04:00
ab53df56bc Pass all unit tests 2024-08-23 16:33:38 -04:00
8b14d74eba Replace "return_type" methods in AST with new type_evaluation 2024-08-23 13:47:22 -04:00
f510cce0ee Pass analyzer test 2024-08-23 12:16:56 -04:00
6a488c2245 Improve constructors 2024-08-23 11:44:47 -04:00
de3c83a6f5 Pass analyzer tests 2024-08-23 07:36:10 -04:00
e84bb2ea70 Improve errors 2024-08-23 05:54:58 -04:00
0d0a2d2237 Add character literal tokens; Change strings to double quotes only 2024-08-23 05:24:48 -04:00
23782c37a3 Complete value overhaul 2024-08-23 03:04:11 -04:00
87bced9719 Begin changing value type 2024-08-23 01:12:20 -04:00
956def5ec6 Clean up 2024-08-20 19:44:22 -04:00
e1e40cf931 Pass analyzer tests 2024-08-20 19:20:38 -04:00
e22d0254f5 Pass analyzer test 2024-08-20 18:48:25 -04:00
08a9b265ec Improve errors 2024-08-20 17:01:30 -04:00
80bf09d807 Clean up 2024-08-20 15:55:35 -04:00
2eff51815a Get hello world working again 2024-08-20 15:43:50 -04:00
a6334070ae Get fizzbuzz working again 2024-08-20 15:16:06 -04:00
f9480ddc24 Add loop and break 2024-08-20 14:45:43 -04:00
169c1a9e3f Pass tests 2024-08-20 12:24:41 -04:00
4846b3f74d Fix parsing bug that fixes garbage collection bug 2024-08-20 12:09:17 -04:00
2e7acbeb64 Add parent contexts and core library 2024-08-20 11:40:37 -04:00
83f856385b Refine errors and error propagation 2024-08-20 11:07:13 -04:00
cf9a9837c8 Add a parser test; Pass VM test 2024-08-20 07:20:44 -04:00
8b5eb9977c Clean up 2024-08-20 04:38:15 -04:00
c0ab5a84a2 Pass VM tests 2024-08-20 04:12:43 -04:00
d5c2ae92c9 Pass VM test 2024-08-20 03:28:13 -04:00
fab66a4877 Pass VM test 2024-08-20 02:25:22 -04:00
e3d821a1c3 Remimplement features 2024-08-20 00:15:19 -04:00
0fd19a623d Improve AST ergonomics 2024-08-17 12:15:47 -04:00
0b64afccb1 Extend VM, abstract tree and parser 2024-08-17 10:07:38 -04:00
fa67a568d9 Pass parser tests; Fix garbage collection 2024-08-17 05:32:18 -04:00
b024d81148 Continue reimplementing the language 2024-08-17 04:06:13 -04:00
207d155a25 Add basic analyzer implementation 2024-08-16 23:18:05 -04:00
447643f315 Expand Value and Expression 2024-08-16 22:43:29 -04:00
8ae453add7 Continue value overhaul 2024-08-16 17:07:49 -04:00
8bff39a7db Prepare for value overhaul 2024-08-16 11:21:20 -04:00
7d721beb31 Implement VM and Analyzer by using the new AST 2024-08-16 09:22:36 -04:00
fedefdb29f Implement "let" and "let mut" lexing/parsing 2024-08-16 07:09:46 -04:00
84429ef187 Implement operator expressions in the VM 2024-08-16 06:43:29 -04:00
e911853cb5 Clean up 2024-08-16 05:24:55 -04:00
bfb07047a5 Implement VM 2024-08-16 05:14:00 -04:00
26348fb82e Continue implementing the VM 2024-08-16 00:41:52 -04:00
b48b5d4369 Begin reimplementing the VM 2024-08-15 23:17:49 -04:00
40b5d15b96 Add parser mode to pass tests 2024-08-15 21:34:47 -04:00
44d6a88faa Pass tests 2024-08-15 21:22:24 -04:00
486530610b Continue passing tests and tweaking 2024-08-15 00:20:36 -04:00
81b7888920 Continue passing tests 2024-08-14 21:15:37 -04:00
f4ee3ffcf8 Continue passing tests 2024-08-14 19:43:12 -04:00
441df54a44 Continue passing tests 2024-08-14 18:30:36 -04:00
f4d29eca38 Begin passing tests again 2024-08-14 16:07:32 -04:00
fdf324c866 Continue AST overhaul 2024-08-14 15:52:04 -04:00
43b2393d8a Begin AST overhaul 2024-08-14 14:28:39 -04:00
e7b5390a55 Add more value mutability 2024-08-14 04:59:27 -04:00
8666f1cd9b Add parsing for mutable assignment 2024-08-14 03:53:15 -04:00
3ff3a7568c Continue adding mutable variables 2024-08-14 01:13:43 -04:00
535e120256 Add mutable values 2024-08-14 01:03:46 -04:00
17286896a8 Add async blocks 2024-08-13 23:45:17 -04:00
d32633272c Add docs and an unused function for fun 2024-08-13 22:57:37 -04:00
58780b5530 Write docs and clean up 2024-08-13 22:25:33 -04:00
64a3ce4cd3 Implement subtraction assignment 2024-08-13 21:24:56 -04:00
5c8e72a6f7 Implement fields struct instantiation 2024-08-13 19:41:36 -04:00
b55a79d6bf Add parsing for fields structs 2024-08-13 18:48:02 -04:00
a78d560a0d Add docs 2024-08-13 18:27:03 -04:00
5757f52dbd Refactor to move assingment out of other binary operations 2024-08-13 17:34:45 -04:00
b1337900fb Add analysis to prevent tuple structs with wrong types 2024-08-13 16:46:54 -04:00
83aa53b998 Add instantiation for tuple structs 2024-08-13 16:22:40 -04:00
049790726a Add parsing for tuple structs 2024-08-13 15:12:32 -04:00
1d75bd6594 Add instantiating unit structs 2024-08-13 14:21:31 -04:00
7e7448fe52 Add parsing for unit struct defintions 2024-08-13 13:54:16 -04:00
87455ed792 Continue adding struct lexing and test 2024-08-13 13:28:22 -04:00
2b8dda14e3 Add anaylsis to check for valid fields and indexes 2024-08-13 13:12:13 -04:00
40a71da3a5 Fix identifier caching; Add docs; Add minor fixes 2024-08-13 12:23:25 -04:00
f3bef42563 Write some failing analyzer tests 2024-08-12 19:39:26 -04:00
390511fa20 Rewrite list indexing to use [] syntax 2024-08-12 19:06:57 -04:00
5ad6012021 Refine parsing and lexing 2024-08-12 16:57:10 -04:00
0fb0b63a97 Fix dot notations precedence; Add some miscellaneous expansions 2024-08-12 15:02:04 -04:00
a61c1756f2 Begin fixing map property parsing 2024-08-12 11:24:24 -04:00
f62cb13089 Allow indexing lists with ranges 2024-08-12 10:43:18 -04:00
2a0737fd45 Add test; Clean up context 2024-08-12 10:29:06 -04:00
0ba54e9717 Add another token type; Add ranges 2024-08-12 10:08:34 -04:00
755fe5d899 Refactor to use parser's 'parse_block' method 2024-08-12 09:06:42 -04:00
c71c4d2d07 Refactor VM to own its context 2024-08-12 08:54:21 -04:00
bf11bd1f0f Fine-tune pratt parsing to support more complexity 2024-08-12 08:35:08 -04:00
c56a187d05 Add parser tests for new unary operations 2024-08-12 06:00:14 -04:00
2c374a1cd7 Begin adding unary operators 2024-08-12 05:44:05 -04:00
74cfef1832 Work out the finer details of the garbage collector 2024-08-12 04:10:07 -04:00
0e3a3e94c8 Add context tests 2024-08-11 22:47:52 -04:00
2463e44301 Test and implement basic garbage collection 2024-08-11 22:41:40 -04:00
78228ce8d6 Add new analyzer and vm tests 2024-08-11 22:02:17 -04:00
f2c0786bfb Simplify "run" function 2024-08-11 21:42:16 -04:00
de30f241a8 Refactor function call dot notation; Add better analysis of function calls and property access 2024-08-11 21:37:44 -04:00
c0254e8a94 Refactor to use type checking 2024-08-11 19:18:13 -04:00
77814c4576 Begin refactoring to use type checking 2024-08-11 19:00:37 -04:00
7259206c98 Add analyzing built-in function calls 2024-08-11 18:11:59 -04:00
3a2dd28efb Improve error layout 2024-08-11 18:01:35 -04:00
3b0c74010e Rename modules 2024-08-11 17:24:05 -04:00
f5836b66dc Clean up 2024-08-11 16:57:52 -04:00
9a9d9458ae Clean up block/map parsing code 2024-08-11 15:39:42 -04:00
9338d73621 Add to_string function; Get fizzbuzz example working 2024-08-11 15:12:19 -04:00
28c65b0715 Clean up control flow code in the VM; Remove returns from if and if/else_if statements 2024-08-11 15:03:26 -04:00
0c73f80947 Implment control flow lexing and parsing 2024-08-11 14:35:33 -04:00
24e21aa0b5 Add test; Attempt to clean up map and block parsing 2024-08-11 13:16:16 -04:00
37e3e1116d Add while loops 2024-08-10 05:23:43 -04:00
1687fd7fe3 Clean up; Add an analyzer test 2024-08-10 04:45:30 -04:00
a46e5dd365 Fix remaining doc tests 2024-08-10 04:32:27 -04:00
8f0d07b546 Fix parsing bug and some docs tests 2024-08-10 04:29:46 -04:00
f2823b6236 Tweak operator precedence 2024-08-10 00:14:38 -04:00
2ae75dcdd0 Make meticulous changes to pratt parser 2024-08-10 00:01:50 -04:00
9ea203f419 Use context and replace old variable map 2024-08-09 21:12:36 -04:00
e1b04328d5 Add context 2024-08-09 20:52:13 -04:00
f50b765c1e Clean up 2024-08-09 20:24:18 -04:00
82fbf796f3 Implement postfix parsing 2024-08-09 18:14:46 -04:00
60f8aab805 Refactor pratt parser 2024-08-09 14:01:01 -04:00
ed82f3c64f Lex, parse and run maps and blocks 2024-08-09 11:41:23 -04:00
f389f7e422 Implement equality operation 2024-08-09 07:15:09 -04:00
24a2642f17 Add modulo support 2024-08-09 07:02:55 -04:00
2cf580d111 Add division 2024-08-09 06:46:24 -04:00
a048577143 Improve assignment parsing 2024-08-09 06:32:44 -04:00
929468338d Add map parsing 2024-08-09 06:09:59 -04:00
55a8661618 Begin adding maps 2024-08-09 05:18:39 -04:00
d0dba35285 Add && and || operators 2024-08-09 04:56:24 -04:00
8c8fde94ce Refactor the abstract syntax tree 2024-08-09 04:23:02 -04:00
b9081f8653 Add comparison statement to replace four separate statements 2024-08-09 03:00:48 -04:00
580b85e2d0 Clean up tokens 2024-08-09 01:55:34 -04:00
83018ec5ec Improve error reports 2024-08-09 01:43:58 -04:00
c1b71ffccc Refactor errors and clean up read_line 2024-08-09 00:49:17 -04:00
9766777a47 Expand lexing of complex floats 2024-08-09 00:31:38 -04:00
60bd8f5352 Implement subtraction and multiplication 2024-08-08 23:28:47 -04:00
57782d3ed6 Clean up docs 2024-08-08 22:44:34 -04:00
cccbe7a325 Write docs 2024-08-08 21:59:09 -04:00
77134e5292 Begin adding fancy errors 2024-08-08 21:47:49 -04:00
4805a53269 Begin expanding errors 2024-08-08 20:58:56 -04:00
fa2ce8a0bf Refactor TokenOwned and add some docs 2024-08-08 20:22:27 -04:00
bf519ec087 Borrow string slices instead of copying them in lexing 2024-08-08 16:19:14 -04:00
1c24286696 Improve identifier cache 2024-08-08 14:58:12 -04:00
d5d51e9849 Clean up 2024-08-08 13:57:53 -04:00
a52e78150e Add lexing and parsing for I/O built-in functions; Refactor built-in function parsing 2024-08-08 13:49:40 -04:00
8dd62e623e Refactor add statement analysis to allow strings 2024-08-08 13:21:27 -04:00
a639641ed2 Parse strings and string concatentation 2024-08-08 13:11:32 -04:00
097b09b6e3 Lex strings and string concatenation 2024-08-08 13:08:53 -04:00
f5e822e916 Add and pass analyzer tests 2024-08-08 13:01:25 -04:00
e4ea402dfa Move built-in functions to a separate module 2024-08-07 19:44:01 -04:00
9840c3c193 Clean up and add docs 2024-08-07 19:12:40 -04:00
1fe26e0296 Remove generic position from nodes and replace with Spans 2024-08-07 19:03:50 -04:00
35eca1f7b4 Clean up 2024-08-07 18:46:40 -04:00
285e9e7217 Implement is_odd and length functions; Pass all tests 2024-08-07 18:43:24 -04:00
b17da5ad3c Get built-in functions working 2024-08-07 18:39:28 -04:00
4d7f59aee2 Begin implementing better built-in features 2024-08-07 18:24:25 -04:00
06f3a9b746 Remove built-in value statements 2024-08-07 15:53:43 -04:00
cda0203242 Replace spans with a generic type 2024-08-07 15:47:37 -04:00
76a67c5117 Add parse examples 2024-08-07 12:32:18 -04:00
e295aebf56 Add examples 2024-08-07 12:13:49 -04:00
f64babc546 Add more analysis to the analyzer 2024-08-07 11:57:15 -04:00
7328467e64 Add analysis step to run function 2024-08-07 11:38:08 -04:00
5d01f1caf9 Implement parsing and evaluation of boolean expressions 2024-08-07 10:50:19 -04:00
692f1145cd Implement lexing for boolean values 2024-08-07 10:41:27 -04:00
a60df0274c Add the expected_type function for statements 2024-08-07 10:03:33 -04:00
dfee50003a Clean up dependencies and add a few more tests 2024-08-05 18:45:43 -04:00
28e0ec27e8 Implement is_even and is_odd 2024-08-05 18:34:20 -04:00
6983d282d8 Begin adding support for more built-in properties 2024-08-05 15:54:48 -04:00
8c5ac0b89e Add parsing for list access 2024-08-05 14:58:58 -04:00
80a7700d68 Implement property access 2024-08-05 14:31:08 -04:00
b81c65629b Add docs 2024-08-05 00:54:12 -04:00
61f136edd2 Refactor and clean up 2024-08-05 00:40:51 -04:00
2268fc827d Add analyzer; Rename some things 2024-08-04 23:11:04 -04:00
f2bfe2ed06 Add basic VM 2024-08-04 22:15:31 -04:00
0ca443b133 Add test 2024-08-04 21:39:57 -04:00
161e99a2dd Add list parsing 2024-08-04 21:31:18 -04:00
bd491c014d Build spans into instructions 2024-08-04 20:08:43 -04:00
51c7ae148c Add float lexing 2024-08-04 19:41:00 -04:00
1607db20f9 Rename Token::Integer to Token::Number 2024-08-04 19:28:28 -04:00
5d7122aefa Add tests and clean up 2024-08-04 19:25:44 -04:00
cc188a233b Overhaul project structure 2024-08-03 20:23:52 -04:00
473f0ee075 Begin adding hand-written parser for Dust 2024-08-03 18:40:27 -04:00
2f06b18c3c Move type module out of abstract tree module 2024-08-02 16:33:40 -04:00
3fae807d9f Clean up with clippy 2024-08-02 15:21:15 -04:00
2047e0bf82 Add some docs and clean up example 2024-08-02 15:10:29 -04:00
0e479197a7 Clean up 2024-08-02 11:01:02 -04:00
175d82d382 Fix and clean up interpreter changes 2024-07-28 13:11:42 -04:00
77e84f9fa8 Separate interpreter into its own module 2024-07-28 13:06:59 -04:00
02dd33ab1a Add error; Clean up struct values 2024-07-28 12:52:07 -04:00
9640feb65b Add pretty errors example; Add validation in math 2024-07-15 17:21:18 -04:00
d3f5585d07 Add interpreter context management 2024-07-15 16:58:54 -04:00
501801b63e Rearrange repo; Add rust example 2024-07-15 16:42:49 -04:00
a3917238d9 Add docs 2024-07-15 15:49:34 -04:00
a02cee0b9f Add two new tests and refactor to pass them 2024-07-13 10:47:24 -04:00
3addb767fa Normalize top-level API 2024-07-12 20:54:23 -04:00
62ece61ce6 Fix test 2024-07-12 19:53:10 -04:00
2dbbc7b128 Make errors nice 2024-07-12 16:16:28 -04:00
c47d09fd1d Add enum type validation 2024-07-12 16:04:47 -04:00
790438d1e3 Add type arguments to enum instances 2024-07-12 11:08:53 -04:00
ad409b69f3 Run clippy and clean up everything 2024-07-12 10:20:52 -04:00
4ab838509b Break one test with new validation 2024-07-11 18:15:03 -04:00
994fa7310b Fix all warnings 2024-07-11 17:57:35 -04:00
c85958064a Fix test; Pass all tests 2024-07-11 17:54:59 -04:00
f3fe03a95f Fix function context bug 2024-07-11 17:22:30 -04:00
1794f7559c Clean up 2024-07-11 08:10:12 -04:00
48f3ccdd58 Fix inheritance; Add a test 2024-07-09 23:01:54 -04:00
dd72faf7c8 Continue experimenting with context and scopes 2024-07-06 02:41:43 -04:00
e84e022eed Experiment with context scopes 2024-07-04 14:40:26 -04:00
0e52ed7a49 Improve context recursion 2024-07-02 21:06:23 -04:00
dce6dfbc40 Improve context recursion 2024-07-02 20:25:47 -04:00
ecd83a17e7 Fix function scope bug 2024-07-02 13:45:32 -04:00
9bd39338d5 Combine define and verify functions on AST nodes 2024-07-02 13:11:31 -04:00
db94fbdb5b Fix test 2024-07-01 18:52:54 -04:00
7ec640a3a1 Improve error 2024-07-01 18:02:12 -04:00
92f098b58b Implement null statement 2024-07-01 17:49:49 -04:00
7e152f9f51 Continue standard library implementation 2024-07-01 16:59:39 -04:00
699576c4c7 Add use statements with pre-compile std library 2024-07-01 14:23:01 -04:00
a79cb0b3e1 Begin adding use statement 2024-07-01 10:40:36 -04:00
1e7636903e Add use token 2024-06-28 15:59:20 -04:00
adfd3aa5d4 Add list type check; Clean up 2024-06-28 15:35:18 -04:00
fe0bb0a0b5 Clean up 2024-06-26 18:09:38 -04:00
29bbcb019d Add and pass validation test 2024-06-26 16:24:41 -04:00
9a2e4f3649 Add more Display implementations 2024-06-26 14:44:23 -04:00
6130f73ca8 Continue writing Display implementations 2024-06-26 14:21:55 -04:00
822f12b44d Add Display implementations for abstract tree 2024-06-26 12:50:46 -04:00
49fe4555c6 Clean up 2024-06-26 11:35:39 -04:00
a177f19f28 Pass all tests 2024-06-24 16:48:39 -04:00
2da0a6a28b Clean up; Add example 2024-06-24 15:07:11 -04:00
7dc7f7a351 Fix type generic bug 2024-06-24 14:44:10 -04:00
97268c272e Use rust-style type parameter syntax 2024-06-24 13:54:37 -04:00
37d54499da Use rust-style turbofish 2024-06-24 13:48:31 -04:00
2cbeb4b551 Clean up 2024-06-24 11:14:43 -04:00
fbaf640fce Add global identifier cache 2024-06-24 10:46:37 -04:00
38ffd9b01b Add validations 2024-06-24 10:26:38 -04:00
a5c5075e6b Pass all tests 2024-06-24 10:09:07 -04:00
63f648c3ac Pass all tests 2024-06-24 09:09:38 -04:00
2e9a523058 Continue fixing built-in functions 2024-06-24 08:17:06 -04:00
18859cda77 Begin fixing built-in functions 2024-06-24 07:13:54 -04:00
f106d64367 Simplify built-in functions 2024-06-24 05:26:49 -04:00
c75dedb117 Add fields to map type 2024-06-24 04:16:05 -04:00
5e8945cab5 Begin adding fields to map type 2024-06-24 04:02:44 -04:00
37d59f562d Fix io.read_line function 2024-06-24 02:58:19 -04:00
49dfdc4e10 Pass all tests 2024-06-24 02:26:19 -04:00
b6b7a61727 Fix if/else bug 2024-06-24 02:18:58 -04:00
fbcb28ce24 Fix built-in function bug 2024-06-24 02:01:08 -04:00
ff5c4972eb Fix built-in function bug 2024-06-24 01:41:16 -04:00
40172e3ffb Fix assignment bug 2024-06-24 00:47:11 -04:00
64ce3d56e4 Fix function call bug 2024-06-24 00:38:06 -04:00
af20dab0d2 Refactor function types 2024-06-23 22:39:33 -04:00
572d5a9d18 Finish built-in function refactoring 2024-06-22 19:44:33 -04:00
34cea3518d Begin refactoring built-ins 2024-06-22 17:17:35 -04:00
6bdefd0698 Continue major refactoring 2024-06-22 13:55:43 -04:00
d06a614cfa Load standard library correctly 2024-06-22 11:50:44 -04:00
a05d9016f2 Begin passing tests 2024-06-22 11:44:09 -04:00
890baa5d51 Refactoring and troubleshooting 2024-06-22 07:46:10 -04:00
041480a953 Complete refactoring 2024-06-22 07:09:31 -04:00
1cd101db3f Refactor context 2024-06-22 06:36:59 -04:00
240c045a0c Begin passing tests 2024-06-22 01:19:30 -04:00
4b89ea0e96 Continue refactoring 2024-06-22 00:58:30 -04:00
88906fb6d7 Continue refactoring 2024-06-21 23:37:25 -04:00
fb413e24b0 Begin AbstractTree refactoring 2024-06-21 20:59:38 -04:00
d98f724355 Refactor AsyncBlock::run 2024-06-21 18:51:42 -04:00
b3e04f987f Rename Type::None to Type::Void 2024-06-21 18:30:16 -04:00
a28ac297c1 Refactor abstract tree traits 2024-06-21 18:28:12 -04:00
578cb6ad16 Add new function for EnumDeclaration 2024-06-21 13:56:04 -04:00
880fb7cd1b Consolidate token symbols 2024-06-20 19:25:46 -04:00
a94251e707 Rework enums and type constructors 2024-06-20 18:41:07 -04:00
1593080b8d Reorganize parser 2024-06-20 16:36:16 -04:00
e429693364 Begin converting type assingment to declarations 2024-06-20 16:28:33 -04:00
0b8880ae55 Fix lots of parsing and type inferencing 2024-06-19 20:14:51 -04:00
e7e5d1c08d Pass all tests 2024-06-19 12:12:28 -04:00
fecc62811d Improve type inference 2024-06-19 12:03:25 -04:00
0de25215b8 Implement basic enum instances 2024-06-19 10:48:22 -04:00
c2d8bd299f Add enum parsing; Add assets for examples 2024-06-19 09:48:01 -04:00
51dd918789 Clean up 2024-06-19 05:08:10 -04:00
ed4820a137 Implement serde traits for Value 2024-06-19 04:56:56 -04:00
859d8db384 Implement serde for Value; Rework comment parsing 2024-06-19 03:32:51 -04:00
d37c618ead Continue implementing type inference 2024-06-19 02:32:17 -04:00
b3dd610949 Continue implementing type inference 2024-06-19 00:22:37 -04:00
69da32d414 Fix type inference bug 2024-06-19 00:09:47 -04:00
aa79bea9a7 Implement type inferencing 2024-06-19 00:05:58 -04:00
ccdcc7c791 Fix tests and comment some out 2024-06-18 22:03:41 -04:00
7dc62bfd5f Continue implementing type construtors 2024-06-18 22:01:18 -04:00
799467b25b Reduce ambiguity for function parsing 2024-06-18 21:44:22 -04:00
7c809fa764 Clean up and prettify parsing errors 2024-06-18 19:42:04 -04:00
c0791ebb83 Fix type error 2024-06-18 19:00:15 -04:00
d5df74363a Clean up 2024-06-18 18:54:17 -04:00
b0d80ab867 Clean up; Add error for assignment without a value 2024-06-17 18:00:42 -04:00
dd062e63f1 Change context storage from BTreeMap to HashMap 2024-06-17 17:39:44 -04:00
cddf199156 Fix tests; Implement type generics 2024-06-17 17:38:24 -04:00
d53ddd07eb Fix tests and refine new parsing 2024-06-17 15:47:07 -04:00
dbabf874b7 Fix type check tests 2024-06-17 11:02:13 -04:00
9e0c0b4db3 Refine type constructor 2024-06-17 10:50:06 -04:00
e448c9dd4c Add type constructor 2024-06-17 10:10:06 -04:00
a0b754cc1c Begin preparing for type expressions 2024-06-16 03:12:04 -04:00
54071eb8c0 Add type aliases 2024-06-15 21:13:11 -04:00
f5bcf9511a Continue implementing JSON parsing 2024-06-05 12:10:57 -04:00
f625568ced Attempt to add JSON parsing 2024-06-04 14:47:15 -04:00
c8dfbda447 Add fs.read_file to standard library 2024-05-25 11:48:43 -04:00
d4c0633fab Continue implementing comments 2024-05-23 18:06:04 -04:00
781e3d4263 Switch to pratt parser for "as" expressions 2024-05-23 17:45:08 -04:00
a0999e30f1 Continue implementing as expression 2024-05-21 19:48:27 -04:00
7b78250eca Continue implementing as expression 2024-05-21 19:27:33 -04:00
8ea6b4be81 Begin implementing as expression 2024-05-21 17:07:12 -04:00
7be9300f14 Update CLI 2024-05-21 16:52:29 -04:00
f7bc43d7e3 Replace context inheritance with parental refs 2024-05-21 16:32:23 -04:00
aadb51e1f1 Fix lexing and parsing errors 2024-05-20 17:15:05 -04:00
9c77ae2410 Fix parsing errors 2024-05-20 15:22:50 -04:00
8fb8a456cd Improve built-in function call parsing 2024-05-18 17:55:58 -04:00
12210fd3ec Clean up 2024-05-18 16:24:17 -04:00
9eb047a913 Revert to pratt parsing for all indexes 2024-05-18 16:21:46 -04:00
42f48e8d76 Improve syntax error reports 2024-05-18 14:27:42 -04:00
47c1617602 Clean up 2024-05-18 14:15:22 -04:00
109c3f033c Clean up example 2024-05-18 11:59:39 -04:00
70f55c85f4 Rewrite io.write_line built-in; Fix memory bug 2024-04-27 05:45:39 -04:00
2b546e7b63 Add example; Clean up pratt parser 2024-04-27 03:40:05 -04:00
d9889ef2d8 Add comments to lexer 2024-04-27 02:22:26 -04:00
71807c0777 Clean up 2024-04-22 17:02:53 -04:00
d400b8bb6a Refine memory management 2024-04-22 08:25:20 -04:00
c659b56105 Refine memory management 2024-04-22 07:56:03 -04:00
bcd8e7c669 Refine memory management 2024-04-22 05:50:26 -04:00
8478d59000 Refine memory management 2024-04-22 03:41:21 -04:00
70face6765 Revise built-in functions; Add memory management 2024-04-22 01:51:34 -04:00
3d64883e2a Continue revising built-in functions 2024-04-21 21:33:21 -04:00
4726288b9a Continue built-in function revision 2024-04-21 18:22:59 -04:00
dbbb912b82 Continue revising built-in functions 2024-04-21 18:06:26 -04:00
fb78798a1d Begin revising built-in functions 2024-04-21 17:00:08 -04:00
fe1e27fd70 Pass test 2024-04-02 23:13:03 -04:00
40783422d8 Add type checking for type arguments 2024-04-02 22:59:49 -04:00
200a5d9127 Add type argument test 2024-03-29 15:52:02 -04:00
e1002b21d9 Clean up 2024-03-29 15:08:45 -04:00
d7a5586bc9 Clean up 2024-03-27 14:11:18 -04:00
70303a77e6 Clean up 2024-03-27 13:53:55 -04:00
e728aa8fbb Make fizzbuzz example work 2024-03-25 01:56:06 -04:00
f1f4d48d3a Remove redundant source position info from AST 2024-03-25 01:36:33 -04:00
e50b231958 Overhaul AST 2024-03-25 00:16:55 -04:00
4b460c0e68 Clean up 2024-03-24 17:34:36 -04:00
2871fd125a Pass all tests 2024-03-24 15:47:23 -04:00
966983920e Toy with chumsky and ariadne 2024-03-24 15:35:19 -04:00
6b0bb0016f Begin implementing type arguments 2024-03-24 12:21:08 -04:00
a0a9bc2fdf Add type arguments to function calls 2024-03-24 10:58:09 -04:00
2b797c19f7 Pass all tests 2024-03-24 09:10:49 -04:00
7dfc026be5 Clean up 2024-03-23 20:36:23 -04:00
b392a4c7aa Improve errors and built-ins 2024-03-23 19:12:18 -04:00
13c95dd12f Improve errors 2024-03-23 17:51:40 -04:00
7263507e84 Refine error reports 2024-03-23 17:07:41 -04:00
004b7be27a Implement better standard library interface 2024-03-23 11:24:25 -04:00
eaff59c88d Continue standard library 2024-03-23 09:35:24 -04:00
a8f840a305 Continue with standard library 2024-03-23 08:47:57 -04:00
9bb4e1b944 Begin standard library 2024-03-23 08:15:48 -04:00
cb56fd05cd Clean up 2024-03-22 17:50:47 -04:00
e858e7e20f Clean up 2024-03-22 17:22:39 -04:00
50b7b4bfc6 Clean up 2024-03-20 23:13:21 -04:00
d7d8fd2499 Run type definitions before other statements 2024-03-20 22:58:13 -04:00
fd0204fefa Add thread.sleep to built-ins 2024-03-20 17:38:40 -04:00
690e248df6 Implement async blocks 2024-03-20 17:18:47 -04:00
e29e092875 Begin implementing async blocks 2024-03-20 17:05:37 -04:00
96afe7d3a3 Improve error reports; Add example 2024-03-20 16:15:45 -04:00
bb7cda1242 Fix lexer and all broken tests 2024-03-20 11:43:47 -04:00
5de92ced6e Clean up 2024-03-20 08:53:51 -04:00
936b1f5de9 Improve named type parsing; Clean up 2024-03-20 08:36:18 -04:00
177888c962 Move tests 2024-03-20 06:56:20 -04:00
169260b8c1 Begin README; Add type check for functions 2024-03-20 05:31:14 -04:00
9b74023ade Begin project restructure 2024-03-20 04:42:13 -04:00
413add3ba8 Add structure errors 2024-03-20 01:29:07 -04:00
896a0855e0 Rename AbstractTree; Add new AbstractTree type 2024-03-20 00:28:28 -04:00
d46a592f87 Implement simple struct error 2024-03-19 19:46:41 -04:00
65ee161a96 Add named types 2024-03-19 19:37:18 -04:00
d076a329d2 Add test; Fix struct type resolution 2024-03-19 19:33:02 -04:00
21fea2b43f Implement structure values 2024-03-19 19:16:33 -04:00
bcc89f2c7d Implement structure parsing 2024-03-19 18:31:52 -04:00
16d443d8a6 Implement struct definition and type 2024-03-19 17:49:24 -04:00
c7b189a83f Begin implementing structures 2024-03-19 17:18:36 -04:00
6d50ac5b37 Remove boxing from parse function 2024-03-19 17:12:32 -04:00
953454a140 Roll back changes from the rewrite branch 2024-03-19 16:46:03 -04:00
b7ae0f1b52 Expand modules and function built-ins 2024-03-18 08:15:30 -04:00
18b8fd6681 Refine error output 2024-03-18 05:47:21 -04:00
7dcfccf7cb Improve errors 2024-03-18 05:39:09 -04:00
1750132ed8 Begin improving errors 2024-03-18 03:24:41 -04:00
3a97ba76a0 Get read_line built-in working 2024-03-17 21:42:53 -04:00
dd5136827c Pass all tests 2024-03-17 21:10:51 -04:00
27c25a587b Begin implementing indexes 2024-03-17 21:07:03 -04:00
791610b350 Improve built-in values and errors 2024-03-17 18:03:43 -04:00
bc5cadc446 Fix errors 2024-03-17 17:39:39 -04:00
c3402394a2 Rewrite while loops 2024-03-17 16:59:52 -04:00
fed119f38b Pass all tests 2024-03-17 13:36:31 -04:00
199e1c9184 Fix tests and parser 2024-03-17 10:19:22 -04:00
062a3b606c Fix tests and parser 2024-03-17 09:54:15 -04:00
765decdd41 Fix tests 2024-03-17 08:30:46 -04:00
1b367d4dfb Fix tests; Add SourcePosition type 2024-03-17 07:48:06 -04:00
4ea19f238e Fix tests; Refine parsing 2024-03-17 07:31:45 -04:00
f9b4b1bc01 Fix tests 2024-03-17 06:52:11 -04:00
0e2d1021cb Fix parser 2024-03-17 06:26:12 -04:00
46419956bd Improve positioning 2024-03-17 02:51:33 -04:00
3224c04f72 Fix everything except the parser 2024-03-17 01:26:05 -04:00
e9bfd9f1f8 Begin using Positioned type 2024-03-17 00:49:01 -04:00
15b1808741 Add spans to all statements 2024-03-16 15:01:45 -04:00
5b79af6e85 Rename and clean up 2024-03-14 11:49:10 -04:00
fdf286cb51 Add memory management 2024-03-11 21:57:27 -04:00
4be32d0a5d Add while loops 2024-03-11 18:22:11 -04:00
346d9ba878 Add parsing for while loops 2024-03-11 17:58:26 -04:00
565fd450a9 Remove expressions from break statements 2024-03-11 17:44:52 -04:00
780ea0858b Pass all tests 2024-03-11 15:17:01 -04:00
bf72e779fe Rename functions 2024-03-11 14:51:02 -04:00
764ea0550d Pass function tests 2024-03-11 14:49:44 -04:00
cabbf8821f Fix if/else type checking and recursion test 2024-03-10 14:48:53 -04:00
f544bd008e Fix lexing error 2024-03-09 20:57:46 -05:00
a3591d19af Clean up type parsing 2024-03-09 15:17:19 -05:00
2dd1628bca Fix function validation and parsing 2024-03-09 12:58:29 -05:00
e272d99bae Implement function calls 2024-03-09 08:10:54 -05:00
eba12b13a3 Begin adding function calls 2024-03-09 07:34:34 -05:00
b9190514c4 Add output function 2024-03-09 06:55:19 -05:00
5f958c72b8 Begin implementing built-in functions 2024-03-08 22:34:17 -05:00
3064a92e73 Pass tests 2024-03-08 21:26:49 -05:00
05c9e70d49 Pass tests 2024-03-08 21:05:56 -05:00
62185ff087 Begin implementing ranges 2024-03-08 20:30:26 -05:00
a92074a77b Add tests 2024-03-08 19:05:17 -05:00
0c1a2f4499 Remove implicit cloning for string values 2024-03-08 16:22:24 -05:00
56fbbdee0b Begin implementing functions 2024-03-08 16:14:47 -05:00
b7288ceed8 Implement if/else, loops and breaks 2024-03-08 14:29:53 -05:00
5571418d44 Begin implementing if/else 2024-03-08 14:01:05 -05:00
ec9f17070c Implement add-assign and subtract-assign 2024-03-08 13:26:55 -05:00
f70c8f2b40 Begin adding add-assign and subtract-assign 2024-03-08 12:39:35 -05:00
d99ebc0a44 Add run Action type 2024-03-08 12:24:11 -05:00
dac7656572 Improve value display 2024-03-07 22:20:59 -05:00
a6a02f26e4 Add map values 2024-03-07 16:19:24 -05:00
32028acab2 Add map parsing 2024-03-07 16:13:15 -05:00
7ee7a083ae Add more math for integers 2024-03-07 12:33:30 -05:00
d99e3cb861 Add indexes 2024-03-07 12:29:07 -05:00
85d954181b Add Control token type 2024-03-07 06:57:33 -05:00
c51b142130 Add math 2024-03-07 06:33:54 -05:00
4d76023775 Add Operator token type 2024-03-07 05:37:26 -05:00
65d2fd3270 Add and pass test 2024-03-06 23:21:07 -05:00
fdf6983ab2 Make one report for each error 2024-03-06 22:15:35 -05:00
37a88df613 Pass test 2024-03-06 19:45:41 -05:00
bff5ba81a3 Add and pass test 2024-03-06 18:15:25 -05:00
4db3ae7cb8 Add more errors 2024-03-06 17:32:31 -05:00
799b5d838c Improve and expand errors 2024-03-06 16:50:44 -05:00
da5122358e Add fancy errors 2024-03-06 16:24:48 -05:00
13394e6a8f Improve API and errors 2024-03-06 15:36:58 -05:00
28efa78db1 Implement basic type checking 2024-03-06 12:15:03 -05:00
76be50eab3 Parse loops 2024-03-01 20:17:55 -05:00
5cb86b80df Add and pass block tests 2024-03-01 19:29:16 -05:00
459acb2d63 Pass assignment test 2024-03-01 19:15:03 -05:00
0ed30c7220 Parse list types 2024-02-28 21:34:14 -05:00
e5aeaa67d8 Expand lexer and parser with more tests 2024-02-28 21:04:38 -05:00
9d5b7b6606 Add logic tests 2024-02-28 18:36:47 -05:00
95d9a720a3 Parse blocks 2024-02-28 18:16:25 -05:00
65ee472a4a Refine abstract tree; Improve parsing and lexing 2024-02-28 17:49:46 -05:00
4137a1a693 Improve logic parsing test 2024-02-28 17:02:30 -05:00
4179f6ebe5 Rework parser and abstract tree 2024-02-26 16:27:01 -05:00
fb7675a782 Add plumbing and test 2024-02-25 14:26:22 -05:00
8ff4b4ba82 Lex, parse and run with passing tests 2024-02-25 13:49:26 -05:00
f70656c837 Add plumbing 2024-02-25 03:12:09 -05:00
989afec531 Use statement-only abstract tree 2024-02-25 00:38:41 -05:00
0eb3df9108 Parse expressions 2024-02-24 19:37:04 -05:00
bec6eb5aeb Continue parser experiment 2024-02-23 12:14:15 -05:00
bdbd1fc412 Continue parser experiment 2024-02-23 08:23:35 -05:00
6dbae12315 Experiment with new parser 2024-02-23 07:40:01 -05:00
6b88fbf8b9 Add io:stdin to built-ins 2024-02-19 23:35:09 -05:00
cc76ca89cc Modify return/break syntax; Change Value::String 2024-02-19 22:32:06 -05:00
25e3941315 Clean up examples 2024-02-19 21:19:27 -05:00
900de8ca4b Edit README; Improve bench script; Optimize 2024-02-19 20:44:26 -05:00
c4b51a1ef9 Change README outline; Fix bench script 2024-02-19 19:23:20 -05:00
939b7464c6 Use GitHub theme for example pics 2024-02-19 18:15:03 -05:00
64fb20c45e Use GitHub theme for example pic 2024-02-19 18:05:55 -05:00
e3b55092b3 Increment cargo version 2024-02-19 17:59:16 -05:00
bd4983b821 Write README; Use GitHub theme for example pics 2024-02-19 17:57:25 -05:00
a1500bf262 Write README 2024-02-19 17:22:14 -05:00
1585145ff4 Write docs; Update logging and error messages 2024-02-19 17:04:13 -05:00
fb3cd6e6da Add image to README 2024-02-19 15:35:27 -05:00
69347ad435 Update grammar and highlight queries 2024-02-19 15:26:49 -05:00
ca72fe04f1 Start new example; Start new syntax features 2024-02-19 15:04:33 -05:00
eaf26fec5e Begin reqriting README 2024-02-19 11:24:54 -05:00
0eac67eb3a Pass enum tests 2024-02-19 11:13:04 -05:00
37fd722fa6 Fix garbage collection bug 2024-02-18 16:43:47 -05:00
255843cb3b Fix type checking bugs 2024-02-18 15:52:47 -05:00
927a2cfbf9 Fix tests 2024-02-18 15:44:57 -05:00
88d05f0dc9 Clean up 2024-02-18 15:19:30 -05:00
0805b96809 Add type argument syntax 2024-02-18 15:07:53 -05:00
a5f3127bcf Fix command tests and parsing 2024-02-18 11:38:35 -05:00
01bdaa308d Fix test 2024-02-18 10:59:49 -05:00
dbf9ab0d00 Fix function test 2024-02-18 10:55:54 -05:00
2bbbfa34a4 Roll back changes to match syntax; Fix match tests 2024-02-18 10:53:34 -05:00
979335f497 Modify struct, enum and match syntax 2024-02-18 10:34:59 -05:00
ef12d97895 Fix enum tests 2024-02-18 10:19:38 -05:00
14eedc6a2a Pass enum match test 2024-02-18 08:27:59 -05:00
5559860699 Fix doc tests 2024-02-18 07:14:32 -05:00
3a63d4973d Implement specific map types 2024-02-18 06:48:42 -05:00
52027db6c3 Pass index tests; Begin implementing specific maps 2024-02-18 06:28:31 -05:00
4afc8face8 Partially fix indexes break/return statements 2024-02-18 05:37:15 -05:00
5450f00174 Fix validation bug 2024-02-18 04:57:05 -05:00
a52eadc5ad Pass function tests; Fix recursion 2024-02-18 04:48:45 -05:00
d4a5424ad5 Improve logging 2024-02-18 04:18:19 -05:00
f835f2817f Pass if_else test 2024-02-18 03:49:38 -05:00
86ce1dc3af Pass value tests 2024-02-18 01:50:15 -05:00
ca04103372 Clean up 2024-02-18 00:40:48 -05:00
dab3d2de8e Add test; Make garbage collection work 2024-02-18 00:32:03 -05:00
6c699ec900 Improve context API 2024-02-17 23:43:00 -05:00
4f5ad1e4aa Implement automatic value dropping 2024-02-17 22:02:15 -05:00
d05b5a8628 Run cargo fix 2024-02-16 20:30:58 -05:00
a46d5bb4ea Add fancy validation errors 2024-02-16 20:18:07 -05:00
1094a5662c Simplify errors and make them fancier 2024-02-16 19:57:24 -05:00
fd33f330f7 Clean up errors; Add more pretty errors 2024-02-16 18:54:00 -05:00
bda217135e Simplify errors; Make another pretty error type 2024-02-16 17:56:36 -05:00
ee4f37080e Write function for lyneate integration 2024-02-16 17:28:57 -05:00
7003c37aac Remove redundant check for syntax errors 2024-02-16 17:11:28 -05:00
a53f83f03a Begin making pretty errors with lyneate 2024-02-16 16:49:01 -05:00
4b0910a545 Implement new math interface for Value 2024-02-16 15:37:07 -05:00
c82f631524 Begin new math implementation for Value; Clean up 2024-02-16 15:07:24 -05:00
d27c98e393 Add method to inherit all context data from another 2024-02-16 13:40:55 -05:00
97640c1b9b Fix test 2024-02-16 13:31:35 -05:00
d2e0de0483 Fix function contexts and recursion 2024-02-16 13:23:58 -05:00
7eecb7b070 Fix type checking with None type 2024-02-16 11:36:25 -05:00
1819c7e646 Fix type lookup for built-in values 2024-02-16 11:32:02 -05:00
ee692b360e Implement return for root 2024-02-16 11:23:07 -05:00
8c4b2c9eef Implement block returns 2024-02-16 11:21:36 -05:00
122d81f252 Clean up docs 2024-02-16 11:04:43 -05:00
d8705c5d50 Fix docs 2024-02-16 11:00:27 -05:00
c466096c8d Fix doc tests; Add from impls for Identifier 2024-02-16 10:58:37 -05:00
9f2b0461df Add statment_kind syntax node 2024-02-16 10:55:15 -05:00
1ce2178af5 Move return syntax node to option for statements 2024-02-16 10:38:51 -05:00
172a6fa860 Remove return statement; Add StatementInner 2024-02-16 10:36:16 -05:00
51869f04b6 Add test for root node 2024-02-16 10:23:33 -05:00
50a7a7aca1 Add built-in identifiers 2024-02-15 17:04:34 -05:00
edded5043d Fix infintite loop 2024-02-15 16:30:47 -05:00
5e105177cf Simplify TypeDefinition type 2024-02-15 16:06:47 -05:00
ec074177d5 Clean up new API 2024-02-15 16:02:27 -05:00
c2ba519240 Overhaul built-ins and identifiers 2024-02-15 15:20:29 -05:00
91e94a5adc Clean up 2024-02-15 10:37:10 -05:00
e7f5d66297 Implement custom and built-in types 2024-02-15 10:33:25 -05:00
e1c3e8bc0d Fix test 2024-02-15 07:12:10 -05:00
a6e52e4ee6 Implement matching for enums 2024-02-15 07:04:38 -05:00
85cb641af8 Write docs 2024-02-15 04:18:30 -05:00
933ab3900b Write docs 2024-02-15 04:16:34 -05:00
d9f065fbb6 Write docs 2024-02-15 03:57:13 -05:00
540f59e6d8 Modify enum variant syntax 2024-02-15 02:22:04 -05:00
b8f2fe7eb4 Implement Result type 2024-02-15 02:08:42 -05:00
ed1f139595 Remove option value type and built-in value syntax 2024-02-15 02:02:48 -05:00
4c68bc0260 Add built-in option definition 2024-02-15 01:51:05 -05:00
d3601be44c Fix test 2024-02-15 00:58:14 -05:00
fc3dfc0e03 Implement structs; Modify tests 2024-02-15 00:53:43 -05:00
97319d28b2 Implement custom types; Add test 2024-02-14 22:46:40 -05:00
89a4c09307 Implement basic enum instantiation 2024-02-14 22:38:45 -05:00
b8c54ea8bd Begin implementing enums 2024-02-14 20:53:42 -05:00
4323c50d32 Rework structs; Add enums; Remove yield statements 2024-02-14 20:23:33 -05:00
390d1aa504 Revert "Remove structure from map types"
This reverts commit 5e685d6641.
2024-02-14 19:15:47 -05:00
5e685d6641 Remove structure from map types 2024-02-14 19:07:34 -05:00
c2f0ec28ba Clean up 2024-02-14 18:57:24 -05:00
a23688803c Add test; Pass test by fixing type validation bug 2024-02-14 18:46:05 -05:00
85419c47be Convert maps to structures for advanced type checks 2024-02-13 12:04:02 -05:00
1f5dacad7d Add type check for type conversion; Add test 2024-02-13 10:49:49 -05:00
52493a0b73 Complete string to list conversion with as 2024-02-13 10:36:43 -05:00
18508fa217 Begin As implementation for AbstractTree; Add tests 2024-02-13 10:26:26 -05:00
e9bc16af0d Add syntax for as expressions 2024-02-13 09:19:23 -05:00
3f4c4ff464 Fix type checking bug 2024-02-13 08:10:34 -05:00
3c72e4f988 Rework built-in function arguments; Fix context bug 2024-02-12 18:55:54 -05:00
41a268389c Fix function recursion 2024-02-12 18:15:49 -05:00
bbab728ce9 Add context to function call nodes 2024-02-12 17:55:45 -05:00
daf78919da Move block contexts to loops and function 2024-02-12 16:51:06 -05:00
1e665a6f13 Fix context error 2024-02-12 15:48:43 -05:00
b7e0828ced Make maps multi-threaded again 2024-02-12 15:07:41 -05:00
924b388f2c Fix bug with loop contexts 2024-02-12 14:19:07 -05:00
d243c030e8 Fix Block Debug formatting 2024-02-11 15:26:09 -05:00
b1266df835 Fix function contexts 2024-02-11 14:18:53 -05:00
b5b317df95 Implement block contexts 2024-02-11 14:10:11 -05:00
f2049225fe Implement new context principles 2024-02-11 13:54:27 -05:00
90c0304af5 Implement context 2024-02-10 20:50:49 -05:00
d997bbd08a Continue implementing context 2024-02-10 19:31:47 -05:00
ddd5912248 Begin implementing new Context type 2024-02-10 18:29:11 -05:00
4479f340d7 Add From implementation for RwLockError 2024-02-09 14:23:41 -05:00
9ef82df3a7 Fix type setting bugs 2024-01-31 21:21:42 -05:00
e486413aca Fix type setting bugs; Rename function 2024-01-31 20:52:34 -05:00
9a465cb42d Continue error overhaul 2024-01-31 19:35:27 -05:00
88ca9c5ea4 Implement error overhaul 2024-01-31 19:07:18 -05:00
7f849f13a3 Begin error overhaul 2024-01-31 13:51:48 -05:00
cd4296c941 Add error for float parse failures 2024-01-31 12:18:30 -05:00
699c37d860 Add error for 2024-01-31 12:17:13 -05:00
820c863f7f Overhaul integer, float and range parsing 2024-01-31 12:10:32 -05:00
65afe9dd29 Clean up build script 2024-01-31 10:19:26 -05:00
9c87d70659 Clean up example tests 2024-01-31 10:18:25 -05:00
c5241bb0af Write docs 2024-01-30 18:19:05 -05:00
4cbfdde4a3 Write docs; Refine library API 2024-01-30 18:13:30 -05:00
7c9be2151d Write docs; Add missing docs warning 2024-01-30 14:48:38 -05:00
0ba3ed51e0 Clean up examples 2024-01-30 14:31:22 -05:00
ba0d154962 Write tests; Clean up 2024-01-30 14:11:18 -05:00
93e2a24a25 Add test; Rename "string" built-in to "str" 2024-01-30 13:57:30 -05:00
f85fed941a Fix serde errors 2024-01-30 13:43:18 -05:00
9762112b3c Clean up dependencies 2024-01-30 12:29:55 -05:00
4e8799e750 Clean up depen 2024-01-30 12:29:30 -05:00
7b101c944a Add tests 2024-01-30 11:48:29 -05:00
61e7079a00 Fix ranges 2024-01-30 09:46:49 -05:00
e756c7eac1 Write docs 2024-01-30 07:21:54 -05:00
36dd23a7ab Write docs 2024-01-30 06:22:07 -05:00
b182e3b945 Format document 2024-01-30 03:03:40 -05:00
281732924c Write docs 2024-01-30 01:38:19 -05:00
5adfa716fd Outline language reference 2024-01-30 00:48:50 -05:00
ce0ca17beb Write tests 2024-01-30 00:24:13 -05:00
8a5efa054b Fix example 2024-01-30 00:11:45 -05:00
2588715f98 Clean up examples 2024-01-30 00:01:16 -05:00
7f30097d45 Improve tests; Clean up 2024-01-29 23:57:13 -05:00
da11431edf Edit README 2024-01-29 23:21:54 -05:00
fb1399ab0d Write docs; Add fs built-in functions 2024-01-29 23:18:09 -05:00
ae278e42ad Edit docs 2024-01-29 21:32:56 -05:00
df34c10d47 Add WIP message to language.md 2024-01-29 21:27:56 -05:00
7f9d43e377 Write docs 2024-01-29 21:24:19 -05:00
ed6fad9843 Begin restructuring the docs; Write some docs 2024-01-29 20:28:05 -05:00
0752ebedf2 Refine command implementation; Add tests 2024-01-29 18:19:06 -05:00
df5cf93e58 Allow empty maps; Write tests 2024-01-29 17:36:21 -05:00
bd6ca6a6c1 Fix function context bug 2024-01-28 18:42:27 -05:00
34173c261b Fix recursion 2024-01-28 18:07:28 -05:00
6a9ce76007 Clean up 2024-01-28 17:46:15 -05:00
433306b3e1 Add path completion to shell 2024-01-28 13:45:08 -05:00
80428a3dd7 Add table output 2024-01-28 13:30:57 -05:00
7b9913309d Clean up 2024-01-28 12:14:43 -05:00
f67ef7f26f Add variable to suggestions 2024-01-28 12:07:25 -05:00
3bb9090afa Improve shell ergonomics 2024-01-28 12:04:33 -05:00
a0b38d329b Improve shell 2024-01-26 21:03:54 -05:00
0eee9f0936 Integrate starship 2024-01-26 18:28:37 -05:00
3d21196768 Add JSON functions; Modify CLI prompt 2024-01-26 17:14:57 -05:00
f6a1e641c9 Repair command implementation 2024-01-26 15:23:24 -05:00
c2fc3362c8 Fix command syntax; Modify shell prompt 2024-01-26 15:10:46 -05:00
54790bc0db Fix stack overflow; Clean up 2024-01-25 09:08:39 -05:00
9a65afa083 Fix quoted string parsing for commands 2024-01-25 08:57:55 -05:00
70ad08128c Add command logic 2024-01-25 08:43:21 -05:00
5bdb9f116f Add command and pipe syntax 2024-01-25 08:27:24 -05:00
fe1f007692 Add command syntax; Write docs 2024-01-25 07:10:45 -05:00
58bbbb749e Revert "Add loading binaries as functions"
This reverts commit 8f3d36fc8d.
2024-01-25 04:45:25 -05:00
8f3d36fc8d Add loading binaries as functions 2024-01-25 04:19:45 -05:00
784e4b309d Implement reedline 2024-01-25 02:17:45 -05:00
ac29f0210f Implement reedline crate with highlighting 2024-01-25 01:28:22 -05:00
151 changed files with 11169 additions and 36493 deletions

3671
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,53 +1,16 @@
[package]
name = "dust-lang"
description = "General purpose programming language"
version = "0.4.1"
repository = "https://git.jeffa.io/jeff/dust.git"
[workspace]
members = ["dust-lang", "dust-shell"]
default-members = ["dust-lang"]
resolver = "2"
[workspace.package]
authors = ["Jeff Anderson"]
edition = "2021"
license = "MIT"
authors = ["Jeff Anderson"]
default-run = "dust"
[[bin]]
name = "dust"
path = "src/main.rs"
readme = "README.md"
repository = "https://git.jeffa.io/jeff/dust.git"
[profile.dev]
opt-level = 1
[profile.dev.package."*"]
opt-level = 3
[dependencies]
ansi_term = "0.12.1"
clap = { version = "4.4.4", features = ["derive"] }
csv = "1.2.2"
egui = "0.24.1"
eframe = { version = "0.24.1", default-features = false, features = [
"accesskit",
"default_fonts",
"glow",
"persistence",
] }
libc = "0.2.148"
log = "0.4.20"
rand = "0.8.5"
rayon = "1.8.0"
reqwest = { version = "0.11.20", features = ["blocking", "json"] }
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
toml = "0.8.1"
tree-sitter = "0.20.10"
egui_extras = "0.24.2"
enum-iterator = "1.4.1"
env_logger = "0.10"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.10"
rustyline = { version = "12.0.0", features = ["derive", "with-file-history"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
wasm-bindgen-futures = "0.4"
[build-dependencies]
cc = "1.0"

317
README.md
View File

@ -1,303 +1,50 @@
# Dust
Dust is a general purpose programming language that emphasises concurrency and correctness.
A basic dust program:
```dust
output("Hello world!")
```
Dust can do two (or more) things at the same time with effortless concurrency:
```dust
async {
output('will this one finish first?')
output('or will this one?')
}
```
You can make *any* block, i.e. `{}`, run its statements in parallel by changing it to `async {}`.
```dust
if random_boolean() {
output("Do something...")
} else async {
output("Do something else instead...")
output("And another thing at the same time...")
}
```
Dust is an interpreted, strictly typed language with first class functions. It emphasises concurrency by allowing any group of statements to be executed in parallel. Dust includes built-in tooling to import and export data in a variety of formats, including JSON, TOML, YAML and CSV.
<!--toc:start-->
- [Dust](#dust)
- [Features](#features)
- [Usage](#usage)
- [Installation](#installation)
- [Benchmarks](#benchmarks)
- [Implementation](#implementation)
- [The Dust Programming Language](#the-dust-programming-language)
- [Declaring Variables](#declaring-variables)
- [Lists](#lists)
- [Maps](#maps)
- [Loops](#loops)
- [Functions](#functions)
- [Option](#option)
- [Concurrency](#concurrency)
- [Acknowledgements](#acknowledgements)
<!--toc:end-->
## Features
- Simplicity: Dust is designed to be easy to learn.
- Speed: Dust is built on [Tree Sitter] and [Rust] to prioritize performance and correctness. See [Benchmarks] below.
- Concurrency: Safe, effortless parallel code using thread pools.
- Safety: Written in safe, stable Rust.
- Correctness: Type checking makes it easy to write good code.
## Usage
Dust is an experimental project under active development. At this stage, features come and go and the API is always changing. It should not be considered for serious use yet.
```sh
cargo install dust-lang
dust -c "output('Hello world!')"
```
```txt
General purpose programming language
Usage: dust [OPTIONS] [PATH]
Arguments:
[PATH] Location of the file to run
Options:
-c, --command <COMMAND> Dust source code to evaluate
-i, --input <INPUT> Data to assign to the "input" variable
-p, --input-path <INPUT_PATH> A path to file whose contents will be assigned to the "input" variable
-t, --tree Show the syntax tree
-h, --help Print help
-V, --version Print version
```
## Installation
You must have the default rust toolchain installed and up-to-date. Install [rustup] if it is not already installed. Run `cargo install dust-lang` then run `dust` to start the interactive shell. Use `dust --help` to see the full command line options.
To build from source, clone the repository and build the parser. To do so, enter the `tree-sitter-dust` directory and run `tree-sitter-generate`. In the project root, run `cargo run` to start the shell. To see other command line options, use `cargo run -- --help`.
## Benchmarks
Dust is at a very early development stage but performs strongly in preliminary benchmarks. The examples given were tested using [Hyperfine] on a single-core cloud instance with 1024 MB RAM. Each test was run 1000 times. The test script is shown below. Each test asks the program to read a JSON file and count the objects. Dust is a command line shell, programming language and data manipulation tool so three appropriate targets were chosen for comparison: nushell, NodeJS and jq. The programs produced identical output with the exception that NodeJS printed in color.
For the first test, a file with four entries was used.
| Command | Mean [ms] | Min [ms] | Max [ms]
|:---|---:|---:|---:|
| Dust | 3.1 ± 0.5 | 2.4 | 8.4 |
| jq | 33.7 ± 2.2 | 30.0 | 61.8 |
| NodeJS | 226.4 ± 13.1 | 197.6 | 346.2 |
| Nushell | 51.6 ± 3.7 | 45.4 | 104.3 |
The second set of data is from the GitHub API, it consists of 100 commits from the jq GitHub repo.
| Command | Mean [ms] | Min [ms] | Max [ms] |
|:---|---:|---:|---:|
| Dust | 6.8 ± 0.6 | 5.7 | 12.0 | 2.20 ± 0.40 |
| jq | 43.3 ± 3.6 | 37.6 | 81.6 | 13.95 ± 2.49 |
| NodeJS | 224.9 ± 12.3 | 194.8 | 298.5 |
| Nushell | 59.2 ± 5.7 | 49.7 | 125.0 | 19.11 ± 3.55 |
This data came from CERN, it is a massive file of 100,000 entries.
| Command | Mean [ms] | Min [ms] | Max [ms] |
|:---|---:|---:|---:|
| Dust | 1080.8 ± 38.7 | 975.3 | 1326.6 |
| jq | 1305.3 ± 64.3 | 1159.7 | 1925.1 |
| NodeJS | 1850.5 ± 72.5 | 1641.9 | 2395.1 |
| Nushell | 1850.5 ± 86.2 | 1625.5 | 2400.7 |
The tests were run after 5 warmup runs and the cache was cleared before each run.
```sh
hyperfine \
--shell none \
--warmup 5 \
--prepare "rm -rf /root/.cache" \
--runs 1000 \
--parameter-list data_path seaCreatures.json,jq_data.json,dielectron.json \
--export-markdown test_output.md \
"dust -c '(length (from_json input))' -p {data_path}" \
"jq 'length' {data_path}" \
"node --eval \"require('node:fs').readFile('{data_path}',(err,data)=>{console.log(JSON.parse(data).length)})\"" \
"nu -c 'open {data_path} | length'"
```
Dust is a high-level interpreted programming language with static types that focuses on ease of use,
performance and correctness.
## Implementation
Dust is formally defined as a Tree Sitter grammar in the tree-sitter-dust directory. Tree sitter generates a parser, written in C, from a set of rules defined in JavaScript. Dust itself is a rust binary that calls the C parser using FFI.
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.
Tests are written in three places: in the Rust library, in Dust as examples and in the Tree Sitter test format. Generally, features are added by implementing and testing the syntax in the tree-sitter-dust repository, then writing library tests to evaluate the new syntax. Implementation tests run the Dust files in the "examples" directory and should be used to demonstrate and verify that features work together.
### Lexer
Tree Sitter generates a concrete syntax tree, which Dust traverses to create an abstract syntax tree that can run the Dust code. The CST generation is an extra step but it allows easy testing of the parser, defining the language in one file and makes the syntax easy to modify and expand. Because it uses Tree Sitter, developer-friendly features like syntax highlighting and code navigation are already available in any text editor that supports Tree Sitter.
The lexer emits tokens from the source code. Dust makes extensive use of Rust's zero-copy
capabilities to avoid unnecessary allocations when creating tokens. A token, depending on its type,
may contain a reference to some data from the source code. The data is only copied in the case of an
error, 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.
## The Dust Programming Language
### Compiler
Dust is easy to learn. Aside from this guide, the best way to learn Dust is to read the examples and tests to get a better idea of what it can do.
The compiler creates a chunk, which contains all of the data needed by the virtual machine to run a
Dust program. It does so by emitting bytecode instructions, constants and locals while parsing the
tokens, which are generated one at a time by the lexer.
### Declaring Variables
#### Parsing
Variables have two parts: a key and a value. The key is always a string. The value can be any of the following data types:
Dust's compiler uses a custom Pratt parser, a kind of recursive descent parser, to translate a
sequence of tokens into a chunk.
- string
- integer
- float
- boolean
- list
- map
- option
- function
#### Optimizing
Here are some examples of variables in dust.
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.
```dust
string = "foobar"
integer = 42
float = 42.42
list = [1 2 string integer float] # Commas are optional when writing lists.
map = {
key = 'value'
}
```
### Instructions
Note that strings can be wrapped with any kind of quote: single, double or backticks. Numbers are always integers by default. Floats are declared by adding a decimal. If you divide integers or do any kind of math with a float, you will create a float value.
### Virtual Machine
Dust enforces strict type checking, but you don't usually need to write the type, dust can figure it out on its own. The **number** and **any** types are special types that allow you to relax the type bounds.
## Previous Implementations
```dust
string <str> = "foobar"
integer <int> = 42
float <float> = 42.42
## Inspiration
numbers <[number]> = [integer float]
stuff <[any]> = [string integer float]
```
### Lists
Lists are sequential collections. They can be built by grouping values with square brackets. Commas are optional. Values can be indexed by their position using a colon `:` followed by an integer. Dust lists are zero-indexed.
```dust
list = [true 41 "Ok"]
assert_equal(list:0 true)
the_answer = list:1 + 1
assert_equal(the_answer, 42) # You can use commas when passing values a function.
```
### Maps
Maps are flexible collections with arbitrary key-value pairs, similar to JSON objects. A map is created with a pair of curly braces and its entries are variables declared inside those braces. Map contents can be accessed using a colon `:`.
```dust
reminder = {
message = "Buy milk"
tags = ["groceries", "home"]
}
output(reminder:message)
```
### Loops
A **while** loop continues until a predicate is false.
```dust
i = 0
while i < 10 {
output(i)
i += 1
}
```
A **for** loop operates on a list without mutating it or the items inside. It does not return a value.
```dust
list = [ 1, 2, 3 ]
for number in list {
output(number + 1)
}
```
### Functions
Functions are first-class values in dust, so they are assigned to variables like any other value.
```dust
# This simple function has no arguments and no return value.
say_hi = () <none> {
output("hi")
}
# This function has one argument and will return a value.
add_one = (number <num>) <num> {
number + 1
}
say_hi()
assert_equal(add_one(3), 4)
```
You don't need commas when listing arguments and you don't need to add whitespace inside the function body but doing so may make your code easier to read.
### Option
An **option** represents a value that may not be present. It has two variants: **some** and **none**. Dust includes built-in functions to work with option values: `is_none`, `is_some` and `either_or`.
```dust
say_something = (message <option(str)>) <str> {
either_or(message, "hiya")
}
say_something(some("goodbye"))
# goodbye
say_something(none)
# hiya
```
### Concurrency
Dust features effortless concurrency anywhere in your code. Any block of code can be made to run its contents asynchronously. Dust's concurrency is written in safe Rust and uses a thread pool whose size depends on the number of cores available.
```dust
# An async block will run each statement in its own thread.
async {
output(random_integer())
output(random_float())
output(random_boolean())
}
```
```dust
data = async {
output("Reading a file...")
read("examples/assets/faithful.csv")
}
```
## Acknowledgements
Dust began as a fork of [evalexpr]. Some of the original code is still in place but the project has dramatically changed and no longer uses any of its parsing or interpreting.
[Tree Sitter]: https://tree-sitter.github.io/tree-sitter/
[Rust]: https://rust-lang.org
[evalexpr]: https://github.com/ISibboI/evalexpr
[rustup]: https://rustup.rs
[Hyperfine]: https://github.com/sharkdp/hyperfine
- [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
- [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
- [Crafting Interpreters](https://craftinginterpreters.com/)

View File

@ -1,15 +0,0 @@
fn main() {
let src_dir = std::path::Path::new("tree-sitter-dust/src");
let mut c_config = cc::Build::new();
c_config.include(src_dir);
c_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable")
.flag_if_supported("-Wno-trigraphs");
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
c_config.compile("parser");
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
}

23
dust-lang/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "dust-lang"
description = "Interpreter library for the Dust programming language"
version = "0.5.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
[dependencies]
annotate-snippets = "0.11.4"
colored = "2.1.0"
log = "0.4.22"
rand = "0.8.5"
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
getrandom = { version = "0.2", features = [
"js",
] } # Indirect dependency, for wasm builds
[dev-dependencies]
env_logger = "0.11.5"

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

@ -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

@ -0,0 +1,66 @@
//! Top-level Dust errors with source code annotations.
use annotate_snippets::{Level, Renderer, Snippet};
use crate::{vm::VmError, CompileError, Span};
/// A top-level error that can occur during the execution of Dust code.
///
/// This error can display nicely formatted messages with source code annotations.
#[derive(Debug, PartialEq)]
pub enum DustError<'src> {
Compile {
error: CompileError,
source: &'src str,
},
Runtime {
error: VmError,
source: &'src str,
},
}
impl<'src> DustError<'src> {
pub fn report(&self) -> String {
let mut report = String::new();
let renderer = Renderer::styled();
match self {
DustError::Runtime { error, source } => {
let position = error.position();
let label = format!("{}: {}", VmError::title(), error.description());
let details = error
.details()
.unwrap_or_else(|| "While running this code".to_string());
let message = Level::Error.title(&label).snippet(
Snippet::source(source)
.fold(false)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
);
report.push_str(&renderer.render(message).to_string());
}
DustError::Compile { error, source } => {
let position = error.position();
let label = format!("{}: {}", CompileError::title(), error.description());
let details = error
.details()
.unwrap_or_else(|| "While parsing this code".to_string());
let message = Level::Error.title(&label).snippet(
Snippet::source(source)
.fold(false)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
);
report.push_str(&renderer.render(message).to_string());
}
}
report
}
}
pub trait AnnotatedError {
fn title() -> &'static str;
fn description(&self) -> &'static str;
fn details(&self) -> Option<String>;
fn position(&self) -> Span;
}

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

@ -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());
}
}

1476
dust-lang/src/lexer.rs Normal file

File diff suppressed because it is too large Load Diff

44
dust-lang/src/lib.rs Normal file
View File

@ -0,0 +1,44 @@
//! The Dust programming language library.
pub mod chunk;
pub mod compiler;
pub mod disassembler;
pub mod dust_error;
pub mod formatter;
pub mod instruction;
pub mod lexer;
pub mod native_function;
pub mod operation;
pub mod optimizer;
pub mod token;
pub mod r#type;
pub mod value;
pub mod vm;
pub use crate::chunk::{Chunk, ChunkError, Local, Scope};
pub use crate::compiler::{compile, CompileError, Compiler};
pub use crate::disassembler::Disassembler;
pub use crate::dust_error::{AnnotatedError, DustError};
pub use crate::formatter::{format, Formatter};
pub use crate::instruction::Instruction;
pub use crate::lexer::{lex, LexError, Lexer};
pub use crate::native_function::{NativeFunction, NativeFunctionError};
pub use crate::operation::Operation;
pub use crate::optimizer::{optimize, Optimizer};
pub use crate::r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
pub use crate::token::{Token, TokenKind, TokenOwned};
pub use crate::value::{ConcreteValue, Function, Value, ValueError};
pub use crate::vm::{run, Vm, VmError};
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)
}
}

642
dust-lang/src/token.rs Normal file
View File

@ -0,0 +1,642 @@
//! Token, TokenOwned and TokenKind types.
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
macro_rules! define_tokens {
($($variant:ident $(($data_type:ty))?),+ $(,)?) => {
/// Source token.
///
/// 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
Boolean(&'src str),
Byte(&'src str),
Character(char),
Float(&'src str),
Identifier(&'src str),
Integer(&'src str),
String(&'src str),
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
Fn,
If,
Int,
Let,
Loop,
Map,
Mut,
Return,
Str,
Struct,
While,
// Symbols
ArrowThin,
BangEqual,
Bang,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessEqual,
Minus,
MinusEqual,
Percent,
PercentEqual,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Slash,
SlashEqual,
Star,
StarEqual,
}
impl<'src> Token<'src> {
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
match self {
Token::Eof => 0,
Token::Boolean(text) => text.len(),
Token::Byte(_) => 3,
Token::Character(_) => 3,
Token::Float(text) => text.len(),
Token::Identifier(text) => text.len(),
Token::Integer(text) => text.len(),
Token::String(text) => text.len() + 2,
Token::Async => 5,
Token::ArrowThin => 2,
Token::Bool => 4,
Token::Break => 5,
Token::Else => 4,
Token::FloatKeyword => 5,
Token::Fn => 2,
Token::If => 2,
Token::Int => 3,
Token::Let => 3,
Token::Loop => 4,
Token::Map => 3,
Token::Mut => 3,
Token::Str => 3,
Token::Struct => 6,
Token::While => 5,
Token::BangEqual => 2,
Token::Bang => 1,
Token::Colon => 1,
Token::Comma => 1,
Token::Dot => 1,
Token::DoubleAmpersand => 2,
Token::DoubleDot => 2,
Token::DoubleEqual => 2,
Token::DoublePipe => 2,
Token::Equal => 1,
Token::Greater => 1,
Token::GreaterEqual => 2,
Token::LeftCurlyBrace => 1,
Token::LeftParenthesis => 1,
Token::LeftSquareBrace => 1,
Token::Less => 1,
Token::LessEqual => 2,
Token::Minus => 1,
Token::MinusEqual => 2,
Token::Percent => 1,
Token::PercentEqual => 2,
Token::Plus => 1,
Token::PlusEqual => 2,
Token::Return => 6,
Token::RightCurlyBrace => 1,
Token::RightParenthesis => 1,
Token::RightSquareBrace => 1,
Token::Semicolon => 1,
Token::Slash => 1,
Token::SlashEqual => 2,
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 {
match self {
Token::ArrowThin => TokenOwned::ArrowThin,
Token::Async => TokenOwned::Async,
Token::BangEqual => TokenOwned::BangEqual,
Token::Bang => TokenOwned::Bang,
Token::Bool => TokenOwned::Bool,
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
Token::Break => TokenOwned::Break,
Token::Byte(byte) => TokenOwned::Byte(byte.to_string()),
Token::Character(character) => TokenOwned::Character(*character),
Token::Colon => TokenOwned::Colon,
Token::Comma => TokenOwned::Comma,
Token::Dot => TokenOwned::Dot,
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
Token::DoubleDot => TokenOwned::DoubleDot,
Token::DoubleEqual => TokenOwned::DoubleEqual,
Token::DoublePipe => TokenOwned::DoublePipe,
Token::Else => TokenOwned::Else,
Token::Eof => TokenOwned::Eof,
Token::Equal => TokenOwned::Equal,
Token::Float(float) => TokenOwned::Float(float.to_string()),
Token::FloatKeyword => TokenOwned::FloatKeyword,
Token::Fn => TokenOwned::Fn,
Token::Greater => TokenOwned::Greater,
Token::GreaterEqual => TokenOwned::GreaterOrEqual,
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
Token::If => TokenOwned::If,
Token::Int => TokenOwned::Int,
Token::Integer(integer) => TokenOwned::Integer(integer.to_string()),
Token::LeftCurlyBrace => TokenOwned::LeftCurlyBrace,
Token::LeftParenthesis => TokenOwned::LeftParenthesis,
Token::LeftSquareBrace => TokenOwned::LeftSquareBrace,
Token::Let => TokenOwned::Let,
Token::Less => TokenOwned::Less,
Token::LessEqual => TokenOwned::LessOrEqual,
Token::Loop => TokenOwned::Loop,
Token::Map => TokenOwned::Map,
Token::Minus => TokenOwned::Minus,
Token::MinusEqual => TokenOwned::MinusEqual,
Token::Mut => TokenOwned::Mut,
Token::Percent => TokenOwned::Percent,
Token::PercentEqual => TokenOwned::PercentEqual,
Token::Plus => TokenOwned::Plus,
Token::PlusEqual => TokenOwned::PlusEqual,
Token::Return => TokenOwned::Return,
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
Token::RightParenthesis => TokenOwned::RightParenthesis,
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
Token::Semicolon => TokenOwned::Semicolon,
Token::Star => TokenOwned::Star,
Token::StarEqual => TokenOwned::StarEqual,
Token::Slash => TokenOwned::Slash,
Token::SlashEqual => TokenOwned::SlashEqual,
Token::String(text) => TokenOwned::String(text.to_string()),
Token::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct,
Token::While => TokenOwned::While,
}
}
pub fn kind(&self) -> TokenKind {
match self {
Token::ArrowThin => TokenKind::ArrowThin,
Token::Async => TokenKind::Async,
Token::BangEqual => TokenKind::BangEqual,
Token::Bang => TokenKind::Bang,
Token::Bool => TokenKind::Bool,
Token::Boolean(_) => TokenKind::Boolean,
Token::Break => TokenKind::Break,
Token::Byte(_) => TokenKind::Byte,
Token::Character(_) => TokenKind::Character,
Token::Colon => TokenKind::Colon,
Token::Comma => TokenKind::Comma,
Token::Dot => TokenKind::Dot,
Token::DoubleAmpersand => TokenKind::DoubleAmpersand,
Token::DoubleDot => TokenKind::DoubleDot,
Token::DoubleEqual => TokenKind::DoubleEqual,
Token::DoublePipe => TokenKind::DoublePipe,
Token::Else => TokenKind::Else,
Token::Eof => TokenKind::Eof,
Token::Equal => TokenKind::Equal,
Token::Float(_) => TokenKind::Float,
Token::FloatKeyword => TokenKind::FloatKeyword,
Token::Fn => TokenKind::Fn,
Token::Greater => TokenKind::Greater,
Token::GreaterEqual => TokenKind::GreaterEqual,
Token::Identifier(_) => TokenKind::Identifier,
Token::If => TokenKind::If,
Token::Int => TokenKind::Int,
Token::Integer(_) => TokenKind::Integer,
Token::LeftCurlyBrace => TokenKind::LeftCurlyBrace,
Token::LeftParenthesis => TokenKind::LeftParenthesis,
Token::LeftSquareBrace => TokenKind::LeftSquareBrace,
Token::Let => TokenKind::Let,
Token::Less => TokenKind::Less,
Token::LessEqual => TokenKind::LessEqual,
Token::Loop => TokenKind::Loop,
Token::Map => TokenKind::Map,
Token::Minus => TokenKind::Minus,
Token::MinusEqual => TokenKind::MinusEqual,
Token::Mut => TokenKind::Mut,
Token::Percent => TokenKind::Percent,
Token::PercentEqual => TokenKind::PercentEqual,
Token::Plus => TokenKind::Plus,
Token::PlusEqual => TokenKind::PlusEqual,
Token::Return => TokenKind::Return,
Token::RightCurlyBrace => TokenKind::RightCurlyBrace,
Token::RightParenthesis => TokenKind::RightParenthesis,
Token::RightSquareBrace => TokenKind::RightSquareBrace,
Token::Semicolon => TokenKind::Semicolon,
Token::Star => TokenKind::Star,
Token::StarEqual => TokenKind::StarEqual,
Token::Slash => TokenKind::Slash,
Token::SlashEqual => TokenKind::SlashEqual,
Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String,
Token::Struct => TokenKind::Struct,
Token::While => TokenKind::While,
}
}
/// Returns true if the token yields a value, begins an expression or is an expression operator.
pub fn is_expression(&self) -> bool {
matches!(
self,
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::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::Slash
| Token::SlashEqual
| Token::Star
| Token::StarEqual
)
}
}
impl<'src> Display for Token<'src> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Token::ArrowThin => write!(f, "->"),
Token::Async => write!(f, "async"),
Token::BangEqual => write!(f, "!="),
Token::Bang => write!(f, "!"),
Token::Bool => write!(f, "bool"),
Token::Boolean(value) => write!(f, "{value}"),
Token::Break => write!(f, "break"),
Token::Byte(value) => write!(f, "{value}"),
Token::Character(value) => write!(f, "{value}"),
Token::Colon => write!(f, ":"),
Token::Comma => write!(f, ","),
Token::Dot => write!(f, "."),
Token::DoubleAmpersand => write!(f, "&&"),
Token::DoubleDot => write!(f, ".."),
Token::DoubleEqual => write!(f, "=="),
Token::DoublePipe => write!(f, "||"),
Token::Else => write!(f, "else"),
Token::Eof => write!(f, "EOF"),
Token::Equal => write!(f, "="),
Token::Float(value) => write!(f, "{value}"),
Token::FloatKeyword => write!(f, "float"),
Token::Fn => write!(f, "fn"),
Token::Greater => write!(f, ">"),
Token::GreaterEqual => write!(f, ">="),
Token::Identifier(value) => write!(f, "{value}"),
Token::If => write!(f, "if"),
Token::Int => write!(f, "int"),
Token::Integer(value) => write!(f, "{value}"),
Token::LeftCurlyBrace => write!(f, "{{"),
Token::LeftParenthesis => write!(f, "("),
Token::LeftSquareBrace => write!(f, "["),
Token::Let => write!(f, "let"),
Token::Less => write!(f, "<"),
Token::LessEqual => write!(f, "<="),
Token::Loop => write!(f, "loop"),
Token::Map => write!(f, "map"),
Token::Minus => write!(f, "-"),
Token::MinusEqual => write!(f, "-="),
Token::Mut => write!(f, "mut"),
Token::Percent => write!(f, "%"),
Token::PercentEqual => write!(f, "%="),
Token::Plus => write!(f, "+"),
Token::PlusEqual => write!(f, "+="),
Token::Return => write!(f, "return"),
Token::RightCurlyBrace => write!(f, "}}"),
Token::RightParenthesis => write!(f, ")"),
Token::RightSquareBrace => write!(f, "]"),
Token::Semicolon => write!(f, ";"),
Token::Slash => write!(f, "/"),
Token::SlashEqual => write!(f, "/="),
Token::Star => write!(f, "*"),
Token::StarEqual => write!(f, "*="),
Token::Str => write!(f, "str"),
Token::String(value) => write!(f, "{value}"),
Token::Struct => write!(f, "struct"),
Token::While => write!(f, "while"),
}
}
}
/// Owned representation of a source token.
///
/// If a [Token] borrows from the source text, its TokenOwned omits the data.
#[derive(Debug, PartialEq, Clone)]
pub enum TokenOwned {
Eof,
Identifier(String),
// Hard-coded values
Boolean(String),
Byte(String),
Character(char),
Float(String),
Integer(String),
String(String),
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
Fn,
If,
Int,
Let,
Loop,
Map,
Mut,
Return,
Str,
While,
// Symbols
ArrowThin,
Bang,
BangEqual,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterOrEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessOrEqual,
Minus,
MinusEqual,
Percent,
PercentEqual,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Star,
StarEqual,
Struct,
Slash,
SlashEqual,
}
impl Display for TokenOwned {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
TokenOwned::ArrowThin => Token::ArrowThin.fmt(f),
TokenOwned::Async => Token::Async.fmt(f),
TokenOwned::Bang => Token::Bang.fmt(f),
TokenOwned::BangEqual => Token::BangEqual.fmt(f),
TokenOwned::Bool => Token::Bool.fmt(f),
TokenOwned::Boolean(boolean) => Token::Boolean(boolean).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::Colon => Token::Colon.fmt(f),
TokenOwned::Comma => Token::Comma.fmt(f),
TokenOwned::Dot => Token::Dot.fmt(f),
TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
TokenOwned::DoubleDot => Token::DoubleDot.fmt(f),
TokenOwned::DoubleEqual => Token::DoubleEqual.fmt(f),
TokenOwned::DoublePipe => Token::DoublePipe.fmt(f),
TokenOwned::Else => Token::Else.fmt(f),
TokenOwned::Eof => Token::Eof.fmt(f),
TokenOwned::Equal => Token::Equal.fmt(f),
TokenOwned::Float(float) => Token::Float(float).fmt(f),
TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenOwned::Fn => Token::Fn.fmt(f),
TokenOwned::Greater => Token::Greater.fmt(f),
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f),
TokenOwned::If => Token::If.fmt(f),
TokenOwned::Int => Token::Int.fmt(f),
TokenOwned::Integer(integer) => Token::Integer(integer).fmt(f),
TokenOwned::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
TokenOwned::LeftParenthesis => Token::LeftParenthesis.fmt(f),
TokenOwned::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
TokenOwned::Let => Token::Let.fmt(f),
TokenOwned::Less => Token::Less.fmt(f),
TokenOwned::LessOrEqual => Token::LessEqual.fmt(f),
TokenOwned::Loop => Token::Loop.fmt(f),
TokenOwned::Map => Token::Map.fmt(f),
TokenOwned::Minus => Token::Minus.fmt(f),
TokenOwned::MinusEqual => Token::MinusEqual.fmt(f),
TokenOwned::Mut => Token::Mut.fmt(f),
TokenOwned::Percent => Token::Percent.fmt(f),
TokenOwned::PercentEqual => Token::PercentEqual.fmt(f),
TokenOwned::Plus => Token::Plus.fmt(f),
TokenOwned::PlusEqual => Token::PlusEqual.fmt(f),
TokenOwned::Return => Token::Return.fmt(f),
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenOwned::Semicolon => Token::Semicolon.fmt(f),
TokenOwned::Star => Token::Star.fmt(f),
TokenOwned::StarEqual => Token::StarEqual.fmt(f),
TokenOwned::Slash => Token::Slash.fmt(f),
TokenOwned::SlashEqual => Token::SlashEqual.fmt(f),
TokenOwned::Str => Token::Str.fmt(f),
TokenOwned::String(string) => Token::String(string).fmt(f),
TokenOwned::Struct => Token::Struct.fmt(f),
TokenOwned::While => Token::While.fmt(f),
}
}
}
impl Display for TokenKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
TokenKind::ArrowThin => Token::ArrowThin.fmt(f),
TokenKind::Async => Token::Async.fmt(f),
TokenKind::Bang => Token::Bang.fmt(f),
TokenKind::BangEqual => Token::BangEqual.fmt(f),
TokenKind::Bool => Token::Bool.fmt(f),
TokenKind::Boolean => write!(f, "boolean value"),
TokenKind::Break => Token::Break.fmt(f),
TokenKind::Byte => write!(f, "byte value"),
TokenKind::Character => write!(f, "character value"),
TokenKind::Colon => Token::Colon.fmt(f),
TokenKind::Comma => Token::Comma.fmt(f),
TokenKind::Dot => Token::Dot.fmt(f),
TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
TokenKind::DoubleDot => Token::DoubleDot.fmt(f),
TokenKind::DoubleEqual => Token::DoubleEqual.fmt(f),
TokenKind::DoublePipe => Token::DoublePipe.fmt(f),
TokenKind::Else => Token::Else.fmt(f),
TokenKind::Eof => Token::Eof.fmt(f),
TokenKind::Equal => Token::Equal.fmt(f),
TokenKind::Float => write!(f, "float value"),
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenKind::Fn => Token::Fn.fmt(f),
TokenKind::Greater => Token::Greater.fmt(f),
TokenKind::GreaterEqual => Token::GreaterEqual.fmt(f),
TokenKind::Identifier => write!(f, "identifier"),
TokenKind::If => Token::If.fmt(f),
TokenKind::Int => Token::Int.fmt(f),
TokenKind::Integer => write!(f, "integer value"),
TokenKind::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
TokenKind::LeftParenthesis => Token::LeftParenthesis.fmt(f),
TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
TokenKind::Let => Token::Let.fmt(f),
TokenKind::Less => Token::Less.fmt(f),
TokenKind::LessEqual => Token::LessEqual.fmt(f),
TokenKind::Loop => Token::Loop.fmt(f),
TokenKind::Map => Token::Map.fmt(f),
TokenKind::Minus => Token::Minus.fmt(f),
TokenKind::MinusEqual => Token::MinusEqual.fmt(f),
TokenKind::Mut => Token::Mut.fmt(f),
TokenKind::Percent => Token::Percent.fmt(f),
TokenKind::PercentEqual => Token::PercentEqual.fmt(f),
TokenKind::Plus => Token::Plus.fmt(f),
TokenKind::PlusEqual => Token::PlusEqual.fmt(f),
TokenKind::Return => Token::Return.fmt(f),
TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenKind::Semicolon => Token::Semicolon.fmt(f),
TokenKind::Star => Token::Star.fmt(f),
TokenKind::StarEqual => Token::StarEqual.fmt(f),
TokenKind::Str => Token::Str.fmt(f),
TokenKind::Slash => Token::Slash.fmt(f),
TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::While => Token::While.fmt(f),
}
}
}

659
dust-lang/src/type.rs Normal file
View File

@ -0,0 +1,659 @@
//! Value types and conflict handling.
use std::{
cmp::Ordering,
collections::HashMap,
fmt::{self, Display, Formatter},
};
use serde::{Deserialize, Serialize};
/// Description of a kind of value.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Type {
Any,
Boolean,
Byte,
Character,
Enum(EnumType),
Float,
Function(FunctionType),
Generic {
identifier_index: u8,
concrete_type: Option<Box<Type>>,
},
Integer,
List {
item_type: Box<Type>,
length: usize,
},
ListEmpty,
ListOf {
item_type: Box<Type>,
},
Map {
pairs: HashMap<u8, Type>,
},
Number,
Range {
r#type: RangeableType,
},
String {
length: Option<usize>,
},
Struct(StructType),
Tuple {
fields: Option<Vec<Type>>,
},
}
impl Type {
/// Returns a concrete type, either the type itself or the concrete type of a generic type.
pub fn concrete_type(&self) -> &Type {
if let Type::Generic {
concrete_type: Some(concrete_type),
..
} = self
{
concrete_type.concrete_type()
} else {
self
}
}
/// Checks that the type is compatible with another type.
pub fn check(&self, other: &Type) -> Result<(), TypeConflict> {
match (self.concrete_type(), other.concrete_type()) {
(Type::Any, _)
| (_, Type::Any)
| (Type::Boolean, Type::Boolean)
| (Type::Byte, Type::Byte)
| (Type::Character, Type::Character)
| (Type::Float, Type::Float)
| (Type::Integer, Type::Integer)
| (Type::String { .. }, Type::String { .. }) => return Ok(()),
(
Type::Generic {
concrete_type: left,
..
},
Type::Generic {
concrete_type: right,
..
},
) => match (left, right) {
(Some(left), Some(right)) => {
if left.check(right).is_ok() {
return Ok(());
}
}
(None, None) => {
return Ok(());
}
_ => {}
},
(Type::Generic { concrete_type, .. }, other)
| (other, Type::Generic { concrete_type, .. }) => {
if let Some(concrete_type) = concrete_type {
if other == concrete_type.as_ref() {
return Ok(());
}
}
}
(Type::Struct(left_struct_type), Type::Struct(right_struct_type)) => {
if left_struct_type == right_struct_type {
return Ok(());
}
}
(
Type::List {
item_type: left_type,
length: left_length,
},
Type::List {
item_type: right_type,
length: right_length,
},
) => {
if left_length != right_length {
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
if left_type.check(right_type).is_err() {
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
return Ok(());
}
(
Type::ListOf {
item_type: left_type,
},
Type::ListOf {
item_type: right_type,
},
) => {
if left_type.check(right_type).is_err() {
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
}
(
Type::List {
item_type: list_item_type,
..
},
Type::ListOf {
item_type: list_of_item_type,
},
)
| (
Type::ListOf {
item_type: list_of_item_type,
},
Type::List {
item_type: list_item_type,
..
},
) => {
// TODO: This is a hack, remove it.
if let Type::Any = **list_of_item_type {
return Ok(());
}
if list_item_type.check(list_of_item_type).is_err() {
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
}
(
Type::Function(FunctionType {
type_parameters: left_type_parameters,
value_parameters: left_value_parameters,
return_type: left_return,
}),
Type::Function(FunctionType {
type_parameters: right_type_parameters,
value_parameters: right_value_parameters,
return_type: right_return,
}),
) => {
if left_return != right_return
|| left_type_parameters != right_type_parameters
|| left_value_parameters != right_value_parameters
{
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
return Ok(());
}
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
if left_type == right_type {
return Ok(());
}
}
(Type::Number, Type::Number | Type::Integer | Type::Float)
| (Type::Integer | Type::Float, Type::Number) => {
return Ok(());
}
_ => {}
}
Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
})
}
}
impl Display for Type {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Type::Any => write!(f, "any"),
Type::Boolean => write!(f, "bool"),
Type::Byte => write!(f, "byte"),
Type::Character => write!(f, "char"),
Type::Enum(EnumType { name, .. }) => write!(f, "{name}"),
Type::Float => write!(f, "float"),
Type::Function(function_type) => write!(f, "{function_type}"),
Type::Generic { concrete_type, .. } => {
match concrete_type.clone().map(|r#box| *r#box) {
Some(Type::Generic {
identifier_index: identifier,
..
}) => write!(f, "{identifier}"),
Some(concrete_type) => write!(f, "implied to be {concrete_type}"),
None => write!(f, "unknown"),
}
}
Type::Integer => write!(f, "int"),
Type::List { item_type, length } => write!(f, "[{item_type}; {length}]"),
Type::ListEmpty => write!(f, "[]"),
Type::ListOf { item_type } => write!(f, "[{item_type}]"),
Type::Map { pairs } => {
write!(f, "map ")?;
write!(f, "{{")?;
for (index, (key, value)) in pairs.iter().enumerate() {
write!(f, "{key}: {value}")?;
if index != pairs.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "}}")
}
Type::Number => write!(f, "num"),
Type::Range { r#type } => write!(f, "{type} range"),
Type::String { .. } => write!(f, "str"),
Type::Struct(struct_type) => write!(f, "{struct_type}"),
Type::Tuple { fields } => {
if let Some(fields) = fields {
write!(f, "(")?;
for (index, r#type) in fields.iter().enumerate() {
write!(f, "{type}")?;
if index != fields.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
} else {
write!(f, "tuple")
}
}
}
}
}
impl PartialOrd for Type {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Type {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Type::Any, Type::Any) => Ordering::Equal,
(Type::Any, _) => Ordering::Greater,
(Type::Boolean, Type::Boolean) => Ordering::Equal,
(Type::Boolean, _) => Ordering::Greater,
(Type::Byte, Type::Byte) => Ordering::Equal,
(Type::Byte, _) => Ordering::Greater,
(Type::Character, Type::Character) => Ordering::Equal,
(Type::Character, _) => Ordering::Greater,
(Type::Enum(left_enum), Type::Enum(right_enum)) => left_enum.cmp(right_enum),
(Type::Enum(_), _) => Ordering::Greater,
(Type::Float, Type::Float) => Ordering::Equal,
(Type::Float, _) => Ordering::Greater,
(Type::Function(left_function), Type::Function(right_function)) => {
left_function.cmp(right_function)
}
(Type::Function(_), _) => Ordering::Greater,
(Type::Generic { .. }, Type::Generic { .. }) => Ordering::Equal,
(Type::Generic { .. }, _) => Ordering::Greater,
(Type::Integer, Type::Integer) => Ordering::Equal,
(Type::Integer, _) => Ordering::Greater,
(
Type::List {
item_type: left_item_type,
length: left_length,
},
Type::List {
item_type: right_item_type,
length: right_length,
},
) => {
if left_length == right_length {
left_item_type.cmp(right_item_type)
} else {
left_length.cmp(right_length)
}
}
(Type::List { .. }, _) => Ordering::Greater,
(Type::ListEmpty, Type::ListEmpty) => Ordering::Equal,
(Type::ListEmpty, _) => Ordering::Greater,
(
Type::ListOf {
item_type: left_item_type,
},
Type::ListOf {
item_type: right_item_type,
},
) => left_item_type.cmp(right_item_type),
(Type::ListOf { .. }, _) => Ordering::Greater,
(Type::Map { pairs: left_pairs }, Type::Map { pairs: right_pairs }) => {
left_pairs.iter().cmp(right_pairs.iter())
}
(Type::Map { .. }, _) => Ordering::Greater,
(Type::Number, Type::Number) => Ordering::Equal,
(Type::Number, _) => Ordering::Greater,
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
left_type.cmp(right_type)
}
(Type::Range { .. }, _) => Ordering::Greater,
(Type::String { length: left }, Type::String { length: right }) => left.cmp(right),
(Type::String { .. }, _) => Ordering::Greater,
(Type::Struct(left_struct), Type::Struct(right_struct)) => {
left_struct.cmp(right_struct)
}
(Type::Struct(_), _) => Ordering::Greater,
(Type::Tuple { fields: left }, Type::Tuple { fields: right }) => left.cmp(right),
(Type::Tuple { .. }, _) => Ordering::Greater,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType {
pub type_parameters: Option<Vec<u8>>,
pub value_parameters: Option<Vec<(u8, Type)>>,
pub return_type: Option<Box<Type>>,
}
impl Display for FunctionType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "fn ")?;
if let Some(type_parameters) = &self.type_parameters {
write!(f, "<")?;
for (index, type_parameter) in type_parameters.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{type_parameter}")?;
}
write!(f, ">")?;
}
write!(f, "(")?;
if let Some(value_parameters) = &self.value_parameters {
for (index, (identifier, r#type)) in value_parameters.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{identifier}: {type}")?;
}
}
write!(f, ")")?;
if let Some(return_type) = &self.return_type {
write!(f, " -> {return_type}")?;
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum StructType {
Unit { name: u8 },
Tuple { name: u8, fields: Vec<Type> },
Fields { name: u8, fields: HashMap<u8, Type> },
}
impl StructType {
pub fn name(&self) -> u8 {
match self {
StructType::Unit { name } => *name,
StructType::Tuple { name, .. } => *name,
StructType::Fields { name, .. } => *name,
}
}
}
impl Display for StructType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
StructType::Unit { name } => write!(f, "{name}"),
StructType::Tuple { name, fields } => {
write!(f, "{name}(")?;
for (index, field) in fields.iter().enumerate() {
write!(f, "{field}")?;
if index != fields.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
}
StructType::Fields { name, fields } => {
write!(f, "{name} {{")?;
for (index, (identifier, r#type)) in fields.iter().enumerate() {
write!(f, "{identifier}: {type}")?;
if index != fields.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "}}")
}
}
}
}
impl PartialOrd for StructType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StructType {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(StructType::Unit { name: left_name }, StructType::Unit { name: right_name }) => {
left_name.cmp(right_name)
}
(StructType::Unit { .. }, _) => Ordering::Greater,
(
StructType::Tuple {
name: left_name,
fields: left_fields,
},
StructType::Tuple {
name: right_name,
fields: right_fields,
},
) => {
let name_cmp = left_name.cmp(right_name);
if name_cmp == Ordering::Equal {
left_fields.cmp(right_fields)
} else {
name_cmp
}
}
(StructType::Tuple { .. }, _) => Ordering::Greater,
(
StructType::Fields {
name: left_name,
fields: left_fields,
},
StructType::Fields {
name: right_name,
fields: right_fields,
},
) => {
let name_cmp = left_name.cmp(right_name);
if name_cmp == Ordering::Equal {
let len_cmp = left_fields.len().cmp(&right_fields.len());
if len_cmp == Ordering::Equal {
left_fields.iter().cmp(right_fields.iter())
} else {
len_cmp
}
} else {
name_cmp
}
}
(StructType::Fields { .. }, _) => Ordering::Greater,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct EnumType {
pub name: u8,
pub variants: Vec<StructType>,
}
impl Display for EnumType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let EnumType { name, variants } = self;
write!(f, "enum {name} {{ ")?;
for (index, variant) in variants.iter().enumerate() {
write!(f, "{variant}")?;
if index != self.variants.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, " }}")
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum RangeableType {
Byte,
Character,
Float,
Integer,
}
impl Display for RangeableType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
RangeableType::Byte => Type::Byte.fmt(f),
RangeableType::Character => Type::Character.fmt(f),
RangeableType::Float => Type::Float.fmt(f),
RangeableType::Integer => Type::Integer.fmt(f),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TypeConflict {
pub expected: Type,
pub actual: Type,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_type_any() {
let foo = Type::Any;
let bar = Type::Any;
foo.check(&bar).unwrap();
}
#[test]
fn check_type_boolean() {
let foo = Type::Boolean;
let bar = Type::Boolean;
foo.check(&bar).unwrap();
}
#[test]
fn check_type_byte() {
let foo = Type::Byte;
let bar = Type::Byte;
foo.check(&bar).unwrap();
}
#[test]
fn check_type_character() {
let foo = Type::Character;
let bar = Type::Character;
foo.check(&bar).unwrap();
}
#[test]
fn errors() {
let foo = Type::Integer;
let bar = Type::String { length: None };
assert_eq!(
foo.check(&bar),
Err(TypeConflict {
actual: bar.clone(),
expected: foo.clone()
})
);
assert_eq!(
bar.check(&foo),
Err(TypeConflict {
actual: foo.clone(),
expected: bar.clone()
})
);
let types = [
Type::Boolean,
Type::Float,
Type::Integer,
Type::List {
item_type: Box::new(Type::Integer),
length: 42,
},
Type::Range {
r#type: RangeableType::Integer,
},
Type::String { length: None },
];
for left in types.clone() {
for right in types.clone() {
if left == right {
continue;
}
assert_eq!(
left.check(&right),
Err(TypeConflict {
actual: right.clone(),
expected: left.clone()
})
);
}
}
}
}

767
dust-lang/src/value.rs Normal file
View File

@ -0,0 +1,767 @@
//! Dust value representation
//!
//! # Examples
//!
//! Each type of value has a corresponding method for instantiation:
//!
//! ```
//! # use dust_lang::Value;
//! let boolean = Value::boolean(true);
//! let float = Value::float(3.14);
//! let integer = Value::integer(42);
//! let string = Value::string("Hello, world!");
//! ```
//!
//! Values have a type, which can be retrieved using the `r#type` method:
//!
//! ```
//! # use dust_lang::*;
//! let value = Value::integer(42);
//!
//! assert_eq!(value.r#type(), Type::Integer);
//! ```
use std::{
cmp::Ordering,
fmt::{self, Debug, Display, Formatter},
ops::{Range, RangeInclusive},
};
use serde::{Deserialize, Serialize};
use crate::{Chunk, FunctionType, RangeableType, Type};
/// Dust value representation
///
/// See the [module-level documentation][self] for more.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Value {
Concrete(ConcreteValue),
Abstract(AbstractValue),
}
impl Value {
pub fn boolean(value: bool) -> Self {
Value::Concrete(ConcreteValue::Boolean(value))
}
pub fn byte(value: u8) -> Self {
Value::Concrete(ConcreteValue::Byte(value))
}
pub fn character(value: char) -> Self {
Value::Concrete(ConcreteValue::Character(value))
}
pub fn float(value: f64) -> Self {
Value::Concrete(ConcreteValue::Float(value))
}
pub fn function(body: Chunk, r#type: FunctionType) -> Self {
Value::Concrete(ConcreteValue::Function(Function {
chunk: body,
r#type: Type::Function(r#type),
}))
}
pub fn integer<T: Into<i64>>(into_i64: T) -> Self {
Value::Concrete(ConcreteValue::Integer(into_i64.into()))
}
pub fn list<T: Into<Vec<Value>>>(items: T) -> Self {
Value::Concrete(ConcreteValue::List(items.into()))
}
pub fn abstract_list(start: u8, end: u8, item_type: Type) -> Self {
Value::Abstract(AbstractValue::List {
start,
end,
item_type,
})
}
pub fn string<T: ToString>(to_string: T) -> Self {
Value::Concrete(ConcreteValue::String(to_string.to_string()))
}
pub fn as_string(&self) -> Option<&String> {
if let Value::Concrete(ConcreteValue::String(string)) = self {
Some(string)
} else {
None
}
}
pub fn is_function(&self) -> bool {
matches!(self, Value::Concrete(ConcreteValue::Function(_)))
}
pub fn r#type(&self) -> Type {
match self {
Value::Concrete(data) => data.r#type(),
Value::Abstract(AbstractValue::List {
start,
end,
item_type,
}) => {
let length = (end - start + 1) as usize;
Type::List {
length,
item_type: Box::new(item_type.clone()),
}
}
}
}
pub fn add(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let sum = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => {
Value::byte(left.saturating_add(*right))
}
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left + right),
(Concrete(Integer(left)), Concrete(Integer(right))) => {
Value::integer(left.saturating_add(*right))
}
(Concrete(String(left)), Concrete(String(right))) => {
Value::string(format!("{}{}", left, right))
}
_ => return Err(ValueError::CannotAdd(self.clone(), other.clone())),
};
Ok(sum)
}
pub fn subtract(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let different = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => {
Value::byte(left.saturating_sub(*right))
}
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left - right),
(Concrete(Integer(left)), Concrete(Integer(right))) => {
Value::integer(left.saturating_sub(*right))
}
_ => return Err(ValueError::CannotSubtract(self.clone(), other.clone())),
};
Ok(different)
}
pub fn multiply(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let product = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => {
Value::byte(left.saturating_mul(*right))
}
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left * right),
(Concrete(Integer(left)), Concrete(Integer(right))) => {
Value::integer(left.saturating_mul(*right))
}
_ => return Err(ValueError::CannotAdd(self.clone(), other.clone())),
};
Ok(product)
}
pub fn divide(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let product = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => {
Value::byte(left.saturating_div(*right))
}
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left / right),
(Concrete(Integer(left)), Concrete(Integer(right))) => {
Value::integer(left.saturating_div(*right))
}
_ => return Err(ValueError::CannotDivide(self.clone(), other.clone())),
};
Ok(product)
}
pub fn modulo(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let product = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => Value::byte(left % right),
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left % right),
(Concrete(Integer(left)), Concrete(Integer(right))) => Value::integer(left % right),
_ => return Err(ValueError::CannotModulo(self.clone(), other.clone())),
};
Ok(product)
}
pub fn less_than(&self, other: &Value) -> Result<Value, ValueError> {
let (left, right) = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
};
Ok(Value::boolean(left < right))
}
pub fn less_than_or_equal(&self, other: &Value) -> Result<Value, ValueError> {
let (left, right) = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
};
Ok(Value::boolean(left <= right))
}
pub fn equal(&self, other: &Value) -> Result<Value, ValueError> {
let (left, right) = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
};
Ok(Value::boolean(left == right))
}
pub fn negate(&self) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let negated = match self {
Concrete(Integer(integer)) => Value::integer(-integer),
Concrete(Float(float)) => Value::float(-float),
_ => return Err(ValueError::CannotNot(self.clone())),
};
Ok(negated)
}
pub fn not(&self) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let not = match self {
Concrete(Boolean(boolean)) => Value::boolean(!boolean),
Concrete(Byte(byte)) => Value::byte(!byte),
Concrete(Integer(integer)) => Value::integer(!integer),
_ => return Err(ValueError::CannotNot(self.clone())),
};
Ok(not)
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Value::boolean(value)
}
}
impl From<u8> for Value {
fn from(value: u8) -> Self {
Value::byte(value)
}
}
impl From<char> for Value {
fn from(value: char) -> Self {
Value::character(value)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Value::float(value)
}
}
impl From<i32> for Value {
fn from(value: i32) -> Self {
Value::integer(value as i64)
}
}
impl From<i64> for Value {
fn from(value: i64) -> Self {
Value::integer(value)
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Value::string(value)
}
}
impl From<&str> for Value {
fn from(str: &str) -> Self {
Value::string(str)
}
}
impl Clone for Value {
fn clone(&self) -> Self {
log::trace!("Cloning value {self}");
match self {
Value::Abstract(object) => Value::Abstract(object.clone()),
Value::Concrete(concrete) => Value::Concrete(concrete.clone()),
}
}
}
impl Display for Value {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Value::Abstract(object) => write!(f, "{object}"),
Value::Concrete(concrete) => write!(f, "{concrete}"),
}
}
}
/// Value representation that can be resolved to a concrete value by the VM.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum AbstractValue {
List { start: u8, end: u8, item_type: Type },
}
impl Display for AbstractValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
AbstractValue::List { start, end, .. } => {
write!(f, "List [R{}..=R{}]", start, end)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum ConcreteValue {
Boolean(bool),
Byte(u8),
Character(char),
Float(f64),
Function(Function),
Integer(i64),
List(Vec<Value>),
Range(RangeValue),
String(String),
}
impl ConcreteValue {
pub fn r#type(&self) -> Type {
match self {
ConcreteValue::Boolean(_) => Type::Boolean,
ConcreteValue::Byte(_) => Type::Byte,
ConcreteValue::Character(_) => Type::Character,
ConcreteValue::Float(_) => Type::Float,
ConcreteValue::Function(Function { r#type, .. }) => r#type.clone(),
ConcreteValue::Integer(_) => Type::Integer,
ConcreteValue::List(list) => Type::List {
item_type: list
.first()
.map(|value| Box::new(value.r#type()))
.unwrap_or_else(|| Box::new(Type::Any)),
length: list.len(),
},
ConcreteValue::Range(range) => range.r#type(),
ConcreteValue::String(string) => Type::String {
length: Some(string.len()),
},
}
}
pub fn is_rangeable(&self) -> bool {
matches!(
self,
ConcreteValue::Integer(_)
| ConcreteValue::Float(_)
| ConcreteValue::Character(_)
| ConcreteValue::Byte(_)
)
}
}
impl Display for ConcreteValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ConcreteValue::Boolean(boolean) => write!(f, "{boolean}"),
ConcreteValue::Byte(byte) => write!(f, "0x{byte:02x}"),
ConcreteValue::Character(character) => write!(f, "{character}"),
ConcreteValue::Float(float) => {
write!(f, "{float}")?;
if float.fract() == 0.0 {
write!(f, ".0")?;
}
Ok(())
}
ConcreteValue::Function(Function { r#type, .. }) => {
write!(f, "{}", r#type)
}
ConcreteValue::Integer(integer) => write!(f, "{integer}"),
ConcreteValue::List(items) => {
write!(f, "[")?;
for (index, item) in items.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{item}")?;
}
write!(f, "]")
}
ConcreteValue::Range(range_value) => {
write!(f, "{range_value}")
}
ConcreteValue::String(string) => write!(f, "{string}"),
}
}
}
impl Eq for ConcreteValue {}
impl PartialOrd for ConcreteValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ConcreteValue {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(ConcreteValue::Boolean(left), ConcreteValue::Boolean(right)) => left.cmp(right),
(ConcreteValue::Boolean(_), _) => Ordering::Greater,
(ConcreteValue::Byte(left), ConcreteValue::Byte(right)) => left.cmp(right),
(ConcreteValue::Byte(_), _) => Ordering::Greater,
(ConcreteValue::Character(left), ConcreteValue::Character(right)) => left.cmp(right),
(ConcreteValue::Character(_), _) => Ordering::Greater,
(ConcreteValue::Float(left), ConcreteValue::Float(right)) => {
if left.is_nan() && right.is_nan() {
Ordering::Equal
} else if left.is_nan() {
Ordering::Less
} else if right.is_nan() {
Ordering::Greater
} else {
left.partial_cmp(right).unwrap()
}
}
(ConcreteValue::Float(_), _) => Ordering::Greater,
(ConcreteValue::Function(left), ConcreteValue::Function(right)) => left.cmp(right),
(ConcreteValue::Function(_), _) => Ordering::Greater,
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => left.cmp(right),
(ConcreteValue::Integer(_), _) => Ordering::Greater,
(ConcreteValue::List(left), ConcreteValue::List(right)) => left.cmp(right),
(ConcreteValue::List(_), _) => Ordering::Greater,
(ConcreteValue::Range(left), ConcreteValue::Range(right)) => left.cmp(right),
(ConcreteValue::Range(_), _) => Ordering::Greater,
(ConcreteValue::String(left), ConcreteValue::String(right)) => left.cmp(right),
(ConcreteValue::String(_), _) => Ordering::Greater,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Function {
chunk: Chunk,
r#type: Type,
}
impl Function {
pub fn new(chunk: Chunk, r#type: Type) -> Self {
Self { chunk, r#type }
}
pub fn chunk(&self) -> &Chunk {
&self.chunk
}
pub fn chunk_mut(&mut self) -> &mut Chunk {
&mut self.chunk
}
pub fn take_chunk(self) -> Chunk {
self.chunk
}
pub fn r#type(&self) -> &Type {
&self.r#type
}
}
impl Display for Function {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.r#type)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum RangeValue {
ByteRange(Range<u8>),
ByteRangeInclusive(RangeInclusive<u8>),
CharacterRange(Range<char>),
CharacterRangeInclusive(RangeInclusive<char>),
FloatRange(Range<f64>),
FloatRangeInclusive(RangeInclusive<f64>),
IntegerRange(Range<i64>),
IntegerRangeInclusive(RangeInclusive<i64>),
}
impl RangeValue {
pub fn r#type(&self) -> Type {
let inner_type = match self {
RangeValue::ByteRange(_) => RangeableType::Byte,
RangeValue::ByteRangeInclusive(_) => RangeableType::Byte,
RangeValue::CharacterRange(_) => RangeableType::Character,
RangeValue::CharacterRangeInclusive(_) => RangeableType::Character,
RangeValue::FloatRange(_) => RangeableType::Float,
RangeValue::FloatRangeInclusive(_) => RangeableType::Float,
RangeValue::IntegerRange(_) => RangeableType::Integer,
RangeValue::IntegerRangeInclusive(_) => RangeableType::Integer,
};
Type::Range { r#type: inner_type }
}
}
impl From<Range<u8>> for RangeValue {
fn from(range: Range<u8>) -> Self {
RangeValue::ByteRange(range)
}
}
impl From<RangeInclusive<u8>> for RangeValue {
fn from(range: RangeInclusive<u8>) -> Self {
RangeValue::ByteRangeInclusive(range)
}
}
impl From<Range<char>> for RangeValue {
fn from(range: Range<char>) -> Self {
RangeValue::CharacterRange(range)
}
}
impl From<RangeInclusive<char>> for RangeValue {
fn from(range: RangeInclusive<char>) -> Self {
RangeValue::CharacterRangeInclusive(range)
}
}
impl From<Range<f64>> for RangeValue {
fn from(range: Range<f64>) -> Self {
RangeValue::FloatRange(range)
}
}
impl From<RangeInclusive<f64>> for RangeValue {
fn from(range: RangeInclusive<f64>) -> Self {
RangeValue::FloatRangeInclusive(range)
}
}
impl From<Range<i32>> for RangeValue {
fn from(range: Range<i32>) -> Self {
RangeValue::IntegerRange(range.start as i64..range.end as i64)
}
}
impl From<RangeInclusive<i32>> for RangeValue {
fn from(range: RangeInclusive<i32>) -> Self {
RangeValue::IntegerRangeInclusive(*range.start() as i64..=*range.end() as i64)
}
}
impl From<Range<i64>> for RangeValue {
fn from(range: Range<i64>) -> Self {
RangeValue::IntegerRange(range)
}
}
impl From<RangeInclusive<i64>> for RangeValue {
fn from(range: RangeInclusive<i64>) -> Self {
RangeValue::IntegerRangeInclusive(range)
}
}
impl Display for RangeValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
RangeValue::ByteRange(range) => write!(f, "{}..{}", range.start, range.end),
RangeValue::ByteRangeInclusive(range) => {
write!(f, "{}..={}", range.start(), range.end())
}
RangeValue::CharacterRange(range) => write!(f, "{}..{}", range.start, range.end),
RangeValue::CharacterRangeInclusive(range) => {
write!(f, "{}..={}", range.start(), range.end())
}
RangeValue::FloatRange(range) => write!(f, "{}..{}", range.start, range.end),
RangeValue::FloatRangeInclusive(range) => {
write!(f, "{}..={}", range.start(), range.end())
}
RangeValue::IntegerRange(range) => write!(f, "{}..{}", range.start, range.end),
RangeValue::IntegerRangeInclusive(range) => {
write!(f, "{}..={}", range.start(), range.end())
}
}
}
}
impl Eq for RangeValue {}
impl PartialOrd for RangeValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RangeValue {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(RangeValue::ByteRange(left), RangeValue::ByteRange(right)) => {
let start_cmp = left.start.cmp(&right.start);
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end.cmp(&right.end)
}
}
(RangeValue::ByteRange(_), _) => Ordering::Greater,
(RangeValue::ByteRangeInclusive(left), RangeValue::ByteRangeInclusive(right)) => {
let start_cmp = left.start().cmp(right.start());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end().cmp(right.end())
}
}
(RangeValue::ByteRangeInclusive(_), _) => Ordering::Greater,
(RangeValue::CharacterRange(left), RangeValue::CharacterRange(right)) => {
let start_cmp = left.start.cmp(&right.start);
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end.cmp(&right.end)
}
}
(RangeValue::CharacterRange(_), _) => Ordering::Greater,
(
RangeValue::CharacterRangeInclusive(left),
RangeValue::CharacterRangeInclusive(right),
) => {
let start_cmp = left.start().cmp(right.start());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end().cmp(right.end())
}
}
(RangeValue::CharacterRangeInclusive(_), _) => Ordering::Greater,
(RangeValue::FloatRange(left), RangeValue::FloatRange(right)) => {
let start_cmp = left.start.to_bits().cmp(&right.start.to_bits());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end.to_bits().cmp(&right.end.to_bits())
}
}
(RangeValue::FloatRange(_), _) => Ordering::Greater,
(RangeValue::FloatRangeInclusive(left), RangeValue::FloatRangeInclusive(right)) => {
let start_cmp = left.start().to_bits().cmp(&right.start().to_bits());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end().to_bits().cmp(&right.end().to_bits())
}
}
(RangeValue::FloatRangeInclusive(_), _) => Ordering::Greater,
(RangeValue::IntegerRange(left), RangeValue::IntegerRange(right)) => {
let start_cmp = left.start.cmp(&right.start);
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end.cmp(&right.end)
}
}
(RangeValue::IntegerRange(_), _) => Ordering::Greater,
(RangeValue::IntegerRangeInclusive(left), RangeValue::IntegerRangeInclusive(right)) => {
let start_cmp = left.start().cmp(right.start());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end().cmp(right.end())
}
}
(RangeValue::IntegerRangeInclusive(_), _) => Ordering::Greater,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ValueError {
CannotAdd(Value, Value),
CannotAnd(Value, Value),
CannotCompare(Value, Value),
CannotDivide(Value, Value),
CannotModulo(Value, Value),
CannotMultiply(Value, Value),
CannotNegate(Value),
CannotNot(Value),
CannotSubtract(Value, Value),
CannotOr(Value, Value),
}
impl Display for ValueError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ValueError::CannotAdd(left, right) => {
write!(f, "Cannot add {left} and {right}")
}
ValueError::CannotAnd(left, right) => {
write!(f, "Cannot use logical AND operation on {left} and {right}")
}
ValueError::CannotCompare(left, right) => {
write!(f, "Cannot compare {left} and {right}")
}
ValueError::CannotDivide(left, right) => {
write!(f, "Cannot divide {left} by {right}")
}
ValueError::CannotModulo(left, right) => {
write!(f, "Cannot use modulo operation on {left} and {right}")
}
ValueError::CannotMultiply(left, right) => {
write!(f, "Cannot multiply {left} by {right}")
}
ValueError::CannotNegate(value) => {
write!(f, "Cannot negate {value}")
}
ValueError::CannotNot(value) => {
write!(f, "Cannot use logical NOT operation on {value}")
}
ValueError::CannotSubtract(left, right) => {
write!(f, "Cannot subtract {right} from {left}")
}
ValueError::CannotOr(left, right) => {
write!(f, "Cannot use logical OR operation on {left} and {right}")
}
}
}
}

765
dust-lang/src/vm.rs Normal file
View File

@ -0,0 +1,765 @@
//! Virtual machine and errors
use std::{
cmp::Ordering,
fmt::{self, Display, Formatter},
ops::Range,
};
use crate::{
compile, value::ConcreteValue, AnnotatedError, Chunk, ChunkError, DustError, FunctionType,
Instruction, Local, NativeFunction, NativeFunctionError, Operation, Span, Type, Value,
ValueError,
};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
let mut chunk = compile(source)?;
let mut vm = Vm::new(&mut chunk, None);
vm.run()
.map(|option| option.cloned())
.map_err(|error| DustError::Runtime { error, source })
}
pub fn run_and_display_output(source: &str) {
match run(source) {
Ok(Some(value)) => println!("{}", value),
Ok(None) => {}
Err(error) => eprintln!("{}", error.report()),
}
}
/// Dust virtual machine.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Debug, Eq, PartialEq)]
pub struct Vm<'chunk, 'parent> {
ip: usize,
chunk: &'chunk mut Chunk,
stack: Vec<Register>,
last_assigned_register: Option<u8>,
parent: Option<&'parent Vm<'chunk, 'parent>>,
}
impl<'chunk, 'parent> Vm<'chunk, 'parent> {
const STACK_LIMIT: usize = u16::MAX as usize;
pub fn new(chunk: &'chunk mut Chunk, parent: Option<&'parent Vm<'chunk, 'parent>>) -> Self {
Self {
ip: 0,
chunk,
stack: Vec::new(),
last_assigned_register: None,
parent,
}
}
pub fn run(&mut self) -> Result<Option<&Value>, VmError> {
// DRY helper to get constant or register values for binary operations
fn get_arguments<'a>(
vm: &'a mut Vm,
instruction: Instruction,
position: Span,
) -> Result<(&'a Value, &'a Value), VmError> {
let left = if instruction.b_is_constant() {
vm.get_constant(instruction.b(), position)?
} else {
vm.open_register(instruction.b(), position)?
};
let right = if instruction.c_is_constant() {
vm.get_constant(instruction.c(), position)?
} else {
vm.open_register(instruction.c(), position)?
};
Ok((left, right))
}
while let Ok((instruction, position)) = self.read(Span(0, 0)).copied() {
log::info!(
"{} | {} | {} | {}",
self.ip - 1,
position,
instruction.operation(),
instruction.disassembly_info(self.chunk)
);
match instruction.operation() {
Operation::Move => {
let to_register = instruction.a();
let from_register = instruction.b();
let from_register_has_value = self
.stack
.get(from_register as usize)
.is_some_and(|register| !matches!(register, Register::Empty));
if from_register_has_value {
self.set_register(
to_register,
Register::StackPointer(from_register),
position,
)?;
}
}
Operation::Close => {
let from_register = instruction.b();
let to_register = instruction.c();
if self.stack.len() < to_register as usize {
return Err(VmError::StackUnderflow { position });
}
for register_index in from_register..to_register {
self.stack[register_index as usize] = Register::Empty;
}
}
Operation::LoadBoolean => {
let to_register = instruction.a();
let boolean = instruction.b_as_boolean();
let jump = instruction.c_as_boolean();
let value = Value::boolean(boolean);
self.set_register(to_register, Register::Value(value), position)?;
if jump {
self.ip += 1;
}
}
Operation::LoadConstant => {
let to_register = instruction.a();
let from_constant = instruction.b();
let jump = instruction.c_as_boolean();
self.set_register(
to_register,
Register::ConstantPointer(from_constant),
position,
)?;
if jump {
self.ip += 1
}
}
Operation::LoadList => {
let to_register = instruction.a();
let start_register = instruction.b();
let item_type = (start_register..to_register)
.find_map(|register_index| {
if let Ok(value) = self.open_register(register_index, position) {
Some(value.r#type())
} else {
None
}
})
.unwrap_or(Type::Any);
let value = Value::abstract_list(start_register, to_register, item_type);
self.set_register(to_register, Register::Value(value), position)?;
}
Operation::LoadSelf => {
let to_register = instruction.a();
let value = Value::function(
self.chunk.clone(),
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: None,
},
);
self.set_register(to_register, Register::Value(value), position)?;
}
Operation::DefineLocal => {
let from_register = instruction.a();
let to_local = instruction.b();
self.define_local(to_local, from_register, position)?;
}
Operation::GetLocal => {
let to_register = instruction.a();
let local_index = instruction.b();
let local = self.get_local(local_index, position)?;
self.set_register(
to_register,
Register::StackPointer(local.register_index),
position,
)?;
}
Operation::SetLocal => {
let register = instruction.a();
let local_index = instruction.b();
self.define_local(local_index, register, position)?;
}
Operation::Add => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let sum = left
.add(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(sum), position)?;
}
Operation::Subtract => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let difference = left
.subtract(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(difference), position)?;
}
Operation::Multiply => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let product = left
.multiply(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(product), position)?;
}
Operation::Divide => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let quotient = left
.divide(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(quotient), position)?;
}
Operation::Modulo => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let remainder = left
.modulo(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(remainder), position)?;
}
Operation::Test => {
let register = instruction.a();
let test_value = instruction.c_as_boolean();
let value = self.open_register(register, position)?;
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
return Err(VmError::ExpectedBoolean {
found: value.clone(),
position,
});
};
if boolean != test_value {
self.ip += 1;
}
}
Operation::TestSet => todo!(),
Operation::Equal => {
debug_assert_eq!(
self.get_instruction(self.ip, position)?.0.operation(),
Operation::Jump
);
let (left, right) = get_arguments(self, instruction, position)?;
let equal_result = left
.equal(right)
.map_err(|error| VmError::Value { error, position })?;
let boolean =
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = equal_result {
boolean
} else {
return Err(VmError::ExpectedBoolean {
found: equal_result.clone(),
position,
});
};
let compare_to = instruction.a_as_boolean();
if boolean == compare_to {
self.ip += 1;
} else {
let (jump, _) = self.get_instruction(self.ip, position)?;
let jump_distance = jump.a();
let is_positive = jump.b_as_boolean();
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize
};
self.ip = new_ip;
}
}
Operation::Less => {
debug_assert_eq!(
self.get_instruction(self.ip, position)?.0.operation(),
Operation::Jump
);
let (left, right) = get_arguments(self, instruction, position)?;
let less_result = left
.less_than(right)
.map_err(|error| VmError::Value { error, position })?;
let boolean =
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = less_result {
boolean
} else {
return Err(VmError::ExpectedBoolean {
found: less_result.clone(),
position,
});
};
let compare_to = instruction.a_as_boolean();
if boolean == compare_to {
self.ip += 1;
} else {
let jump = self.get_instruction(self.ip, position)?.0;
let jump_distance = jump.a();
let is_positive = jump.b_as_boolean();
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize
};
self.ip = new_ip;
}
}
Operation::LessEqual => {
debug_assert_eq!(
self.get_instruction(self.ip, position)?.0.operation(),
Operation::Jump
);
let (left, right) = get_arguments(self, instruction, position)?;
let less_or_equal_result = left
.less_than_or_equal(right)
.map_err(|error| VmError::Value { error, position })?;
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) =
less_or_equal_result
{
boolean
} else {
return Err(VmError::ExpectedBoolean {
found: less_or_equal_result.clone(),
position,
});
};
let compare_to = instruction.a_as_boolean();
if boolean == compare_to {
self.ip += 1;
} else {
let jump = self.get_instruction(self.ip, position)?.0;
let jump_distance = jump.a();
let is_positive = jump.b_as_boolean();
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize
};
self.ip = new_ip;
}
}
Operation::Negate => {
let value = if instruction.b_is_constant() {
self.get_constant(instruction.b(), position)?
} else {
self.open_register(instruction.b(), position)?
};
let negated = value
.negate()
.map_err(|error| VmError::Value { error, position })?;
self.set_register(instruction.a(), Register::Value(negated), position)?;
}
Operation::Not => {
let value = if instruction.b_is_constant() {
self.get_constant(instruction.b(), position)?
} else {
self.open_register(instruction.b(), position)?
};
let not = value
.not()
.map_err(|error| VmError::Value { error, position })?;
self.set_register(instruction.a(), Register::Value(not), position)?;
}
Operation::Jump => {
let jump_distance = instruction.b();
let is_positive = instruction.c_as_boolean();
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize - 1
};
self.ip = new_ip;
}
Operation::Call => {
let to_register = instruction.a();
let function_register = instruction.b();
let argument_count = instruction.c();
let value = self.open_register(function_register, position)?.clone();
let mut function =
if let Value::Concrete(ConcreteValue::Function(function)) = value {
function
} else {
return Err(VmError::ExpectedFunction {
found: value,
position,
});
};
let mut function_vm = Vm::new(function.chunk_mut(), Some(self));
let first_argument_index = function_register + 1;
for argument_index in
first_argument_index..first_argument_index + argument_count
{
let top_of_stack = function_vm.stack.len() as u8;
function_vm.set_register(
top_of_stack,
Register::ParentStackPointer(argument_index),
position,
)?
}
let return_value = function_vm.run()?.cloned();
if let Some(value) = return_value {
self.set_register(to_register, Register::Value(value), position)?;
}
}
Operation::CallNative => {
let native_function = NativeFunction::from(instruction.b());
let return_value = native_function.call(instruction, self, position)?;
if let Some(value) = return_value {
let to_register = instruction.a();
self.set_register(to_register, Register::Value(value), position)?;
}
}
Operation::Return => {
let should_return_value = instruction.b_as_boolean();
if !should_return_value {
return Ok(None);
}
let return_value = if let Some(register_index) = self.last_assigned_register {
self.open_register(register_index, position)?
} else {
return Err(VmError::StackUnderflow { position });
};
return Ok(Some(return_value));
}
}
}
Ok(None)
}
fn set_register(
&mut self,
to_register: u8,
register: Register,
position: Span,
) -> Result<(), VmError> {
self.last_assigned_register = Some(to_register);
let length = self.stack.len();
let to_register = to_register as usize;
if length == Self::STACK_LIMIT {
return Err(VmError::StackOverflow { position });
}
match to_register.cmp(&length) {
Ordering::Less => {
log::trace!("Change R{to_register} to {register}");
self.stack[to_register] = register;
Ok(())
}
Ordering::Equal => {
log::trace!("Set R{to_register} to {register}");
self.stack.push(register);
Ok(())
}
Ordering::Greater => {
let difference = to_register - length;
for index in 0..difference {
log::trace!("Set R{index} to {register}");
self.stack.push(Register::Empty);
}
log::trace!("Set R{to_register} to {register}");
self.stack.push(register);
Ok(())
}
}
}
fn get_constant(&self, index: u8, position: Span) -> Result<&Value, VmError> {
self.chunk
.get_constant(index)
.map_err(|error| VmError::Chunk { error, position })
}
pub fn open_register(&self, register_index: u8, position: Span) -> Result<&Value, VmError> {
let register_index = register_index as usize;
let register =
self.stack
.get(register_index)
.ok_or_else(|| VmError::RegisterIndexOutOfBounds {
index: register_index,
position,
})?;
match register {
Register::Value(value) => Ok(value),
Register::StackPointer(register_index) => self.open_register(*register_index, position),
Register::ConstantPointer(constant_index) => {
self.get_constant(*constant_index, position)
}
Register::ParentStackPointer(register_index) => {
let parent = self
.parent
.as_ref()
.ok_or(VmError::ExpectedParent { position })?;
parent.open_register(*register_index, position)
}
Register::ParentConstantPointer(constant_index) => {
let parent = self
.parent
.as_ref()
.ok_or(VmError::ExpectedParent { position })?;
parent.get_constant(*constant_index, position)
}
Register::Empty => Err(VmError::EmptyRegister {
index: register_index,
position,
}),
}
}
pub fn open_nonempty_registers(
&self,
register_index_range: Range<u8>,
position: Span,
) -> Result<Vec<&Value>, VmError> {
let mut values = Vec::with_capacity(register_index_range.len());
for register_index in register_index_range.clone() {
let register_index = register_index as usize;
let register = self.stack.get(register_index).ok_or_else(|| {
VmError::RegisterIndexOutOfBounds {
index: register_index,
position,
}
})?;
let value = match register {
Register::Value(value) => value,
Register::StackPointer(register_index) => {
self.open_register(*register_index, position)?
}
Register::ConstantPointer(constant_index) => {
self.get_constant(*constant_index, position)?
}
Register::ParentStackPointer(register_index) => {
let parent = self
.parent
.as_ref()
.ok_or(VmError::ExpectedParent { position })?;
parent.open_register(*register_index, position)?
}
Register::ParentConstantPointer(constant_index) => {
let parent = self
.parent
.as_ref()
.ok_or(VmError::ExpectedParent { position })?;
parent.get_constant(*constant_index, position)?
}
Register::Empty => continue,
};
values.push(value);
}
if values.is_empty() {
Err(VmError::EmptyRegisters {
indexes: register_index_range,
position,
})
} else {
Ok(values)
}
}
fn read(&mut self, position: Span) -> Result<&(Instruction, Span), VmError> {
self.chunk
.expect_not_poisoned()
.map_err(|error| VmError::Chunk { error, position })?;
let max_ip = self.chunk.len() - 1;
if self.ip > max_ip {
return self.get_instruction(max_ip, position);
} else {
self.ip += 1;
}
self.get_instruction(self.ip - 1, position)
}
fn define_local(
&mut self,
local_index: u8,
register_index: u8,
position: Span,
) -> Result<(), VmError> {
let local = self
.chunk
.get_local_mut(local_index)
.map_err(|error| VmError::Chunk { error, position })?;
log::debug!("Define local L{}", local_index);
local.register_index = register_index;
Ok(())
}
fn get_local(&self, local_index: u8, position: Span) -> Result<&Local, VmError> {
self.chunk
.get_local(local_index)
.map_err(|error| VmError::Chunk { error, position })
}
fn get_instruction(
&self,
index: usize,
position: Span,
) -> Result<&(Instruction, Span), VmError> {
self.chunk
.get_instruction(index)
.map_err(|error| VmError::Chunk { error, position })
}
}
#[derive(Debug, Eq, PartialEq)]
enum Register {
Empty,
Value(Value),
StackPointer(u8),
ConstantPointer(u8),
ParentStackPointer(u8),
ParentConstantPointer(u8),
}
impl Display for Register {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Empty => write!(f, "empty"),
Self::Value(value) => write!(f, "{}", value),
Self::StackPointer(index) => write!(f, "R{}", index),
Self::ConstantPointer(index) => write!(f, "C{}", index),
Self::ParentStackPointer(index) => write!(f, "PR{}", index),
Self::ParentConstantPointer(index) => write!(f, "PC{}", index),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum VmError {
// Stack errors
StackOverflow { position: Span },
StackUnderflow { position: Span },
// Register errors
EmptyRegister { index: usize, position: Span },
EmptyRegisters { indexes: Range<u8>, position: Span },
RegisterIndexOutOfBounds { index: usize, position: Span },
// Execution errors
ExpectedBoolean { found: Value, position: Span },
ExpectedFunction { found: Value, position: Span },
ExpectedParent { position: Span },
// Wrappers for foreign errors
Chunk { error: ChunkError, position: Span },
NativeFunction(NativeFunctionError),
Value { error: ValueError, position: Span },
}
impl AnnotatedError for VmError {
fn title() -> &'static str {
"Runtime Error"
}
fn description(&self) -> &'static str {
match self {
Self::Chunk { .. } => "Chunk error",
Self::EmptyRegister { .. } => "Empty register",
Self::EmptyRegisters { .. } => "Empty registers",
Self::ExpectedBoolean { .. } => "Expected boolean",
Self::ExpectedFunction { .. } => "Expected function",
Self::ExpectedParent { .. } => "Expected parent",
Self::NativeFunction(error) => error.description(),
Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds",
Self::StackOverflow { .. } => "Stack overflow",
Self::StackUnderflow { .. } => "Stack underflow",
Self::Value { .. } => "Value error",
}
}
fn details(&self) -> Option<String> {
match self {
Self::Chunk { error, .. } => Some(error.to_string()),
Self::EmptyRegister { index, .. } => Some(format!("Register R{index} is empty")),
Self::EmptyRegisters { indexes: range, .. } => Some(format!(
"Registers R{} to R{} are empty",
range.start, range.end
)),
Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")),
Self::RegisterIndexOutOfBounds { index, .. } => {
Some(format!("Register {index} does not exist"))
}
Self::NativeFunction(error) => error.details(),
Self::Value { error, .. } => Some(error.to_string()),
_ => None,
}
}
fn position(&self) -> Span {
match self {
Self::Chunk { position, .. } => *position,
Self::EmptyRegister { position, .. } => *position,
Self::EmptyRegisters { position, .. } => *position,
Self::ExpectedBoolean { position, .. } => *position,
Self::ExpectedFunction { position, .. } => *position,
Self::ExpectedParent { position } => *position,
Self::NativeFunction(error) => error.position(),
Self::RegisterIndexOutOfBounds { position, .. } => *position,
Self::StackOverflow { position } => *position,
Self::StackUnderflow { position } => *position,
Self::Value { position, .. } => *position,
}
}
}

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"))))
}

244
dust-lang/tests/scopes.rs Normal file
View File

@ -0,0 +1,244 @@
use dust_lang::*;
#[test]
fn allow_access_to_parent_scope() {
let source = r#"
let x = 1;
{
x
}
"#;
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn block_scope() {
let source = "
let a = 0;
{
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, 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!(
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
position: Span(52, 53),
variable_scope: Scope::new(1, 1),
access_scope: Scope::new(0, 0),
},
source
})
);
}
#[test]
fn disallow_access_to_child_scope_nested() {
let source = r#"
{
{
let x = 1;
}
x
}
"#;
assert_eq!(
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
position: Span(78, 79),
variable_scope: Scope::new(2, 2),
access_scope: Scope::new(1, 1),
},
source
})
);
}
#[test]
fn disallow_access_to_sibling_scope() {
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(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))));
}

15
dust-shell/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "dust-shell"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
[dependencies]
clap = { version = "4.5.14", features = ["derive"] }
colored = "2.1.0"
dust-lang = { path = "../dust-lang" }
env_logger = "0.11.5"
log = "0.4.22"

138
dust-shell/src/main.rs Normal file
View File

@ -0,0 +1,138 @@
use std::{fs::read_to_string, io::Write};
use clap::Parser;
use colored::Colorize;
use dust_lang::{compile, format, run};
use log::{Level, LevelFilter};
#[derive(Parser)]
struct Cli {
/// Source code sent via command line
#[arg(short, long)]
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)]
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>,
}
fn main() {
let args = Cli::parse();
let mut logger = env_logger::builder();
logger.format(|buf, record| {
let level_display = match record.level() {
Level::Info => "INFO".bold().white(),
Level::Debug => "DEBUG".bold().blue(),
Level::Warn => "WARN".bold().yellow(),
Level::Error => "ERROR".bold().red(),
Level::Trace => "TRACE".bold().purple(),
};
let 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());
writeln!(buf, "{display}")
});
if let Some(level) = args.log {
logger.filter_level(level).init();
} else {
logger.parse_env("DUST_LOG").init();
}
let source = if let Some(path) = &args.path {
&read_to_string(path).expect("Failed to read file")
} else if let Some(command) = &args.command {
command
} else {
eprintln!("No input provided");
return;
};
if args.format {
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());
}
}
}
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,6 @@
[
{ "name": "Sammy", "type": "shark", "clams": 5 },
{ "name": "Bubbles", "type": "orca", "clams": 3 },
{ "name": "Splish", "type": "dolphin", "clams": 2 },
{ "name": "Splash", "type": "dolphin", "clams": 2 }
]

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));

View File

@ -1,19 +0,0 @@
create_random_numbers = (count <int>) <none> {
numbers = []
while length(numbers) < count {
numbers += random:integer()
}
output("Made " + length(numbers) + " numbers.")
}
output("This will print first.")
async {
create_random_numbers(1000)
create_random_numbers(100)
create_random_numbers(10)
}
output("This will print last.")

21
examples/async_count.ds Normal file
View File

@ -0,0 +1,21 @@
count_slowly = fn (
multiplier: int,
) {
i = 0
while i < 10 {
sleep_time = i * multiplier;
thread.sleep(sleep_time)
thread.write_line(i as str)
i += 1
}
}
async {
count_slowly(50)
count_slowly(100)
count_slowly(200)
count_slowly(250)
}

View File

@ -1,11 +0,0 @@
cast = download("https://api.sampleapis.com/futurama/cast")
characters = download("https://api.sampleapis.com/futurama/characters")
episodes = download("https://api.sampleapis.com/futurama/episodes")
async {
cast_len = length(from_json(cast))
characters_len = length(from_json(characters))
episodes_len = length(from_json(episodes))
}
output ([cast_len, characters_len, episodes_len])

View File

@ -1,54 +0,0 @@
cards = {
rooms = ['Library' 'Kitchen' 'Conservatory']
suspects = ['White' 'Green' 'Scarlett']
weapons = ['Rope' 'Lead_Pipe' 'Knife']
}
is_ready_to_solve = (cards <map>) <bool> {
(length(cards:suspects) == 1)
&& (length(cards:rooms) == 1)
&& (length(cards:weapons) == 1)
}
remove_card = (cards <map>, opponent_card <str>) <none> {
cards:rooms -= opponent_card
cards:suspects -= opponent_card
cards:weapons -= opponent_card
}
make_guess = (cards <map>, current_room <str>) <none> {
if is_ready_to_solve(cards) {
output(
'I accuse '
+ cards:suspects:0
+ ' in the '
+ cards:rooms:0
+ ' with the '
+ cards:weapons:0
+ '!'
)
} else {
output(
'I question '
+ random:from(cards:suspects)
+ ' in the '
+ current_room
+ ' with the '
+ random:from(cards:weapons)
+ '.'
)
}
}
take_turn = (cards <map>, opponent_card <str>, current_room <str>) <none> {
remove_card(cards opponent_card)
make_guess(cards current_room)
}
take_turn(cards 'Rope' 'Kitchen')
take_turn(cards 'Library' 'Kitchen')
take_turn(cards 'Conservatory' 'Kitchen')
take_turn(cards 'White' 'Kitchen')
take_turn(cards 'Green' 'Kitchen')
take_turn(cards 'Knife' 'Kitchen')

5
examples/count.ds Normal file
View File

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

View File

@ -1,9 +0,0 @@
cast = (download "https://api.sampleapis.com/futurama/cast")
characters = (download "https://api.sampleapis.com/futurama/characters")
episodes = (download "https://api.sampleapis.com/futurama/episodes")
cast_len = (length (from_json cast))
characters_len = (length (from_json characters))
episodes_len = (length (from_json episodes))
(output [cast_len, characters_len, episodes_len])

View File

@ -1,10 +0,0 @@
raw_data = download("https://api.sampleapis.com/futurama/cast")
cast_data = from_json(raw_data)
names = []
for cast_member in cast_data {
names += cast_member:name
}
assert_equal("Billy West", names:0)

View File

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

View File

@ -1,18 +1,20 @@
count = 1
let mut count = 1
while count <= 15 {
divides_by_3 = count % 3 == 0
divides_by_5 = count % 5 == 0
let divides_by_3 = count % 3 == 0
let divides_by_5 = count % 5 == 0
if divides_by_3 && divides_by_5 {
output('fizzbuzz')
let output = if divides_by_3 && divides_by_5 {
"fizzbuzz"
} else if divides_by_3 {
output('fizz')
"fizz"
} else if divides_by_5 {
output('buzz')
"buzz"
} else {
output(count)
to_string(count)
}
write_line(output)
count += 1
}

View File

@ -1,6 +0,0 @@
list = [1 2 3]
for i in list {
i += 1
output(i)
}

19
examples/guessing_game.ds Normal file
View File

@ -0,0 +1,19 @@
write_line("Guess the number.")
let secret_number = random(0..100);
loop {
write_line("Input your guess.")
let input = io.read_line();
let guess = int.parse(input);
if guess < secret_number {
io.write_line("Too low!")
} else if guess > secret_number {
io.write_line("Too high!")
} else {
io.write_line("You win!")
break
}
}

View File

@ -1 +1,6 @@
output('Hello, world!')
write_line("Hello, world!")
write_line("Enter your name...")
let name = read_line()
write_line("Hello " + name + "!")

View File

@ -1,12 +0,0 @@
data = json:parse(fs:read('examples/assets/jq_data.json'))
new_data = []
for commit_data in data {
new_data += {
message = commit_data:commit:message
name = commit_data:commit:committer:name
}
}
new_data

4
examples/json_length.ds Normal file
View File

@ -0,0 +1,4 @@
input = fs.read_file('examples/assets/data.json')
data = json.parse(input)
length(data)

View File

@ -1,9 +0,0 @@
numbers = [1, 2, 3]
x = numbers:0
y = numbers:1
z = numbers:2
assert_equal(x + y, z)
numbers

View File

@ -1,11 +0,0 @@
dictionary = {
dust = "awesome"
answer = 42
}
output(
'Dust is '
+ dictionary:dust
+ '! The answer is '
+ dictionary:answer
)

View File

@ -1,12 +0,0 @@
foo_or_bar = match random:boolean() {
true => "foo"
false => "bar"
}
num = match random:integer() {
1 => "one",
2 => { "two" },
* => "neither",
}
[foo_or_bar, num]

View File

@ -1,22 +0,0 @@
create_user = (fn email <str>, name <option(str)>) <map> {
{
email = email
username = (either_or name email)
}
}
(assert_equal
{
email = "bob@example.com"
username = "bob"
},
(create_user "bob@example.com" some("bob"))
)
(assert_equal
{
email = "sue@example.com"
username = "sue@example.com"
},
(create_user "sue@example.com" none)
)

View File

@ -1,13 +0,0 @@
stuff = [
random:integer()
random:integer()
random:integer()
random:float()
random:float()
random:float()
random:boolean()
random:boolean()
random:boolean()
]
random:from(stuff)

View File

@ -1,19 +0,0 @@
raw_data = fs:read('examples/assets/seaCreatures.json')
sea_creatures = json:parse(raw_data)
data = {
creatures = []
total_clams = 0
dolphin_clams = 0
}
for creature in sea_creatures {
data:creatures += creature:name
data:total_clams += creature:clams
if creature:type == 'dolphin' {
data:dolphin_clams += creature:clams
}
}
data

View File

@ -1,3 +0,0 @@
handle = thread:spawn("my_thread", {
})

View File

@ -0,0 +1,20 @@
// This function returns its argument.
foo = fn <T>(x: T) -> T { x }
// Use turbofish to supply type information.
bar = foo::<str>("hi")
// Use type annotation
baz: str = foo("hi")
// The `json.parse` function takes a string and returns the specified type
// Use turbofish
x = json.parse::<int>("1")
// Use type annotation
x: int = json.parse("1")
x: int = {
json.parse("1")
}

View File

@ -1,10 +0,0 @@
x = 1
y = "hello dust!"
z = 42.0
list = [3, 2, x]
big_list = [x, y, z, list]
foo = {
x = "bar"
y = 42
z = 0
}

View File

@ -1,6 +0,0 @@
i = 0
while i < 10 {
output(i)
i += 1
}

View File

@ -1,13 +0,0 @@
add_one = (numbers <[int]>) <[int]> {
new_numbers = []
for number in numbers {
new_numbers += number + 1
}
new_numbers
}
foo = [1, 2, 3] -> add_one
assert_equal([2 3 4] foo)

View File

@ -1,35 +0,0 @@
#!/usr/bin/fish
# This script is has the following prerequisites (aside from fish):
# - hyperfine
# - dust (can be installed with "cargo install dust-lang")
# - jq
# - nodejs
# - nushell
# - dielectron.json (can be downloaded from https://opendata.cern.ch/record/304)
hyperfine \
--shell none \
--parameter-list data_path examples/assets/seaCreatures.json \
--warmup 3 \
"dust -c 'length(json:parse(input))' -p {data_path}" \
"jq 'length' {data_path}" \
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
"nu -c 'open {data_path} | length'"
hyperfine \
--shell none \
--parameter-list data_path examples/assets/jq_data.json \
--warmup 3 \
"dust -c 'length(json:parse(input))' -p {data_path}" \
"jq 'length' {data_path}" \
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
"nu -c 'open {data_path} | length'"
hyperfine \
--shell none \
--parameter-list data_path dielectron.json \
--warmup 3 \
"dust -c 'length(json:parse(input))' -p {data_path}" \
"jq 'length' {data_path}" \
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
"nu -c 'open {data_path} | length'"

View File

@ -1,8 +0,0 @@
#!/usr/bin/fish
# Build the project in debug mode.
cd tree-sitter-dust/
tree-sitter generate --debug-build --no-bindings
cd ..
cargo build

View File

@ -1,8 +0,0 @@
#!/bin/fish
# Build the project in release mode.
cd tree-sitter-dust/
tree-sitter generate --no-bindings
cd ..
cargo build --release

View File

@ -1,9 +0,0 @@
#!/usr/bin/fish
# Build the project in debug mode.
cd tree-sitter-dust/
tree-sitter generate --debug-build --no-bindings
tree-sitter test
cd ..
cargo test

View File

@ -1,155 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, AssignmentOperator, Error, Format, Identifier, Map, Result, Statement,
SyntaxNode, SyntaxPosition, Type, TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Assignment {
identifier: Identifier,
type_specification: Option<TypeSpecification>,
operator: AssignmentOperator,
statement: Statement,
syntax_position: SyntaxPosition,
}
impl AbstractTree for Assignment {
fn from_syntax(syntax_node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "assignment", syntax_node)?;
let child_count = syntax_node.child_count();
let identifier_node = syntax_node.child(0).unwrap();
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
let type_node = syntax_node.child(1).unwrap();
let type_specification = if type_node.kind() == "type_specification" {
Some(TypeSpecification::from_syntax(type_node, source, context)?)
} else {
None
};
let operator_node = syntax_node.child(child_count - 2).unwrap();
let operator = AssignmentOperator::from_syntax(operator_node, source, context)?;
let statement_node = syntax_node.child(child_count - 1).unwrap();
let statement = Statement::from_syntax(statement_node, source, context)?;
if let AssignmentOperator::Equal = operator {
let r#type = if let Some(definition) = &type_specification {
definition.inner().clone()
} else {
statement.expected_type(context)?
};
context.set_type(identifier.inner().clone(), r#type)?;
}
Ok(Assignment {
identifier,
type_specification,
operator,
statement,
syntax_position: syntax_node.range().into(),
})
}
fn check_type(&self, source: &str, context: &Map) -> Result<()> {
let actual_type = self.statement.expected_type(context)?;
if let Some(type_specification) = &self.type_specification {
match self.operator {
AssignmentOperator::Equal => {
type_specification
.inner()
.check(&actual_type)
.map_err(|error| error.at_source_position(source, self.syntax_position))?;
}
AssignmentOperator::PlusEqual => {
if let Type::List(item_type) = type_specification.inner() {
item_type.check(&actual_type).map_err(|error| {
error.at_source_position(source, self.syntax_position)
})?;
} else {
type_specification
.inner()
.check(&self.identifier.expected_type(context)?)
.map_err(|error| {
error.at_source_position(source, self.syntax_position)
})?;
}
}
AssignmentOperator::MinusEqual => todo!(),
}
} else {
match self.operator {
AssignmentOperator::Equal => {}
AssignmentOperator::PlusEqual => {
if let Type::List(item_type) = self.identifier.expected_type(context)? {
item_type.check(&actual_type).map_err(|error| {
error.at_source_position(source, self.syntax_position)
})?;
}
}
AssignmentOperator::MinusEqual => todo!(),
}
}
self.statement
.check_type(source, context)
.map_err(|error| error.at_source_position(source, self.syntax_position))?;
Ok(())
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let key = self.identifier.inner();
let value = self.statement.run(source, context)?;
let new_value = match self.operator {
AssignmentOperator::PlusEqual => {
if let Some((mut previous_value, _)) = context.variables()?.get(key).cloned() {
previous_value += value;
previous_value
} else {
return Err(Error::VariableIdentifierNotFound(key.clone()));
}
}
AssignmentOperator::MinusEqual => {
if let Some((mut previous_value, _)) = context.variables()?.get(key).cloned() {
previous_value -= value;
previous_value
} else {
return Err(Error::VariableIdentifierNotFound(key.clone()));
}
}
AssignmentOperator::Equal => value,
};
context.set(key.clone(), new_value)?;
Ok(Value::none())
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(Type::None)
}
}
impl Format for Assignment {
fn format(&self, output: &mut String, indent_level: u8) {
self.identifier.format(output, indent_level);
if let Some(type_specification) = &self.type_specification {
type_specification.format(output, indent_level);
}
output.push(' ');
self.operator.format(output, indent_level);
output.push(' ');
self.statement.format(output, 0);
}
}

View File

@ -1,51 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Error, Format, Map, Result, SyntaxNode, Type, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum AssignmentOperator {
Equal,
PlusEqual,
MinusEqual,
}
impl AbstractTree for AssignmentOperator {
fn from_syntax(node: SyntaxNode, source: &str, _context: &crate::Map) -> Result<Self> {
Error::expect_syntax_node(source, "assignment_operator", node)?;
let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() {
"=" => AssignmentOperator::Equal,
"+=" => AssignmentOperator::PlusEqual,
"-=" => AssignmentOperator::MinusEqual,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "=, += or -=".to_string(),
actual: operator_node.kind().to_string(),
location: operator_node.start_position(),
relevant_source: source[operator_node.byte_range()].to_string(),
})
}
};
Ok(operator)
}
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
Ok(Value::none())
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(Type::None)
}
}
impl Format for AssignmentOperator {
fn format(&self, output: &mut String, _indent_level: u8) {
match self {
AssignmentOperator::Equal => output.push('='),
AssignmentOperator::PlusEqual => output.push_str("+="),
AssignmentOperator::MinusEqual => output.push_str("-="),
}
}
}

View File

@ -1,150 +0,0 @@
use std::sync::RwLock;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Error, Format, Map, Result, Statement, SyntaxNode, Type, Value};
/// Abstract representation of a block.
///
/// A block is almost identical to the root except that it must have curly
/// braces and can optionally be asynchronous. A block evaluates to the value of
/// its final statement but an async block will short-circuit if a statement
/// results in an error. Note that this will be the first statement to encounter
/// an error at runtime, not necessarilly the first statement as they are
/// written.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Block {
is_async: bool,
statements: Vec<Statement>,
}
impl AbstractTree for Block {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "block", node)?;
let first_child = node.child(0).unwrap();
let is_async = first_child.kind() == "async";
let statement_count = if is_async {
node.child_count() - 3
} else {
node.child_count() - 2
};
let mut statements = Vec::with_capacity(statement_count);
for index in 1..node.child_count() - 1 {
let child_node = node.child(index).unwrap();
if child_node.kind() == "statement" {
let statement = Statement::from_syntax(child_node, source, context)?;
statements.push(statement);
}
}
Ok(Block {
is_async,
statements,
})
}
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
for statement in &self.statements {
if let Statement::Return(inner_statement) = statement {
return inner_statement.check_type(_source, _context);
} else {
statement.check_type(_source, _context)?;
}
}
Ok(())
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
if self.is_async {
let statements = &self.statements;
let final_result = RwLock::new(Ok(Value::none()));
statements
.into_par_iter()
.enumerate()
.find_map_first(|(index, statement)| {
let result = statement.run(source, context);
let is_last_statement = index == statements.len() - 1;
let is_return_statement = if let Statement::Return(_) = statement {
true
} else {
false
};
if is_return_statement || result.is_err() {
Some(result)
} else if is_last_statement {
let get_write_lock = final_result.write();
match get_write_lock {
Ok(mut final_result) => {
*final_result = result;
None
}
Err(error) => Some(Err(error.into())),
}
} else {
None
}
})
.unwrap_or(final_result.into_inner()?)
} else {
let mut prev_result = None;
for statement in &self.statements {
if let Statement::Return(inner_statement) = statement {
return inner_statement.run(source, context);
}
prev_result = Some(statement.run(source, context));
}
prev_result.unwrap_or(Ok(Value::none()))
}
}
fn expected_type(&self, context: &Map) -> Result<Type> {
if let Some(statement) = self.statements.iter().find(|statement| {
if let Statement::Return(_) = statement {
true
} else {
false
}
}) {
statement.expected_type(context)
} else if let Some(statement) = self.statements.last() {
statement.expected_type(context)
} else {
Ok(Type::None)
}
}
}
impl Format for Block {
fn format(&self, output: &mut String, indent_level: u8) {
if self.is_async {
output.push_str("async {\n");
} else {
output.push_str("{\n");
}
for (index, statement) in self.statements.iter().enumerate() {
if index > 0 {
output.push('\n');
}
statement.format(output, indent_level + 1);
}
output.push('\n');
Block::indent(output, indent_level);
output.push('}');
}
}

View File

@ -1,163 +0,0 @@
use std::{collections::BTreeMap, env::args, sync::OnceLock};
use enum_iterator::{all, Sequence};
use serde::{Deserialize, Serialize};
use crate::{
built_in_functions::string_functions, AbstractTree, BuiltInFunction, Format, Function, List,
Map, Result, SyntaxNode, Type, Value,
};
static ARGS: OnceLock<Value> = OnceLock::new();
static FS: OnceLock<Value> = OnceLock::new();
static JSON: OnceLock<Value> = OnceLock::new();
static RANDOM: OnceLock<Value> = OnceLock::new();
static STRING: OnceLock<Value> = OnceLock::new();
pub fn built_in_values() -> impl Iterator<Item = BuiltInValue> {
all()
}
#[derive(Sequence, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BuiltInValue {
Args,
AssertEqual,
Fs,
Json,
Length,
Output,
Random,
String,
}
impl BuiltInValue {
pub fn name(&self) -> &'static str {
match self {
BuiltInValue::Args => "args",
BuiltInValue::AssertEqual => "assert_equal",
BuiltInValue::Fs => "fs",
BuiltInValue::Json => "json",
BuiltInValue::Length => "length",
BuiltInValue::Output => "output",
BuiltInValue::Random => "random",
BuiltInValue::String => "string",
}
}
pub fn r#type(&self) -> Type {
match self {
BuiltInValue::Args => Type::list(Type::String),
BuiltInValue::AssertEqual => BuiltInFunction::AssertEqual.r#type(),
BuiltInValue::Fs => Type::Map(None),
BuiltInValue::Json => Type::Map(None),
BuiltInValue::Length => BuiltInFunction::Length.r#type(),
BuiltInValue::Output => BuiltInFunction::Output.r#type(),
BuiltInValue::Random => Type::Map(None),
BuiltInValue::String => Type::Map(None),
}
}
pub fn get(&self) -> &Value {
match self {
BuiltInValue::Args => ARGS.get_or_init(|| {
let args = args().map(|arg| Value::string(arg.to_string())).collect();
Value::List(List::with_items(args))
}),
BuiltInValue::AssertEqual => {
&Value::Function(Function::BuiltIn(BuiltInFunction::AssertEqual))
}
BuiltInValue::Fs => FS.get_or_init(|| {
let fs_context = Map::new();
fs_context
.set(
"read".to_string(),
Value::Function(Function::BuiltIn(BuiltInFunction::FsRead)),
)
.unwrap();
Value::Map(fs_context)
}),
BuiltInValue::Json => JSON.get_or_init(|| {
let json_context = Map::new();
json_context
.set(
BuiltInFunction::JsonParse.name().to_string(),
Value::Function(Function::BuiltIn(BuiltInFunction::JsonParse)),
)
.unwrap();
Value::Map(json_context)
}),
BuiltInValue::Length => &Value::Function(Function::BuiltIn(BuiltInFunction::Length)),
BuiltInValue::Output => &Value::Function(Function::BuiltIn(BuiltInFunction::Output)),
BuiltInValue::Random => RANDOM.get_or_init(|| {
let mut random_context = BTreeMap::new();
for built_in_function in [
BuiltInFunction::RandomBoolean,
BuiltInFunction::RandomFloat,
BuiltInFunction::RandomFrom,
BuiltInFunction::RandomInteger,
] {
let key = built_in_function.name().to_string();
let value = Value::Function(Function::BuiltIn(built_in_function));
let r#type = built_in_function.r#type();
random_context.insert(key, (value, r#type));
}
Value::Map(Map::with_variables(random_context))
}),
BuiltInValue::String => STRING.get_or_init(|| {
let mut string_context = BTreeMap::new();
for string_function in string_functions() {
let key = string_function.name().to_string();
let value = Value::Function(Function::BuiltIn(BuiltInFunction::String(
string_function,
)));
let r#type = string_function.r#type();
string_context.insert(key, (value, r#type));
}
Value::Map(Map::with_variables(string_context))
}),
}
}
}
impl AbstractTree for BuiltInValue {
fn from_syntax(node: SyntaxNode, _source: &str, _context: &Map) -> Result<Self> {
let built_in_value = match node.kind() {
"args" => BuiltInValue::Args,
"assert_equal" => BuiltInValue::AssertEqual,
"fs" => BuiltInValue::Fs,
"json" => BuiltInValue::Json,
"length" => BuiltInValue::Length,
"output" => BuiltInValue::Output,
"random" => BuiltInValue::Random,
"string" => BuiltInValue::String,
_ => todo!(),
};
Ok(built_in_value)
}
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
Ok(self.get().clone())
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(self.r#type())
}
}
impl Format for BuiltInValue {
fn format(&self, output: &mut String, _indent_level: u8) {
output.push_str(&self.get().to_string());
}
}

View File

@ -1,115 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
value_node::ValueNode, AbstractTree, Error, Format, FunctionCall, Identifier, Index, Logic,
Map, Math, New, Result, SyntaxNode, Type, Value, Yield,
};
/// Abstract representation of an expression statement.
///
/// Unlike statements, which can involve complex logic, an expression is
/// expected to evaluate to a value. However, an expression can still contain
/// nested statements and may evaluate to an empty value.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum Expression {
Value(ValueNode),
Identifier(Identifier),
Index(Box<Index>),
Math(Box<Math>),
Logic(Box<Logic>),
FunctionCall(Box<FunctionCall>),
Yield(Box<Yield>),
New(New),
}
impl AbstractTree for Expression {
fn from_syntax(node: SyntaxNode, source: &str, _context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "expression", node)?;
let child = if node.child(0).unwrap().is_named() {
node.child(0).unwrap()
} else {
node.child(1).unwrap()
};
let expression = match child.kind() {
"value" => Expression::Value(ValueNode::from_syntax(child, source, _context)?),
"identifier" => {
Expression::Identifier(Identifier::from_syntax(child, source, _context)?)
}
"index" => Expression::Index(Box::new(Index::from_syntax(child, source, _context)?)),
"math" => Expression::Math(Box::new(Math::from_syntax(child, source, _context)?)),
"logic" => Expression::Logic(Box::new(Logic::from_syntax(child, source, _context)?)),
"function_call" => Expression::FunctionCall(Box::new(FunctionCall::from_syntax(
child, source, _context,
)?)),
"yield" => Expression::Yield(Box::new(Yield::from_syntax(child, source, _context)?)),
"new" => Expression::New(New::from_syntax(child, source, _context)?),
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "value, identifier, index, math, logic, function call, new or ->"
.to_string(),
actual: child.kind().to_string(),
location: child.start_position(),
relevant_source: source[child.byte_range()].to_string(),
})
}
};
Ok(expression)
}
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
match self {
Expression::Value(value_node) => value_node.check_type(_source, _context),
Expression::Identifier(identifier) => identifier.check_type(_source, _context),
Expression::Math(math) => math.check_type(_source, _context),
Expression::Logic(logic) => logic.check_type(_source, _context),
Expression::FunctionCall(function_call) => function_call.check_type(_source, _context),
Expression::Index(index) => index.check_type(_source, _context),
Expression::Yield(r#yield) => r#yield.check_type(_source, _context),
Expression::New(new) => new.check_type(_source, _context),
}
}
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
match self {
Expression::Value(value_node) => value_node.run(_source, _context),
Expression::Identifier(identifier) => identifier.run(_source, _context),
Expression::Math(math) => math.run(_source, _context),
Expression::Logic(logic) => logic.run(_source, _context),
Expression::FunctionCall(function_call) => function_call.run(_source, _context),
Expression::Index(index) => index.run(_source, _context),
Expression::Yield(r#yield) => r#yield.run(_source, _context),
Expression::New(new) => new.run(_source, _context),
}
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
match self {
Expression::Value(value_node) => value_node.expected_type(_context),
Expression::Identifier(identifier) => identifier.expected_type(_context),
Expression::Math(math) => math.expected_type(_context),
Expression::Logic(logic) => logic.expected_type(_context),
Expression::FunctionCall(function_call) => function_call.expected_type(_context),
Expression::Index(index) => index.expected_type(_context),
Expression::Yield(r#yield) => r#yield.expected_type(_context),
Expression::New(new) => new.expected_type(_context),
}
}
}
impl Format for Expression {
fn format(&self, _output: &mut String, _indent_level: u8) {
match self {
Expression::Value(value_node) => value_node.format(_output, _indent_level),
Expression::Identifier(identifier) => identifier.format(_output, _indent_level),
Expression::Math(math) => math.format(_output, _indent_level),
Expression::Logic(logic) => logic.format(_output, _indent_level),
Expression::FunctionCall(function_call) => function_call.format(_output, _indent_level),
Expression::Index(index) => index.format(_output, _indent_level),
Expression::Yield(r#yield) => r#yield.format(_output, _indent_level),
Expression::New(new) => new.format(_output, _indent_level),
}
}
}

View File

@ -1,98 +0,0 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, Block, Error, Expression, Format, Identifier, Map, Result, SyntaxNode, Type,
Value,
};
/// Abstract representation of a for loop statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct For {
is_async: bool,
item_id: Identifier,
collection: Expression,
block: Block,
}
impl AbstractTree for For {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "for", node)?;
let for_node = node.child(0).unwrap();
let is_async = match for_node.kind() {
"for" => false,
"async for" => true,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "for or async for".to_string(),
actual: for_node.kind().to_string(),
location: for_node.start_position(),
relevant_source: source[for_node.byte_range()].to_string(),
})
}
};
let identifier_node = node.child(1).unwrap();
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
let expression_node = node.child(3).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?;
let item_node = node.child(4).unwrap();
let item = Block::from_syntax(item_node, source, context)?;
Ok(For {
is_async,
item_id: identifier,
collection: expression,
block: item,
})
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let expression_run = self.collection.run(source, context)?;
let values = expression_run.as_list()?.items();
let key = self.item_id.inner();
if self.is_async {
values.par_iter().try_for_each(|value| {
let iter_context = Map::clone_from(context)?;
iter_context.set(key.clone(), value.clone())?;
self.block.run(source, &iter_context).map(|_value| ())
})?;
} else {
let loop_context = Map::clone_from(context)?;
for value in values.iter() {
loop_context.set(key.clone(), value.clone())?;
self.block.run(source, &loop_context)?;
}
}
Ok(Value::none())
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(Type::None)
}
}
impl Format for For {
fn format(&self, output: &mut String, indent_level: u8) {
if self.is_async {
output.push_str("async for ");
} else {
output.push_str("for ");
}
self.item_id.format(output, indent_level);
output.push_str(" in ");
self.collection.format(output, indent_level);
output.push(' ');
self.block.format(output, indent_level);
}
}

View File

@ -1,165 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, Error, Expression, Format, FunctionExpression, Map, Result, SyntaxNode,
SyntaxPosition, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct FunctionCall {
function_expression: FunctionExpression,
arguments: Vec<Expression>,
syntax_position: SyntaxPosition,
}
impl FunctionCall {
pub fn new(
function_expression: FunctionExpression,
arguments: Vec<Expression>,
syntax_position: SyntaxPosition,
) -> Self {
Self {
function_expression,
arguments,
syntax_position,
}
}
}
impl AbstractTree for FunctionCall {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "function_call", node)?;
let function_node = node.child(0).unwrap();
let function_expression = FunctionExpression::from_syntax(function_node, source, context)?;
let mut arguments = Vec::new();
for index in 2..node.child_count() - 1 {
let child = node.child(index).unwrap();
if child.is_named() {
let expression = Expression::from_syntax(child, source, context)?;
arguments.push(expression);
}
}
Ok(FunctionCall {
function_expression,
arguments,
syntax_position: node.range().into(),
})
}
fn check_type(&self, source: &str, context: &Map) -> Result<()> {
let function_expression_type = self.function_expression.expected_type(context)?;
let parameter_types = match function_expression_type {
Type::Function {
parameter_types, ..
} => parameter_types,
Type::Any => return Ok(()),
_ => {
return Err(Error::TypeCheckExpectedFunction {
actual: function_expression_type,
}
.at_source_position(source, self.syntax_position))
}
};
if self.arguments.len() != parameter_types.len() {
return Err(Error::ExpectedFunctionArgumentAmount {
expected: parameter_types.len(),
actual: self.arguments.len(),
}
.at_source_position(source, self.syntax_position));
}
for (index, expression) in self.arguments.iter().enumerate() {
if let Some(r#type) = parameter_types.get(index) {
r#type
.check(&expression.expected_type(context)?)
.map_err(|error| error.at_source_position(source, self.syntax_position))?;
}
}
Ok(())
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let value = match &self.function_expression {
FunctionExpression::Identifier(identifier) => {
let key = identifier.inner();
let variables = context.variables()?;
if let Some((value, _)) = variables.get(key) {
value.clone()
} else {
return Err(Error::FunctionIdentifierNotFound(
identifier.inner().clone(),
));
}
}
FunctionExpression::FunctionCall(function_call) => {
function_call.run(source, context)?
}
FunctionExpression::Value(value_node) => value_node.run(source, context)?,
FunctionExpression::Index(index) => index.run(source, context)?,
FunctionExpression::Yield(r#yield) => r#yield.run(source, context)?,
};
let mut arguments = Vec::with_capacity(self.arguments.len());
for expression in &self.arguments {
let value = expression.run(source, context)?;
arguments.push(value);
}
value.as_function()?.call(&arguments, source, context)
}
fn expected_type(&self, context: &Map) -> Result<Type> {
match &self.function_expression {
FunctionExpression::Identifier(identifier) => {
let identifier_type = identifier.expected_type(context)?;
if let Type::Function {
parameter_types: _,
return_type,
} = &identifier_type
{
Ok(*return_type.clone())
} else {
Ok(identifier_type)
}
}
FunctionExpression::FunctionCall(function_call) => function_call.expected_type(context),
FunctionExpression::Value(value_node) => {
let value_type = value_node.expected_type(context)?;
if let Type::Function { return_type, .. } = value_type {
Ok(*return_type)
} else {
Ok(value_type)
}
}
FunctionExpression::Index(index) => index.expected_type(context),
FunctionExpression::Yield(r#yield) => r#yield.expected_type(context),
}
}
}
impl Format for FunctionCall {
fn format(&self, output: &mut String, indent_level: u8) {
self.function_expression.format(output, indent_level);
output.push('(');
for expression in &self.arguments {
expression.format(output, indent_level);
}
output.push(')');
}
}

View File

@ -1,87 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, Error, Format, FunctionCall, Identifier, Index, Map, Result, SyntaxNode, Type,
Value, ValueNode, Yield,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum FunctionExpression {
Identifier(Identifier),
FunctionCall(Box<FunctionCall>),
Value(ValueNode),
Index(Index),
Yield(Box<Yield>),
}
impl AbstractTree for FunctionExpression {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "function_expression", node)?;
let first_child = node.child(0).unwrap();
let child = if first_child.is_named() {
first_child
} else {
node.child(1).unwrap()
};
let function_expression = match child.kind() {
"identifier" => {
FunctionExpression::Identifier(Identifier::from_syntax(child, source, context)?)
}
"function_call" => FunctionExpression::FunctionCall(Box::new(
FunctionCall::from_syntax(child, source, context)?,
)),
"value" => FunctionExpression::Value(ValueNode::from_syntax(child, source, context)?),
"index" => FunctionExpression::Index(Index::from_syntax(child, source, context)?),
"yield" => {
FunctionExpression::Yield(Box::new(Yield::from_syntax(child, source, context)?))
}
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "identifier, function call, value, index or yield".to_string(),
actual: child.kind().to_string(),
location: child.start_position(),
relevant_source: source[child.byte_range()].to_string(),
})
}
};
Ok(function_expression)
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
match self {
FunctionExpression::Identifier(identifier) => identifier.run(source, context),
FunctionExpression::FunctionCall(function_call) => function_call.run(source, context),
FunctionExpression::Value(value_node) => value_node.run(source, context),
FunctionExpression::Index(index) => index.run(source, context),
FunctionExpression::Yield(r#yield) => r#yield.run(source, context),
}
}
fn expected_type(&self, context: &Map) -> Result<Type> {
match self {
FunctionExpression::Identifier(identifier) => identifier.expected_type(context),
FunctionExpression::FunctionCall(function_call) => function_call.expected_type(context),
FunctionExpression::Value(value_node) => value_node.expected_type(context),
FunctionExpression::Index(index) => index.expected_type(context),
FunctionExpression::Yield(r#yield) => r#yield.expected_type(context),
}
}
}
impl Format for FunctionExpression {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
FunctionExpression::Value(value_node) => value_node.format(output, indent_level),
FunctionExpression::Identifier(identifier) => identifier.format(output, indent_level),
FunctionExpression::FunctionCall(function_call) => {
function_call.format(output, indent_level)
}
FunctionExpression::Index(index) => index.format(output, indent_level),
FunctionExpression::Yield(r#yield) => r#yield.format(output, indent_level),
}
}
}

View File

@ -1,200 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, Block, Error, Format, Function, Identifier, Map, Result, SyntaxNode,
SyntaxPosition, Type, TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct FunctionNode {
parameters: Vec<Identifier>,
body: Block,
r#type: Type,
syntax_position: SyntaxPosition,
context: Map,
}
impl FunctionNode {
pub fn new(
parameters: Vec<Identifier>,
body: Block,
r#type: Type,
syntax_position: SyntaxPosition,
) -> Self {
Self {
parameters,
body,
r#type,
syntax_position,
context: Map::new(),
}
}
pub fn set(&self, key: String, value: Value) -> Result<Option<(Value, Type)>> {
self.context.set(key, value)
}
pub fn parameters(&self) -> &Vec<Identifier> {
&self.parameters
}
pub fn body(&self) -> &Block {
&self.body
}
pub fn r#type(&self) -> &Type {
&self.r#type
}
pub fn syntax_position(&self) -> &SyntaxPosition {
&self.syntax_position
}
pub fn return_type(&self) -> &Type {
match &self.r#type {
Type::Function {
parameter_types: _,
return_type,
} => return_type.as_ref(),
_ => &Type::None,
}
}
pub fn call(&self, arguments: &[Value], source: &str, outer_context: &Map) -> Result<Value> {
for (key, (value, r#type)) in outer_context.variables()?.iter() {
if let Value::Function(Function::ContextDefined(function_node)) = value {
if self == function_node {
continue;
}
}
if r#type.is_function() {
self.context.set(key.clone(), value.clone())?;
}
}
let parameter_argument_pairs = self.parameters.iter().zip(arguments.iter());
for (identifier, value) in parameter_argument_pairs {
let key = identifier.inner().clone();
self.context.set(key, value.clone())?;
}
let return_value = self.body.run(source, &self.context)?;
Ok(return_value)
}
}
impl AbstractTree for FunctionNode {
fn from_syntax(node: SyntaxNode, source: &str, outer_context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "function", node)?;
let child_count = node.child_count();
let mut parameters = Vec::new();
let mut parameter_types = Vec::new();
for index in 1..child_count - 3 {
let child = node.child(index).unwrap();
if child.kind() == "identifier" {
let identifier = Identifier::from_syntax(child, source, outer_context)?;
parameters.push(identifier);
}
if child.kind() == "type_specification" {
let type_specification =
TypeSpecification::from_syntax(child, source, outer_context)?;
parameter_types.push(type_specification.take_inner());
}
}
let return_type_node = node.child(child_count - 2).unwrap();
let return_type = TypeSpecification::from_syntax(return_type_node, source, outer_context)?;
let function_context = Map::new();
for (key, (_value, r#type)) in outer_context.variables()?.iter() {
if r#type.is_function() {
function_context.set_type(key.clone(), r#type.clone())?;
}
}
for (parameter, parameter_type) in parameters.iter().zip(parameter_types.iter()) {
function_context.set_type(parameter.inner().clone(), parameter_type.clone())?;
}
let body_node = node.child(child_count - 1).unwrap();
let body = Block::from_syntax(body_node, source, &function_context)?;
let r#type = Type::function(parameter_types, return_type.take_inner());
let syntax_position = node.range().into();
Ok(FunctionNode {
parameters,
body,
r#type,
syntax_position,
context: function_context,
})
}
fn check_type(&self, source: &str, _context: &Map) -> Result<()> {
self.return_type()
.check(&self.body.expected_type(&self.context)?)
.map_err(|error| error.at_source_position(source, self.syntax_position))?;
self.body.check_type(source, &self.context)?;
Ok(())
}
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
Ok(Value::Function(Function::ContextDefined(self.clone())))
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(self.r#type().clone())
}
}
impl Format for FunctionNode {
fn format(&self, output: &mut String, indent_level: u8) {
let (parameter_types, return_type) = if let Type::Function {
parameter_types,
return_type,
} = &self.r#type
{
(parameter_types, return_type)
} else {
return;
};
output.push('(');
for (identifier, r#type) in self.parameters.iter().zip(parameter_types.iter()) {
identifier.format(output, indent_level);
output.push_str(" <");
r#type.format(output, indent_level);
output.push('>');
}
output.push_str(") <");
return_type.format(output, indent_level);
output.push_str("> ");
self.body.format(output, indent_level);
}
}
impl Display for FunctionNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut string = String::new();
self.format(&mut string, 0);
f.write_str(&string)
}
}

View File

@ -1,70 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Error, Format, Map, Result, SyntaxNode, Type, Value};
/// A string by which a variable is known to a context.
///
/// Every variable is a key-value pair. An identifier holds the key part of that
/// pair. Its inner value can be used to retrieve a Value instance from a Map.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Identifier(String);
impl Identifier {
pub fn new<T: Into<String>>(inner: T) -> Self {
Identifier(inner.into())
}
pub fn take_inner(self) -> String {
self.0
}
pub fn inner(&self) -> &String {
&self.0
}
}
impl AbstractTree for Identifier {
fn from_syntax(node: SyntaxNode, source: &str, _context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "identifier", node)?;
let text = &source[node.byte_range()];
debug_assert!(!text.is_empty());
Ok(Identifier(text.to_string()))
}
fn run(&self, _source: &str, context: &Map) -> Result<Value> {
if let Some((value, _)) = context.variables()?.get(&self.0) {
Ok(value.clone())
} else {
Err(Error::VariableIdentifierNotFound(self.0.clone()))
}
}
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
Ok(())
}
fn expected_type(&self, context: &Map) -> Result<Type> {
if let Some((_value, r#type)) = context.variables()?.get(&self.0) {
Ok(r#type.clone())
} else {
Err(Error::VariableIdentifierNotFound(self.0.clone()))
}
}
}
impl Format for Identifier {
fn format(&self, output: &mut String, _indent_level: u8) {
output.push_str(&self.0);
}
}
impl Display for Identifier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

View File

@ -1,112 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Block, Expression, Format, Map, Result, SyntaxNode, Type, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct IfElse {
if_expression: Expression,
if_block: Block,
else_if_expressions: Vec<Expression>,
else_if_blocks: Vec<Block>,
else_block: Option<Block>,
}
impl AbstractTree for IfElse {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
let if_expression_node = node.child(0).unwrap().child(1).unwrap();
let if_expression = Expression::from_syntax(if_expression_node, source, context)?;
let if_block_node = node.child(0).unwrap().child(2).unwrap();
let if_block = Block::from_syntax(if_block_node, source, context)?;
let child_count = node.child_count();
let mut else_if_expressions = Vec::new();
let mut else_if_blocks = Vec::new();
let mut else_block = None;
for index in 1..child_count {
let child = node.child(index).unwrap();
if child.kind() == "else_if" {
let expression_node = child.child(1).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?;
else_if_expressions.push(expression);
let block_node = child.child(2).unwrap();
let block = Block::from_syntax(block_node, source, context)?;
else_if_blocks.push(block);
}
if child.kind() == "else" {
let else_node = child.child(1).unwrap();
else_block = Some(Block::from_syntax(else_node, source, context)?);
}
}
Ok(IfElse {
if_expression,
if_block,
else_if_expressions,
else_if_blocks,
else_block,
})
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let if_boolean = self.if_expression.run(source, context)?.as_boolean()?;
if if_boolean {
self.if_block.run(source, context)
} else {
let expressions = &self.else_if_expressions;
for (index, expression) in expressions.iter().enumerate() {
let if_boolean = expression.run(source, context)?.as_boolean()?;
if if_boolean {
let block = self.else_if_blocks.get(index).unwrap();
return block.run(source, context);
}
}
if let Some(block) = &self.else_block {
block.run(source, context)
} else {
Ok(Value::none())
}
}
}
fn expected_type(&self, context: &Map) -> Result<Type> {
self.if_block.expected_type(context)
}
}
impl Format for IfElse {
fn format(&self, output: &mut String, indent_level: u8) {
output.push_str("if ");
self.if_expression.format(output, indent_level);
output.push(' ');
self.if_block.format(output, indent_level);
let else_ifs = self
.else_if_expressions
.iter()
.zip(self.else_if_blocks.iter());
for (expression, block) in else_ifs {
output.push_str("else if ");
expression.format(output, indent_level);
output.push(' ');
block.format(output, indent_level);
}
if let Some(block) = &self.else_block {
output.push_str("else ");
block.format(output, indent_level);
}
}
}

View File

@ -1,114 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, Error, Format, IndexExpression, List, Map, Result, SyntaxNode, Type, Value,
};
/// Abstract representation of an index expression.
///
/// An index is a means of accessing values stored in list, maps and strings.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Index {
pub collection: IndexExpression,
pub index: IndexExpression,
pub index_end: Option<IndexExpression>,
}
impl AbstractTree for Index {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "index", node)?;
let collection_node = node.child(0).unwrap();
let collection = IndexExpression::from_syntax(collection_node, source, context)?;
let index_node = node.child(2).unwrap();
let index = IndexExpression::from_syntax(index_node, source, context)?;
let index_end_node = node.child(4);
let index_end = if let Some(index_end_node) = index_end_node {
Some(IndexExpression::from_syntax(
index_end_node,
source,
context,
)?)
} else {
None
};
Ok(Index {
collection,
index,
index_end,
})
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let collection = self.collection.run(source, context)?;
match collection {
Value::List(list) => {
let index = self.index.run(source, context)?.as_integer()? as usize;
let item = if let Some(index_end) = &self.index_end {
let index_end = index_end.run(source, context)?.as_integer()? as usize;
let sublist = list.items()[index..=index_end].to_vec();
Value::List(List::with_items(sublist))
} else {
list.items().get(index).cloned().unwrap_or_default()
};
Ok(item)
}
Value::Map(map) => {
let value = if let IndexExpression::Identifier(identifier) = &self.index {
let key = identifier.inner();
map.variables()?
.get(key)
.map(|(value, _)| value.clone())
.unwrap_or_default()
} else {
let value = self.index.run(source, context)?;
let key = value.as_string()?;
map.variables()?
.get(key.as_str())
.map(|(value, _)| value.clone())
.unwrap_or_default()
};
Ok(value)
}
Value::String(string) => {
let index = self.index.run(source, context)?.as_integer()? as usize;
let item = string.chars().nth(index).unwrap_or_default();
Ok(Value::string(item.to_string()))
}
_ => Err(Error::ExpectedCollection { actual: collection }),
}
}
fn expected_type(&self, context: &Map) -> Result<Type> {
match self.collection.expected_type(context)? {
Type::List(item_type) => Ok(*item_type.clone()),
Type::Map(_) => Ok(Type::Any),
Type::None => Ok(Type::None),
r#type => Ok(r#type),
}
}
}
impl Format for Index {
fn format(&self, output: &mut String, indent_level: u8) {
self.collection.format(output, indent_level);
output.push(':');
self.index.format(output, indent_level);
if let Some(expression) = &self.index_end {
output.push_str("..");
expression.format(output, indent_level);
}
}
}

View File

@ -1,90 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, AssignmentOperator, Error, Format, Index, IndexExpression, Map, Result,
Statement, SyntaxNode, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct IndexAssignment {
index: Index,
operator: AssignmentOperator,
statement: Statement,
}
impl AbstractTree for IndexAssignment {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "index_assignment", node)?;
let index_node = node.child(0).unwrap();
let index = Index::from_syntax(index_node, source, context)?;
let operator_node = node.child(1).unwrap();
let operator = AssignmentOperator::from_syntax(operator_node, source, context)?;
let statement_node = node.child(2).unwrap();
let statement = Statement::from_syntax(statement_node, source, context)?;
Ok(IndexAssignment {
index,
operator,
statement,
})
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let index_collection = self.index.collection.run(source, context)?;
let index_context = index_collection.as_map().unwrap_or(context);
let index_key = if let IndexExpression::Identifier(identifier) = &self.index.index {
identifier.inner()
} else {
return Err(Error::VariableIdentifierNotFound(
self.index.index.run(source, context)?.to_string(),
));
};
let value = self.statement.run(source, context)?;
let new_value = match self.operator {
AssignmentOperator::PlusEqual => {
if let Some((mut previous_value, _)) =
index_context.variables()?.get(index_key).cloned()
{
previous_value += value;
previous_value
} else {
Value::none()
}
}
AssignmentOperator::MinusEqual => {
if let Some((mut previous_value, _)) =
index_context.variables()?.get(index_key).cloned()
{
previous_value -= value;
previous_value
} else {
Value::none()
}
}
AssignmentOperator::Equal => value,
};
index_context.set(index_key.clone(), new_value)?;
Ok(Value::none())
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(Type::None)
}
}
impl Format for IndexAssignment {
fn format(&self, output: &mut String, indent_level: u8) {
self.index.format(output, indent_level);
output.push(' ');
self.operator.format(output, indent_level);
output.push(' ');
self.statement.format(output, indent_level);
}
}

View File

@ -1,87 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
value_node::ValueNode, AbstractTree, Error, Format, FunctionCall, Identifier, Index, Map,
Result, SyntaxNode, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum IndexExpression {
Value(ValueNode),
Identifier(Identifier),
Index(Box<Index>),
FunctionCall(Box<FunctionCall>),
}
impl AbstractTree for IndexExpression {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "index_expression", node)?;
let first_child = node.child(0).unwrap();
let child = if first_child.is_named() {
first_child
} else {
node.child(1).unwrap()
};
let abstract_node = match child.kind() {
"value" => IndexExpression::Value(ValueNode::from_syntax(child, source, context)?),
"identifier" => {
IndexExpression::Identifier(Identifier::from_syntax(child, source, context)?)
}
"index" => {
IndexExpression::Index(Box::new(Index::from_syntax(child, source, context)?))
}
"function_call" => IndexExpression::FunctionCall(Box::new(FunctionCall::from_syntax(
child, source, context,
)?)),
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "value, identifier, index or function call".to_string(),
actual: child.kind().to_string(),
location: child.start_position(),
relevant_source: source[child.byte_range()].to_string(),
})
}
};
Ok(abstract_node)
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
match self {
IndexExpression::Value(value_node) => value_node.run(source, context),
IndexExpression::Identifier(identifier) => identifier.run(source, context),
IndexExpression::Index(index) => index.run(source, context),
IndexExpression::FunctionCall(function_call) => function_call.run(source, context),
}
}
fn expected_type(&self, context: &Map) -> Result<Type> {
match self {
IndexExpression::Value(value_node) => value_node.expected_type(context),
IndexExpression::Identifier(identifier) => identifier.expected_type(context),
IndexExpression::Index(index) => index.expected_type(context),
IndexExpression::FunctionCall(function_call) => function_call.expected_type(context),
}
}
}
impl Format for IndexExpression {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
IndexExpression::Value(value_node) => {
if let ValueNode::BuiltInValue(built_in_value) = value_node {
output.push_str(built_in_value.name());
} else {
value_node.format(output, indent_level);
}
}
IndexExpression::Identifier(identifier) => identifier.format(output, indent_level),
IndexExpression::FunctionCall(function_call) => {
function_call.format(output, indent_level)
}
IndexExpression::Index(index) => index.format(output, indent_level),
}
}
}

View File

@ -1,84 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, Error, Expression, Format, LogicOperator, Map, Result, SyntaxNode, Type, Value,
};
/// Abstract representation of a logic expression.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Logic {
left: Expression,
operator: LogicOperator,
right: Expression,
}
impl AbstractTree for Logic {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "logic", node)?;
let first_node = node.child(0).unwrap();
let (left_node, operator_node, right_node) = {
if first_node.is_named() {
(first_node, node.child(1).unwrap(), node.child(2).unwrap())
} else {
(
node.child(1).unwrap(),
node.child(2).unwrap(),
node.child(3).unwrap(),
)
}
};
let left = Expression::from_syntax(left_node, source, context)?;
let operator = LogicOperator::from_syntax(operator_node, source, context)?;
let right = Expression::from_syntax(right_node, source, context)?;
Ok(Logic {
left,
operator,
right,
})
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?;
let result = match self.operator {
LogicOperator::Equal => {
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {
left_num == right_num
} else {
left == right
}
}
LogicOperator::NotEqual => {
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {
left_num != right_num
} else {
left != right
}
}
LogicOperator::And => left.as_boolean()? && right.as_boolean()?,
LogicOperator::Or => left.as_boolean()? || right.as_boolean()?,
LogicOperator::Greater => left > right,
LogicOperator::Less => left < right,
LogicOperator::GreaterOrEqual => left >= right,
LogicOperator::LessOrEqual => left <= right,
};
Ok(Value::Boolean(result))
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(Type::Boolean)
}
}
impl Format for Logic {
fn format(&self, output: &mut String, indent_level: u8) {
self.left.format(output, indent_level);
output.push(' ');
self.operator.format(output, indent_level);
output.push(' ');
self.right.format(output, indent_level);
}
}

View File

@ -1,66 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Error, Format, Map, Result, SyntaxNode, Type, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum LogicOperator {
Equal,
NotEqual,
And,
Or,
Greater,
Less,
GreaterOrEqual,
LessOrEqual,
}
impl AbstractTree for LogicOperator {
fn from_syntax(node: SyntaxNode, source: &str, _context: &Map) -> crate::Result<Self> {
Error::expect_syntax_node(source, "logic_operator", node)?;
let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() {
"==" => LogicOperator::Equal,
"!=" => LogicOperator::NotEqual,
"&&" => LogicOperator::And,
"||" => LogicOperator::Or,
">" => LogicOperator::Greater,
"<" => LogicOperator::Less,
">=" => LogicOperator::GreaterOrEqual,
"<=" => LogicOperator::LessOrEqual,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "==, !=, &&, ||, >, <, >= or <=".to_string(),
actual: operator_node.kind().to_string(),
location: operator_node.start_position(),
relevant_source: source[operator_node.byte_range()].to_string(),
})
}
};
Ok(operator)
}
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
Ok(Value::none())
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(Type::None)
}
}
impl Format for LogicOperator {
fn format(&self, output: &mut String, _indent_level: u8) {
match self {
LogicOperator::Equal => output.push('='),
LogicOperator::NotEqual => output.push_str("!="),
LogicOperator::And => output.push_str("&&"),
LogicOperator::Or => output.push_str("||"),
LogicOperator::Greater => output.push('>'),
LogicOperator::Less => output.push('<'),
LogicOperator::GreaterOrEqual => output.push_str(">="),
LogicOperator::LessOrEqual => output.push_str("<="),
}
}
}

View File

@ -1,105 +0,0 @@
//! Pattern matching.
//!
//! Note that this module is called "match" but is escaped as "r#match" because
//! "match" is a keyword in Rust.
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, Error, Expression, Format, Map, Result, Statement, SyntaxNode, Type, Value,
};
/// Abstract representation of a match statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Match {
matcher: Expression,
options: Vec<(Expression, Statement)>,
fallback: Option<Box<Statement>>,
}
impl AbstractTree for Match {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "match", node)?;
let matcher_node = node.child(1).unwrap();
let matcher = Expression::from_syntax(matcher_node, source, context)?;
let mut options = Vec::new();
let mut previous_expression = None;
let mut next_statement_is_fallback = false;
let mut fallback = None;
for index in 2..node.child_count() {
let child = node.child(index).unwrap();
if child.kind() == "*" {
next_statement_is_fallback = true;
}
if child.kind() == "expression" {
previous_expression = Some(Expression::from_syntax(child, source, context)?);
}
if child.kind() == "statement" {
let statement = Statement::from_syntax(child, source, context)?;
if next_statement_is_fallback {
fallback = Some(Box::new(statement));
next_statement_is_fallback = false;
} else if let Some(expression) = &previous_expression {
options.push((expression.clone(), statement));
}
}
}
Ok(Match {
matcher,
options,
fallback,
})
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let matcher_value = self.matcher.run(source, context)?;
for (expression, statement) in &self.options {
let option_value = expression.run(source, context)?;
if matcher_value == option_value {
return statement.run(source, context);
}
}
if let Some(fallback) = &self.fallback {
fallback.run(source, context)
} else {
Ok(Value::none())
}
}
fn expected_type(&self, context: &Map) -> Result<Type> {
let (_, first_statement) = self.options.first().unwrap();
first_statement.expected_type(context)
}
}
impl Format for Match {
fn format(&self, output: &mut String, indent_level: u8) {
output.push_str("match ");
self.matcher.format(output, indent_level);
output.push_str(" {");
for (expression, statement) in &self.options {
expression.format(output, indent_level);
output.push_str(" => ");
statement.format(output, indent_level);
}
if let Some(statement) = &self.fallback {
output.push_str("* => ");
statement.format(output, indent_level);
}
output.push('}');
}
}

View File

@ -1,65 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, Error, Expression, Format, Map, MathOperator, Result, SyntaxNode, Type, Value,
};
/// Abstract representation of a math operation.
///
/// Dust currently supports the four basic operations and the modulo (or
/// remainder) operator.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Math {
left: Expression,
operator: MathOperator,
right: Expression,
}
impl AbstractTree for Math {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "math", node)?;
let left_node = node.child(0).unwrap();
let left = Expression::from_syntax(left_node, source, context)?;
let operator_node = node.child(1).unwrap();
let operator = MathOperator::from_syntax(operator_node, source, context)?;
let right_node = node.child(2).unwrap();
let right = Expression::from_syntax(right_node, source, context)?;
Ok(Math {
left,
operator,
right,
})
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?;
let value = match self.operator {
MathOperator::Add => left + right,
MathOperator::Subtract => left - right,
MathOperator::Multiply => left * right,
MathOperator::Divide => left / right,
MathOperator::Modulo => left % right,
}?;
Ok(value)
}
fn expected_type(&self, context: &Map) -> Result<Type> {
self.left.expected_type(context)
}
}
impl Format for Math {
fn format(&self, output: &mut String, indent_level: u8) {
self.left.format(output, indent_level);
output.push(' ');
self.operator.format(output, indent_level);
output.push(' ');
self.right.format(output, indent_level);
}
}

View File

@ -1,57 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Error, Format, Map, Result, SyntaxNode, Type, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum MathOperator {
Add,
Subtract,
Multiply,
Divide,
Modulo,
}
impl AbstractTree for MathOperator {
fn from_syntax(node: SyntaxNode, source: &str, _context: &Map) -> Result<Self> {
let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() {
"+" => MathOperator::Add,
"-" => MathOperator::Subtract,
"*" => MathOperator::Multiply,
"/" => MathOperator::Divide,
"%" => MathOperator::Modulo,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "+, -, *, / or %".to_string(),
actual: operator_node.kind().to_string(),
location: operator_node.start_position(),
relevant_source: source[operator_node.byte_range()].to_string(),
})
}
};
Ok(operator)
}
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
Ok(Value::none())
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(Type::None)
}
}
impl Format for MathOperator {
fn format(&self, output: &mut String, _indent_level: u8) {
let char = match self {
MathOperator::Add => '+',
MathOperator::Subtract => '-',
MathOperator::Multiply => '*',
MathOperator::Divide => '/',
MathOperator::Modulo => '%',
};
output.push(char);
}
}

View File

@ -1,172 +0,0 @@
//! Abstract, executable representations of corresponding items found in Dust
//! source code. The types that implement [AbstractTree] are inteded to be
//! created by an [Evaluator].
//!
//! When adding new lanugage features, first extend the grammar to recognize new
//! syntax nodes. Then add a new AbstractTree type using the existing types as
//! examples.
pub mod assignment;
pub mod assignment_operator;
pub mod block;
pub mod built_in_value;
pub mod expression;
pub mod r#for;
pub mod function_call;
pub mod function_expression;
pub mod function_node;
pub mod identifier;
pub mod if_else;
pub mod index;
pub mod index_assignment;
pub mod index_expression;
pub mod logic;
pub mod logic_operator;
pub mod r#match;
pub mod math;
pub mod math_operator;
pub mod new;
pub mod statement;
pub mod r#type;
pub mod type_specification;
pub mod value_node;
pub mod r#while;
pub mod r#yield;
pub use {
assignment::*, assignment_operator::*, block::*, built_in_value::*, expression::*,
function_call::*, function_expression::*, function_node::*, identifier::*, if_else::*,
index::*, index_assignment::IndexAssignment, index_expression::*, logic::*, logic_operator::*,
math::*, math_operator::*, new::*, r#for::*, r#match::*, r#type::*, r#while::*, r#yield::*,
statement::*, type_specification::*, value_node::*,
};
use serde::{Deserialize, Serialize};
use crate::{Error, Map, Result, SyntaxNode, Value};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SyntaxPosition {
pub start_byte: usize,
pub end_byte: usize,
pub start_row: usize,
pub start_column: usize,
pub end_row: usize,
pub end_column: usize,
}
impl From<tree_sitter::Range> for SyntaxPosition {
fn from(range: tree_sitter::Range) -> Self {
SyntaxPosition {
start_byte: range.start_byte,
end_byte: range.end_byte,
start_row: range.start_point.row + 1,
start_column: range.start_point.column,
end_row: range.end_point.row + 1,
end_column: range.end_point.column,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Root {
statements: Vec<Statement>,
}
// TODO Change Root to use tree sitter's cursor to traverse the statements
// instead of indexes. This will be more performant when there are a lot of
// top-level statements in the tree.
impl AbstractTree for Root {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "root", node)?;
let statement_count = node.child_count();
let mut statements = Vec::with_capacity(statement_count);
for index in 0..statement_count {
let statement_node = node.child(index).unwrap();
let statement = Statement::from_syntax(statement_node, source, context)?;
statements.push(statement);
}
Ok(Root { statements })
}
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
for statement in &self.statements {
if let Statement::Return(inner_statement) = statement {
return inner_statement.check_type(_source, _context);
} else {
statement.check_type(_source, _context)?;
}
}
Ok(())
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let mut value = Value::none();
for statement in &self.statements {
if let Statement::Return(inner_statement) = statement {
return inner_statement.run(source, context);
} else {
value = statement.run(source, context)?;
}
}
Ok(value)
}
fn expected_type(&self, context: &Map) -> Result<Type> {
self.statements.last().unwrap().expected_type(context)
}
}
impl Format for Root {
fn format(&self, output: &mut String, indent_level: u8) {
for (index, statement) in self.statements.iter().enumerate() {
if index > 0 {
output.push('\n');
}
statement.format(output, indent_level);
output.push('\n');
}
}
}
/// This trait is implemented by the Evaluator's internal types to form an
/// executable tree that resolves to a single value.
pub trait AbstractTree: Sized + Format {
/// Interpret the syntax tree at the given node and return the abstraction.
///
/// This function is used to convert nodes in the Tree Sitter concrete
/// syntax tree into executable nodes in an abstract tree. This function is
/// where the tree should be traversed by accessing sibling and child nodes.
/// Each node in the CST should be traversed only once.
///
/// If necessary, the source code can be accessed directly by getting the
/// node's byte range.
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self>;
/// Verify the type integrity of the node.
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
Ok(())
}
/// Execute dust code by traversing the tree.
fn run(&self, source: &str, context: &Map) -> Result<Value>;
fn expected_type(&self, context: &Map) -> Result<Type>;
}
pub trait Format {
fn format(&self, output: &mut String, indent_level: u8);
fn indent(output: &mut String, indent_level: u8) {
for _ in 0..indent_level {
output.push_str(" ");
}
}
}

View File

@ -1,40 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{
AbstractTree, Format, Identifier, Map, Result, Type, TypeSpecification, Value, ValueNode,
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct New {
identifier: Identifier,
properties: Vec<(Identifier, ValueNode, Option<TypeSpecification>)>,
}
impl AbstractTree for New {
fn from_syntax(node: Node, source: &str, context: &Map) -> Result<Self> {
let identifier_node = node.child(1).unwrap();
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
let properties = Vec::new();
Ok(New {
identifier,
properties,
})
}
fn run(&self, _source: &str, _context: &crate::Map) -> crate::Result<Value> {
todo!()
}
fn expected_type(&self, _context: &crate::Map) -> crate::Result<Type> {
todo!()
}
}
impl Format for New {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,131 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, Assignment, Block, Error, Expression, For, Format, IfElse, IndexAssignment, Map,
Match, Result, SyntaxNode, Type, Value, While,
};
/// Abstract representation of a statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum Statement {
Assignment(Box<Assignment>),
Expression(Expression),
IfElse(Box<IfElse>),
Match(Match),
While(Box<While>),
Block(Box<Block>),
Return(Box<Statement>),
For(Box<For>),
IndexAssignment(Box<IndexAssignment>),
}
impl AbstractTree for Statement {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "statement", node)?;
let child = node.child(0).unwrap();
match child.kind() {
"assignment" => Ok(Statement::Assignment(Box::new(
Assignment::from_syntax(child, source, context)?,
))),
"expression" => Ok(Statement::Expression(Expression::from_syntax(
child, source, context,
)?)),
"if_else" => Ok(Statement::IfElse(Box::new(IfElse::from_syntax(
child, source, context,
)?))),
"while" => Ok(Statement::While(Box::new(While::from_syntax(
child, source, context,
)?))),
"block" => Ok(Statement::Block(Box::new(Block::from_syntax(
child, source, context,
)?))),
"for" => Ok(Statement::For(Box::new(For::from_syntax(
child, source, context,
)?))),
"index_assignment" => Ok(Statement::IndexAssignment(Box::new(
IndexAssignment::from_syntax(child, source, context)?,
))),
"match" => Ok(Statement::Match(Match::from_syntax(
child, source, context,
)?)),
"return" => {
let statement_node = child.child(1).unwrap();
Ok(Statement::Return(Box::new(Statement::from_syntax(statement_node, source, context)?)))
},
_ => Err(Error::UnexpectedSyntaxNode {
expected:
"assignment, index assignment, expression, block, return, if...else, while, for or match".to_string(),
actual: child.kind().to_string(),
location: child.start_position(),
relevant_source: source[child.byte_range()].to_string(),
}),
}
}
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
match self {
Statement::Assignment(assignment) => assignment.check_type(_source, _context),
Statement::Expression(expression) => expression.check_type(_source, _context),
Statement::IfElse(if_else) => if_else.check_type(_source, _context),
Statement::Match(r#match) => r#match.check_type(_source, _context),
Statement::While(r#while) => r#while.check_type(_source, _context),
Statement::Block(block) => block.check_type(_source, _context),
Statement::For(r#for) => r#for.check_type(_source, _context),
Statement::IndexAssignment(index_assignment) => {
index_assignment.check_type(_source, _context)
}
Statement::Return(statement) => statement.check_type(_source, _context),
}
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
match self {
Statement::Assignment(assignment) => assignment.run(source, context),
Statement::Expression(expression) => expression.run(source, context),
Statement::IfElse(if_else) => if_else.run(source, context),
Statement::Match(r#match) => r#match.run(source, context),
Statement::While(r#while) => r#while.run(source, context),
Statement::Block(block) => block.run(source, context),
Statement::For(r#for) => r#for.run(source, context),
Statement::IndexAssignment(index_assignment) => index_assignment.run(source, context),
Statement::Return(statement) => statement.run(source, context),
}
}
fn expected_type(&self, context: &Map) -> Result<Type> {
match self {
Statement::Assignment(assignment) => assignment.expected_type(context),
Statement::Expression(expression) => expression.expected_type(context),
Statement::IfElse(if_else) => if_else.expected_type(context),
Statement::Match(r#match) => r#match.expected_type(context),
Statement::While(r#while) => r#while.expected_type(context),
Statement::Block(block) => block.expected_type(context),
Statement::For(r#for) => r#for.expected_type(context),
Statement::IndexAssignment(index_assignment) => index_assignment.expected_type(context),
Statement::Return(statement) => statement.expected_type(context),
}
}
}
impl Format for Statement {
fn format(&self, output: &mut String, indent_level: u8) {
Statement::indent(output, indent_level);
match self {
Statement::Assignment(assignment) => assignment.format(output, indent_level),
Statement::Expression(expression) => expression.format(output, indent_level),
Statement::IfElse(if_else) => if_else.format(output, indent_level),
Statement::Match(r#match) => r#match.format(output, indent_level),
Statement::While(r#while) => r#while.format(output, indent_level),
Statement::Block(block) => block.format(output, indent_level),
Statement::For(r#for) => r#for.format(output, indent_level),
Statement::IndexAssignment(index_assignment) => {
index_assignment.format(output, indent_level)
}
Statement::Return(statement) => statement.format(output, indent_level),
}
}
}

View File

@ -1,320 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Error, Format, Identifier, Map, Result, Structure, SyntaxNode, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum Type {
Any,
Boolean,
Collection,
Custom(Identifier),
Float,
Function {
parameter_types: Vec<Type>,
return_type: Box<Type>,
},
Integer,
List(Box<Type>),
Map(Option<Structure>),
None,
Number,
String,
Range,
Option(Box<Type>),
}
impl Type {
pub fn list(item_type: Type) -> Self {
Type::List(Box::new(item_type))
}
pub fn function(parameter_types: Vec<Type>, return_type: Type) -> Self {
Type::Function {
parameter_types,
return_type: Box::new(return_type),
}
}
pub fn option(optional_type: Type) -> Self {
Type::Option(Box::new(optional_type))
}
pub fn check(&self, other: &Type) -> Result<()> {
log::info!("Checking type {self} against {other}.");
match (self, other) {
(Type::Any, _)
| (_, Type::Any)
| (Type::Boolean, Type::Boolean)
| (Type::Collection, Type::Collection)
| (Type::Collection, Type::List(_))
| (Type::List(_), Type::Collection)
| (Type::Collection, Type::Map(_))
| (Type::Map(_), Type::Collection)
| (Type::Collection, Type::String)
| (Type::String, Type::Collection)
| (Type::Float, Type::Float)
| (Type::Integer, Type::Integer)
| (Type::Map(_), Type::Map(_))
| (Type::Number, Type::Number)
| (Type::Number, Type::Integer)
| (Type::Number, Type::Float)
| (Type::Integer, Type::Number)
| (Type::Float, Type::Number)
| (Type::None, Type::None)
| (Type::String, Type::String) => Ok(()),
(Type::Custom(left), Type::Custom(right)) => {
if left == right {
Ok(())
} else {
Err(Error::TypeCheck {
expected: self.clone(),
actual: other.clone(),
})
}
}
(Type::Option(left), Type::Option(right)) => {
if left == right {
Ok(())
} else if let Type::Any = left.as_ref() {
Ok(())
} else if let Type::Any = right.as_ref() {
Ok(())
} else {
Err(Error::TypeCheck {
expected: self.clone(),
actual: other.clone(),
})
}
}
(Type::Option(_), Type::None) | (Type::None, Type::Option(_)) => Ok(()),
(Type::List(self_item_type), Type::List(other_item_type)) => {
if self_item_type.check(other_item_type).is_err() {
Err(Error::TypeCheck {
expected: self.clone(),
actual: other.clone(),
})
} else {
Ok(())
}
}
(
Type::Function {
parameter_types: self_parameter_types,
return_type: self_return_type,
},
Type::Function {
parameter_types: other_parameter_types,
return_type: other_return_type,
},
) => {
let parameter_type_pairs = self_parameter_types
.iter()
.zip(other_parameter_types.iter());
for (self_parameter_type, other_parameter_type) in parameter_type_pairs {
if self_parameter_type.check(other_parameter_type).is_err() {
return Err(Error::TypeCheck {
expected: self.clone(),
actual: other.clone(),
});
}
}
if self_return_type.check(other_return_type).is_err() {
Err(Error::TypeCheck {
expected: self.clone(),
actual: other.clone(),
})
} else {
Ok(())
}
}
_ => Err(Error::TypeCheck {
expected: self.clone(),
actual: other.clone(),
}),
}
}
pub fn is_function(&self) -> bool {
matches!(self, Type::Function { .. })
}
pub fn is_list(&self) -> bool {
matches!(self, Type::List(_))
}
pub fn is_map(&self) -> bool {
matches!(self, Type::Map(_))
}
}
impl AbstractTree for Type {
fn from_syntax(node: SyntaxNode, _source: &str, _context: &Map) -> Result<Self> {
Error::expect_syntax_node(_source, "type", node)?;
let type_node = node.child(0).unwrap();
let r#type = match type_node.kind() {
"[" => {
let item_type_node = node.child(1).unwrap();
let item_type = Type::from_syntax(item_type_node, _source, _context)?;
Type::List(Box::new(item_type))
}
"any" => Type::Any,
"bool" => Type::Boolean,
"collection" => Type::Collection,
"float" => Type::Float,
"(" => {
let child_count = node.child_count();
let mut parameter_types = Vec::new();
for index in 1..child_count - 2 {
let child = node.child(index).unwrap();
if child.is_named() {
let parameter_type = Type::from_syntax(child, _source, _context)?;
parameter_types.push(parameter_type);
}
}
let final_node = node.child(child_count - 1).unwrap();
let return_type = if final_node.is_named() {
Type::from_syntax(final_node, _source, _context)?
} else {
Type::None
};
Type::Function {
parameter_types,
return_type: Box::new(return_type),
}
}
"int" => Type::Integer,
"map" => Type::Map(None),
"num" => Type::Number,
"none" => Type::None,
"str" => Type::String,
"option" => {
let inner_type_node = node.child(2).unwrap();
let inner_type = Type::from_syntax(inner_type_node, _source, _context)?;
Type::Option(Box::new(inner_type))
}
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "any, bool, float, int, num, str, option, (, [ or {".to_string(),
actual: type_node.kind().to_string(),
location: type_node.start_position(),
relevant_source: _source[type_node.byte_range()].to_string(),
})
}
};
Ok(r#type)
}
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
Ok(Value::none())
}
fn expected_type(&self, _context: &Map) -> Result<Type> {
Ok(Type::None)
}
}
impl Format for Type {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
Type::Any => output.push_str("any"),
Type::Boolean => output.push_str("bool"),
Type::Collection => output.push_str("collection"),
Type::Custom(_) => todo!(),
Type::Float => output.push_str("float"),
Type::Function {
parameter_types,
return_type,
} => {
output.push('(');
for (index, parameter_type) in parameter_types.iter().enumerate() {
parameter_type.format(output, indent_level);
if index != parameter_types.len() - 1 {
output.push(' ');
}
}
output.push_str(") -> ");
return_type.format(output, indent_level);
}
Type::Integer => output.push_str("int"),
Type::List(item_type) => {
output.push('[');
item_type.format(output, indent_level);
output.push(']');
}
Type::Map(structure_option) => {
if let Some(structure) = structure_option {
output.push_str(&structure.to_string());
} else {
output.push_str("map");
}
}
Type::None => output.push_str("none"),
Type::Number => output.push_str("num"),
Type::String => output.push_str("str"),
Type::Option(optional_type) => {
output.push_str("option(");
optional_type.format(output, indent_level);
output.push(')');
}
Type::Range => todo!(),
}
}
}
impl Display for Type {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Type::Any => write!(f, "any"),
Type::Boolean => write!(f, "bool"),
Type::Collection => write!(f, "collection"),
Type::Custom(identifier) => write!(f, "{identifier}"),
Type::Float => write!(f, "float"),
Type::Function {
parameter_types,
return_type,
} => {
write!(f, "(")?;
for (index, parameter_type) in parameter_types.iter().enumerate() {
write!(f, "{parameter_type}")?;
if index != parameter_types.len() - 1 {
write!(f, " ")?;
}
}
write!(f, ")")?;
write!(f, " -> {return_type}")
}
Type::Integer => write!(f, "int"),
Type::List(item_type) => write!(f, "[{item_type}]"),
Type::Map(_) => write!(f, "map"),
Type::Number => write!(f, "num"),
Type::None => write!(f, "none"),
Type::String => write!(f, "str"),
Type::Option(inner_type) => {
write!(f, "option({})", inner_type)
}
Type::Range => todo!(),
}
}
}

View File

@ -1,49 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Error, Format, Map, Result, SyntaxNode, Type, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct TypeSpecification {
r#type: Type,
}
impl TypeSpecification {
pub fn new(r#type: Type) -> Self {
Self { r#type }
}
pub fn inner(&self) -> &Type {
&self.r#type
}
pub fn take_inner(self) -> Type {
self.r#type
}
}
impl AbstractTree for TypeSpecification {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "type_specification", node)?;
let type_node = node.child(1).unwrap();
let r#type = Type::from_syntax(type_node, source, context)?;
Ok(TypeSpecification { r#type })
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
self.r#type.run(source, context)
}
fn expected_type(&self, context: &Map) -> Result<Type> {
self.r#type.expected_type(context)
}
}
impl Format for TypeSpecification {
fn format(&self, output: &mut String, indent_level: u8) {
output.push('<');
self.r#type.format(output, indent_level);
output.push('>');
}
}

View File

@ -1,398 +0,0 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::{
AbstractTree, BuiltInValue, Error, Expression, Format, Function, FunctionNode, Identifier,
List, Map, Range, Result, Statement, Structure, SyntaxNode, Type, TypeDefintion,
TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum ValueNode {
Boolean(String),
Float(String),
Function(Function),
Integer(String),
String(String),
List(Vec<Expression>),
Option(Option<Box<Expression>>),
Map(BTreeMap<String, (Statement, Option<Type>)>),
BuiltInValue(BuiltInValue),
Structure(BTreeMap<String, (Option<Statement>, Type)>),
Range(Range),
}
impl AbstractTree for ValueNode {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "value", node)?;
let child = node.child(0).unwrap();
let value_node = match child.kind() {
"boolean" => ValueNode::Boolean(source[child.byte_range()].to_string()),
"float" => ValueNode::Float(source[child.byte_range()].to_string()),
"function" => {
let function_node = FunctionNode::from_syntax(child, source, context)?;
ValueNode::Function(Function::ContextDefined(function_node))
}
"integer" => ValueNode::Integer(source[child.byte_range()].to_string()),
"string" => {
let without_quotes = child.start_byte() + 1..child.end_byte() - 1;
ValueNode::String(source[without_quotes].to_string())
}
"list" => {
let mut expressions = Vec::new();
for index in 1..child.child_count() - 1 {
let current_node = child.child(index).unwrap();
if current_node.is_named() {
let expression = Expression::from_syntax(current_node, source, context)?;
expressions.push(expression);
}
}
ValueNode::List(expressions)
}
"map" => {
let mut child_nodes = BTreeMap::new();
let mut current_key = "".to_string();
let mut current_type = None;
for index in 0..child.child_count() - 1 {
let child = child.child(index).unwrap();
if child.kind() == "identifier" {
current_key = Identifier::from_syntax(child, source, context)?.take_inner();
current_type = None;
}
if child.kind() == "type_specification" {
current_type = Some(
TypeSpecification::from_syntax(child, source, context)?.take_inner(),
);
}
if child.kind() == "statement" {
let statement = Statement::from_syntax(child, source, context)?;
if let Some(type_specification) = &current_type {
type_specification.check(&statement.expected_type(context)?)?;
}
child_nodes.insert(current_key.clone(), (statement, current_type.clone()));
}
}
ValueNode::Map(child_nodes)
}
"option" => {
let first_grandchild = child.child(0).unwrap();
if first_grandchild.kind() == "none" {
ValueNode::Option(None)
} else {
let expression_node = child.child(2).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?;
ValueNode::Option(Some(Box::new(expression)))
}
}
"built_in_value" => {
let built_in_value_node = child.child(0).unwrap();
ValueNode::BuiltInValue(BuiltInValue::from_syntax(
built_in_value_node,
source,
context,
)?)
}
"structure" => {
let mut btree_map = BTreeMap::new();
let mut current_identifier: Option<Identifier> = None;
let mut current_type: Option<Type> = None;
let mut current_statement = None;
for index in 2..child.child_count() - 1 {
let child_syntax_node = child.child(index).unwrap();
if child_syntax_node.kind() == "identifier" {
if current_statement.is_none() {
if let (Some(identifier), Some(r#type)) =
(&current_identifier, &current_type)
{
btree_map
.insert(identifier.inner().clone(), (None, r#type.clone()));
}
}
current_type = None;
current_identifier =
Some(Identifier::from_syntax(child_syntax_node, source, context)?);
}
if child_syntax_node.kind() == "type_specification" {
current_type = Some(
TypeSpecification::from_syntax(child_syntax_node, source, context)?
.take_inner(),
);
}
if child_syntax_node.kind() == "statement" {
current_statement =
Some(Statement::from_syntax(child_syntax_node, source, context)?);
if let Some(identifier) = &current_identifier {
let r#type = if let Some(r#type) = &current_type {
r#type.clone()
} else if let Some(statement) = &current_statement {
statement.expected_type(context)?
} else {
Type::None
};
btree_map.insert(
identifier.inner().clone(),
(current_statement.clone(), r#type.clone()),
);
}
}
}
ValueNode::Structure(btree_map)
}
"range" => {
let start_node = child.child(0).unwrap();
let end_node = child.child(2).unwrap();
let start = source[start_node.byte_range()].parse().unwrap();
let end = source[end_node.byte_range()].parse().unwrap();
ValueNode::Range(Range { start, end })
}
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected:
"string, integer, float, boolean, range, list, map, option or structure"
.to_string(),
actual: child.kind().to_string(),
location: child.start_position(),
relevant_source: source[child.byte_range()].to_string(),
})
}
};
Ok(value_node)
}
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
match self {
ValueNode::Function(function) => {
if let Function::ContextDefined(function_node) = function {
function_node.check_type(_source, _context)?;
}
}
_ => {}
}
Ok(())
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
let value = match self {
ValueNode::Boolean(value_source) => Value::Boolean(value_source.parse().unwrap()),
ValueNode::Float(value_source) => Value::Float(value_source.parse().unwrap()),
ValueNode::Function(function) => Value::Function(function.clone()),
ValueNode::Integer(value_source) => Value::Integer(value_source.parse().unwrap()),
ValueNode::String(value_source) => Value::string(value_source.clone()),
ValueNode::List(expressions) => {
let mut values = Vec::with_capacity(expressions.len());
for node in expressions {
let value = node.run(source, context)?;
values.push(value);
}
Value::List(List::with_items(values))
}
ValueNode::Option(option) => {
let option_value = if let Some(expression) = option {
Some(Box::new(expression.run(source, context)?))
} else {
None
};
Value::Option(option_value)
}
ValueNode::Map(key_statement_pairs) => {
let map = Map::new();
{
for (key, (statement, _)) in key_statement_pairs {
let value = statement.run(source, context)?;
map.set(key.clone(), value)?;
}
}
Value::Map(map)
}
ValueNode::BuiltInValue(built_in_value) => built_in_value.run(source, context)?,
ValueNode::Structure(node_map) => {
let mut value_map = BTreeMap::new();
for (key, (statement_option, r#type)) in node_map {
let value_option = if let Some(statement) = statement_option {
Some(statement.run(source, context)?)
} else {
None
};
value_map.insert(key.to_string(), (value_option, r#type.clone()));
}
Value::TypeDefinition(TypeDefintion::Structure(Structure::new(value_map)))
}
ValueNode::Range(range) => Value::Range(*range),
};
Ok(value)
}
fn expected_type(&self, context: &Map) -> Result<Type> {
let r#type = match self {
ValueNode::Boolean(_) => Type::Boolean,
ValueNode::Float(_) => Type::Float,
ValueNode::Function(function) => function.r#type().clone(),
ValueNode::Integer(_) => Type::Integer,
ValueNode::String(_) => Type::String,
ValueNode::List(expressions) => {
let mut previous_type = None;
for expression in expressions {
let expression_type = expression.expected_type(context)?;
if let Some(previous) = previous_type {
if expression_type != previous {
return Ok(Type::List(Box::new(Type::Any)));
}
}
previous_type = Some(expression_type);
}
if let Some(previous) = previous_type {
Type::List(Box::new(previous))
} else {
Type::List(Box::new(Type::Any))
}
}
ValueNode::Option(option) => {
if let Some(expression) = option {
Type::Option(Box::new(expression.expected_type(context)?))
} else {
Type::None
}
}
ValueNode::Map(_) => Type::Map(None),
ValueNode::BuiltInValue(built_in_value) => built_in_value.expected_type(context)?,
ValueNode::Structure(node_map) => {
let mut value_map = BTreeMap::new();
for (key, (_statement_option, r#type)) in node_map {
value_map.insert(key.to_string(), (None, r#type.clone()));
}
Type::Map(Some(Structure::new(value_map)))
}
ValueNode::Range(_) => Type::Range,
};
Ok(r#type)
}
}
impl Format for ValueNode {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
ValueNode::Boolean(source) | ValueNode::Float(source) | ValueNode::Integer(source) => {
output.push_str(source)
}
ValueNode::String(source) => {
output.push('\'');
output.push_str(source);
output.push('\'');
}
ValueNode::Function(function) => function.format(output, indent_level),
ValueNode::List(expressions) => {
output.push('[');
for expression in expressions {
expression.format(output, indent_level);
}
output.push(']');
}
ValueNode::Option(option) => {
if let Some(expression) = option {
output.push_str("some(");
expression.format(output, indent_level);
output.push(')');
} else {
output.push_str("none");
}
}
ValueNode::Map(nodes) => {
output.push_str("{\n");
for (key, (statement, type_option)) in nodes {
if let Some(r#type) = type_option {
ValueNode::indent(output, indent_level + 1);
output.push_str(key);
output.push_str(" <");
r#type.format(output, 0);
output.push_str("> = ");
statement.format(output, 0);
} else {
ValueNode::indent(output, indent_level + 1);
output.push_str(key);
output.push_str(" = ");
statement.format(output, 0);
}
output.push('\n');
}
ValueNode::indent(output, indent_level);
output.push('}');
}
ValueNode::BuiltInValue(built_in_value) => built_in_value.format(output, indent_level),
ValueNode::Structure(nodes) => {
output.push('{');
for (key, (value_option, r#type)) in nodes {
if let Some(value) = value_option {
output.push_str(" ");
output.push_str(key);
output.push_str(" <");
r#type.format(output, indent_level);
output.push_str("> = ");
value.format(output, indent_level);
} else {
output.push_str(" ");
output.push_str(key);
output.push_str(" <");
r#type.format(output, indent_level);
output.push('>');
}
}
output.push('}');
}
ValueNode::Range(_) => todo!(),
}
}
}

View File

@ -1,50 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Block, Error, Expression, Format, Map, Result, SyntaxNode, Type, Value};
/// Abstract representation of a while loop.
///
/// While executes its block repeatedly until its expression evaluates to true.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct While {
expression: Expression,
block: Block,
}
impl AbstractTree for While {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> crate::Result<Self> {
Error::expect_syntax_node(source, "while", node)?;
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?;
let block_node = node.child(2).unwrap();
let block = Block::from_syntax(block_node, source, context)?;
Ok(While { expression, block })
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
while self.expression.run(source, context)?.as_boolean()? {
self.block.run(source, context)?;
}
Ok(Value::none())
}
fn expected_type(&self, context: &Map) -> Result<Type> {
self.block.expected_type(context)
}
}
impl Format for While {
fn format(&self, output: &mut String, indent_level: u8) {
output.push('\n');
While::indent(output, indent_level);
output.push_str("while ");
self.expression.format(output, indent_level);
output.push(' ');
self.block.format(output, indent_level);
output.push('\n');
}
}

View File

@ -1,58 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
function_expression::FunctionExpression, AbstractTree, Error, Expression, Format, FunctionCall,
Map, Result, SyntaxNode, Type, Value,
};
/// Abstract representation of a yield expression.
///
/// Yield is an alternate means of calling and passing values to a function.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Yield {
call: FunctionCall,
}
impl AbstractTree for Yield {
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self> {
Error::expect_syntax_node(source, "yield", node)?;
let input_node = node.child(0).unwrap();
let input = Expression::from_syntax(input_node, source, context)?;
let function_node = node.child(2).unwrap();
let function_expression = FunctionExpression::from_syntax(function_node, source, context)?;
let mut arguments = Vec::new();
arguments.push(input);
for index in 3..node.child_count() - 1 {
let child = node.child(index).unwrap();
if child.is_named() {
let expression = Expression::from_syntax(child, source, context)?;
arguments.push(expression);
}
}
let call = FunctionCall::new(function_expression, arguments, node.range().into());
Ok(Yield { call })
}
fn run(&self, source: &str, context: &Map) -> Result<Value> {
self.call.run(source, context)
}
fn expected_type(&self, context: &Map) -> Result<Type> {
self.call.expected_type(context)
}
}
impl Format for Yield {
fn format(&self, output: &mut String, indent_level: u8) {
self.call.format(output, indent_level);
}
}

View File

@ -1,165 +0,0 @@
mod string;
use std::{
fmt::{self, Display, Formatter},
fs::read_to_string,
};
use rand::{random, thread_rng, Rng};
use serde::{Deserialize, Serialize};
use crate::{Error, Format, Map, Result, Type, Value};
pub use string::{string_functions, StringFunction};
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum BuiltInFunction {
AssertEqual,
FsRead,
JsonParse,
Length,
Output,
RandomBoolean,
RandomFloat,
RandomFrom,
RandomInteger,
String(StringFunction),
}
impl BuiltInFunction {
pub fn name(&self) -> &'static str {
match self {
BuiltInFunction::AssertEqual => "assert_equal",
BuiltInFunction::FsRead => "read",
BuiltInFunction::JsonParse => "parse",
BuiltInFunction::Length => "length",
BuiltInFunction::Output => "output",
BuiltInFunction::RandomBoolean => "boolean",
BuiltInFunction::RandomFloat => "float",
BuiltInFunction::RandomFrom => "from",
BuiltInFunction::RandomInteger => "integer",
BuiltInFunction::String(string_function) => string_function.name(),
}
}
pub fn r#type(&self) -> Type {
match self {
BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None),
BuiltInFunction::FsRead => Type::function(vec![Type::String], Type::String),
BuiltInFunction::JsonParse => Type::function(vec![Type::String], Type::Any),
BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer),
BuiltInFunction::Output => Type::function(vec![Type::Any], Type::None),
BuiltInFunction::RandomBoolean => Type::function(vec![], Type::Boolean),
BuiltInFunction::RandomFloat => Type::function(vec![], Type::Float),
BuiltInFunction::RandomFrom => Type::function(vec![Type::Collection], Type::Any),
BuiltInFunction::RandomInteger => Type::function(vec![], Type::Integer),
BuiltInFunction::String(string_function) => string_function.r#type(),
}
}
pub fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> {
match self {
BuiltInFunction::AssertEqual => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let left = arguments.first().unwrap();
let right = arguments.get(1).unwrap();
Ok(Value::Boolean(left == right))
}
BuiltInFunction::FsRead => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let path = arguments.first().unwrap().as_string()?;
let file_content = read_to_string(path.as_str())?;
Ok(Value::string(file_content))
}
BuiltInFunction::JsonParse => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let value = serde_json::from_str(string)?;
Ok(value)
}
BuiltInFunction::Length => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
let length = if let Ok(list) = value.as_list() {
list.items().len()
} else if let Ok(map) = value.as_map() {
map.variables()?.len()
} else if let Ok(str) = value.as_string() {
str.chars().count()
} else {
return Err(Error::ExpectedCollection {
actual: value.clone(),
});
};
Ok(Value::Integer(length as i64))
}
BuiltInFunction::Output => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
println!("{value}");
Ok(Value::none())
}
BuiltInFunction::RandomBoolean => {
Error::expect_argument_amount(self.name(), 0, arguments.len())?;
Ok(Value::Boolean(random()))
}
BuiltInFunction::RandomFloat => {
Error::expect_argument_amount(self.name(), 0, arguments.len())?;
Ok(Value::Float(random()))
}
BuiltInFunction::RandomFrom => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
if let Ok(list) = value.as_list() {
let items = list.items();
if items.len() == 0 {
Ok(Value::none())
} else {
let random_index = thread_rng().gen_range(0..items.len());
let random_value = items.get(random_index).cloned().unwrap_or_default();
Ok(random_value)
}
} else {
todo!()
}
}
BuiltInFunction::RandomInteger => {
Error::expect_argument_amount(self.name(), 0, arguments.len())?;
Ok(Value::Integer(random()))
}
BuiltInFunction::String(string_function) => {
string_function.call(arguments, _source, _outer_context)
}
}
}
}
impl Format for BuiltInFunction {
fn format(&self, output: &mut String, _indent_level: u8) {
output.push_str(self.name());
}
}
impl Display for BuiltInFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}

View File

@ -1,524 +0,0 @@
use enum_iterator::{all, Sequence};
use serde::{Deserialize, Serialize};
use crate::{Error, List, Map, Result, Type, Value};
pub fn string_functions() -> impl Iterator<Item = StringFunction> {
all()
}
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum StringFunction {
AsBytes,
EndsWith,
Find,
Insert,
IsAscii,
IsEmpty,
Lines,
Matches,
Parse,
Remove,
ReplaceRange,
Retain,
Split,
SplitAt,
SplitInclusive,
SplitN,
SplitOnce,
SplitTerminator,
SplitWhitespace,
StartsWith,
StripPrefix,
ToLowercase,
ToUppercase,
Trim,
TrimEnd,
TrimEndMatches,
TrimMatches,
TrimStart,
TrimStartMatches,
Truncate,
}
impl StringFunction {
pub fn name(&self) -> &'static str {
match self {
StringFunction::AsBytes => "as_bytes",
StringFunction::EndsWith => "ends_with",
StringFunction::Find => "find",
StringFunction::Insert => "insert",
StringFunction::IsAscii => "is_ascii",
StringFunction::IsEmpty => "is_empty",
StringFunction::Lines => "lines",
StringFunction::Matches => "matches",
StringFunction::Parse => "parse",
StringFunction::Remove => "remove",
StringFunction::ReplaceRange => "replace_range",
StringFunction::Retain => "retain",
StringFunction::Split => "split",
StringFunction::SplitAt => "split_at",
StringFunction::SplitInclusive => "split_inclusive",
StringFunction::SplitN => "split_n",
StringFunction::SplitOnce => "split_once",
StringFunction::SplitTerminator => "split_terminator",
StringFunction::SplitWhitespace => "split_whitespace",
StringFunction::StartsWith => "starts_with",
StringFunction::StripPrefix => "strip_prefix",
StringFunction::ToLowercase => "to_lowercase",
StringFunction::ToUppercase => "to_uppercase",
StringFunction::Trim => "trim",
StringFunction::TrimEnd => "trim_end",
StringFunction::TrimEndMatches => "trim_end_matches",
StringFunction::TrimMatches => "trim_matches",
StringFunction::TrimStart => "trim_start",
StringFunction::TrimStartMatches => "trim_start_matches",
StringFunction::Truncate => "truncate",
}
}
pub fn r#type(&self) -> Type {
match self {
StringFunction::AsBytes => {
Type::function(vec![Type::String], Type::list(Type::Integer))
}
StringFunction::EndsWith => {
Type::function(vec![Type::String, Type::String], Type::Boolean)
}
StringFunction::Find => Type::function(
vec![Type::String, Type::String],
Type::option(Type::Integer),
),
StringFunction::Insert => Type::function(
vec![Type::String, Type::Integer, Type::String],
Type::String,
),
StringFunction::IsAscii => Type::function(vec![Type::String], Type::Boolean),
StringFunction::IsEmpty => Type::function(vec![Type::String], Type::Boolean),
StringFunction::Lines => Type::function(vec![Type::String], Type::list(Type::String)),
StringFunction::Matches => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StringFunction::Parse => Type::function(vec![Type::String], Type::Any),
StringFunction::Remove => Type::function(
vec![Type::String, Type::Integer],
Type::option(Type::String),
),
StringFunction::ReplaceRange => Type::function(
vec![Type::String, Type::list(Type::Integer), Type::String],
Type::String,
),
StringFunction::Retain => Type::function(
vec![
Type::String,
Type::function(vec![Type::String], Type::Boolean),
],
Type::String,
),
StringFunction::Split => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StringFunction::SplitAt => {
Type::function(vec![Type::String, Type::Integer], Type::list(Type::String))
}
StringFunction::SplitInclusive => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StringFunction::SplitN => Type::function(
vec![Type::String, Type::Integer, Type::String],
Type::list(Type::String),
),
StringFunction::SplitOnce => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StringFunction::SplitTerminator => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StringFunction::SplitWhitespace => {
Type::function(vec![Type::String], Type::list(Type::String))
}
StringFunction::StartsWith => {
Type::function(vec![Type::String, Type::String], Type::Boolean)
}
StringFunction::StripPrefix => {
Type::function(vec![Type::String, Type::String], Type::option(Type::String))
}
StringFunction::ToLowercase => Type::function(vec![Type::String], Type::String),
StringFunction::ToUppercase => Type::function(vec![Type::String], Type::String),
StringFunction::Truncate => {
Type::function(vec![Type::String, Type::Integer], Type::String)
}
StringFunction::Trim => Type::function(vec![Type::String], Type::String),
StringFunction::TrimEnd => Type::function(vec![Type::String], Type::String),
StringFunction::TrimEndMatches => {
Type::function(vec![Type::String, Type::String], Type::String)
}
StringFunction::TrimMatches => {
Type::function(vec![Type::String, Type::String], Type::String)
}
StringFunction::TrimStart => Type::function(vec![Type::String], Type::String),
StringFunction::TrimStartMatches => {
Type::function(vec![Type::String, Type::String], Type::String)
}
}
}
pub fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> {
let value = match self {
StringFunction::AsBytes => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let bytes = string
.bytes()
.map(|byte| Value::Integer(byte as i64))
.collect();
Value::List(List::with_items(bytes))
}
StringFunction::EndsWith => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
Value::Boolean(string.ends_with(pattern))
}
StringFunction::Find => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let find = string
.find(pattern)
.map(|index| Box::new(Value::Integer(index as i64)));
Value::Option(find)
}
StringFunction::IsAscii => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
Value::Boolean(string.is_ascii())
}
StringFunction::IsEmpty => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
Value::Boolean(string.is_empty())
}
StringFunction::Insert => {
Error::expect_argument_amount(self.name(), 3, arguments.len())?;
let mut string = arguments.first().unwrap().as_string()?.clone();
let index = arguments.get(1).unwrap().as_integer()? as usize;
let insertion = arguments.get(2).unwrap().as_string()?;
string.insert_str(index, insertion);
Value::String(string)
}
StringFunction::Lines => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let lines = string
.lines()
.map(|line| Value::string(line.to_string()))
.collect();
Value::List(List::with_items(lines))
}
StringFunction::Matches => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let matches = string
.matches(pattern)
.map(|pattern| Value::string(pattern.to_string()))
.collect();
Value::List(List::with_items(matches))
}
StringFunction::Parse => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
if let Ok(integer) = string.parse::<i64>() {
Value::option(Some(Value::Integer(integer)))
} else if let Ok(float) = string.parse::<f64>() {
Value::option(Some(Value::Float(float)))
} else {
Value::none()
}
}
StringFunction::Remove => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let index = arguments.get(1).unwrap().as_integer()? as usize;
let chars = string.chars().collect::<Vec<char>>();
if index < chars.len() {
let new_string = chars
.iter()
.map(|char| char.to_string())
.collect::<String>();
Value::some(Value::string(new_string))
} else {
Value::none()
}
}
StringFunction::ReplaceRange => {
Error::expect_argument_amount(self.name(), 3, arguments.len())?;
let mut string = arguments.first().unwrap().as_string()?.clone();
let range = arguments.get(1).unwrap().as_list()?.items();
let start = range.first().unwrap_or_default().as_integer()? as usize;
let end = range.get(1).unwrap_or_default().as_integer()? as usize;
let pattern = arguments.get(2).unwrap().as_string()?;
string.replace_range(start..end, pattern);
Value::String(string)
}
StringFunction::Retain => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let mut string = arguments.first().unwrap().as_string()?.clone();
let predicate = arguments.get(1).unwrap().as_function()?;
string.retain(|char| {
predicate
.call(&[Value::string(char)], _source, _outer_context)
.unwrap()
.as_boolean()
.unwrap()
});
Value::String(string)
}
StringFunction::Split => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string
.split(pattern)
.map(|section| Value::string(section.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StringFunction::SplitAt => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let index = arguments.get(1).unwrap().as_integer()?;
let (left, right) = string.split_at(index as usize);
Value::List(List::with_items(vec![
Value::string(left.to_string()),
Value::string(right.to_string()),
]))
}
StringFunction::SplitInclusive => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string
.split(pattern)
.map(|pattern| Value::string(pattern.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StringFunction::SplitN => {
Error::expect_argument_amount(self.name(), 3, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let count = arguments.get(1).unwrap().as_integer()?;
let pattern_string = arguments.get(2).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string
.splitn(count as usize, pattern)
.map(|pattern| Value::string(pattern.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StringFunction::SplitOnce => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string.split_once(pattern).map(|(left, right)| {
Value::List(List::with_items(vec![
Value::string(left.to_string()),
Value::string(right.to_string()),
]))
});
Value::option(sections)
}
StringFunction::SplitTerminator => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string
.split_terminator(pattern)
.map(|section| Value::string(section.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StringFunction::SplitWhitespace => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let sections = string
.split_whitespace()
.map(|section| Value::string(section.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StringFunction::StartsWith => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
Value::Boolean(string.starts_with(pattern))
}
StringFunction::StripPrefix => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let prefix_string = arguments.get(1).unwrap().as_string()?;
let prefix = prefix_string.as_str();
let stripped = string
.strip_prefix(prefix)
.map(|remainder| Value::string(remainder.to_string()));
Value::option(stripped)
}
StringFunction::ToLowercase => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let lowercase = string.to_lowercase();
Value::string(lowercase)
}
StringFunction::ToUppercase => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let uppercase = string.to_uppercase();
Value::string(uppercase)
}
StringFunction::Trim => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let trimmed = arguments.first().unwrap().as_string()?.trim().to_string();
Value::string(trimmed)
}
StringFunction::TrimEnd => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let trimmed = arguments
.first()
.unwrap()
.as_string()?
.trim_end()
.to_string();
Value::string(trimmed)
}
StringFunction::TrimEndMatches => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let trimmed = string.trim_end_matches(pattern).to_string();
Value::string(trimmed)
}
StringFunction::TrimMatches => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern = arguments
.get(1)
.unwrap()
.as_string()?
.chars()
.collect::<Vec<char>>();
let trimmed = string.trim_matches(pattern.as_slice()).to_string();
Value::string(trimmed)
}
StringFunction::TrimStart => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let trimmed = arguments
.first()
.unwrap()
.as_string()?
.trim_start()
.to_string();
Value::string(trimmed)
}
StringFunction::TrimStartMatches => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern = arguments
.get(1)
.unwrap()
.as_string()?
.chars()
.collect::<Vec<char>>();
let trimmed = string.trim_start_matches(pattern.as_slice()).to_string();
Value::string(trimmed)
}
StringFunction::Truncate => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
let input_string = arguments.first().unwrap().as_string()?;
let new_length = arguments.get(1).unwrap().as_integer()? as usize;
let new_string = input_string
.chars()
.take(new_length)
.map(|char| char.to_string())
.collect();
Value::String(new_string)
}
};
Ok(value)
}
}

View File

@ -1,458 +0,0 @@
//! Error and Result types.
//!
//! To deal with errors from dependencies, either create a new error variant
//! or use the ToolFailure variant if the error can only occur inside a tool.
use serde::{Deserialize, Serialize};
use tree_sitter::{LanguageError, Node, Point};
use crate::{value::Value, SyntaxPosition, Type};
use std::{
fmt::{self, Formatter},
io,
num::ParseFloatError,
string::FromUtf8Error,
sync::PoisonError,
time,
};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub enum Error {
AtSourcePosition {
error: Box<Error>,
source: String,
start_row: usize,
start_column: usize,
end_row: usize,
end_column: usize,
},
UnexpectedSyntaxNode {
expected: String,
actual: String,
#[serde(skip)]
location: Point,
relevant_source: String,
},
TypeCheck {
expected: Type,
actual: Type,
},
TypeCheckExpectedFunction {
actual: Type,
},
/// The 'assert' macro did not resolve successfully.
AssertEqualFailed {
expected: Value,
actual: Value,
},
/// The 'assert' macro did not resolve successfully.
AssertFailed,
/// A row was inserted to a table with the wrong amount of values.
WrongColumnAmount {
expected: usize,
actual: usize,
},
/// An operator was called with the wrong amount of arguments.
ExpectedOperatorArgumentAmount {
expected: usize,
actual: usize,
},
/// A function was called with the wrong amount of arguments.
ExpectedBuiltInFunctionArgumentAmount {
function_name: String,
expected: usize,
actual: usize,
},
/// A function was called with the wrong amount of arguments.
ExpectedFunctionArgumentAmount {
expected: usize,
actual: usize,
},
/// A function was called with the wrong amount of arguments.
ExpectedFunctionArgumentMinimum {
source: String,
minumum_expected: usize,
actual: usize,
},
ExpectedFunctionType {
actual: Type,
},
ExpectedString {
actual: Value,
},
ExpectedInteger {
actual: Value,
},
ExpectedFloat {
actual: Value,
},
/// An integer, floating point or value was expected.
ExpectedNumber {
actual: Value,
},
/// An integer, floating point or string value was expected.
ExpectedNumberOrString {
actual: Value,
},
ExpectedBoolean {
actual: Value,
},
ExpectedList {
actual: Value,
},
ExpectedMinLengthList {
minimum_len: usize,
actual_len: usize,
},
ExpectedFixedLenList {
expected_len: usize,
actual: Value,
},
ExpectedNone {
actual: Value,
},
ExpectedMap {
actual: Value,
},
ExpectedTable {
actual: Value,
},
ExpectedFunction {
actual: Value,
},
ExpectedOption {
actual: Value,
},
/// A string, list, map or table value was expected.
ExpectedCollection {
actual: Value,
},
/// Failed to find a variable with a value for this key.
VariableIdentifierNotFound(String),
/// Failed to find a variable with a function value for this key.
FunctionIdentifierNotFound(String),
/// The function failed due to an external error.
External(String),
/// A custom error explained by its message.
CustomMessage(String),
/// Invalid user input.
Syntax {
source: String,
#[serde(skip)]
location: Point,
},
SerdeJson(String),
ParserCancelled,
}
impl Error {
pub fn at_source_position(self, source: &str, position: SyntaxPosition) -> Self {
let byte_range = position.start_byte..position.end_byte;
Error::AtSourcePosition {
error: Box::new(self),
source: source[byte_range].to_string(),
start_row: position.start_row,
start_column: position.start_column,
end_row: position.end_row,
end_column: position.end_column,
}
}
pub fn expect_syntax_node(source: &str, expected: &str, actual: Node) -> Result<()> {
log::info!("Converting {} to abstract node", actual.kind());
if expected == actual.kind() {
Ok(())
} else if actual.is_error() {
Err(Error::Syntax {
source: source[actual.byte_range()].to_string(),
location: actual.start_position(),
})
} else {
Err(Error::UnexpectedSyntaxNode {
expected: expected.to_string(),
actual: actual.kind().to_string(),
location: actual.start_position(),
relevant_source: source[actual.byte_range()].to_string(),
})
}
}
pub fn expect_argument_amount(
function_name: &str,
expected: usize,
actual: usize,
) -> Result<()> {
if expected == actual {
Ok(())
} else {
Err(Error::ExpectedBuiltInFunctionArgumentAmount {
function_name: function_name.to_string(),
expected,
actual,
})
}
}
pub fn is_error(&self, other: &Error) -> bool {
match self {
Error::AtSourcePosition { error, .. } => error.as_ref() == other,
_ => self == other,
}
}
}
impl From<LanguageError> for Error {
fn from(value: LanguageError) -> Self {
Error::External(value.to_string())
}
}
impl<T> From<PoisonError<T>> for Error {
fn from(value: PoisonError<T>) -> Self {
Error::External(value.to_string())
}
}
impl From<FromUtf8Error> for Error {
fn from(value: FromUtf8Error) -> Self {
Error::External(value.to_string())
}
}
impl From<ParseFloatError> for Error {
fn from(value: ParseFloatError) -> Self {
Error::External(value.to_string())
}
}
impl From<csv::Error> for Error {
fn from(value: csv::Error) -> Self {
Error::External(value.to_string())
}
}
impl From<io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::External(value.to_string())
}
}
impl From<reqwest::Error> for Error {
fn from(value: reqwest::Error) -> Self {
Error::External(value.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Error::SerdeJson(value.to_string())
}
}
impl From<time::SystemTimeError> for Error {
fn from(value: time::SystemTimeError) -> Self {
Error::External(value.to_string())
}
}
impl From<toml::de::Error> for Error {
fn from(value: toml::de::Error) -> Self {
Error::External(value.to_string())
}
}
impl std::error::Error for Error {}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use Error::*;
match self {
AssertEqualFailed { expected, actual } => {
write!(
f,
"Equality assertion failed. {expected} does not equal {actual}."
)
}
AssertFailed => write!(
f,
"Assertion failed. A false value was passed to \"assert\"."
),
ExpectedOperatorArgumentAmount { expected, actual } => write!(
f,
"An operator expected {} arguments, but got {}.",
expected, actual
),
ExpectedBuiltInFunctionArgumentAmount {
function_name: tool_name,
expected,
actual,
} => write!(
f,
"{tool_name} expected {expected} arguments, but got {actual}.",
),
ExpectedFunctionArgumentAmount { expected, actual } => {
write!(f, "Expected {expected} arguments, but got {actual}.",)
}
ExpectedFunctionArgumentMinimum {
source,
minumum_expected,
actual,
} => {
write!(
f,
"{source} expected at least {minumum_expected} arguments, but got {actual}."
)
}
ExpectedString { actual } => {
write!(f, "Expected a string but got {actual}.")
}
ExpectedInteger { actual } => write!(f, "Expected an integer, but got {actual}."),
ExpectedFloat { actual } => write!(f, "Expected a float, but got {actual}."),
ExpectedNumber { actual } => {
write!(f, "Expected a float or integer but got {actual}.",)
}
ExpectedNumberOrString { actual } => {
write!(f, "Expected a number or string, but got {actual}.")
}
ExpectedBoolean { actual } => {
write!(f, "Expected a boolean, but got {actual}.")
}
ExpectedList { actual } => write!(f, "Expected a list, but got {actual}."),
ExpectedMinLengthList {
minimum_len,
actual_len,
} => write!(
f,
"Expected a list of at least {minimum_len} values, but got one with {actual_len}.",
),
ExpectedFixedLenList {
expected_len,
actual,
} => write!(
f,
"Expected a list of len {}, but got {:?}.",
expected_len, actual
),
ExpectedNone { actual } => write!(f, "Expected an empty value, but got {actual}."),
ExpectedMap { actual } => write!(f, "Expected a map, but got {actual}."),
ExpectedTable { actual } => write!(f, "Expected a table, but got {actual}."),
ExpectedFunction { actual } => {
write!(f, "Expected function, but got {actual}.")
}
ExpectedOption { actual } => write!(f, "Expected option, but got {actual}."),
ExpectedCollection { actual } => {
write!(
f,
"Expected a string, list, map or table, but got {actual}.",
)
}
VariableIdentifierNotFound(key) => write!(
f,
"Variable identifier is not bound to anything by context: {key}.",
),
FunctionIdentifierNotFound(key) => write!(
f,
"Function identifier is not bound to anything by context: {key}."
),
UnexpectedSyntaxNode {
expected,
actual,
location,
relevant_source,
} => {
let location = get_position(location);
write!(
f,
"Expected {expected}, but got {actual} at {location}. Code: {relevant_source} ",
)
}
WrongColumnAmount { expected, actual } => write!(
f,
"Wrong column amount. Expected {expected} but got {actual}."
),
External(message) => write!(f, "External error: {message}"),
CustomMessage(message) => write!(f, "{message}"),
Syntax { source, location } => {
let location = get_position(location);
write!(f, "Syntax error at {location}: {source}")
}
TypeCheck { expected, actual } => write!(
f,
"Type check error. Expected type {expected} but got type {actual}."
),
TypeCheckExpectedFunction { actual } => {
write!(f, "Type check error. Expected a function but got {actual}.")
}
AtSourcePosition {
error,
source,
start_row,
start_column,
end_row,
end_column,
} => {
write!(
f,
"{error} Occured at ({start_row}, {start_column}) to ({end_row}, {end_column}). Source: {source}"
)
}
SerdeJson(message) => write!(f, "JSON processing error: {message}"),
ParserCancelled => write!(
f,
"Parsing was cancelled either manually or because it took too long."
),
ExpectedFunctionType { actual } => write!(f, "Expected a function but got {actual}."),
}
}
}
fn get_position(position: &Point) -> String {
format!("column {}, row {}", position.row + 1, position.column)
}

View File

@ -1,150 +0,0 @@
//! The top level of Dust's API with functions to interpret Dust code.
//!
//! You can use this library externally by calling either of the "eval"
//! functions or by constructing your own Evaluator.
use tree_sitter::{Parser, Tree as TSTree, TreeCursor};
use crate::{language, AbstractTree, Error, Format, Map, Result, Root, SyntaxNode, Value};
/// Interpret the given source code.
///
/// Returns a vector of results from evaluating the source code. Each comment
/// and statemtent will have its own result.
///
/// # Examples
///
/// ```rust
/// # use dust_lang::*;
/// assert_eq!(interpret("1 + 2 + 3"), Ok(Value::Integer(6)));
/// ```
pub fn interpret(source: &str) -> Result<Value> {
interpret_with_context(source, Map::new())
}
/// Interpret the given source code with the given context.
///
/// # Examples
///
/// ```rust
/// # use dust_lang::*;
/// let context = Map::new();
///
/// context.set("one".into(), 1.into());
/// context.set("two".into(), 2.into());
/// context.set("three".into(), 3.into());
///
/// let dust_code = "four = 4 one + two + three + four";
///
/// assert_eq!(
/// interpret_with_context(dust_code, context),
/// Ok(Value::Integer(10))
/// );
/// ```
pub fn interpret_with_context(source: &str, context: Map) -> Result<Value> {
let mut interpreter = Interpreter::new(context);
let value = interpreter.run(source)?;
Ok(value)
}
/// A source code interpreter for the Dust language.
pub struct Interpreter {
parser: Parser,
context: Map,
syntax_tree: Option<TSTree>,
abstract_tree: Option<Root>,
}
impl Interpreter {
pub fn new(context: Map) -> Self {
let mut parser = Parser::new();
parser
.set_language(language())
.expect("Language version is incompatible with tree sitter version.");
Interpreter {
parser,
context,
syntax_tree: None,
abstract_tree: None,
}
}
pub fn parse(&mut self, source: &str) -> Result<()> {
fn check_for_error(node: SyntaxNode, source: &str, cursor: &mut TreeCursor) -> Result<()> {
if node.is_error() {
Err(Error::Syntax {
source: source[node.byte_range()].to_string(),
location: node.start_position(),
})
} else {
for child in node.children(&mut cursor.clone()) {
check_for_error(child, source, cursor)?;
}
Ok(())
}
}
let syntax_tree = self.parser.parse(source, None);
if let Some(tree) = &syntax_tree {
let root = tree.root_node();
let mut cursor = root.walk();
check_for_error(root, source, &mut cursor)?;
}
self.syntax_tree = syntax_tree;
Ok(())
}
pub fn run(&mut self, source: &str) -> Result<Value> {
self.parse(source)?;
self.abstract_tree = if let Some(syntax_tree) = &self.syntax_tree {
Some(Root::from_syntax(
syntax_tree.root_node(),
source,
&self.context,
)?)
} else {
return Err(Error::ParserCancelled);
};
if let Some(abstract_tree) = &self.abstract_tree {
abstract_tree.check_type(source, &self.context)?;
abstract_tree.run(source, &self.context)
} else {
Ok(Value::none())
}
}
pub fn syntax_tree(&self) -> Result<String> {
if let Some(syntax_tree) = &self.syntax_tree {
Ok(syntax_tree.root_node().to_sexp())
} else {
Err(Error::ParserCancelled)
}
}
pub fn format(&self) -> String {
if let Some(root_node) = &self.abstract_tree {
let mut formatted_source = String::new();
root_node.format(&mut formatted_source, 0);
formatted_source
} else {
String::with_capacity(0)
}
}
}
impl Default for Interpreter {
fn default() -> Self {
Interpreter::new(Map::new())
}
}

View File

@ -1,52 +0,0 @@
//! The Dust library is used to implement the Dust language, `src/main.rs` implements the command
//! line binary.
//!
//! Using this library is simple and straightforward, see the [inferface] module for instructions on
//! interpreting Dust code. Most of the language's features are implemented in the [tools] module.
pub use crate::{
abstract_tree::*, built_in_functions::BuiltInFunction, error::*, interpret::*, value::*,
};
pub use tree_sitter::Node as SyntaxNode;
mod abstract_tree;
pub mod built_in_functions;
mod error;
mod interpret;
mod value;
use tree_sitter::Language;
extern "C" {
fn tree_sitter_dust() -> Language;
}
/// Get the tree-sitter [Language][] for this grammar.
///
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
pub fn language() -> Language {
unsafe { tree_sitter_dust() }
}
/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
pub const NODE_TYPES: &str = include_str!("../tree-sitter-dust/src/node-types.json");
// Uncomment these to include any queries that this grammar contains
// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm");
// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm");
// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
#[cfg(test)]
mod tests {
#[test]
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(super::language())
.expect("Error loading dust language");
}
}

View File

@ -1,264 +0,0 @@
//! Command line interface for the dust programming language.
use clap::{Parser, Subcommand};
use rustyline::{
completion::FilenameCompleter,
config::Builder,
error::ReadlineError,
highlight::Highlighter,
hint::{Hint, Hinter, HistoryHinter},
history::DefaultHistory,
ColorMode, Completer, CompletionType, Context, Editor, Helper, Validator,
};
use std::{borrow::Cow, fs::read_to_string};
use dust_lang::{built_in_values, Interpreter, Map, Value};
/// Command-line arguments to be parsed.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Dust source code to evaluate.
#[arg(short, long)]
command: Option<String>,
/// Data to assign to the "input" variable.
#[arg(short, long)]
input: Option<String>,
/// File whose contents will be assigned to the "input" variable.
#[arg(short = 'p', long)]
input_path: Option<String>,
/// Command for alternate functionality besides running the source.
#[command(subcommand)]
cli_command: Option<CliCommand>,
/// Location of the file to run.
path: Option<String>,
}
#[derive(Subcommand, Debug)]
pub enum CliCommand {
/// Output a formatted version of the input.
Format,
/// Output a concrete syntax tree of the input.
Syntax,
}
fn main() {
env_logger::init();
let args = Args::parse();
let context = Map::new();
if let Some(input) = args.input {
context
.set("input".to_string(), Value::string(input))
.unwrap();
}
if let Some(path) = args.input_path {
let file_contents = read_to_string(path).unwrap();
context
.set("input".to_string(), Value::string(file_contents))
.unwrap();
}
if args.path.is_none() && args.command.is_none() {
return run_cli_shell(context);
}
let source = if let Some(path) = &args.path {
read_to_string(path).unwrap()
} else if let Some(command) = args.command {
command
} else {
String::with_capacity(0)
};
let mut interpreter = Interpreter::new(context);
if let Some(CliCommand::Syntax) = args.cli_command {
interpreter.parse(&source).unwrap();
println!("{}", interpreter.syntax_tree().unwrap());
return;
}
if let Some(CliCommand::Format) = args.cli_command {
println!("{}", interpreter.format());
return;
}
let eval_result = interpreter.run(&source);
match eval_result {
Ok(value) => {
if !value.is_none() {
println!("{value}")
}
}
Err(error) => eprintln!("{error}"),
}
}
#[derive(Helper, Completer, Validator)]
struct DustReadline {
#[rustyline(Completer)]
completer: FilenameCompleter,
hints: Vec<ToolHint>,
#[rustyline(Hinter)]
_hinter: HistoryHinter,
}
impl DustReadline {
fn new() -> Self {
let mut hints = Vec::new();
for built_in_value in built_in_values() {
let mut display = built_in_value.name().to_string();
if built_in_value.r#type().is_function() {
display.push_str("()");
}
if built_in_value.r#type().is_map() {
let value = built_in_value.get();
if let Value::Map(map) = value {
for (key, (value, _)) in map.variables().unwrap().iter() {
let display = if value.is_function() {
format!("{display}:{key}()")
} else {
format!("{display}:{key}")
};
hints.push(ToolHint {
complete_to: display.len(),
display,
})
}
}
}
hints.push(ToolHint {
complete_to: display.len(),
display,
})
}
hints.push(ToolHint {
display: "output".to_string(),
complete_to: 0,
});
Self {
completer: FilenameCompleter::new(),
_hinter: HistoryHinter {},
hints,
}
}
}
struct ToolHint {
display: String,
complete_to: usize,
}
impl Hint for ToolHint {
fn display(&self) -> &str {
&self.display
}
fn completion(&self) -> Option<&str> {
if self.complete_to > 0 {
Some(&self.display[..self.complete_to])
} else {
None
}
}
}
impl ToolHint {
fn suffix(&self, strip_chars: usize) -> ToolHint {
ToolHint {
display: self.display[strip_chars..].to_string(),
complete_to: self.complete_to.saturating_sub(strip_chars),
}
}
}
impl Hinter for DustReadline {
type Hint = ToolHint;
fn hint(&self, line: &str, pos: usize, _ctx: &Context) -> Option<Self::Hint> {
if line.is_empty() || pos < line.len() {
return None;
}
self.hints.iter().find_map(|tool_hint| {
if tool_hint.display.starts_with(line) {
Some(tool_hint.suffix(pos))
} else {
None
}
})
}
}
impl Highlighter for DustReadline {
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
let highlighted = ansi_term::Colour::Yellow.paint(hint).to_string();
Cow::Owned(highlighted)
}
}
fn run_cli_shell(context: Map) {
let mut interpreter = Interpreter::new(context);
let config = Builder::new()
.color_mode(ColorMode::Enabled)
.completion_type(CompletionType::List)
.build();
let mut rl: Editor<DustReadline, DefaultHistory> =
Editor::with_config(config).expect("Line editor could not be configured properly.");
rl.set_helper(Some(DustReadline::new()));
if rl.load_history("target/history.txt").is_err() {
println!("No previous history.");
}
loop {
let readline = rl.readline("* ");
match readline {
Ok(line) => {
let input = line.to_string();
rl.add_history_entry(line).unwrap();
let eval_result = interpreter.run(&input);
match eval_result {
Ok(value) => println!("{value}"),
Err(error) => {
eprintln!("{error}")
}
}
}
Err(ReadlineError::Interrupted) => break,
Err(ReadlineError::Eof) => break,
Err(error) => eprintln!("{error}"),
}
}
rl.save_history("target/history.txt").unwrap();
}

Some files were not shown because too many files have changed in this diff Show More