Compare commits

..

550 Commits

Author SHA1 Message Date
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
161 changed files with 9020 additions and 55105 deletions

1774
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +1,16 @@
[package]
name = "dust-lang"
description = "General purpose programming language"
version = "0.4.2"
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]
clap = { version = "4.4.4", features = ["derive"] }
csv = "1.2.2"
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"
enum-iterator = "1.4.1"
env_logger = "0.10"
reedline = { version = "0.28.0", features = ["clipboard", "sqlite"] }
crossterm = "0.27.0"
nu-ansi-term = "0.49.0"
humantime = "2.1.0"
stanza = "0.5.1"
colored = "2.1.0"
lyneate = "0.2.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.10"
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
wasm-bindgen-futures = "0.4"
[build-dependencies]
cc = "1.0"

155
README.md
View File

@ -1,100 +1,109 @@
# Dust
High-level programming language with effortless concurrency, automatic memory management, type safety and strict error handling.
High-level programming language with effortless concurrency, automatic memory management and type
safety.
![Dust version of an example from The Rust Programming Language.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/example_0.png)
Dust is a work in progress. Because it aims to deliver a high level of safety, extensive testing
is required. The language is still in the design phase, and the syntax is subject to change.
<!--toc:start-->
- [Dust](#dust)
- [Features](#features)
- [Easy to Read and Write](#easy-to-read-and-write)
- [Effortless Concurrency](#effortless-concurrency)
- [Helpful Errors](#helpful-errors)
- [Static analysis](#static-analysis)
- [Debugging](#debugging)
- [Automatic Memory Management](#automatic-memory-management)
- [Error Handling](#error-handling)
- [Installation and Usage](#installation-and-usage)
<!--toc:end-->
## Usage
## Features
The Dust command line tool can be used to run Dust programs. It is not yet available outside of
this repository.
### Easy to Read and Write
Dust has simple, easy-to-learn syntax.
```js
output('Hello world!')
```sh
cargo run --package dust-shell -- examples/hello_world.ds
```
```sh
cargo run --package dust-shell -- -c '"Hello my name is " + read_line() + "!"'
```
Dust is easily embedded in another program. You can run a dust program of any size or complexity
with a single function.
```rust
use dust_lang::{run, Value};
fn main() {
let code = "
let x = 'Dust'
let y = ' is awesome!'
write_line(x + y)
42
";
let result = run(code);
assert_eq!(result, Ok(Some(Value::integer(42))));
}
```
## Concepts
### Effortless Concurrency
Write multi-threaded code as easily as you would write code for a single thread.
Dust makes concurrency as effortless as possible. Dust is organized into **statements**, and any
sequence of statements can be run concurrently by simply adding the `async` keyword before the block
of statements.
```rust
// Counts from 0 to 9, sleeping for an increasing amount of time between each.
let count_slowly = fn (multiplier: int) {
i = 0
while i < 10 {
sleep(i * multiplier)
write_line(i.to_string())
i += 1
}
}
```js
async {
output('Will this one print first?')
output('Or will this one?')
output('Who knows! Each "output" will run in its own thread!')
count_slowly(200) // Finishes last
count_slowly(100) // Finishes second
count_slowly(50) // Finishes first
}
```
### Helpful Errors
Dust shows you exactly where your code went wrong and suggests changes.
![Example of syntax error output.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/syntax_error.png)
### Static analysis
Your code is always validated for safety before it is run.
![Example of type error output.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/type_error.png)
Dust
### Debugging
Just set the environment variable `DUST_LOG=info` and Dust will tell you exactly what your code is doing while it's doing it. If you set `DUST_LOG=trace`, it will output detailed logs about parsing, abstraction, validation, memory management and runtime. Here are some of the logs from the end of a simple [fizzbuzz example](https://git.jeffa.io/jeff/dust/src/branch/main/examples/fizzbuzz.ds).
![Example of debug output.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/debugging.png)
### Automatic Memory Management
Thanks to static analysis, Dust knows exactly how many times each variable is used. This allows Dust to free memory as soon as the variable will no longer be used, without any help from the user.
Dust uses a garbage collector to automatically manage memory.
### Error Handling
```rust
let x = 0 // x is assigned but never used
// x is removed from memory
Runtime errors are no problem with Dust. The `Result` type represents the output of an operation that might fail. The user must decide what to do in the case of an error.
let y = 41 // y is assigned
let z = y + 1 // y is kept alive for this statement
// y is removed from memory
```dust
match io:stdin() {
Result::Ok(input) -> output("We read this input: " + input)
Result::Error(message) -> output("We got this error: " + message)
}
write_line(z) // z is kept alive for this statement
// z is removed from memory
```
## Installation and Usage
### Type Safety
There are two ways to compile Dust. **It is best to clone the repository and compile the latest code**, otherwise the program may be a different version than the one shown on GitHub. Either way, you must have `rustup`, `cmake` and a C compiler installed.
Dust is statically typed and null-free, but the type of a value can usually be inferred from its
usage. Dust will refuse to run programs with type errors, but will usually not require type
annotations.
To install from the git repository:
```rust
// These two statements are identical to Dust
let x = 1
let x: int = 1
```fish
git clone https://git.jeffa.io/jeff/dust
cd dust
cargo run --release
// Numbers with decimals are floats
let y = 10.0
let y: float = 10.0
// Strings are enclosed in double quotes and are guaranteed to be valid UTF-8
let z = "Hello, world!"
let z: str = "Hello, world!"
```
To install with cargo:
```fish
cargo install dust-lang
dust
```
## Benchmarks
## Development Status
Currently, Dust is being prepared for version 1.0. Until then, there may be breaking changes to the language and CLI.
Aside from the ubiqutous `bool`, `int`, `float`, and `str` types, Dust also has lists, maps,
ranges, structures, enums and functions.

View File

@ -1,17 +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());
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,515 +0,0 @@
# Dust Language Reference
!!! This is a **work in progress** and has incomplete information. !!!
This is an in-depth description of the syntax and abstractions used by the Dust language. It is not
necessary to read or understand all of it before you start using Dust. Instead, refer to it when
you need help with the syntax or understanding how the code is run.
Each section of this document corresponds to a node in the concrete syntax tree. Creating this tree
is the first step in interpreting Dust code. Second, the syntax tree is traversed and an abstract
tree is generated. Each node in the syntax tree corresponds to a node in the abstract tree. Third,
the abstract tree is verified to ensure that it will not generate any values that violate the type
restrictions. Finally, the abstract tree is run, beginning at the [root](#root).
You may reference the [grammar file](tree-sitter-dust/grammar.js) and the [Tree Sitter docs]
(https://tree-sitter.github.io/) while reading this guide to understand how the language is parsed.
<!--toc:start-->
- [Dust Language Reference](#dust-language-reference)
- [Root](#root)
- [Values](#values)
- [Boolean](#boolean)
- [Integer](#integer)
- [Float](#float)
- [Range](#range)
- [String](#string)
- [List](#list)
- [Map](#map)
- [Function](#function)
- [Option](#option)
- [Structure](#structure)
- [Types](#types)
- [Basic Types](#basic-types)
- [Number](#number)
- [Any](#any)
- [None](#none)
- [List Type](#list-type)
- [Map Type](#map-type)
- [Iter](#iter)
- [Function Type](#function-type)
- [Option Type](#option-type)
- [Custom Types](#custom-types)
- [Statements](#statements)
- [Assignment](#assignment)
- [Blocks](#blocks)
- [Synchronous Blocks](#synchronous-blocks)
- [Asynchronous Blocks](#asynchronous-blocks)
- [Break](#break)
- [For Loop](#for-loop)
- [While Loop](#while-loop)
- [If/Else](#ifelse)
- [Match](#match)
- [Pipe](#pipe)
- [Expression](#expression)
- [Expressions](#expressions)
- [Identifier](#identifier)
- [Index](#index)
- [Logic](#logic)
- [Math](#math)
- [Value](#value)
- [New](#new)
- [Command](#command)
- [Built-In Values](#built-in-values)
- [Comments](#comments)
<!--toc:end-->
## Root
The root node represents all of the source code. It is a sequence of [statements](#statements) that
are executed synchronously, in order. The output of the program is always the result of the final
statement or the first error encountered.
## Values
There are ten kinds of value in Dust. Some are very simple and are parsed directly from the source
code, some are collections and others are used in special ways, like functions and structures. All
values can be assinged to an [identifier](#identifiers).
Dust does not have a null type. Absent values are represented with the `none` value, which is a
kind of [option](#option). You may not create a variable without a value and no variable can ever
be in an 'undefined' state during execution.
### Boolean
Booleans are true or false. They are represented by the literal tokens `true` and `false`.
### Integer
Integers are whole numbers that may be positive, negative or zero. Internally, an integer is a
signed 64-bit value.
```dust
42
```
Integers always **overflow** when their maximum or minimum value is reached. Overflowing means that
if the value is too high or low for the 64-bit integer, it will wrap around. You can use the built-
in values `int:max` and `int:min` to get the highest and lowest possible values.
```dust
assert_equal(int:max + 1, int:min)
assert_equal(int:min - 1, int:max)
```
### Float
A float is a numeric value with a decimal. Floats are 64-bit and, like integers, will **overflow**
at their bounds.
```dust
42.0
```
### Range
A range represents a contiguous sequence of integers. Dust ranges are **inclusive** so both the high
and low bounds will be represented.
```dust
0..100
```
### String
A string is a **utf-8** sequence used to represent text. Strings can be wrapped in single or double quotes as well as backticks.
```dust
'42'
"42"
`42`
'forty-two'
```
### List
A list is **collection** of values stored as a sequence and accessible by [indexing](#index) their position with an integer. Lists indexes begin at zero for the first item.
```dust
[ 42 'forty-two' ]
[ 123, 'one', 'two', 'three' ]
```
Note that the commas are optional, including trailing commas.
```dust
[1 2 3 4 5]:2
# Output: 3
```
### Map
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 `:`. Commas may optionally be included after the key-value
pairs.
```dust
reminder = {
message = "Buy milk"
tags = ["groceries", "home"]
}
reminder:message
# Output: Buy milk
```
Internally a map is represented by a B-tree. The implicit advantage of using a B-tree instead of a
hash map is that a B-tree is sorted and therefore can be easily compared to another. Maps are also
used by the interpreter as the data structure for holding variables. You can even inspect the active
**execution context** by calling the built-in `context()` function.
The map stores each [identifier](#identifiers)'s key with a value and the value's type. For internal
use by the interpreter, a type can be set to a key without a value. This makes it possible to check
the types of values before they are computed.
### Function
A function encapsulates a section of the abstract tree so that it can be run seperately and with
different arguments. The function body is a [block](#block), so adding `async` will cause the body
to run like any other `async` block. Unlike some languages, there are no concepts like futures or
async functions in Dust.
Functions are **first-class values** in Dust, so they can be assigned to variables like any other
value.
```dust
# This simple function has no arguments and no return value.
say_hi = () <none> {
output("hi") # The "output" function is a built-in that prints to stdout.
}
# 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)
```
Functions can also be **anonymous**. This is useful for using **callbacks** (i.e. functions that are
called by another function).
```dust
# Use a callback to retain only the numeric characters in a string.
str:retain(
'a1b2c3'
(char <str>) <bool> {
is_some(int:parse(char))
}
)
```
### Option
An option represents a value that may not be present. It has two variants: **some** and **none**.
```dust
say_something = (message <option(str)>) <str> {
either_or(message, "hiya")
}
say_something(some("goodbye"))
# goodbye
say_something(none)
# hiya
```
Dust includes built-in functions to work with option values: `is_none`, `is_some` and `either_or`.
### Structure
A structure is a **concrete type value**. It is a value, like any other, and can be [assigned]
(#assignment) to an [identifier](#identifier). It can then be instantiated as a [map](#map) that
will only allow the variables present in the structure. Default values may be provided for each
variable in the structure, which will be propagated to the map it creates. Values without defaults
must be given a value during instantiation.
```dust
struct User {
name <str>
email <str>
id <int> = generate_id()
}
bob = new User {
name = "Bob"
email = "bob@example.com"
}
# The variable "bob" is a structured map.
```
A map created by using [new](#new) is called a **structured map**. In other languages it may be
called a "homomorphic mapped type". Dust will generate errors if you try to set any values on the
structured map that are not allowed by the structure.
## Types
Dust enforces strict type checking. To make the language easier to write, **type inference** is used
to allow variables to be declared without specifying the type. Instead, the interpreter will figure
it out and set the strictest type possible.
To make the type-setting syntax easier to distinguish from the rest of your code, a **type
specification** is wrapped in pointed brackets. So variable assignment using types looks like this:
```dust
my_float <float> = 666.0
```
### Basic Types
The simple types, and their notation are:
- boolean `bool`
- integer `int`
- float `float`
- string `str`
### Number
The `num` type may represent a value of type `int` or `float`.
### Any
The `any` type does not enforce type bounds.
### None
The `none` type indicates that no value should be found after executing the statement or block, with
one expection: the `none` variant of the `option` type.
### List Type
A list's contents can be specified to create type-safe lists. The `list(str)` type would only allow
string values. Writing `list` without the parentheses and content type is equivalent to writing
`list(any)`.
### Map Type
The `map` type is unstructured and can hold any key-value pair.
### Iter
The `iter` type refers to types that can be used with a [for loop](#for-loop). These include `list`,
`range`, `string` and `map`.
### Function Type
A function's type specification is more complex than other types. A function value must always have
its arguments and return type specified when the **function value** is created.
```dust
my_function = (number <int>, text <str>) <none> {
output(number)
output(text)
}
```
But what if we need to specify a **function type** without creating the function value? This is
necessary when using callbacks or defining structures that have functions set at instantiation.
```dust
use_adder = (adder <(int) -> int>, number <int>) -> <int> {
adder(number)
}
use_adder(
(i <int>) <int> { i + 2 }
40
)
# Output: 42
```
```dust
struct Message {
send_n_times <(str, int) -> none>
}
stdout_message = new Message {
send_n_times = (content <str>, n <int>) <none> {
for _ in 0..n {
output(content)
}
}
}
```
### Option Type
The `option(type)` type is expected to be either `some(value)` or `none`. The type of the value
inside the `some` is always specified.
```dust
result <option(str)> = none
for file in fs:read_dir("./") {
if file:size > 100 {
result = some(file:path)
break
}
}
output(result)
```
```dust
get_line_break_index(text <str>) <some(int)> {
str:find(text, '\n')
}
```
### Custom Types
Custom types such as **structures** are referenced by their variable identifier.
```dust
File = struct {
path <str>
size <int>
type <str>
}
print_file_info(file <File>) <none> {
info = file:path
+ '\n'
+ file:size
+ '\n'
+ file:type
output(info)
}
```
## Statements
TODO
### Assignment
TODO
### Blocks
TODO
#### Synchronous Blocks
TODO
#### Asynchronous Blocks
```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")
}
```
### Break
TODO
### For Loop
TODO
```dust
list = [ 1, 2, 3 ]
for number in list {
output(number + 1)
}
```
### While Loop
TODO
A **while** loop continues until a predicate is false.
```dust
i = 0
while i < 10 {
output(i)
i += 1
}
```
### If/Else
TODO
### Match
TODO
### Pipe
TODO
### Expression
TODO
## Expressions
TODO
#### Identifier
TODO
#### Index
TODO
#### Logic
TODO
#### Math
TODO
#### Value
TODO
#### New
TODO
#### Command
TODO
## Built-In Values
TODO
## Comments
TODO

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

@ -0,0 +1,22 @@
[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"
env_logger = "0.11.3"
log = "0.4.22"
rand = "0.8.5"
rayon = "1.9.0"
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
[dev-dependencies]
env_logger = "0.11.5"

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

@ -0,0 +1,570 @@
use std::fmt::{self, Debug, Display, Formatter};
use colored::Colorize;
use serde::{Deserialize, Serialize};
use crate::{AnnotatedError, Identifier, Instruction, Operation, Span, Value};
#[derive(Clone)]
pub struct Chunk {
instructions: Vec<(Instruction, Span)>,
constants: Vec<Option<Value>>,
locals: Vec<Local>,
scope_depth: usize,
}
impl Chunk {
pub fn new() -> Self {
Self {
instructions: Vec::new(),
constants: Vec::new(),
locals: Vec::new(),
scope_depth: 0,
}
}
pub fn with_data(
instructions: Vec<(Instruction, Span)>,
constants: Vec<Value>,
locals: Vec<Local>,
) -> Self {
Self {
instructions,
constants: constants.into_iter().map(Some).collect(),
locals,
scope_depth: 0,
}
}
pub fn scope_depth(&self) -> usize {
self.scope_depth
}
pub fn len(&self) -> usize {
self.instructions.len()
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn get_instruction(
&self,
offset: usize,
position: Span,
) -> Result<&(Instruction, Span), ChunkError> {
self.instructions
.get(offset)
.ok_or(ChunkError::CodeIndexOfBounds { offset, position })
}
pub fn push_instruction(&mut self, instruction: Instruction, position: Span) {
self.instructions.push((instruction, position));
}
pub fn insert_instruction(&mut self, index: usize, instruction: Instruction, position: Span) {
self.instructions.insert(index, (instruction, position));
}
pub fn pop_instruction(&mut self, position: Span) -> Result<(Instruction, Span), ChunkError> {
self.instructions
.pop()
.ok_or(ChunkError::InstructionUnderflow { position })
}
pub fn get_previous(&self, position: Span) -> Result<&(Instruction, Span), ChunkError> {
self.instructions
.last()
.ok_or(ChunkError::InstructionUnderflow { position })
}
pub fn get_last_operation(&self, position: Span) -> Result<Operation, ChunkError> {
self.get_previous(position)
.map(|(instruction, _)| instruction.operation())
}
pub fn get_constant(&self, index: u8, position: Span) -> Result<&Value, ChunkError> {
let index = index as usize;
self.constants
.get(index)
.ok_or(ChunkError::ConstantIndexOutOfBounds { index, position })
.and_then(|value| {
value
.as_ref()
.ok_or(ChunkError::ConstantAlreadyUsed { index, position })
})
}
pub fn take_constant(&mut self, index: u8, position: Span) -> Result<Value, ChunkError> {
let index = index as usize;
self.constants
.get_mut(index)
.ok_or_else(|| ChunkError::ConstantIndexOutOfBounds { index, position })?
.take()
.ok_or(ChunkError::ConstantAlreadyUsed { index, position })
}
pub fn push_constant(&mut self, value: Value, position: Span) -> Result<u8, ChunkError> {
let starting_length = self.constants.len();
if starting_length + 1 > (u8::MAX as usize) {
Err(ChunkError::ConstantOverflow { position })
} else {
self.constants.push(Some(value));
Ok(starting_length as u8)
}
}
pub fn locals(&self) -> &[Local] {
&self.locals
}
pub fn get_local(&self, index: u8, position: Span) -> Result<&Local, ChunkError> {
let index = index as usize;
self.locals
.get(index)
.ok_or(ChunkError::LocalIndexOutOfBounds { index, position })
}
pub fn get_identifier(&self, index: u8) -> Option<&Identifier> {
self.locals
.get(index as usize)
.map(|local| &local.identifier)
}
pub fn get_local_index(
&self,
identifier: &Identifier,
position: Span,
) -> Result<u8, ChunkError> {
self.locals
.iter()
.enumerate()
.rev()
.find_map(|(index, local)| {
if &local.identifier == identifier {
Some(index as u8)
} else {
None
}
})
.ok_or(ChunkError::IdentifierNotFound {
identifier: identifier.clone(),
position,
})
}
pub fn declare_local(
&mut self,
identifier: Identifier,
mutable: bool,
register_index: u8,
position: Span,
) -> Result<u8, ChunkError> {
let starting_length = self.locals.len();
if starting_length + 1 > (u8::MAX as usize) {
Err(ChunkError::IdentifierOverflow { position })
} else {
self.locals.push(Local::new(
identifier,
mutable,
self.scope_depth,
Some(register_index),
));
Ok(starting_length as u8)
}
}
pub fn define_local(
&mut self,
local_index: u8,
register_index: u8,
position: Span,
) -> Result<(), ChunkError> {
let local = self.locals.get_mut(local_index as usize).ok_or_else(|| {
ChunkError::LocalIndexOutOfBounds {
index: local_index as usize,
position,
}
})?;
local.register_index = Some(register_index);
Ok(())
}
pub fn begin_scope(&mut self) {
self.scope_depth += 1;
}
pub fn end_scope(&mut self) {
self.scope_depth -= 1;
}
pub fn disassembler<'a>(&'a self, name: &'a str) -> ChunkDisassembler<'a> {
ChunkDisassembler::new(name, self)
}
}
impl Default for Chunk {
fn default() -> Self {
Self::new()
}
}
impl Display for Chunk {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}",
self.disassembler("Chunk Display")
.styled(true)
.disassemble()
)
}
}
impl Debug for Chunk {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}",
self.disassembler("Chunk Debug Display")
.styled(false)
.disassemble()
)
}
}
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
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Local {
pub identifier: Identifier,
pub mutable: bool,
pub depth: usize,
pub register_index: Option<u8>,
}
impl Local {
pub fn new(
identifier: Identifier,
mutable: bool,
depth: usize,
register_index: Option<u8>,
) -> Self {
Self {
identifier,
mutable,
depth,
register_index,
}
}
}
pub struct ChunkDisassembler<'a> {
name: &'a str,
chunk: &'a Chunk,
width: Option<usize>,
styled: bool,
}
impl<'a> ChunkDisassembler<'a> {
const INSTRUCTION_HEADER: [&'static str; 5] = [
"",
"Instructions",
"------------",
"INDEX BYTECODE OPERATION INFO POSITION",
"----- -------- --------------- ------------------------------ --------",
];
const CONSTANT_HEADER: [&'static str; 5] = [
"",
"Constants",
"---------",
"INDEX VALUE ",
"----- ---------",
];
const LOCAL_HEADER: [&'static str; 5] = [
"",
"Locals",
"------",
"INDEX IDENTIFIER MUTABLE DEPTH REGISTER",
"----- ---------- ------- ----- --------",
];
/// 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 = Self::INSTRUCTION_HEADER[4];
longest_line.chars().count()
}
pub fn new(name: &'a str, chunk: &'a Chunk) -> Self {
Self {
name,
chunk,
width: None,
styled: false,
}
}
pub fn width(&mut self, width: usize) -> &mut Self {
self.width = Some(width);
self
}
pub fn styled(&mut self, styled: bool) -> &mut Self {
self.styled = styled;
self
}
pub fn disassemble(&self) -> String {
let width = self.width.unwrap_or_else(Self::default_width);
let center = |line: &str| format!("{line:^width$}\n");
let style = |line: String| {
if self.styled {
line.bold().to_string()
} else {
line
}
};
let mut disassembly = String::with_capacity(self.predict_length());
let name_line = style(center(self.name));
disassembly.push_str(&name_line);
let info_line = center(&format!(
"{} instructions, {} constants, {} locals",
self.chunk.instructions.len(),
self.chunk.constants.len(),
self.chunk.locals.len()
));
let styled_info_line = {
if self.styled {
info_line.dimmed().to_string()
} else {
info_line
}
};
disassembly.push_str(&styled_info_line);
for line in Self::INSTRUCTION_HEADER {
disassembly.push_str(&style(center(line)));
}
for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() {
let position = position.to_string();
let operation = instruction.operation().to_string();
let info_option = instruction.disassembly_info(Some(self.chunk));
let bytecode = u32::from(instruction);
let instruction_display = if let Some(info) = info_option {
format!("{index:<5} {bytecode:<8X} {operation:15} {info:30} {position:8}")
} else {
format!(
"{index:<5} {bytecode:<8X} {operation:15} {:30} {position:8}",
" "
)
};
disassembly.push_str(&center(&instruction_display));
}
for line in Self::CONSTANT_HEADER {
disassembly.push_str(&style(center(line)));
}
for (index, value_option) in self.chunk.constants.iter().enumerate() {
let value_display = value_option
.as_ref()
.map(|value| value.to_string())
.unwrap_or("empty".to_string());
let trucated_length = 8;
let with_elipsis = trucated_length - 3;
let constant_display = if value_display.len() > with_elipsis {
format!("{index:<5} {value_display:.<trucated_length$.with_elipsis$}")
} else {
format!("{index:<5} {value_display:<trucated_length$}")
};
disassembly.push_str(&center(&constant_display));
}
for line in Self::LOCAL_HEADER {
disassembly.push_str(&style(center(line)));
}
for (
index,
Local {
identifier,
depth,
register_index,
mutable,
},
) in self.chunk.locals.iter().enumerate()
{
let register_display = register_index
.as_ref()
.map(|value| value.to_string())
.unwrap_or_else(|| "empty".to_string());
let identifier_display = identifier.as_str();
let local_display = format!(
"{index:<5} {identifier_display:10} {mutable:7} {depth:<5} {register_display:8}"
);
disassembly.push_str(&center(&local_display));
}
let expected_length = self.predict_length();
let actual_length = disassembly.len();
if !self.styled && expected_length != actual_length {
log::debug!(
"Chunk disassembly was not optimized correctly, expected string length {expected_length}, got {actual_length}",
);
}
if self.styled && expected_length > actual_length {
log::debug!(
"Chunk disassembly was not optimized correctly, expected string length to be at least{expected_length}, got {actual_length}",
);
}
disassembly
}
/// Predicts the capacity of the disassembled output. This is used to pre-allocate the string
/// buffer to avoid reallocations.
///
/// The capacity is calculated as follows:
/// - Get the number of static lines, i.e. lines that are always present in the disassembly
/// - Get the number of dynamic lines, i.e. lines that are generated from the chunk
/// - Add an one to the width to account for the newline character
/// - Multiply the total number of lines by the width of the disassembly output
///
/// The result is accurate only if the output is not styled. Otherwise the extra bytes added by
/// the ANSI escape codes will make the result too low. It still works as a lower bound in that
/// case.
fn predict_length(&self) -> usize {
const EXTRA_LINES: usize = 2; // There is one info line and one empty line after the name
let static_line_count = Self::INSTRUCTION_HEADER.len()
+ Self::CONSTANT_HEADER.len()
+ Self::LOCAL_HEADER.len()
+ EXTRA_LINES;
let dynamic_line_count =
self.chunk.instructions.len() + self.chunk.constants.len() + self.chunk.locals.len();
let total_line_count = static_line_count + dynamic_line_count;
let width = self.width.unwrap_or_else(Self::default_width) + 1;
total_line_count * width
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ChunkError {
CodeIndexOfBounds {
offset: usize,
position: Span,
},
ConstantAlreadyUsed {
index: usize,
position: Span,
},
ConstantOverflow {
position: Span,
},
ConstantIndexOutOfBounds {
index: usize,
position: Span,
},
InstructionUnderflow {
position: Span,
},
LocalIndexOutOfBounds {
index: usize,
position: Span,
},
IdentifierOverflow {
position: Span,
},
IdentifierNotFound {
identifier: Identifier,
position: Span,
},
}
impl AnnotatedError for ChunkError {
fn title() -> &'static str {
"Chunk Error"
}
fn description(&self) -> &'static str {
match self {
ChunkError::CodeIndexOfBounds { .. } => "Code index out of bounds",
ChunkError::ConstantAlreadyUsed { .. } => "Constant already used",
ChunkError::ConstantOverflow { .. } => "Constant overflow",
ChunkError::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds",
ChunkError::InstructionUnderflow { .. } => "Instruction underflow",
ChunkError::LocalIndexOutOfBounds { .. } => "Identifier index out of bounds",
ChunkError::IdentifierOverflow { .. } => "Identifier overflow",
ChunkError::IdentifierNotFound { .. } => "Identifier not found",
}
}
fn details(&self) -> Option<String> {
match self {
ChunkError::CodeIndexOfBounds { offset, .. } => Some(format!("Code index: {}", offset)),
ChunkError::ConstantAlreadyUsed { index, .. } => {
Some(format!("Constant index: {}", index))
}
ChunkError::ConstantIndexOutOfBounds { index, .. } => {
Some(format!("Constant index: {}", index))
}
ChunkError::InstructionUnderflow { .. } => None,
ChunkError::LocalIndexOutOfBounds { index, .. } => {
Some(format!("Identifier index: {}", index))
}
ChunkError::IdentifierNotFound { identifier, .. } => {
Some(format!("Identifier: {}", identifier))
}
ChunkError::IdentifierOverflow { .. } => Some("Identifier overflow".to_string()),
ChunkError::ConstantOverflow { .. } => Some("Constant overflow".to_string()),
}
}
fn position(&self) -> Span {
match self {
ChunkError::CodeIndexOfBounds { position, .. } => *position,
ChunkError::ConstantAlreadyUsed { position, .. } => *position,
ChunkError::ConstantIndexOutOfBounds { position, .. } => *position,
ChunkError::IdentifierNotFound { position, .. } => *position,
ChunkError::InstructionUnderflow { position, .. } => *position,
ChunkError::LocalIndexOutOfBounds { position, .. } => *position,
ChunkError::IdentifierOverflow { position, .. } => *position,
ChunkError::ConstantOverflow { position, .. } => *position,
}
}
}

View File

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

View File

@ -0,0 +1,67 @@
use annotate_snippets::{Level, Renderer, Snippet};
use crate::{vm::VmError, LexError, ParseError, Span};
#[derive(Debug, PartialEq)]
pub enum DustError<'src> {
Lex {
error: LexError,
source: &'src str,
},
Parse {
error: ParseError,
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!("Runtime error: {}", 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(true)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
);
report.push_str(&renderer.render(message).to_string());
}
DustError::Parse { error, source } => {
let position = error.position();
let label = format!("Parse error: {}", 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(true)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
);
report.push_str(&renderer.render(message).to_string());
}
_ => todo!(),
}
report
}
}
pub trait AnnotatedError {
fn title() -> &'static str;
fn description(&self) -> &'static str;
fn details(&self) -> Option<String>;
fn position(&self) -> Span;
}

137
dust-lang/src/identifier.rs Normal file
View File

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

View File

@ -0,0 +1,738 @@
use std::fmt::{self, Display, Formatter};
use crate::{Chunk, Operation, Span};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Instruction(u32);
impl Instruction {
pub fn r#move(to_register: u8, from_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::Move as u32);
instruction.set_destination(to_register);
instruction.set_first_argument(from_register);
instruction
}
pub fn close(from_register: u8, to_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::Close as u32);
instruction.set_first_argument(from_register);
instruction.set_second_argument(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_destination(to_register);
instruction.set_first_argument(if value { 1 } else { 0 });
instruction.set_second_argument(if skip { 1 } else { 0 });
instruction
}
pub fn load_constant(to_register: u8, constant_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::LoadConstant as u32);
instruction.set_destination(to_register);
instruction.set_first_argument(constant_index);
instruction
}
pub fn load_list(to_register: u8, start_register: u8, list_length: u8) -> Instruction {
let mut instruction = Instruction(Operation::LoadList as u32);
instruction.set_destination(to_register);
instruction.set_first_argument(start_register);
instruction.set_second_argument(list_length);
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_destination(to_register);
instruction.set_first_argument(local_index);
instruction.set_second_argument(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_destination(to_register);
instruction.set_first_argument(local_index);
instruction
}
pub fn set_local(from_register: u8, local_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::SetLocal as u32);
instruction.set_destination(from_register);
instruction.set_first_argument(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_destination(to_register);
instruction.set_first_argument(left_index);
instruction.set_second_argument(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_destination(to_register);
instruction.set_first_argument(left_index);
instruction.set_second_argument(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_destination(to_register);
instruction.set_first_argument(left_index);
instruction.set_second_argument(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_destination(to_register);
instruction.set_first_argument(left_index);
instruction.set_second_argument(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_destination(to_register);
instruction.set_first_argument(left_index);
instruction.set_second_argument(right_index);
instruction
}
pub fn test(to_register: u8, test_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::Test as u32);
instruction.set_destination(to_register);
instruction.set_second_argument_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_destination(to_register);
instruction.set_first_argument(argument_index);
instruction.set_second_argument_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_destination(if comparison_boolean { 1 } else { 0 });
instruction.set_first_argument(left_index);
instruction.set_second_argument(right_index);
instruction
}
pub fn negate(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Negate as u32);
instruction.set_destination(to_register);
instruction.set_first_argument(from_index);
instruction
}
pub fn not(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Not as u32);
instruction.set_destination(to_register);
instruction.set_first_argument(from_index);
instruction
}
pub fn jump(offset: u8, is_positive: bool) -> Instruction {
let mut instruction = Instruction(Operation::Jump as u32);
instruction.set_first_argument(offset);
instruction.set_second_argument(if is_positive { 1 } else { 0 });
instruction
}
pub fn r#return(from_register: u8, to_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::Return as u32);
instruction.set_destination(from_register);
instruction.set_first_argument(to_register);
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 destination(&self) -> u8 {
(self.0 >> 24) as u8
}
pub fn destination_as_boolean(&self) -> bool {
(self.0 >> 24) != 0
}
pub fn set_destination_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_destination(if boolean { 1 } else { 0 });
self
}
pub fn set_destination(&mut self, destination: u8) {
self.0 &= 0x00FFFFFF;
self.0 |= (destination as u32) << 24;
}
pub fn first_argument(&self) -> u8 {
(self.0 >> 16) as u8
}
pub fn first_argument_is_constant(&self) -> bool {
self.0 & 0b1000_0000 != 0
}
pub fn first_argument_as_boolean(&self) -> bool {
self.first_argument() != 0
}
pub fn set_first_argument_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_first_argument(if boolean { 1 } else { 0 });
self
}
pub fn set_first_argument_to_constant(&mut self) -> &mut Self {
self.0 |= 0b1000_0000;
self
}
pub fn set_first_argument(&mut self, argument: u8) {
self.0 |= (argument as u32) << 16;
}
pub fn second_argument(&self) -> u8 {
(self.0 >> 8) as u8
}
pub fn second_argument_is_constant(&self) -> bool {
self.0 & 0b0100_0000 != 0
}
pub fn second_argument_as_boolean(&self) -> bool {
self.second_argument() != 0
}
pub fn set_second_argument_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_second_argument(if boolean { 1 } else { 0 });
self
}
pub fn set_second_argument_to_constant(&mut self) -> &mut Self {
self.0 |= 0b0100_0000;
self
}
pub fn set_second_argument(&mut self, argument: u8) {
self.0 |= (argument as u32) << 8;
}
pub fn disassemble(&self, chunk: &Chunk) -> String {
let mut disassembled = format!("{:16} ", self.operation().to_string());
if let Some(info) = self.disassembly_info(Some(chunk)) {
disassembled.push_str(&info);
}
disassembled
}
pub fn disassembly_info(&self, chunk: Option<&Chunk>) -> Option<String> {
let format_arguments = || {
let first_argument = if self.first_argument_is_constant() {
format!("C{}", self.first_argument())
} else {
format!("R{}", self.first_argument())
};
let second_argument = if self.second_argument_is_constant() {
format!("C{}", self.second_argument())
} else {
format!("R{}", self.second_argument())
};
(first_argument, second_argument)
};
let info = match self.operation() {
Operation::Move => {
format!("R{} = R{}", self.destination(), self.first_argument())
}
Operation::Close => {
let from_register = self.first_argument();
let to_register = self.second_argument().saturating_sub(1);
format!("R{from_register}..=R{to_register}")
}
Operation::LoadBoolean => {
let to_register = self.destination();
let boolean = self.first_argument_as_boolean();
let skip_display = if self.second_argument_as_boolean() {
"IP++"
} else {
""
};
format!("R{to_register} = {boolean} {skip_display}",)
}
Operation::LoadConstant => {
let constant_index = self.first_argument();
if let Some(chunk) = chunk {
match chunk.get_constant(constant_index, Span(0, 0)) {
Ok(value) => {
format!("R{} = C{} {}", self.destination(), constant_index, value)
}
Err(error) => {
format!("R{} = C{} {:?}", self.destination(), constant_index, error)
}
}
} else {
format!("R{} = C{}", self.destination(), constant_index)
}
}
Operation::LoadList => {
let destination = self.destination();
let first_index = self.first_argument();
let last_index = destination - 1;
format!("R{} = [R{}..=R{}]", destination, first_index, last_index)
}
Operation::DefineLocal => {
let destination = self.destination();
let local_index = self.first_argument();
let identifier_display = if let Some(chunk) = chunk {
match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
}
} else {
"???".to_string()
};
let mutable_display = if self.second_argument_as_boolean() {
"mut "
} else {
""
};
format!("L{local_index} = R{destination} {mutable_display}{identifier_display}")
}
Operation::GetLocal => {
let local_index = self.first_argument();
format!("R{} = L{}", self.destination(), local_index)
}
Operation::SetLocal => {
let local_index = self.first_argument();
let identifier_display = if let Some(chunk) = chunk {
match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
}
} else {
"???".to_string()
};
format!(
"L{} = R{} {}",
local_index,
self.destination(),
identifier_display
)
}
Operation::Add => {
let destination = self.destination();
let (first_argument, second_argument) = format_arguments();
format!("R{destination} = {first_argument} + {second_argument}",)
}
Operation::Subtract => {
let destination = self.destination();
let (first_argument, second_argument) = format_arguments();
format!("R{destination} = {first_argument} - {second_argument}",)
}
Operation::Multiply => {
let destination = self.destination();
let (first_argument, second_argument) = format_arguments();
format!("R{destination} = {first_argument} * {second_argument}",)
}
Operation::Divide => {
let destination = self.destination();
let (first_argument, second_argument) = format_arguments();
format!("R{destination} = {first_argument} / {second_argument}",)
}
Operation::Modulo => {
let destination = self.destination();
let (first_argument, second_argument) = format_arguments();
format!("R{destination} = {first_argument} % {second_argument}",)
}
Operation::Test => {
let destination = self.destination();
let test_value = self.second_argument_as_boolean();
let bang = if test_value { "" } else { "!" };
format!("if {bang}R{destination} {{ IP++ }}",)
}
Operation::TestSet => {
let destination = self.destination();
let argument = format!("R{}", self.first_argument());
let test_value = self.second_argument_as_boolean();
let bang = if test_value { "" } else { "!" };
format!(
"if {bang}R{destination} {{ R{destination} = R{argument} }} else {{ IP++ }}",
)
}
Operation::Equal => {
let comparison_symbol = if self.destination_as_boolean() {
"=="
} else {
"!="
};
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ IP += 1 }}",)
}
Operation::Less => {
let comparison_symbol = if self.destination_as_boolean() {
"<"
} else {
">="
};
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} IP++",)
}
Operation::LessEqual => {
let comparison_symbol = if self.destination_as_boolean() {
"<="
} else {
">"
};
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} IP++",)
}
Operation::Negate => {
let destination = self.destination();
let argument = if self.first_argument_is_constant() {
format!("C{}", self.first_argument())
} else {
format!("R{}", self.first_argument())
};
format!("R{destination} = -{argument}")
}
Operation::Not => {
let destination = self.destination();
let argument = if self.first_argument_is_constant() {
format!("C{}", self.first_argument())
} else {
format!("R{}", self.first_argument())
};
format!("R{destination} = !{argument}")
}
Operation::Jump => {
let offset = self.first_argument();
let positive = self.second_argument() != 0;
if positive {
format!("IP += {}", offset)
} else {
format!("IP -= {}", offset)
}
}
Operation::Return => {
let from_register = self.destination();
let to_register = self.first_argument();
format!("R{from_register}..=R{to_register}")
}
};
let trucated_length = 30;
let with_elipsis = trucated_length - 3;
let truncated_info = if info.len() > with_elipsis {
format!("{info:.<trucated_length$.with_elipsis$}")
} else {
info
};
Some(truncated_info)
}
}
impl Display for Instruction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if let Some(info) = self.disassembly_info(None) {
write!(f, "{} {}", self.operation(), info)
} else {
write!(f, "{}", self.operation())
}
}
}
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_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Move);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test]
fn close() {
let instruction = Instruction::close(1, 2);
assert_eq!(instruction.operation(), Operation::Close);
assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), 2);
}
#[test]
fn load_boolean() {
let instruction = Instruction::load_boolean(4, true, true);
assert_eq!(instruction.operation(), Operation::LoadBoolean);
assert_eq!(instruction.destination(), 4);
assert!(instruction.first_argument_as_boolean());
assert!(instruction.second_argument_as_boolean());
}
#[test]
fn load_constant() {
let mut instruction = Instruction::load_constant(0, 1);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::LoadConstant);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test]
fn declare_local() {
let mut instruction = Instruction::define_local(0, 1, true);
instruction.set_first_argument_to_constant();
assert_eq!(instruction.operation(), Operation::DefineLocal);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), true as u8);
assert!(instruction.first_argument_is_constant());
}
#[test]
fn add() {
let mut instruction = Instruction::add(1, 1, 0);
instruction.set_first_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Add);
assert_eq!(instruction.destination(), 1);
assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), 0);
assert!(instruction.first_argument_is_constant());
}
#[test]
fn subtract() {
let mut instruction = Instruction::subtract(0, 1, 2);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Subtract);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), 2);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test]
fn multiply() {
let mut instruction = Instruction::multiply(0, 1, 2);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Multiply);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), 2);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test]
fn divide() {
let mut instruction = Instruction::divide(0, 1, 2);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Divide);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), 2);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test]
fn and() {
let instruction = Instruction::test(4, true);
assert_eq!(instruction.operation(), Operation::Test);
assert_eq!(instruction.destination(), 4);
assert!(instruction.second_argument_as_boolean());
}
#[test]
fn or() {
let instruction = Instruction::test_set(4, 1, true);
assert_eq!(instruction.operation(), Operation::TestSet);
assert_eq!(instruction.destination(), 4);
assert_eq!(instruction.first_argument(), 1);
assert!(instruction.second_argument_as_boolean());
}
#[test]
fn equal() {
let mut instruction = Instruction::equal(true, 1, 2);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Equal);
assert!(instruction.destination_as_boolean());
assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), 2);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test]
fn negate() {
let mut instruction = Instruction::negate(0, 1);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Negate);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test]
fn not() {
let mut instruction = Instruction::not(0, 1);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Not);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test]
fn jump() {
let instruction = Instruction::jump(4, true);
assert_eq!(instruction.operation(), Operation::Jump);
assert_eq!(instruction.first_argument(), 4);
assert!(instruction.first_argument_as_boolean());
}
#[test]
fn r#return() {
let instruction = Instruction::r#return(4, 8);
assert_eq!(instruction.operation(), Operation::Return);
assert_eq!(instruction.destination(), 4);
assert_eq!(instruction.first_argument(), 8);
}
}

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

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,36 @@
mod chunk;
mod constructor;
mod dust_error;
mod identifier;
mod instruction;
mod lexer;
mod operation;
mod parser;
mod token;
mod r#type;
mod value;
mod vm;
use std::fmt::Display;
pub use chunk::{Chunk, ChunkDisassembler, ChunkError, Local};
pub use constructor::Constructor;
pub use dust_error::{AnnotatedError, DustError};
pub use identifier::Identifier;
pub use instruction::Instruction;
pub use lexer::{lex, LexError, Lexer};
pub use operation::Operation;
pub use parser::{parse, ParseError, Parser};
pub use r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
pub use token::{Token, TokenKind, TokenOwned};
pub use value::{Enum, Function, Struct, Value, ValueError};
pub use vm::{run, Vm, VmError};
#[derive(Clone, Copy, Debug, PartialEq)]
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)
}
}

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

@ -0,0 +1,179 @@
use std::fmt::{self, Display, Formatter};
const MOVE: u8 = 0b0000_0000;
const CLOSE: u8 = 0b000_0001;
const LOAD_BOOLEAN: u8 = 0b0000_0010;
const LOAD_CONSTANT: u8 = 0b0000_0011;
const LOAD_LIST: u8 = 0b0000_0100;
const DECLARE_LOCAL: u8 = 0b0000_0101;
const GET_LOCAL: u8 = 0b0000_0110;
const SET_LOCAL: u8 = 0b0000_0111;
const ADD: u8 = 0b0000_1000;
const SUBTRACT: u8 = 0b0000_1001;
const MULTIPLY: u8 = 0b0000_1010;
const DIVIDE: u8 = 0b0000_1011;
const MODULO: u8 = 0b0000_1100;
const TEST: u8 = 0b0000_1101;
const TEST_SET: u8 = 0b0000_1110;
const EQUAL: u8 = 0b0000_1111;
const LESS: u8 = 0b0001_0000;
const LESS_EQUAL: u8 = 0b0001_0001;
const NEGATE: u8 = 0b0001_0010;
const NOT: u8 = 0b0001_0011;
const JUMP: u8 = 0b0001_0100;
const RETURN: u8 = 0b0001_0101;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operation {
// Stack manipulation
Move = MOVE as isize,
Close = CLOSE as isize,
// Value loading
LoadBoolean = LOAD_BOOLEAN as isize,
LoadConstant = LOAD_CONSTANT as isize,
LoadList = LOAD_LIST as isize,
// Variables
DefineLocal = DECLARE_LOCAL as isize,
GetLocal = GET_LOCAL as isize,
SetLocal = SET_LOCAL as isize,
// Binary operations
Add = ADD as isize,
Subtract = SUBTRACT as isize,
Multiply = MULTIPLY as isize,
Divide = DIVIDE as isize,
Modulo = MODULO as isize,
// Logical operations
Test = TEST as isize,
TestSet = TEST_SET as isize,
// Relational operations
Equal = EQUAL as isize,
Less = LESS as isize,
LessEqual = LESS_EQUAL as isize,
// Unary operations
Negate = NEGATE as isize,
Not = NOT as isize,
// Control flow
Jump = JUMP as isize,
Return = RETURN as isize,
}
impl Operation {
pub fn is_binary(&self) -> bool {
matches!(
self,
Operation::Add
| Operation::Subtract
| Operation::Multiply
| Operation::Divide
| Operation::Modulo
)
}
}
impl From<u8> for Operation {
fn from(byte: u8) -> Self {
match byte {
MOVE => Operation::Move,
CLOSE => Operation::Close,
LOAD_BOOLEAN => Operation::LoadBoolean,
LOAD_CONSTANT => Operation::LoadConstant,
LOAD_LIST => Operation::LoadList,
DECLARE_LOCAL => Operation::DefineLocal,
GET_LOCAL => Operation::GetLocal,
SET_LOCAL => Operation::SetLocal,
ADD => Operation::Add,
SUBTRACT => Operation::Subtract,
MULTIPLY => Operation::Multiply,
DIVIDE => Operation::Divide,
MODULO => Operation::Modulo,
TEST => Operation::Test,
TEST_SET => Operation::TestSet,
EQUAL => Operation::Equal,
LESS => Operation::Less,
LESS_EQUAL => Operation::LessEqual,
NEGATE => Operation::Negate,
NOT => Operation::Not,
JUMP => Operation::Jump,
RETURN => Operation::Return,
_ => {
if cfg!(test) {
panic!("Invalid operation byte: {}", byte)
} else {
Operation::Return
}
}
}
}
}
impl From<Operation> for u8 {
fn from(operation: Operation) -> Self {
match operation {
Operation::Move => MOVE,
Operation::Close => CLOSE,
Operation::LoadBoolean => LOAD_BOOLEAN,
Operation::LoadConstant => LOAD_CONSTANT,
Operation::LoadList => LOAD_LIST,
Operation::DefineLocal => DECLARE_LOCAL,
Operation::GetLocal => GET_LOCAL,
Operation::SetLocal => SET_LOCAL,
Operation::Add => ADD,
Operation::Subtract => SUBTRACT,
Operation::Multiply => MULTIPLY,
Operation::Divide => DIVIDE,
Operation::Modulo => MODULO,
Operation::Test => TEST,
Operation::TestSet => TEST_SET,
Operation::Equal => EQUAL,
Operation::Less => LESS,
Operation::LessEqual => LESS_EQUAL,
Operation::Negate => NEGATE,
Operation::Not => NOT,
Operation::Jump => JUMP,
Operation::Return => RETURN,
}
}
}
impl Display for Operation {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Operation::Move => write!(f, "MOVE"),
Operation::Close => write!(f, "CLOSE"),
Operation::LoadBoolean => write!(f, "LOAD_BOOLEAN"),
Operation::LoadConstant => write!(f, "LOAD_CONSTANT"),
Operation::LoadList => write!(f, "LOAD_LIST"),
Operation::DefineLocal => write!(f, "DEFINE_LOCAL"),
Operation::GetLocal => write!(f, "GET_LOCAL"),
Operation::SetLocal => write!(f, "SET_LOCAL"),
Operation::Add => write!(f, "ADD"),
Operation::Subtract => write!(f, "SUBTRACT"),
Operation::Multiply => write!(f, "MULTIPLY"),
Operation::Divide => write!(f, "DIVIDE"),
Operation::Modulo => write!(f, "MODULO"),
Operation::Test => write!(f, "TEST"),
Operation::TestSet => write!(f, "TEST_SET"),
Operation::Equal => write!(f, "EQUAL"),
Operation::Less => write!(f, "LESS"),
Operation::LessEqual => write!(f, "LESS_EQUAL"),
Operation::Negate => write!(f, "NEGATE"),
Operation::Not => write!(f, "NOT"),
Operation::Jump => write!(f, "JUMP"),
Operation::Return => write!(f, "RETURN"),
}
}
}

1178
dust-lang/src/parser/mod.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,391 @@
use crate::Local;
use super::*;
#[test]
fn equality_assignment_long() {
let source = "let a = if 4 == 4 { true } else { false };";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(
*Instruction::equal(true, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(13, 15)
),
(Instruction::jump(1, true), Span(13, 15)),
(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)),
],
vec![Value::integer(4), Value::integer(4),],
vec![Local::new(Identifier::new("a"), false, 0, Some(0)),]
)),
);
}
#[test]
fn equality_assignment_short() {
let source = "let a = 4 == 4;";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(
*Instruction::equal(true, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(10, 12)
),
(Instruction::jump(1, true), Span(10, 12)),
(Instruction::load_boolean(0, true, true), Span(14, 15)),
(Instruction::load_boolean(0, false, false), Span(14, 15)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
],
vec![Value::integer(4), Value::integer(4),],
vec![Local::new(Identifier::new("a"), false, 0, Some(0)),]
)),
);
}
#[test]
fn if_else_expression() {
let source = "if 1 == 1 { 2 } else { 3 }";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(
*Instruction::equal(true, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_constant(0, 2), Span(12, 13)),
(Instruction::load_constant(1, 3), Span(23, 24)),
(Instruction::r#return(1, 1), Span(0, 26)),
],
vec![
Value::integer(1),
Value::integer(1),
Value::integer(2),
Value::integer(3)
],
vec![]
)),
);
}
#[test]
fn list_with_expression() {
let source = "[1, 2 + 3, 4]";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0), Span(1, 2)),
(
*Instruction::add(1, 1, 2)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(6, 7)
),
(Instruction::load_constant(2, 3), Span(11, 12)),
(Instruction::load_list(3, 0, 3), Span(0, 13)),
(Instruction::r#return(3, 3), Span(0, 13)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
],
vec![]
)),
);
}
#[test]
fn list() {
let source = "[1, 2, 3]";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0), Span(1, 2)),
(Instruction::load_constant(1, 1), Span(4, 5)),
(Instruction::load_constant(2, 2), Span(7, 8)),
(Instruction::load_list(3, 0, 3), Span(0, 9)),
(Instruction::r#return(3, 3), Span(0, 9)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(3),],
vec![]
)),
);
}
#[test]
fn block_scope() {
let source = "
let a = 0;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let e = 1;
";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0), Span(17, 18)),
(Instruction::define_local(0, 0, false), Span(13, 14)),
(Instruction::load_constant(1, 1), Span(50, 52)),
(Instruction::define_local(1, 1, false), Span(46, 47)),
(Instruction::load_constant(2, 2), Span(92, 93)),
(Instruction::define_local(2, 2, false), Span(88, 89)),
(Instruction::load_constant(3, 3), Span(129, 130)),
(Instruction::define_local(3, 3, false), Span(125, 126)),
(Instruction::load_constant(4, 4), Span(158, 159)),
(Instruction::define_local(4, 4, false), Span(154, 155)),
],
vec![
Value::integer(0),
Value::integer(42),
Value::integer(1),
Value::integer(2),
Value::integer(1)
],
vec![
Local::new(Identifier::new("a"), false, 0, Some(0)),
Local::new(Identifier::new("b"), false, 1, Some(1)),
Local::new(Identifier::new("c"), false, 2, Some(2)),
Local::new(Identifier::new("d"), false, 1, Some(3)),
Local::new(Identifier::new("e"), false, 0, Some(4)),
]
)),
);
}
#[test]
fn empty() {
assert_eq!(parse(""), Ok(Chunk::with_data(vec![], vec![], vec![])),);
}
#[test]
fn set_local() {
assert_eq!(
parse("let mut x = 41; x = 42;"),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(Instruction::load_constant(1, 1), Span(20, 22)),
(Instruction::set_local(1, 0), Span(16, 17)),
],
vec![Value::integer(41), Value::integer(42)],
vec![Local::new(Identifier::new("x"), true, 0, Some(0)),]
)),
);
}
#[test]
fn parentheses_precedence() {
assert_eq!(
parse("(1 + 2) * 3"),
Ok(Chunk::with_data(
vec![
(
*Instruction::add(0, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(3, 4)
),
(
*Instruction::multiply(1, 0, 2).set_second_argument_to_constant(),
Span(8, 9)
),
(Instruction::r#return(1, 1), Span(0, 11)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
vec![]
))
);
}
#[test]
fn math_operator_precedence() {
assert_eq!(
parse("1 + 2 - 3 * 4 / 5"),
Ok(Chunk::with_data(
vec![
(
*Instruction::multiply(2, 2, 3)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(10, 11)
),
(
*Instruction::add(0, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(2, 3)
),
(
*Instruction::divide(3, 2, 4).set_second_argument_to_constant(),
Span(14, 15)
),
(Instruction::subtract(1, 0, 3), Span(6, 7)),
(Instruction::r#return(1, 1), Span(0, 17)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
Value::integer(5),
],
vec![]
))
);
}
#[test]
fn declare_local() {
assert_eq!(
parse("let x = 42;"),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0), Span(8, 10)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
],
vec![Value::integer(42)],
vec![Local::new(Identifier::new("x"), false, 0, Some(0))]
)),
);
}
#[test]
fn and() {
assert_eq!(
parse("true && false"),
Ok(Chunk::with_data(
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(1, 1), Span(0, 13)),
],
vec![],
vec![]
))
);
}
#[test]
fn divide() {
assert_eq!(
parse("1 / 2"),
Ok(Chunk::with_data(
vec![
(
*Instruction::divide(0, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(2, 3)
),
(Instruction::r#return(0, 0), Span(0, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
}
#[test]
fn multiply() {
assert_eq!(
parse("1 * 2"),
Ok(Chunk::with_data(
vec![
(
*Instruction::multiply(0, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(2, 3)
),
(Instruction::r#return(0, 0), Span(0, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
}
#[test]
fn add() {
assert_eq!(
parse("1 + 2"),
Ok(Chunk::with_data(
vec![
(
*Instruction::add(0, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(2, 3)
),
(Instruction::r#return(0, 0), Span(0, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
}
#[test]
fn subtract() {
assert_eq!(
parse("1 - 2"),
Ok(Chunk::with_data(
vec![
(
*Instruction::subtract(0, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(2, 3)
),
(Instruction::r#return(0, 0), Span(0, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
}
#[test]
fn constant() {
assert_eq!(
parse("42"),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0), Span(0, 2)),
(Instruction::r#return(0, 0), Span(0, 2)),
],
vec![Value::integer(42)],
vec![]
))
);
}

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

@ -0,0 +1,529 @@
//! Token and TokenOwned types.
use std::fmt::{self, Display, Formatter};
/// Source code token.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Token<'src> {
// End of file
Eof,
// 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,
If,
Int,
Let,
Loop,
Map,
Mut,
Str,
Struct,
While,
// Symbols
BangEqual,
Bang,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessEqual,
Minus,
MinusEqual,
Percent,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Slash,
Star,
}
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::Bool => 4,
Token::Break => 5,
Token::Else => 4,
Token::FloatKeyword => 5,
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::Plus => 1,
Token::PlusEqual => 2,
Token::RightCurlyBrace => 1,
Token::RightParenthesis => 1,
Token::RightSquareBrace => 1,
Token::Semicolon => 1,
Token::Slash => 1,
Token::Star => 1,
}
}
pub fn to_owned(&self) -> TokenOwned {
match self {
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::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::Plus => TokenOwned::Plus,
Token::PlusEqual => TokenOwned::PlusEqual,
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
Token::RightParenthesis => TokenOwned::RightParenthesis,
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
Token::Semicolon => TokenOwned::Semicolon,
Token::Star => TokenOwned::Star,
Token::Slash => TokenOwned::Slash,
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::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::Greater => TokenKind::Greater,
Token::GreaterEqual => TokenKind::GreaterOrEqual,
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::LessOrEqual,
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::Plus => TokenKind::Plus,
Token::PlusEqual => TokenKind::PlusEqual,
Token::RightCurlyBrace => TokenKind::RightCurlyBrace,
Token::RightParenthesis => TokenKind::RightParenthesis,
Token::RightSquareBrace => TokenKind::RightSquareBrace,
Token::Semicolon => TokenKind::Semicolon,
Token::Star => TokenKind::Star,
Token::Slash => TokenKind::Slash,
Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String,
Token::Struct => TokenKind::Struct,
Token::While => TokenKind::While,
}
}
}
impl<'src> Display for Token<'src> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
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::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::Plus => write!(f, "+"),
Token::PlusEqual => write!(f, "+="),
Token::RightCurlyBrace => write!(f, "}}"),
Token::RightParenthesis => write!(f, ")"),
Token::RightSquareBrace => write!(f, "]"),
Token::Semicolon => write!(f, ";"),
Token::Slash => write!(f, "/"),
Token::Star => write!(f, "*"),
Token::Str => write!(f, "str"),
Token::String(value) => write!(f, "\"{}\"", value),
Token::Struct => write!(f, "struct"),
Token::While => write!(f, "while"),
}
}
}
/// Owned version of `Token`, which owns all the strings.
///
/// This is used for errors.
#[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
Bool,
Break,
Else,
FloatKeyword,
If,
Int,
Let,
Loop,
Map,
Mut,
Str,
While,
// Symbols
Async,
Bang,
BangEqual,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterOrEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessOrEqual,
Minus,
MinusEqual,
Percent,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Star,
Struct,
Slash,
}
impl Display for TokenOwned {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
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::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::Plus => Token::Plus.fmt(f),
TokenOwned::PlusEqual => Token::PlusEqual.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::Slash => Token::Slash.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),
}
}
}
/// Token representation that holds no data.
#[derive(Debug, PartialEq, Clone)]
pub enum TokenKind {
Eof,
Identifier,
// Hard-coded values
Boolean,
Byte,
Character,
Float,
Integer,
String,
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
If,
Int,
Let,
Loop,
Map,
Str,
While,
// Symbols
BangEqual,
Bang,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterOrEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessOrEqual,
Minus,
MinusEqual,
Mut,
Percent,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Star,
Struct,
Slash,
}
impl Display for TokenKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
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::Greater => Token::Greater.fmt(f),
TokenKind::GreaterOrEqual => 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::LessOrEqual => 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::Plus => Token::Plus.fmt(f),
TokenKind::PlusEqual => Token::PlusEqual.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::Str => Token::Str.fmt(f),
TokenKind::Slash => Token::Slash.fmt(f),
TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::While => Token::While.fmt(f),
}
}
}

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

@ -0,0 +1,729 @@
//! Description of a kind of value.
//!
//! Most types are concrete and specific, the exceptions are the Generic and Any types.
//!
//! Generic types are temporary placeholders that describe a type that will be defined later. The
//! interpreter should use the analysis phase to enforce that all Generic types have a concrete
//! type assigned to them before the program is run.
//!
//! The Any type is used in cases where a value's type does not matter. For example, the standard
//! library's "length" function does not care about the type of item in the list, only the list
//! itself. So the input is defined as `[any]`, i.e. `Type::ListOf(Box::new(Type::Any))`.
use std::{
cmp::Ordering,
collections::HashMap,
fmt::{self, Display, Formatter},
};
use serde::{Deserialize, Serialize};
use crate::{Constructor, Identifier};
/// Description of a kind of value.
///
/// See the [module documentation](index.html) for more information.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Type {
Any,
Boolean,
Byte,
Character,
Enum(EnumType),
Float,
Function(FunctionType),
Generic {
identifier: Identifier,
concrete_type: Option<Box<Type>>,
},
Integer,
List {
item_type: Box<Type>,
length: usize,
},
ListEmpty,
ListOf {
item_type: Box<Type>,
},
Map {
pairs: HashMap<Identifier, 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 {
name: left_name,
type_parameters: left_type_parameters,
value_parameters: left_value_parameters,
return_type: left_return,
}),
Type::Function(FunctionType {
name: right_name,
type_parameters: right_type_parameters,
value_parameters: right_value_parameters,
return_type: right_return,
}),
) => {
if left_name != right_name
|| 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(),
})
}
pub fn has_field(&self, field: &Identifier) -> bool {
match field.as_str() {
"to_string" => true,
"length" => {
matches!(
self,
Type::List { .. }
| Type::ListOf { .. }
| Type::ListEmpty
| Type::Map { .. }
| Type::String { .. }
)
}
"is_even" | "is_odd" => matches!(self, Type::Integer | Type::Float),
_ => match self {
Type::Struct(StructType::Fields { fields, .. }) => fields.contains_key(field),
Type::Map { pairs } => pairs.contains_key(field),
_ => false,
},
}
}
pub fn get_field_type(&self, field: &Identifier) -> Option<Type> {
match field.as_str() {
"length" => match self {
Type::List { .. } => Some(Type::Integer),
Type::ListOf { .. } => Some(Type::Integer),
Type::ListEmpty => Some(Type::Integer),
Type::Map { .. } => Some(Type::Integer),
Type::String { .. } => Some(Type::Integer),
_ => None,
},
"is_even" | "is_odd" => Some(Type::Boolean),
_ => match self {
Type::Struct(StructType::Fields { fields, .. }) => fields.get(field).cloned(),
Type::Map { pairs } => pairs.get(field).cloned(),
_ => None,
},
}
}
}
impl Display for Type {
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, .. }) => 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 name: Identifier,
pub type_parameters: Option<Vec<Identifier>>,
pub value_parameters: Option<Vec<(Identifier, 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() {
write!(f, "{type_parameter}")?;
if index != type_parameters.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ">")?;
}
write!(f, "(")?;
if let Some(value_parameters) = &self.value_parameters {
for (index, (identifier, r#type)) in value_parameters.iter().enumerate() {
write!(f, "{identifier}: {type}")?;
if index != value_parameters.len() - 1 {
write!(f, ", ")?;
}
}
}
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: Identifier,
},
Tuple {
name: Identifier,
fields: Vec<Type>,
},
Fields {
name: Identifier,
fields: HashMap<Identifier, Type>,
},
}
impl StructType {
pub fn name(&self) -> &Identifier {
match self {
StructType::Unit { name } => name,
StructType::Tuple { name, .. } => name,
StructType::Fields { name, .. } => name,
}
}
pub fn constructor(&self) -> Constructor {
Constructor {
struct_type: self.clone(),
}
}
}
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: Identifier,
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()
})
);
}
}
}
}

1889
dust-lang/src/value.rs Normal file

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,507 @@
use crate::{
parse, AnnotatedError, Chunk, ChunkError, DustError, Identifier, Instruction, Local, Operation,
Span, Value, ValueError,
};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
let chunk = parse(source)?;
let mut vm = Vm::new(chunk);
vm.run()
.map_err(|error| DustError::Runtime { error, source })
}
#[derive(Debug, Eq, PartialEq)]
pub struct Vm {
chunk: Chunk,
ip: usize,
register_stack: Vec<Option<Value>>,
}
impl Vm {
const STACK_LIMIT: usize = u16::MAX as usize;
pub fn new(chunk: Chunk) -> Self {
Self {
chunk,
ip: 0,
register_stack: Vec::new(),
}
}
pub fn take_chunk(self) -> Chunk {
self.chunk
}
pub fn run(&mut self) -> Result<Option<Value>, VmError> {
// DRY helper to take constants or clone registers for binary operations
fn take_constants_or_clone(
vm: &mut Vm,
instruction: Instruction,
position: Span,
) -> Result<(Value, Value), VmError> {
let left = if instruction.first_argument_is_constant() {
vm.chunk
.take_constant(instruction.first_argument(), position)?
} else {
vm.clone(instruction.first_argument(), position)?
};
let right = if instruction.second_argument_is_constant() {
vm.chunk
.take_constant(instruction.second_argument(), position)?
} else {
vm.clone(instruction.second_argument(), position)?
};
Ok((left, right))
}
while let Ok((instruction, position)) = self.read(Span(0, 0)).copied() {
log::trace!("Running instruction {instruction} at {position}");
match instruction.operation() {
Operation::Move => {
let from = instruction.first_argument();
let to = instruction.destination();
let value = self.clone(from, position)?;
self.insert(value, to, position)?;
}
Operation::Close => {
let from = instruction.first_argument();
let to = instruction.second_argument();
for register_index in from..to {
self.register_stack[register_index as usize] = None;
}
}
Operation::LoadBoolean => {
let to_register = instruction.destination();
let boolean = instruction.first_argument_as_boolean();
let skip = instruction.second_argument_as_boolean();
let value = Value::boolean(boolean);
self.insert(value, to_register, position)?;
if boolean && skip {
self.ip += 1;
}
}
Operation::LoadConstant => {
let to_register = instruction.destination();
let from_constant = instruction.first_argument();
let value = self.chunk.take_constant(from_constant, position)?;
self.insert(value, to_register, position)?;
}
Operation::LoadList => {
let to_register = instruction.destination();
let length = instruction.first_argument();
let first_register = to_register - length - 1;
let last_register = to_register - 1;
let mut list = Vec::with_capacity(length as usize);
for register_index in first_register..=last_register {
let value = match self.clone(register_index, position) {
Ok(value) => value,
Err(VmError::EmptyRegister { .. }) => continue,
Err(error) => return Err(error),
};
list.push(value);
}
self.insert(Value::list(list), to_register, position)?;
}
Operation::DefineLocal => {
let from_register = instruction.destination();
let to_local = instruction.first_argument();
self.chunk.define_local(to_local, from_register, position)?;
}
Operation::GetLocal => {
let register_index = instruction.destination();
let local_index = instruction.first_argument();
let local = self.chunk.get_local(local_index, position)?.clone();
let value = self.clone_as_variable(local, position)?;
self.insert(value, register_index, position)?;
}
Operation::SetLocal => {
let register_index = instruction.destination();
let local_index = instruction.first_argument();
let local = self.chunk.get_local(local_index, position)?.clone();
let value = self.clone_as_variable(local, position)?;
let new_value = if instruction.first_argument_is_constant() {
self.chunk.take_constant(register_index, position)?
} else {
self.clone(register_index, position)?
};
value
.mutate(new_value)
.map_err(|error| VmError::Value { error, position })?;
self.insert(value, register_index, position)?;
}
Operation::Add => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let sum = left
.add(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(sum, instruction.destination(), position)?;
}
Operation::Subtract => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let difference = left
.subtract(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(difference, instruction.destination(), position)?;
}
Operation::Multiply => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let product = left
.multiply(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(product, instruction.destination(), position)?;
}
Operation::Divide => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let quotient = left
.divide(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(quotient, instruction.destination(), position)?;
}
Operation::Modulo => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let remainder = left
.modulo(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(remainder, instruction.destination(), position)?;
}
Operation::Test => {
let register = instruction.destination();
let test_value = instruction.second_argument_as_boolean();
let value = self.clone(register, position)?;
let boolean = value.as_boolean().ok_or_else(|| VmError::ExpectedBoolean {
found: value,
position,
})?;
if boolean != test_value {
self.ip += 1;
}
}
Operation::TestSet => {
let to_register = instruction.destination();
let argument = instruction.first_argument();
let test_value = instruction.second_argument_as_boolean();
let value = self.clone(argument, position)?;
let boolean = value.as_boolean().ok_or_else(|| VmError::ExpectedBoolean {
found: value.clone(),
position,
})?;
if boolean == test_value {
self.insert(value, to_register, position)?;
} else {
self.ip += 1;
}
}
Operation::Equal => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let equal = left
.equal(&right)
.map_err(|error| VmError::Value { error, position })?;
let compare_to = instruction.destination_as_boolean();
if let Some(boolean) = equal.as_boolean() {
if boolean == compare_to {
self.ip += 1;
}
}
}
Operation::Less => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let less = left
.less_than(&right)
.map_err(|error| VmError::Value { error, position })?;
let compare_to = instruction.destination() != 0;
if let Some(boolean) = less.as_boolean() {
if boolean != compare_to {
self.ip += 1;
}
}
}
Operation::LessEqual => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let less_equal = left
.less_than_or_equal(&right)
.map_err(|error| VmError::Value { error, position })?;
let compare_to = instruction.destination() != 0;
if let Some(boolean) = less_equal.as_boolean() {
if boolean != compare_to {
self.ip += 1;
}
}
}
Operation::Negate => {
let value = if instruction.first_argument_is_constant() {
self.chunk
.take_constant(instruction.first_argument(), position)?
} else {
self.clone(instruction.first_argument(), position)?
};
let negated = value
.negate()
.map_err(|error| VmError::Value { error, position })?;
self.insert(negated, instruction.destination(), position)?;
}
Operation::Not => {
let value = if instruction.first_argument_is_constant() {
self.chunk
.take_constant(instruction.first_argument(), position)?
} else {
self.clone(instruction.first_argument(), position)?
};
let not = value
.not()
.map_err(|error| VmError::Value { error, position })?;
self.insert(not, instruction.destination(), position)?;
}
Operation::Jump => {
let offset = instruction.first_argument();
let is_positive = instruction.second_argument_as_boolean();
let new_ip = if is_positive {
self.ip + offset as usize
} else {
self.ip - offset as usize
};
self.ip = new_ip;
}
Operation::Return => {
let return_value = self.pop(position)?;
return Ok(Some(return_value));
}
}
}
Ok(None)
}
fn insert(&mut self, value: Value, index: u8, position: Span) -> Result<(), VmError> {
if self.register_stack.len() == Self::STACK_LIMIT {
Err(VmError::StackOverflow { position })
} else {
let index = index as usize;
while index >= self.register_stack.len() {
self.register_stack.push(None);
}
self.register_stack[index] = Some(value);
Ok(())
}
}
fn take(&mut self, index: u8, position: Span) -> Result<Value, VmError> {
let index = index as usize;
if let Some(register) = self.register_stack.get_mut(index) {
let value = register
.take()
.ok_or_else(|| VmError::EmptyRegister { index, position })?;
Ok(value)
} else {
Err(VmError::RegisterIndexOutOfBounds { position })
}
}
fn clone(&mut self, index: u8, position: Span) -> Result<Value, VmError> {
let index = index as usize;
if let Some(register) = self.register_stack.get_mut(index) {
let cloneable = if let Some(value) = register.take() {
if value.is_raw() {
value.into_reference()
} else {
value
}
} else {
return Err(VmError::EmptyRegister { index, position });
};
*register = Some(cloneable.clone());
Ok(cloneable)
} else {
Err(VmError::RegisterIndexOutOfBounds { position })
}
}
fn clone_mutable(&mut self, index: u8, position: Span) -> Result<Value, VmError> {
let index = index as usize;
if let Some(register) = self.register_stack.get_mut(index) {
let cloneable = if let Some(value) = register.take() {
value.into_mutable()
} else {
return Err(VmError::EmptyRegister { index, position });
};
*register = Some(cloneable.clone());
Ok(cloneable)
} else {
Err(VmError::RegisterIndexOutOfBounds { position })
}
}
fn clone_as_variable(&mut self, local: Local, position: Span) -> Result<Value, VmError> {
let index = if let Some(index) = local.register_index {
index
} else {
return Err(VmError::UndefinedVariable {
identifier: local.identifier,
position,
});
};
let clone_result = if local.mutable {
self.clone_mutable(index, position)
} else {
self.clone(index, position)
};
match clone_result {
Err(VmError::EmptyRegister { .. }) => Err(VmError::UndefinedVariable {
identifier: local.identifier,
position,
}),
_ => clone_result,
}
}
fn pop(&mut self, position: Span) -> Result<Value, VmError> {
if let Some(register) = self.register_stack.pop() {
let value = register.ok_or(VmError::EmptyRegister {
index: self.register_stack.len().saturating_sub(1),
position,
})?;
Ok(value)
} else {
Err(VmError::StackUnderflow { position })
}
}
fn read(&mut self, position: Span) -> Result<&(Instruction, Span), VmError> {
let current = self.chunk.get_instruction(self.ip, position)?;
self.ip += 1;
Ok(current)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum VmError {
EmptyRegister {
index: usize,
position: Span,
},
ExpectedBoolean {
found: Value,
position: Span,
},
RegisterIndexOutOfBounds {
position: Span,
},
InvalidInstruction {
instruction: Instruction,
position: Span,
},
StackOverflow {
position: Span,
},
StackUnderflow {
position: Span,
},
UndefinedVariable {
identifier: Identifier,
position: Span,
},
// Wrappers for foreign errors
Chunk(ChunkError),
Value {
error: ValueError,
position: Span,
},
}
impl From<ChunkError> for VmError {
fn from(error: ChunkError) -> Self {
Self::Chunk(error)
}
}
impl AnnotatedError for VmError {
fn title() -> &'static str {
"Runtime Error"
}
fn description(&self) -> &'static str {
match self {
Self::EmptyRegister { .. } => "Empty register",
Self::ExpectedBoolean { .. } => "Expected boolean",
Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds",
Self::InvalidInstruction { .. } => "Invalid instruction",
Self::StackOverflow { .. } => "Stack overflow",
Self::StackUnderflow { .. } => "Stack underflow",
Self::UndefinedVariable { .. } => "Undefined variable",
Self::Value { .. } => "Value error",
Self::Chunk(error) => error.description(),
}
}
fn details(&self) -> Option<String> {
match self {
Self::EmptyRegister { index, .. } => Some(format!("Register {index} is empty")),
Self::UndefinedVariable { identifier, .. } => {
Some(format!("{identifier} is not in scope"))
}
Self::Chunk(error) => error.details(),
Self::Value { error, .. } => Some(error.to_string()),
_ => None,
}
}
fn position(&self) -> Span {
match self {
Self::EmptyRegister { position, .. } => *position,
Self::ExpectedBoolean { position, .. } => *position,
Self::RegisterIndexOutOfBounds { position } => *position,
Self::InvalidInstruction { position, .. } => *position,
Self::StackUnderflow { position } => *position,
Self::StackOverflow { position } => *position,
Self::UndefinedVariable { position, .. } => *position,
Self::Chunk(error) => error.position(),
Self::Value { position, .. } => *position,
}
}
}

View File

@ -0,0 +1,26 @@
use dust_lang::*;
#[test]
fn long_math() {
assert_eq!(run("1 + 2 * 3 - 4 / 2"), Ok(Some(Value::integer(5))));
}
#[test]
fn add() {
assert_eq!(run("1 + 2"), Ok(Some(Value::integer(3))));
}
#[test]
fn subtract() {
assert_eq!(run("1 - 2"), Ok(Some(Value::integer(-1))));
}
#[test]
fn multiply() {
assert_eq!(run("2 * 3"), Ok(Some(Value::integer(6))));
}
#[test]
fn divide() {
assert_eq!(run("6 / 3"), Ok(Some(Value::integer(2))));
}

56
dust-lang/tests/values.rs Normal file
View File

@ -0,0 +1,56 @@
use dust_lang::*;
#[test]
fn boolean() {
assert_eq!(run("true"), Ok(Some(Value::boolean(true))));
}
#[test]
fn byte() {
assert_eq!(run("0xff"), Ok(Some(Value::byte(0xff))));
}
#[test]
fn float_simple() {
assert_eq!(run("42.0"), Ok(Some(Value::float(42.0))));
}
#[test]
fn float_negative() {
assert_eq!(run("-42.0"), Ok(Some(Value::float(-42.0))));
}
#[test]
fn float_exponential() {
assert_eq!(run("4.2e1"), Ok(Some(Value::float(42.0))));
}
#[test]
fn float_exponential_negative() {
assert_eq!(run("4.2e-1"), Ok(Some(Value::float(0.42))));
}
#[test]
fn float_infinity_and_nan() {
assert_eq!(run("Infinity"), Ok(Some(Value::float(f64::INFINITY))));
assert_eq!(run("-Infinity"), Ok(Some(Value::float(f64::NEG_INFINITY))));
assert!(run("NaN").unwrap().unwrap().as_float().unwrap().is_nan());
}
#[test]
fn integer() {
assert_eq!(run("42"), Ok(Some(Value::integer(42))));
}
#[test]
fn integer_negative() {
assert_eq!(run("-42"), Ok(Some(Value::integer(-42))));
}
#[test]
fn string() {
assert_eq!(
run("\"Hello, world!\""),
Ok(Some(Value::string("Hello, world!")))
);
}

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"

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

@ -0,0 +1,87 @@
use std::{fs::read_to_string, io::Write};
use clap::Parser;
use colored::Colorize;
use dust_lang::{parse, run};
use log::Level;
#[derive(Parser)]
struct Cli {
#[arg(short, long)]
command: Option<String>,
#[arg(short, long)]
parse: bool,
#[arg(short, long)]
styled: bool,
path: Option<String>,
}
fn main() {
env_logger::builder()
.parse_env("DUST_LOG")
.format(|buf, record| {
let level = match record.level() {
Level::Error => "ERROR".red(),
Level::Warn => "WARN".yellow(),
Level::Info => "INFO".white(),
Level::Debug => "DEBUG".blue(),
Level::Trace => "TRACE".purple(),
}
.bold();
let level_display = format!("[{level:^5}]").white().on_black();
let module = record
.module_path()
.map(|path| path.split("::").last().unwrap_or("unknown"))
.unwrap_or("unknown")
.dimmed();
writeln!(buf, "{level_display} {module:^6} {}", record.args())
})
.init();
let args = Cli::parse();
if let Some(command) = &args.command {
if args.parse {
parse_and_display_errors(command, args.styled);
} else {
run_and_display_errors(command);
}
} else if let Some(path) = &args.path {
let source = read_to_string(path).expect("Failed to read file");
if args.parse {
parse_and_display_errors(&source, args.styled);
} else {
run_and_display_errors(&source);
}
}
}
fn parse_and_display_errors(source: &str, pretty_print: bool) {
match parse(source) {
Ok(chunk) => println!(
"{}",
chunk
.disassembler("Dust CLI Input")
.styled(pretty_print)
.disassemble()
),
Err(error) => {
eprintln!("{}", error.report());
}
}
}
fn run_and_display_errors(source: &str) {
match run(source) {
Ok(Some(value)) => println!("{}", value),
Ok(_) => {}
Err(error) => {
eprintln!("{}", error.report());
}
}
}

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

@ -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.")

View File

@ -1,17 +0,0 @@
async {
{
^echo 'Starting 1...'
^sleep 1
^echo 'Finished 1.'
}
{
^echo 'Starting 2...'
^sleep 2
^echo 'Finished 2.'
}
{
^echo 'Starting 3...'
^sleep 3
^echo 'Finished 3.'
}
}

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,20 +0,0 @@
cast_len = 0
characters_len = 0
episodes_len = 0
async {
{
cast = download("https://api.sampleapis.com/futurama/cast")
cast_len = length(from_json(cast))
}
{
characters = download("https://api.sampleapis.com/futurama/characters")
characters_len = length(from_json(characters))
}
{
episodes = download("https://api.sampleapis.com/futurama/episodes")
episodes_len = length(from_json(episodes))
}
}
output ([cast_len, characters_len, episodes_len])

View File

@ -1,53 +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')

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 +0,0 @@
fib = (i <int>) <int> {
if i <= 1 {
1
} else {
fib(i - 1) + fib(i - 2)
}
}
fib(8)

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')
} else if divides_by_3 {
output('fizz')
let output = if divides_by_3 && divides_by_5 {
"fizzbuzz"
} else if divides_by_3 {
"fizz"
} else if divides_by_5 {
output('buzz')
} else {
output(count)
"buzz"
} else {
count.to_string()
}
write_line(output)
count += 1
}

View File

@ -1,26 +1,19 @@
# This is a Dust version of an example from the Rust Book.
#
# https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
write_line("Guess the number.")
output("Guess the number.")
secret_number = int:random_range(0..=100)
let secret_number = random(0..100);
loop {
output("Please input your guess.")
write_line("Input your guess.")
input = io:stdin():expect("Failed to read line.")
guess = int:parse(input)
let input = io.read_line();
let guess = int.parse(input);
output("You guessed: " + guess)
match cmp(guess, secret_number) {
Ordering::Less -> output("Too small!")
Ordering::Greater -> output("Too big!")
Ordering::Equal -> {
output("You win!")
break
}
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_file('examples/assets/jq_data.json'))
new_data = []
for commit_data in data as collection {
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,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_file('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

@ -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,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 \
"target/release/dust -c 'length(json:parse(fs:read_file(\"{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 \
"target/release/dust -c 'length(json:parse(fs:read_file(\"{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 \
"target/release/dust -c 'length(json:parse(fs:read_file(\"{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,131 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, List, SourcePosition, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct As {
expression: Expression,
r#type: Type,
position: SourcePosition,
}
impl AbstractTree for As {
fn from_syntax(node: Node, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("as", node)?;
let expression_node = node.child(0).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?;
let type_node = node.child(2).unwrap();
let r#type = Type::from_syntax(type_node, source, context)?;
Ok(As {
expression,
r#type,
position: SourcePosition::from(node.range()),
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(self.r#type.clone())
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
let initial_type = self.expression.expected_type(context)?;
if self.r#type.accepts(&initial_type) {
return Ok(());
}
if let Type::ListOf(item_type) = &self.r#type {
match &initial_type {
Type::ListOf(expected_item_type) => {
println!("{item_type} {expected_item_type}");
if !item_type.accepts(&expected_item_type) {
return Err(ValidationError::TypeCheck {
expected: self.r#type.clone(),
actual: initial_type.clone(),
position: self.position,
});
}
}
Type::String => {
if let Type::String = item_type.as_ref() {
} else {
return Err(ValidationError::ConversionImpossible {
initial_type,
target_type: self.r#type.clone(),
});
}
}
Type::Any => {
// Do no validation when converting from "any" to a list.
// This effectively defers to runtime behavior, potentially
// causing a runtime error.
}
_ => {
return Err(ValidationError::ConversionImpossible {
initial_type,
target_type: self.r#type.clone(),
})
}
}
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = self.expression.run(source, context)?;
let converted_value = if self.r#type.accepts(&value.r#type()?) {
return Ok(value);
} else if let Type::ListOf(_) = self.r#type {
match value {
Value::List(list) => Value::List(list),
Value::String(string) => {
let chars = string
.chars()
.map(|char| Value::String(char.to_string()))
.collect();
Value::List(List::with_items(chars))
}
_ => {
return Err(RuntimeError::ConversionImpossible {
from: value.r#type()?,
to: self.r#type.clone(),
position: self.position.clone(),
});
}
}
} else if let Type::Integer = self.r#type {
match value {
Value::Integer(integer) => Value::Integer(integer),
Value::Float(float) => Value::Integer(float as i64),
_ => {
return Err(RuntimeError::ConversionImpossible {
from: value.r#type()?,
to: self.r#type.clone(),
position: self.position.clone(),
})
}
}
} else {
todo!()
};
Ok(converted_value)
}
}
impl Format for As {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,186 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
context::Context,
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, AssignmentOperator, Format, Function, Identifier, SourcePosition, Statement,
SyntaxNode, Type, TypeSpecification, Value,
};
/// Variable assignment, including add-assign and subtract-assign operations.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Assignment {
identifier: Identifier,
type_specification: Option<TypeSpecification>,
operator: AssignmentOperator,
statement: Statement,
syntax_position: SourcePosition,
}
impl AbstractTree for Assignment {
fn from_syntax(
syntax_node: SyntaxNode,
source: &str,
context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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)?;
Ok(Assignment {
identifier,
type_specification,
operator,
statement,
syntax_position: syntax_node.range().into(),
})
}
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError> {
if let AssignmentOperator::Equal = self.operator {
let r#type = if let Some(definition) = &self.type_specification {
definition.inner().clone()
} else {
self.statement.expected_type(context)?
};
log::info!("Setting type: {} <{}>", self.identifier, r#type);
context.set_type(self.identifier.clone(), r#type)?;
}
if let Some(type_specification) = &self.type_specification {
match self.operator {
AssignmentOperator::Equal => {
let expected = type_specification.inner();
let actual = self.statement.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.clone(),
actual,
position: self.syntax_position,
});
}
}
AssignmentOperator::PlusEqual => {
if let Type::ListOf(expected) = type_specification.inner() {
let actual = self.identifier.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.as_ref().clone(),
actual,
position: self.syntax_position,
});
}
} else {
let expected = type_specification.inner();
let actual = self.identifier.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.clone(),
actual,
position: self.syntax_position,
});
}
}
}
AssignmentOperator::MinusEqual => todo!(),
}
} else {
match self.operator {
AssignmentOperator::Equal => {}
AssignmentOperator::PlusEqual => {
if let Type::ListOf(expected) = self.identifier.expected_type(context)? {
let actual = self.statement.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.as_ref().clone(),
actual,
position: self.syntax_position,
});
}
}
}
AssignmentOperator::MinusEqual => todo!(),
}
}
self.statement.validate(source, context)?;
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let right = self.statement.run(source, context)?;
let new_value = match self.operator {
AssignmentOperator::PlusEqual => {
let left = self.identifier.run(source, context)?;
left.add(right, self.syntax_position)?
}
AssignmentOperator::MinusEqual => {
if let Some(left) = context.get_value(&self.identifier)? {
left.subtract(right, self.syntax_position)?
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(self.identifier.clone()),
));
}
}
AssignmentOperator::Equal => right,
};
if let Value::Function(Function::ContextDefined(function_node)) = &new_value {
function_node
.context()
.set_value(self.identifier.clone(), new_value.clone())?;
}
log::info!("RUN assignment: {} = {}", self.identifier, new_value);
context.set_value(self.identifier.clone(), new_value)?;
Ok(Value::none())
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
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,62 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, SyntaxNode, Type, Value,
};
/// Operators that be used in an assignment statement.
#[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: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("assignment_operator", node)?;
let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() {
"=" => AssignmentOperator::Equal,
"+=" => AssignmentOperator::PlusEqual,
"-=" => AssignmentOperator::MinusEqual,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "=, += or -=".to_string(),
actual: operator_node.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(operator)
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
}
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,170 +0,0 @@
use std::{
fmt::{self, Formatter},
sync::RwLock,
};
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
error::{rw_lock_error::RwLockError, RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, 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(Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Block {
is_async: bool,
contains_return: bool,
statements: Vec<Statement>,
}
impl Block {
pub fn contains_return(&self) -> bool {
self.contains_return
}
}
impl AbstractTree for Block {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("block", node)?;
let first_child = node.child(0).unwrap();
let is_async = first_child.kind() == "async";
let mut contains_return = false;
let statement_count = if is_async {
node.child_count() - 3
} else {
node.child_count() - 2
};
let mut statements = Vec::with_capacity(statement_count);
let block_context = Context::with_variables_from(context)?;
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, &block_context)?;
if statement.is_return() {
contains_return = true;
}
statements.push(statement);
}
}
Ok(Block {
is_async,
contains_return,
statements,
})
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
for statement in &self.statements {
statement.validate(_source, _context)?;
}
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
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 should_return = if self.contains_return {
statement.is_return()
} else {
index == statements.len() - 1
};
if should_return {
let get_write_lock = final_result.write();
match get_write_lock {
Ok(mut final_result) => {
*final_result = result;
None
}
Err(_error) => Some(Err(RuntimeError::RwLock(RwLockError))),
}
} else {
None
}
})
.unwrap_or(final_result.into_inner().map_err(|_| RwLockError)?)
} else {
for (index, statement) in self.statements.iter().enumerate() {
if statement.is_return() {
return statement.run(_source, _context);
}
if index == self.statements.len() - 1 {
return statement.run(_source, _context);
}
}
Ok(Value::none())
}
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
for (index, statement) in self.statements.iter().enumerate() {
if statement.is_return() {
return statement.expected_type(_context);
}
if index == self.statements.len() - 1 {
return statement.expected_type(_context);
}
}
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('}');
}
}
impl fmt::Debug for Block {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Block")
.field("is_async", &self.is_async)
.field("statements", &self.statements)
.finish()
}
}

View File

@ -1,76 +0,0 @@
use std::process::{self, Stdio};
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Type, Value,
};
/// An external program invokation.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Command {
command_text: String,
command_arguments: Vec<String>,
}
impl AbstractTree for Command {
fn from_syntax(
node: SyntaxNode,
source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("command", node)?;
let command_text_node = node.child(1).unwrap();
let command_text = source[command_text_node.byte_range()].to_string();
let mut command_arguments = Vec::new();
for index in 2..node.child_count() {
let text_node = node.child(index).unwrap();
let mut text = source[text_node.byte_range()].to_string();
if (text.starts_with('\'') && text.ends_with('\''))
|| (text.starts_with('"') && text.ends_with('"'))
|| (text.starts_with('`') && text.ends_with('`'))
{
text = text[1..text.len() - 1].to_string();
}
command_arguments.push(text);
}
Ok(Command {
command_text,
command_arguments,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::String)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
let output = process::Command::new(&self.command_text)
.args(&self.command_arguments)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()?
.wait_with_output()?
.stdout;
Ok(Value::String(String::from_utf8(output)?))
}
}
impl Format for Command {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,92 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, EnumInstance, Format, Identifier, Type, TypeDefinition, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct EnumDefinition {
identifier: Identifier,
variants: Vec<(Identifier, Vec<Type>)>,
}
impl EnumDefinition {
pub fn new(identifier: Identifier, variants: Vec<(Identifier, Vec<Type>)>) -> Self {
Self {
identifier,
variants,
}
}
pub fn instantiate(&self, variant: Identifier, content: Option<Value>) -> EnumInstance {
EnumInstance::new(self.identifier.clone(), variant, content)
}
pub fn identifier(&self) -> &Identifier {
&self.identifier
}
pub fn variants(&self) -> &Vec<(Identifier, Vec<Type>)> {
&self.variants
}
}
impl AbstractTree for EnumDefinition {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("enum_definition", node)?;
let identifier_node = node.child(1).unwrap();
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
let mut variants = Vec::new();
let mut current_identifier: Option<Identifier> = None;
let mut types = Vec::new();
for index in 3..node.child_count() - 1 {
let child = node.child(index).unwrap();
if child.kind() == "identifier" {
if let Some(identifier) = &current_identifier {
variants.push((identifier.clone(), types));
}
current_identifier = Some(Identifier::from_syntax(child, source, context)?);
types = Vec::new();
}
if child.kind() == "type" {
let r#type = Type::from_syntax(child, source, context)?;
types.push(r#type);
}
}
Ok(EnumDefinition {
identifier,
variants,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
context.set_definition(self.identifier.clone(), TypeDefinition::Enum(self.clone()))?;
self.identifier.validate(_source, context)?;
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
}
impl Format for EnumDefinition {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,70 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct EnumPattern {
name: Identifier,
variant: Identifier,
inner_identifier: Option<Identifier>,
}
impl EnumPattern {
pub fn name(&self) -> &Identifier {
&self.name
}
pub fn variant(&self) -> &Identifier {
&self.variant
}
pub fn inner_identifier(&self) -> &Option<Identifier> {
&self.inner_identifier
}
}
impl AbstractTree for EnumPattern {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("enum_pattern", node)?;
let enum_name_node = node.child(0).unwrap();
let name = Identifier::from_syntax(enum_name_node, source, context)?;
let enum_variant_node = node.child(2).unwrap();
let variant = Identifier::from_syntax(enum_variant_node, source, context)?;
let inner_identifier = if let Some(child) = node.child(4) {
Some(Identifier::from_syntax(child, source, context)?)
} else {
None
};
Ok(EnumPattern {
name,
variant,
inner_identifier,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
}
impl Format for EnumPattern {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,120 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
value_node::ValueNode,
AbstractTree, As, Command, Context, Format, FunctionCall, Identifier, Index, Logic, Math,
SyntaxNode, Type, Value,
};
/// 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>),
Command(Command),
As(Box<As>),
}
impl AbstractTree for Expression {
fn from_syntax(
node: SyntaxNode,
source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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() {
"as" => Expression::As(Box::new(As::from_syntax(child, source, _context)?)),
"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,
)?)),
"command" => Expression::Command(Command::from_syntax(child, source, _context)?),
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "value, identifier, index, math, logic, function call, as or command"
.to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(expression)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
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::Command(command) => command.expected_type(_context),
Expression::As(r#as) => r#as.expected_type(_context),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
match self {
Expression::Value(value_node) => value_node.validate(_source, _context),
Expression::Identifier(identifier) => identifier.validate(_source, _context),
Expression::Math(math) => math.validate(_source, _context),
Expression::Logic(logic) => logic.validate(_source, _context),
Expression::FunctionCall(function_call) => function_call.validate(_source, _context),
Expression::Index(index) => index.validate(_source, _context),
Expression::Command(command) => command.validate(_source, _context),
Expression::As(r#as) => r#as.validate(_source, _context),
}
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
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::Command(command) => command.run(_source, _context),
Expression::As(r#as) => r#as.run(_source, _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::Command(command) => command.format(_output, _indent_level),
Expression::As(r#as) => r#as.format(_output, _indent_level),
}
}
}

View File

@ -1,153 +0,0 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Block, Context, Expression, Format, Identifier, SourcePosition, 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,
source_position: SourcePosition,
#[serde(skip)]
context: Context,
}
impl AbstractTree for For {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("for", node)?;
let for_node = node.child(0).unwrap();
let is_async = match for_node.kind() {
"for" => false,
"async for" => true,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "for or async for".to_string(),
actual: for_node.kind().to_string(),
position: node.range().into(),
})
}
};
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 loop_context = Context::with_variables_from(context)?;
let item_node = node.child(4).unwrap();
let item = Block::from_syntax(item_node, source, &loop_context)?;
Ok(For {
is_async,
item_id: identifier,
collection: expression,
block: item,
source_position: SourcePosition::from(node.range()),
context: loop_context,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
self.collection.validate(_source, context)?;
let collection_type = self.collection.expected_type(context)?;
let item_type = match collection_type {
Type::Any => Type::Any,
Type::Collection => Type::Any,
Type::List => Type::Any,
Type::ListOf(_) => todo!(),
Type::ListExact(_) => todo!(),
Type::Map(_) => todo!(),
Type::String => todo!(),
Type::Range => todo!(),
_ => {
return Err(ValidationError::TypeCheck {
expected: Type::Collection,
actual: collection_type,
position: self.source_position,
});
}
};
let key = self.item_id.clone();
self.context.inherit_all_from(context)?;
self.context.set_type(key, item_type)?;
self.item_id.validate(_source, &self.context)?;
self.block.validate(_source, &self.context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
self.context.inherit_all_from(context)?;
let expression_run = self.collection.run(source, context)?;
let key = &self.item_id;
if let Value::Range(range) = expression_run {
if self.is_async {
range.into_par_iter().try_for_each(|integer| {
self.context.add_allowance(key)?;
self.context
.set_value(key.clone(), Value::Integer(integer))?;
self.block.run(source, &self.context).map(|_value| ())
})?;
} else {
for i in range {
self.context.add_allowance(key)?;
self.context.set_value(key.clone(), Value::Integer(i))?;
self.block.run(source, &self.context)?;
}
}
return Ok(Value::none());
}
if let Value::List(list) = &expression_run {
if self.is_async {
list.items()?.par_iter().try_for_each(|value| {
self.context.add_allowance(key)?;
self.context.set_value(key.clone(), value.clone())?;
self.block.run(source, &self.context).map(|_value| ())
})?;
} else {
for value in list.items()?.iter() {
self.context.add_allowance(key)?;
self.context.set_value(key.clone(), value.clone())?;
self.block.run(source, &self.context)?;
}
}
}
Ok(Value::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,202 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
built_in_functions::Callable,
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, Function, FunctionExpression, SourcePosition,
SyntaxNode, Type, Value,
};
/// A function being invoked and the arguments it is being passed.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct FunctionCall {
function_expression: FunctionExpression,
arguments: Vec<Expression>,
syntax_position: SourcePosition,
}
impl FunctionCall {
/// Returns a new FunctionCall.
pub fn new(
function_expression: FunctionExpression,
arguments: Vec<Expression>,
syntax_position: SourcePosition,
) -> Self {
Self {
function_expression,
arguments,
syntax_position,
}
}
}
impl AbstractTree for FunctionCall {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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 expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
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) => {
let index_type = index.expected_type(context)?;
if let Type::Function { return_type, .. } = index_type {
Ok(*return_type)
} else {
Ok(index_type)
}
}
}
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
self.function_expression.validate(_source, context)?;
let function_expression_type = self.function_expression.expected_type(context)?;
let parameter_types = if let Type::Function {
parameter_types, ..
} = function_expression_type
{
parameter_types
} else {
return Err(ValidationError::TypeCheckExpectedFunction {
actual: function_expression_type,
position: self.syntax_position,
});
};
if self.arguments.len() != parameter_types.len() {
return Err(ValidationError::ExpectedFunctionArgumentAmount {
expected: parameter_types.len(),
actual: self.arguments.len(),
position: self.syntax_position,
});
}
for (index, expression) in self.arguments.iter().enumerate() {
expression.validate(_source, context)?;
if let Some(expected) = parameter_types.get(index) {
let actual = expression.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.clone(),
actual,
position: self.syntax_position,
});
}
}
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = match &self.function_expression {
FunctionExpression::Identifier(identifier) => {
if let Some(value) = context.get_value(identifier)? {
value.clone()
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(identifier.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)?,
};
let function = value.as_function()?;
match function {
Function::BuiltIn(built_in_function) => {
let mut arguments = Vec::with_capacity(self.arguments.len());
for expression in &self.arguments {
let value = expression.run(source, context)?;
arguments.push(value);
}
built_in_function.call(&arguments, source, context)
}
Function::ContextDefined(function_node) => {
let call_context = Context::with_variables_from(function_node.context())?;
call_context.inherit_from(context)?;
let parameter_expression_pairs =
function_node.parameters().iter().zip(self.arguments.iter());
for (identifier, expression) in parameter_expression_pairs {
let value = expression.run(source, context)?;
call_context.set_value(identifier.clone(), value)?;
}
function_node.body().run(source, &call_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,91 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, FunctionCall, Identifier, Index, SyntaxNode, Type, Value,
ValueNode,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum FunctionExpression {
Identifier(Identifier),
FunctionCall(Box<FunctionCall>),
Value(ValueNode),
Index(Index),
}
impl AbstractTree for FunctionExpression {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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)?),
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "identifier, function call, value or index".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(function_expression)
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
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),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
match self {
FunctionExpression::Identifier(identifier) => identifier.validate(_source, _context),
FunctionExpression::FunctionCall(function_call) => {
function_call.validate(_source, _context)
}
FunctionExpression::Value(value_node) => value_node.validate(_source, _context),
FunctionExpression::Index(index) => index.validate(_source, _context),
}
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
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),
}
}
}
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),
}
}
}

View File

@ -1,179 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Block, Context, Format, Function, Identifier, SourcePosition, SyntaxNode, 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: SourcePosition,
#[serde(skip)]
context: Context,
}
impl FunctionNode {
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) -> &SourcePosition {
&self.syntax_position
}
pub fn context(&self) -> &Context {
&self.context
}
pub fn return_type(&self) -> &Type {
match &self.r#type {
Type::Function {
parameter_types: _,
return_type,
} => return_type.as_ref(),
_ => &Type::None,
}
}
}
impl AbstractTree for FunctionNode {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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, context)?;
parameters.push(identifier);
}
if child.kind() == "type_specification" {
let type_specification = TypeSpecification::from_syntax(child, source, 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, context)?;
let function_context = Context::with_variables_from(context)?;
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 expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(self.r#type().clone())
}
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError> {
if let Type::Function {
parameter_types,
return_type,
} = &self.r#type
{
self.context.inherit_from(context)?;
for (parameter, r#type) in self.parameters.iter().zip(parameter_types.iter()) {
self.context.set_type(parameter.clone(), r#type.clone())?;
}
let actual = self.body.expected_type(&self.context)?;
if !return_type.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: return_type.as_ref().clone(),
actual,
position: self.syntax_position,
});
}
self.body.validate(source, &self.context)?;
Ok(())
} else {
Err(ValidationError::TypeCheckExpectedFunction {
actual: self.r#type.clone(),
position: self.syntax_position,
})
}
}
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
self.context.inherit_from(context)?;
let self_as_value = Value::Function(Function::ContextDefined(self.clone()));
Ok(self_as_value)
}
}
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,167 +0,0 @@
use std::{
fmt::{self, Display, Formatter},
sync::Arc,
};
use serde::{de::Visitor, Deserialize, Serialize};
use crate::{
built_in_identifiers::all_built_in_identifiers,
built_in_values::all_built_in_values,
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, 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, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Identifier(Arc<String>);
impl Identifier {
pub fn new(key: &str) -> Self {
for built_in_identifier in all_built_in_identifiers() {
let identifier = built_in_identifier.get();
if &key == identifier.inner().as_ref() {
return identifier.clone();
}
}
Identifier(Arc::new(key.to_string()))
}
pub fn from_raw_parts(arc: Arc<String>) -> Self {
Identifier(arc)
}
pub fn inner(&self) -> &Arc<String> {
&self.0
}
pub fn contains(&self, string: &str) -> bool {
self.0.as_ref() == string
}
}
impl AbstractTree for Identifier {
fn from_syntax(
node: SyntaxNode,
source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("identifier", node)?;
let text = &source[node.byte_range()];
debug_assert!(!text.is_empty());
Ok(Identifier::new(text))
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
let variable_exists = context.add_allowance(self)?;
if variable_exists {
Ok(())
} else {
for built_in_value in all_built_in_values() {
if built_in_value.name() == self.inner().as_ref() {
return Ok(());
}
}
Err(ValidationError::VariableIdentifierNotFound(self.clone()))
}
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
if let Some(r#type) = context.get_type(self)? {
Ok(r#type)
} else {
for built_in_value in all_built_in_values() {
if built_in_value.name() == self.inner().as_ref() {
return Ok(built_in_value.get().r#type()?);
}
}
Err(ValidationError::VariableIdentifierNotFound(self.clone()))
}
}
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
if let Some(value) = context.get_value(self)? {
return Ok(value);
} else {
for built_in_value in all_built_in_values() {
if built_in_value.name() == self.inner().as_ref() {
return Ok(built_in_value.get().clone());
}
}
}
Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(self.clone()),
))
}
}
impl Format for Identifier {
fn format(&self, output: &mut String, _indent_level: u8) {
output.push_str(&self.0);
}
}
impl From<String> for Identifier {
fn from(value: String) -> Self {
Identifier::from_raw_parts(Arc::new(value))
}
}
impl From<&str> for Identifier {
fn from(value: &str) -> Self {
Identifier::new(value)
}
}
impl Display for Identifier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Serialize for Identifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.0.as_ref())
}
}
impl<'de> Deserialize<'de> for Identifier {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_string(IdentifierVisitor)
}
}
struct IdentifierVisitor;
impl<'de> Visitor<'de> for IdentifierVisitor {
type Value = Identifier;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("valid UFT-8 sequence")
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Identifier(Arc::new(v)))
}
}

View File

@ -1,160 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Block, Context, Expression, Format, SourcePosition, 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>,
source_position: SourcePosition,
}
impl AbstractTree for IfElse {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
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,
source_position: SourcePosition::from(node.range()),
})
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
self.if_block.expected_type(context)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
self.if_expression.validate(_source, context)?;
self.if_block.validate(_source, context)?;
let expected = self.if_block.expected_type(context)?;
let else_ifs = self
.else_if_expressions
.iter()
.zip(self.else_if_blocks.iter());
for (expression, block) in else_ifs {
expression.validate(_source, context)?;
block.validate(_source, context)?;
let actual = block.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected,
actual,
position: self.source_position,
});
}
}
if let Some(block) = &self.else_block {
block.validate(_source, context)?;
let actual = block.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected,
actual,
position: self.source_position,
});
}
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let if_boolean = self.if_expression.run(source, context)?.as_boolean()?;
if if_boolean {
self.if_block.run(source, context)
} else {
let else_ifs = self
.else_if_expressions
.iter()
.zip(self.else_if_blocks.iter());
for (expression, block) in else_ifs {
let if_boolean = expression.run(source, context)?.as_boolean()?;
if if_boolean {
return block.run(source, context);
}
}
if let Some(block) = &self.else_block {
block.run(source, context)
} else {
Ok(Value::none())
}
}
}
}
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,134 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, IndexExpression, SourcePosition, 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,
source_position: SourcePosition,
}
impl AbstractTree for Index {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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)?;
Ok(Index {
collection,
index,
source_position: SourcePosition::from(node.range()),
})
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
match self.collection.expected_type(context)? {
Type::ListOf(item_type) => Ok(*item_type.clone()),
Type::Map(map_types_option) => {
if let (Some(map_type), IndexExpression::Identifier(identifier)) =
(map_types_option, &self.index)
{
if let Some(r#type) = map_type.get(&identifier) {
Ok(r#type.clone())
} else {
Ok(Type::Any)
}
} else {
Ok(Type::Any)
}
}
Type::None => Ok(Type::None),
r#type => Ok(r#type),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.collection.validate(_source, _context)?;
let collection_type = self.collection.expected_type(_context)?;
if let (Type::Map(type_map_option), IndexExpression::Identifier(identifier)) =
(collection_type, &self.index)
{
if let Some(type_map) = type_map_option {
if !type_map.contains_key(identifier) {
return Err(ValidationError::VariableIdentifierNotFound(
identifier.clone(),
));
}
}
} else {
self.index.validate(_source, _context)?;
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = self.collection.run(source, context)?;
match value {
Value::List(list) => {
let index = self.index.run(source, context)?.as_integer()? as usize;
let item = list.items()?.get(index).cloned().unwrap_or_default();
Ok(item)
}
Value::Map(map) => {
let map = map.inner();
let value = if let IndexExpression::Identifier(identifier) = &self.index {
if let Some(value) = map.get(identifier) {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(identifier.clone()),
));
}
} else {
let index_value = self.index.run(source, context)?;
let identifier = Identifier::new(index_value.as_string()?);
if let Some(value) = map.get(&identifier) {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(identifier.clone()),
));
}
};
Ok(value.clone())
}
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(RuntimeError::ValidationFailure(
ValidationError::ExpectedCollection { actual: value },
)),
}
}
}
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);
}
}

View File

@ -1,96 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, AssignmentOperator, Context, Format, Identifier, Index, IndexExpression,
SourcePosition, Statement, SyntaxNode, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct IndexAssignment {
index: Index,
operator: AssignmentOperator,
statement: Statement,
position: SourcePosition,
}
impl AbstractTree for IndexAssignment {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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,
position: node.range().into(),
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.index.validate(_source, _context)?;
self.statement.validate(_source, _context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let _index_collection = self.index.collection.run(source, context)?;
let index_identifier = if let IndexExpression::Identifier(identifier) = &self.index.index {
identifier
} else {
let index_run = self.index.index.run(source, context)?;
let expected_identifier = Identifier::new(index_run.as_string()?);
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(expected_identifier),
));
};
let value = self.statement.run(source, context)?;
let new_value = match self.operator {
AssignmentOperator::PlusEqual => {
if let Some(previous_value) = context.get_value(index_identifier)? {
previous_value.add(value, self.position)?
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(index_identifier.clone()),
));
}
}
AssignmentOperator::MinusEqual => {
if let Some(previous_value) = context.get_value(index_identifier)? {
previous_value.subtract(value, self.position)?
} else {
Value::none()
}
}
AssignmentOperator::Equal => value,
};
context.set_value(index_identifier.clone(), new_value)?;
Ok(Value::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,98 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
value_node::ValueNode,
AbstractTree, Context, Format, FunctionCall, Identifier, Index, 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: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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(SyntaxError::UnexpectedSyntaxNode {
expected: "value, identifier, index or function call".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(abstract_node)
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
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),
}
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
match self {
IndexExpression::Value(value_node) => value_node.validate(_source, context),
IndexExpression::Identifier(identifier) => {
context.add_allowance(identifier)?;
Ok(())
}
IndexExpression::Index(index) => index.validate(_source, context),
IndexExpression::FunctionCall(function_call) => {
function_call.validate(_source, context)
}
}
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
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),
}
}
}
impl Format for IndexExpression {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
IndexExpression::Value(value_node) => {
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,95 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, LogicOperator, 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: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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 expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::Boolean)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
log::info!("VALIDATE logic expression");
self.left.validate(_source, _context)?;
self.right.validate(_source, _context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?;
log::info!("RUN logic expression: {left} {} {right}", self.operator);
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))
}
}
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,93 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, 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: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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(SyntaxError::UnexpectedSyntaxNode {
expected: "==, !=, &&, ||, >, <, >= or <=".to_string(),
actual: operator_node.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(operator)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::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("<="),
}
}
}
impl Display for LogicOperator {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
LogicOperator::Equal => write!(f, "="),
LogicOperator::NotEqual => write!(f, "!="),
LogicOperator::And => write!(f, "&&"),
LogicOperator::Or => write!(f, "||"),
LogicOperator::Greater => write!(f, ">"),
LogicOperator::Less => write!(f, "<"),
LogicOperator::GreaterOrEqual => write!(f, ">="),
LogicOperator::LessOrEqual => write!(f, "<="),
}
}
}

View File

@ -1,117 +0,0 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, Map, SourcePosition, Statement, Type,
TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct MapNode {
properties: BTreeMap<Identifier, (Statement, Option<Type>)>,
position: SourcePosition,
}
impl MapNode {
pub fn properties(&self) -> &BTreeMap<Identifier, (Statement, Option<Type>)> {
&self.properties
}
}
impl AbstractTree for MapNode {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("map", node)?;
let mut properties = BTreeMap::new();
let mut current_identifier = None;
let mut current_type = None;
for index in 0..node.child_count() - 1 {
let child = node.child(index).unwrap();
if child.kind() == "identifier" {
current_identifier = Some(Identifier::from_syntax(child, source, context)?);
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(identifier) = &current_identifier {
properties.insert(identifier.clone(), (statement, current_type.clone()));
}
}
}
Ok(MapNode {
properties,
position: SourcePosition::from(node.range()),
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
if self.properties.is_empty() {
return Ok(Type::Map(None));
}
let mut type_map = BTreeMap::new();
for (identifier, (statement, r#type_option)) in &self.properties {
let r#type = if let Some(r#type) = type_option {
r#type.clone()
} else {
statement.expected_type(_context)?
};
type_map.insert(identifier.clone(), r#type);
}
Ok(Type::Map(Some(type_map)))
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
for (_key, (statement, r#type)) in &self.properties {
statement.validate(_source, context)?;
if let Some(expected) = r#type {
let actual = statement.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.clone(),
actual,
position: self.position.clone(),
});
}
}
}
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
let mut map = Map::new();
for (key, (statement, _)) in &self.properties {
let value = statement.run(_source, _context)?;
map.set(key.clone(), value);
}
Ok(Value::Map(map))
}
}
impl Format for MapNode {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,145 +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 tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, MatchPattern, Statement, Type, Value,
};
/// Abstract representation of a match statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Match {
matcher: Expression,
options: Vec<(MatchPattern, Statement)>,
fallback: Option<Box<Statement>>,
#[serde(skip)]
context: Context,
}
impl AbstractTree for Match {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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_pattern = 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() == "match_pattern" {
previous_pattern = Some(MatchPattern::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_pattern {
options.push((expression.clone(), statement));
}
}
}
Ok(Match {
matcher,
options,
fallback,
context: Context::default(),
})
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
let (_, first_statement) = self.options.first().unwrap();
first_statement.expected_type(context)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.matcher.validate(_source, _context)?;
for (match_pattern, statement) in &self.options {
if let MatchPattern::EnumPattern(enum_pattern) = match_pattern {
if let Some(identifier) = enum_pattern.inner_identifier() {
self.context.set_type(identifier.clone(), Type::Any)?;
}
}
match_pattern.validate(_source, _context)?;
statement.validate(_source, &self.context)?;
}
if let Some(statement) = &self.fallback {
statement.validate(_source, _context)?;
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let matcher_value = self.matcher.run(source, context)?;
for (pattern, statement) in &self.options {
if let (Value::Enum(enum_instance), MatchPattern::EnumPattern(enum_pattern)) =
(&matcher_value, pattern)
{
if enum_instance.name() == enum_pattern.name()
&& enum_instance.variant() == enum_pattern.variant()
{
let statement_context = Context::with_variables_from(context)?;
if let (Some(identifier), Some(value)) =
(enum_pattern.inner_identifier(), enum_instance.value())
{
statement_context.set_value(identifier.clone(), value.as_ref().clone())?;
}
return statement.run(source, &statement_context);
}
}
let pattern_value = pattern.run(source, context)?;
if matcher_value == pattern_value {
return statement.run(source, context);
}
}
if let Some(fallback) = &self.fallback {
fallback.run(source, context)
} else {
Ok(Value::none())
}
}
}
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,64 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, EnumPattern, Format, Type, Value, ValueNode,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum MatchPattern {
EnumPattern(EnumPattern),
Value(ValueNode),
Wildcard,
}
impl AbstractTree for MatchPattern {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("match_pattern", node)?;
let child = node.child(0).unwrap();
let pattern = match child.kind() {
"enum_pattern" => {
MatchPattern::EnumPattern(EnumPattern::from_syntax(child, source, context)?)
}
"value" => MatchPattern::Value(ValueNode::from_syntax(child, source, context)?),
"*" => MatchPattern::Wildcard,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "enum pattern or value".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(pattern)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self {
MatchPattern::EnumPattern(enum_pattern) => enum_pattern.expected_type(_context),
MatchPattern::Value(value_node) => value_node.expected_type(_context),
MatchPattern::Wildcard => todo!(),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
match self {
MatchPattern::EnumPattern(enum_pattern) => enum_pattern.run(_source, _context),
MatchPattern::Value(value_node) => value_node.run(_source, _context),
MatchPattern::Wildcard => todo!(),
}
}
}
impl Format for MatchPattern {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,74 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, MathOperator, SourcePosition, 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,
position: SourcePosition,
}
impl AbstractTree for Math {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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,
position: node.range().into(),
})
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
self.left.expected_type(context)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.left.validate(_source, _context)?;
self.right.validate(_source, _context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?;
let value = match self.operator {
MathOperator::Add => left.add(right, self.position)?,
MathOperator::Subtract => left.subtract(right, self.position)?,
MathOperator::Multiply => left.multiply(right, self.position)?,
MathOperator::Divide => left.divide(right, self.position)?,
MathOperator::Modulo => left.modulo(right, self.position)?,
};
Ok(value)
}
}
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,69 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, 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: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("math_operator", node)?;
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(SyntaxError::UnexpectedSyntaxNode {
expected: "+, -, *, / or %".to_string(),
actual: operator_node.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(operator)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::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,178 +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 [Interpreter].
pub mod r#as;
pub mod assignment;
pub mod assignment_operator;
pub mod block;
pub mod command;
pub mod enum_defintion;
pub mod enum_pattern;
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 map_node;
pub mod r#match;
pub mod match_pattern;
pub mod math;
pub mod math_operator;
pub mod statement;
pub mod struct_definition;
pub mod r#type;
pub mod type_definition;
pub mod type_specification;
pub mod value_node;
pub mod r#while;
pub use {
assignment::*, assignment_operator::*, block::*, command::*, enum_defintion::*,
enum_pattern::*, expression::*, function_call::*, function_expression::*, function_node::*,
identifier::*, if_else::*, index::*, index_assignment::IndexAssignment, index_expression::*,
logic::*, logic_operator::*, map_node::*, match_pattern::*, math::*, math_operator::*, r#as::*,
r#for::*, r#match::*, r#type::*, r#while::*, statement::*, struct_definition::*,
type_definition::*, type_specification::*, value_node::*,
};
use serde::{Deserialize, Serialize};
use crate::{
context::Context,
error::{RuntimeError, SyntaxError, ValidationError},
SyntaxNode, Value,
};
/// A detailed report of a position in the source code string.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SourcePosition {
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 SourcePosition {
fn from(range: tree_sitter::Range) -> Self {
SourcePosition {
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,
}
}
}
/// Abstraction that represents a whole, executable unit of dust code.
#[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: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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 validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
for statement in &self.statements {
statement.validate(_source, _context)?;
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let mut value = Value::none();
for statement in &self.statements {
value = statement.run(source, context)?;
if statement.is_return() {
return Ok(value);
}
}
Ok(value)
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
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.
/// Returns a syntax error if the source is invalid.
///
/// 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: &Context) -> Result<Self, SyntaxError>;
/// Return the type of the value that this abstract node will create when
/// run. Returns a validation error if the tree is invalid.
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError>;
/// Verify the type integrity of the node. Returns a validation error if the
/// tree is invalid.
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError>;
/// Execute this node's logic and return a value. Returns a runtime error if
/// the node cannot resolve to a value.
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError>;
}
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,45 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, 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: &Context) -> Result<Self, SyntaxError> {
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 expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
todo!()
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
todo!()
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
todo!()
}
}
impl Format for New {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,202 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Assignment, Block, Context, Expression, For, Format, IfElse, IndexAssignment,
Match, SyntaxNode, Type, TypeDefinition, Value, While,
};
/// Abstract representation of a statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Statement {
is_return: bool,
statement_kind: StatementKind,
}
impl Statement {
pub fn is_return(&self) -> bool {
self.is_return
}
}
impl AbstractTree for Statement {
fn from_syntax(
node: SyntaxNode,
source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("statement", node)?;
let first_child = node.child(0).unwrap();
let mut is_return = first_child.kind() == "return" || first_child.kind() == "break";
let child = if is_return {
node.child(1).unwrap()
} else {
first_child
};
let statement_kind = StatementKind::from_syntax(child, source, _context)?;
if let StatementKind::Block(block) = &statement_kind {
if block.contains_return() {
is_return = true;
}
};
Ok(Statement {
is_return,
statement_kind,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
self.statement_kind.expected_type(_context)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.statement_kind.validate(_source, _context)
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
self.statement_kind.run(_source, _context)
}
}
impl Format for Statement {
fn format(&self, _output: &mut String, _indent_level: u8) {
self.statement_kind.format(_output, _indent_level)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
enum StatementKind {
Assignment(Box<Assignment>),
Expression(Expression),
IfElse(Box<IfElse>),
Match(Match),
While(Box<While>),
Block(Box<Block>),
For(Box<For>),
IndexAssignment(Box<IndexAssignment>),
TypeDefinition(TypeDefinition),
}
impl AbstractTree for StatementKind {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("statement_kind", node)?;
let child = node.child(0).unwrap();
match child.kind() {
"assignment" => Ok(StatementKind::Assignment(Box::new(
Assignment::from_syntax(child, source, context)?,
))),
"expression" => Ok(StatementKind::Expression(Expression::from_syntax(
child, source, context,
)?)),
"if_else" => Ok(StatementKind::IfElse(Box::new(IfElse::from_syntax(
child, source, context,
)?))),
"while" => Ok(StatementKind::While(Box::new(While::from_syntax(
child, source, context,
)?))),
"block" => Ok(StatementKind::Block(Box::new(Block::from_syntax(
child, source, context,
)?))),
"for" => Ok(StatementKind::For(Box::new(For::from_syntax(
child, source, context,
)?))),
"index_assignment" => Ok(StatementKind::IndexAssignment(Box::new(
IndexAssignment::from_syntax(child, source, context)?,
))),
"match" => Ok(StatementKind::Match(Match::from_syntax(
child, source, context,
)?)),
"type_definition" => Ok(StatementKind::TypeDefinition(TypeDefinition::from_syntax(
child, source, context
)?)),
_ => Err(SyntaxError::UnexpectedSyntaxNode {
expected:
"assignment, index assignment, expression, type_definition, block, return, if...else, while, for or match".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
}),
}
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self {
StatementKind::Assignment(assignment) => assignment.expected_type(_context),
StatementKind::Expression(expression) => expression.expected_type(_context),
StatementKind::IfElse(if_else) => if_else.expected_type(_context),
StatementKind::Match(r#match) => r#match.expected_type(_context),
StatementKind::While(r#while) => r#while.expected_type(_context),
StatementKind::Block(block) => block.expected_type(_context),
StatementKind::For(r#for) => r#for.expected_type(_context),
StatementKind::IndexAssignment(index_assignment) => {
index_assignment.expected_type(_context)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.expected_type(_context)
}
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
match self {
StatementKind::Assignment(assignment) => assignment.validate(_source, _context),
StatementKind::Expression(expression) => expression.validate(_source, _context),
StatementKind::IfElse(if_else) => if_else.validate(_source, _context),
StatementKind::Match(r#match) => r#match.validate(_source, _context),
StatementKind::While(r#while) => r#while.validate(_source, _context),
StatementKind::Block(block) => block.validate(_source, _context),
StatementKind::For(r#for) => r#for.validate(_source, _context),
StatementKind::IndexAssignment(index_assignment) => {
index_assignment.validate(_source, _context)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.validate(_source, _context)
}
}
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
match self {
StatementKind::Assignment(assignment) => assignment.run(_source, _context),
StatementKind::Expression(expression) => expression.run(_source, _context),
StatementKind::IfElse(if_else) => if_else.run(_source, _context),
StatementKind::Match(r#match) => r#match.run(_source, _context),
StatementKind::While(r#while) => r#while.run(_source, _context),
StatementKind::Block(block) => block.run(_source, _context),
StatementKind::For(r#for) => r#for.run(_source, _context),
StatementKind::IndexAssignment(index_assignment) => {
index_assignment.run(_source, _context)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.run(_source, _context)
}
}
}
}
impl Format for StatementKind {
fn format(&self, output: &mut String, indent_level: u8) {
StatementKind::indent(output, indent_level);
match self {
StatementKind::Assignment(assignment) => assignment.format(output, indent_level),
StatementKind::Expression(expression) => expression.format(output, indent_level),
StatementKind::IfElse(if_else) => if_else.format(output, indent_level),
StatementKind::Match(r#match) => r#match.format(output, indent_level),
StatementKind::While(r#while) => r#while.format(output, indent_level),
StatementKind::Block(block) => block.format(output, indent_level),
StatementKind::For(r#for) => r#for.format(output, indent_level),
StatementKind::IndexAssignment(index_assignment) => {
index_assignment.format(output, indent_level)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.format(output, indent_level)
}
}
}
}

View File

@ -1,120 +0,0 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, Map, MapNode, Statement, StructInstance, Type,
TypeDefinition, TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct StructDefinition {
name: Identifier,
properties: BTreeMap<Identifier, (Option<Statement>, Type)>,
}
impl StructDefinition {
pub fn instantiate(
&self,
new_properties: &MapNode,
source: &str,
context: &Context,
) -> Result<StructInstance, RuntimeError> {
let mut all_properties = Map::new();
for (key, (statement_option, _)) in &self.properties {
if let Some(statement) = statement_option {
let value = statement.run(source, context)?;
all_properties.set(key.clone(), value);
}
}
for (key, (statement, _)) in new_properties.properties() {
let value = statement.run(source, context)?;
all_properties.set(key.clone(), value);
}
Ok(StructInstance::new(self.name.clone(), all_properties))
}
}
impl AbstractTree for StructDefinition {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("struct_definition", node)?;
let name_node = node.child(1).unwrap();
let name = Identifier::from_syntax(name_node, source, context)?;
let mut properties = 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..node.child_count() - 1 {
let child_syntax_node = node.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) {
properties.insert(identifier.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 {
Type::None
};
properties.insert(
identifier.clone(),
(current_statement.clone(), r#type.clone()),
);
}
}
}
Ok(StructDefinition { name, properties })
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
context.set_definition(self.name.clone(), TypeDefinition::Struct(self.clone()))?;
Ok(Value::none())
}
}
impl Format for StructDefinition {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,394 +0,0 @@
use std::{
collections::BTreeMap,
fmt::{self, Display, Formatter},
};
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
built_in_types::BuiltInType,
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum Type {
Any,
Boolean,
Collection,
Custom {
name: Identifier,
arguments: Vec<Type>,
},
Float,
Function {
parameter_types: Vec<Type>,
return_type: Box<Type>,
},
Integer,
List,
ListOf(Box<Type>),
ListExact(Vec<Type>),
Map(Option<BTreeMap<Identifier, Type>>),
None,
Number,
String,
Range,
}
impl Type {
pub fn custom(name: Identifier, arguments: Vec<Type>) -> Self {
Type::Custom { name, arguments }
}
pub fn option(inner_type: Option<Type>) -> Self {
BuiltInType::Option(inner_type).get().clone()
}
pub fn list(item_type: Type) -> Self {
Type::ListOf(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),
}
}
/// Returns a boolean indicating whether is type is accepting of the other.
///
/// The types do not need to match exactly. For example, the Any variant matches all of the
/// others and the Number variant accepts Number, Integer and Float.
pub fn accepts(&self, other: &Type) -> bool {
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::String)
| (Type::Collection, Type::List)
| (Type::List, Type::Collection)
| (Type::Collection, Type::ListExact(_))
| (Type::ListExact(_), Type::Collection)
| (Type::Collection, Type::ListOf(_))
| (Type::ListOf(_), Type::Collection)
| (Type::Collection, Type::Map(_))
| (Type::Map(_), Type::Collection)
| (Type::String, Type::Collection)
| (Type::Float, Type::Float)
| (Type::Integer, Type::Integer)
| (Type::List, Type::List)
| (Type::Map(None), Type::Map(None))
| (Type::Number, Type::Number)
| (Type::Number, Type::Integer)
| (Type::Number, Type::Float)
| (Type::Integer, Type::Number)
| (Type::Float, Type::Number)
| (Type::String, Type::String)
| (Type::None, Type::None) => true,
(Type::Map(left_types), Type::Map(right_types)) => left_types == right_types,
(
Type::Custom {
name: left_name,
arguments: left_arguments,
},
Type::Custom {
name: right_name,
arguments: right_arguments,
},
) => left_name == right_name && left_arguments == right_arguments,
(Type::ListOf(self_item_type), Type::ListOf(other_item_type)) => {
self_item_type.accepts(&other_item_type)
}
(Type::ListExact(self_types), Type::ListExact(other_types)) => {
for (left, right) in self_types.iter().zip(other_types.iter()) {
if !left.accepts(right) {
return false;
}
}
true
}
(Type::ListExact(exact_types), Type::ListOf(of_type))
| (Type::ListOf(of_type), Type::ListExact(exact_types)) => {
exact_types.iter().all(|r#type| r#type == of_type.as_ref())
}
(
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 == other_parameter_type {
return false;
}
}
self_return_type == other_return_type
}
_ => false,
}
}
pub fn is_function(&self) -> bool {
matches!(self, Type::Function { .. })
}
pub fn is_list(&self) -> bool {
matches!(self, Type::ListOf(_))
}
pub fn is_map(&self) -> bool {
matches!(self, Type::Map(_))
}
}
impl AbstractTree for Type {
fn from_syntax(
node: SyntaxNode,
_source: &str,
context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("type", node)?;
let type_node = node.child(0).unwrap();
let r#type = match type_node.kind() {
"identifier" => {
let name = Identifier::from_syntax(type_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 r#type = Type::from_syntax(child, _source, context)?;
arguments.push(r#type);
}
}
Type::custom(name, arguments)
}
"{" => {
let mut type_map = BTreeMap::new();
let mut previous_identifier = None;
for index in 1..node.child_count() - 1 {
let child = node.child(index).unwrap();
if let Some(identifier) = previous_identifier {
let type_specification =
TypeSpecification::from_syntax(child, _source, context)?;
type_map.insert(identifier, type_specification.take_inner());
previous_identifier = None;
} else {
previous_identifier =
Some(Identifier::from_syntax(child, _source, context)?)
}
}
Type::Map(Some(type_map))
}
"[" => {
let item_type_node = node.child(1).unwrap();
let item_type = Type::from_syntax(item_type_node, _source, context)?;
Type::ListOf(Box::new(item_type))
}
"list" => {
let item_type_node = node.child(1);
if let Some(child) = item_type_node {
Type::ListOf(Box::new(Type::from_syntax(child, _source, context)?))
} else {
Type::List
}
}
"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::option(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,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "any, bool, float, int, num, str, list, map, custom type, (, [ or {"
.to_string(),
actual: type_node.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(r#type)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::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 {
name: _,
arguments: _,
} => 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 => todo!(),
Type::ListOf(item_type) => {
output.push('[');
item_type.format(output, indent_level);
output.push(']');
}
Type::ListExact(_) => todo!(),
Type::Map(_) => {
output.push_str("map");
}
Type::None => output.push_str("Option::None"),
Type::Number => output.push_str("num"),
Type::String => output.push_str("str"),
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 { name, arguments } => {
if !arguments.is_empty() {
write!(f, "<")?;
for (index, r#type) in arguments.into_iter().enumerate() {
if index == arguments.len() - 1 {
write!(f, "{}", r#type)?;
} else {
write!(f, "{}, ", r#type)?;
}
}
write!(f, ">")
} else {
write!(f, "{name}")
}
}
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 => write!(f, "list"),
Type::ListOf(item_type) => write!(f, "[{item_type}]"),
Type::ListExact(types) => {
write!(f, "[")?;
for (index, r#type) in types.into_iter().enumerate() {
if index == types.len() - 1 {
write!(f, "{}", r#type)?;
} else {
write!(f, "{}, ", r#type)?;
}
}
write!(f, "]")
}
Type::Map(_) => write!(f, "map"),
Type::Number => write!(f, "num"),
Type::None => write!(f, "none"),
Type::String => write!(f, "str"),
Type::Range => todo!(),
}
}
}

View File

@ -1,73 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, EnumDefinition, Format, Identifier, StructDefinition, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum TypeDefinition {
Enum(EnumDefinition),
Struct(StructDefinition),
}
impl TypeDefinition {
pub fn identifier(&self) -> &Identifier {
match self {
TypeDefinition::Enum(enum_definition) => enum_definition.identifier(),
TypeDefinition::Struct(_) => todo!(),
}
}
}
impl AbstractTree for TypeDefinition {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("type_definition", node)?;
let child = node.child(0).unwrap();
match child.kind() {
"enum_definition" => Ok(TypeDefinition::Enum(EnumDefinition::from_syntax(
child, source, context,
)?)),
"struct_definition" => Ok(TypeDefinition::Struct(StructDefinition::from_syntax(
child, source, context,
)?)),
_ => Err(SyntaxError::UnexpectedSyntaxNode {
expected: "enum or struct definition".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
}),
}
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self {
TypeDefinition::Enum(enum_definition) => enum_definition.expected_type(_context),
TypeDefinition::Struct(struct_definition) => struct_definition.expected_type(_context),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
match self {
TypeDefinition::Enum(enum_definition) => enum_definition.validate(_source, _context),
TypeDefinition::Struct(struct_definition) => {
struct_definition.validate(_source, _context)
}
}
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
match self {
TypeDefinition::Enum(enum_definition) => enum_definition.run(_source, _context),
TypeDefinition::Struct(struct_definition) => struct_definition.run(_source, _context),
}
}
}
impl Format for TypeDefinition {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,56 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, 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: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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 expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
self.r#type.expected_type(context)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
self.r#type.run(source, 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,357 +0,0 @@
use std::{cmp::Ordering, ops::RangeInclusive};
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, Function, FunctionNode,
Identifier, List, Type, Value, TypeDefinition, MapNode,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum ValueNode {
Boolean(String),
Float(String),
Function(Function),
Integer(String),
String(String),
List(Vec<Expression>),
Map(MapNode),
Range(RangeInclusive<i64>),
Struct {
name: Identifier,
properties: MapNode,
},
Enum {
name: Identifier,
variant: Identifier,
expression: Option<Box<Expression>>,
},
}
impl AbstractTree for ValueNode {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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" => {
ValueNode::Map(MapNode::from_syntax(child, source, context)?)
}
"range" => {
let mut split = source[child.byte_range()].split("..");
let start = split.next().unwrap().parse().unwrap();
let end = split.next().unwrap().parse().unwrap();
ValueNode::Range(start..=end)
}
"enum_instance" => {
let name_node = child.child(0).unwrap();
let name = Identifier::from_syntax(name_node, source, context)?;
let variant_node = child.child(2).unwrap();
let variant = Identifier::from_syntax(variant_node, source, context)?;
let expression = if let Some(expression_node) = child.child(4) {
Some(Box::new(Expression::from_syntax(expression_node, source, context)?))
} else {
None
};
ValueNode::Enum { name, variant , expression }
}
"struct_instance" => {
let name_node = child.child(0).unwrap();
let name = Identifier::from_syntax(name_node, source, context)?;
let properties_node = child.child(2).unwrap();
let properties = MapNode::from_syntax(properties_node, source, context)?;
ValueNode::Struct
{
name,
properties
}
}
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected:
"string, integer, float, boolean, range, list, map, option, function, struct or enum"
.to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(value_node)
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
let r#type = match self {
ValueNode::Boolean(_) => Type::Boolean,
ValueNode::Float(_) => Type::Float,
ValueNode::Function(function) => function.r#type(),
ValueNode::Integer(_) => Type::Integer,
ValueNode::String(_) => Type::String,
ValueNode::List(expressions) => {
let mut item_types = Vec::new();
for expression in expressions {
let expression_type = expression.expected_type(context)?;
item_types.push(expression_type);
}
Type::ListExact(item_types)
}
ValueNode::Map(map_node) => map_node.expected_type(context)?,
ValueNode::Struct { name, .. } => {
Type::custom(name.clone(), Vec::with_capacity(0))
}
ValueNode::Range(_) => Type::Range,
ValueNode::Enum { name, variant, expression: _ } => {
let types: Vec<Type> = if let Some(type_definition) = context.get_definition(name)? {
if let TypeDefinition::Enum(enum_definition) = type_definition {
let types = enum_definition.variants().into_iter().find_map(|(identifier, types)| {
if identifier == variant {
Some(types.clone())
} else {
None
}
});
if let Some(types) = types {
types
} else {
return Err(ValidationError::VariableIdentifierNotFound(variant.clone()));
}
} else {
return Err(ValidationError::ExpectedEnumDefintion { actual: type_definition.clone() });
}
} else {
return Err(ValidationError::VariableIdentifierNotFound(name.clone()));
};
Type::custom(name.clone(), types.clone())
},
};
Ok(r#type)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
match self {
ValueNode::Function(function) => {
if let Function::ContextDefined(function_node) = function {
function_node.validate(_source, context)?;
}
}
ValueNode::Map(map_node) => map_node.validate(_source, context)?,
ValueNode::Enum { name, expression, .. } => {
name.validate(_source, context)?;
if let Some(expression) = expression {
expression.validate(_source, context)?;
}
}
_ => {},
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = match self {
ValueNode::Boolean(value_source) => Value::Boolean(value_source.parse().unwrap()),
ValueNode::Float(value_source) => {
let float = value_source.parse()?;
Value::Float(float)
}
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::Map(map_node) => map_node.run(source, context)?,
ValueNode::Range(range) => Value::Range(range.clone()),
ValueNode::Struct { name, properties } => {
let instance = if let Some(definition) = context.get_definition(name)? {
if let TypeDefinition::Struct(struct_definition) = definition {
struct_definition.instantiate(properties, source, context)?
} else {
return Err(RuntimeError::ValidationFailure(ValidationError::ExpectedStructDefintion { actual: definition.clone() }))
}
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::TypeDefinitionNotFound(name.clone())
));
};
Value::Struct(instance)
}
ValueNode::Enum { name, variant, expression } => {
let value = if let Some(expression) = expression {
expression.run(source, context)?
} else {
Value::none()
};
let instance = if let Some(definition) = context.get_definition(name)? {
if let TypeDefinition::Enum(enum_defintion) = definition {
enum_defintion.instantiate(variant.clone(), Some(value))
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedEnumDefintion {
actual: definition.clone()
}
));
}
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::TypeDefinitionNotFound(name.clone())
));
};
Value::Enum(instance)
},
};
Ok(value)
}
}
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::Map(map_node) => map_node.format(output, indent_level),
ValueNode::Struct { name, properties } => {
name.format(output, indent_level);
properties.format(output, indent_level);
}
ValueNode::Range(_) => todo!(),
ValueNode::Enum { .. } => todo!(),
}
}
}
impl Ord for ValueNode {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(ValueNode::Boolean(left), ValueNode::Boolean(right)) => left.cmp(right),
(ValueNode::Boolean(_), _) => Ordering::Greater,
(ValueNode::Float(left), ValueNode::Float(right)) => left.cmp(right),
(ValueNode::Float(_), _) => Ordering::Greater,
(ValueNode::Function(left), ValueNode::Function(right)) => left.cmp(right),
(ValueNode::Function(_), _) => Ordering::Greater,
(ValueNode::Integer(left), ValueNode::Integer(right)) => left.cmp(right),
(ValueNode::Integer(_), _) => Ordering::Greater,
(ValueNode::String(left), ValueNode::String(right)) => left.cmp(right),
(ValueNode::String(_), _) => Ordering::Greater,
(ValueNode::List(left), ValueNode::List(right)) => left.cmp(right),
(ValueNode::List(_), _) => Ordering::Greater,
(ValueNode::Map(left), ValueNode::Map(right)) => left.cmp(right),
(ValueNode::Map(_), _) => Ordering::Greater,
(ValueNode::Struct{ name: left_name, properties: left_properties }, ValueNode::Struct {name: right_name, properties: right_properties} ) => {
let name_cmp = left_name.cmp(right_name);
if name_cmp.is_eq() {
left_properties.cmp(right_properties)
} else {
name_cmp
}
},
(ValueNode::Struct {..}, _) => Ordering::Greater,
(
ValueNode::Enum {
name: left_name, variant: left_variant, expression: left_expression
},
ValueNode::Enum {
name: right_name, variant: right_variant, expression: right_expression
}
) => {
let name_cmp = left_name.cmp(right_name);
if name_cmp.is_eq() {
let variant_cmp = left_variant.cmp(right_variant);
if variant_cmp.is_eq() {
left_expression.cmp(right_expression)
} else {
variant_cmp
}
} else {
name_cmp
}
},
(ValueNode::Enum { .. }, _) => Ordering::Greater,
(ValueNode::Range(left), ValueNode::Range(right)) => left.clone().cmp(right.clone()),
(ValueNode::Range(_), _) => Ordering::Less,
}
}
}
impl PartialOrd for ValueNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

View File

@ -1,64 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Block, Context, Expression, Format, 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: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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 expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
self.block.expected_type(context)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
log::info!("VALIDATE while loop");
self.expression.validate(_source, context)?;
self.block.validate(_source, context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
log::info!("RUN while loop start");
while self.expression.run(source, context)?.as_boolean()? {
self.block.run(source, context)?;
}
log::info!("RUN while loop end");
Ok(Value::none())
}
}
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,59 +0,0 @@
use std::{fs::File, io::Read};
use enum_iterator::{all, Sequence};
use serde::{Deserialize, Serialize};
use crate::{error::RuntimeError, Context, Type, Value};
use super::Callable;
pub fn fs_functions() -> impl Iterator<Item = Fs> {
all()
}
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum Fs {
ReadFile,
}
impl Callable for Fs {
fn name(&self) -> &'static str {
match self {
Fs::ReadFile => "read_file",
}
}
fn description(&self) -> &'static str {
match self {
Fs::ReadFile => "Read the contents of a file to a string.",
}
}
fn r#type(&self) -> Type {
match self {
Fs::ReadFile => Type::function(vec![Type::String], Type::String),
}
}
fn call(
&self,
arguments: &[Value],
_source: &str,
_outer_context: &Context,
) -> Result<Value, RuntimeError> {
match self {
Fs::ReadFile => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let path = arguments.first().unwrap().as_string()?;
let mut file = File::open(path)?;
let file_size = file.metadata()?.len() as usize;
let mut file_content = String::with_capacity(file_size);
file.read_to_string(&mut file_content)?;
Ok(Value::string(file_content))
}
}
}
}

View File

@ -1,77 +0,0 @@
use enum_iterator::Sequence;
use serde::{Deserialize, Serialize};
use crate::{error::RuntimeError, Context, Type, Value};
use super::Callable;
pub fn json_functions() -> impl Iterator<Item = Json> {
enum_iterator::all()
}
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum Json {
Create,
CreatePretty,
Parse,
}
impl Callable for Json {
fn name(&self) -> &'static str {
match self {
Json::Create => "create",
Json::CreatePretty => "create_pretty",
Json::Parse => "parse",
}
}
fn description(&self) -> &'static str {
match self {
Json::Create => "Convert a value to a JSON string.",
Json::CreatePretty => "Convert a value to a formatted JSON string.",
Json::Parse => "Convert JSON to a value",
}
}
fn r#type(&self) -> Type {
match self {
Json::Create => Type::function(vec![Type::Any], Type::String),
Json::CreatePretty => Type::function(vec![Type::Any], Type::String),
Json::Parse => Type::function(vec![Type::String], Type::Any),
}
}
fn call(
&self,
arguments: &[Value],
_source: &str,
_outer_context: &Context,
) -> Result<Value, RuntimeError> {
match self {
Json::Create => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
let json_string = serde_json::to_string(value)?;
Ok(Value::String(json_string))
}
Json::CreatePretty => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
let json_string = serde_json::to_string_pretty(value)?;
Ok(Value::String(json_string))
}
Json::Parse => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let json_string = arguments.first().unwrap().as_string()?;
let value = serde_json::from_str(json_string)?;
Ok(value)
}
}
}
}

View File

@ -1,198 +0,0 @@
pub mod fs;
pub mod json;
pub mod str;
use std::fmt::{self, Display, Formatter};
use rand::{random, thread_rng, Rng};
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, ValidationError},
Context, EnumInstance, Format, Identifier, Type, Value,
};
use self::{fs::Fs, json::Json, str::StrFunction};
pub trait Callable {
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn r#type(&self) -> Type;
fn call(
&self,
arguments: &[Value],
source: &str,
context: &Context,
) -> Result<Value, RuntimeError>;
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum BuiltInFunction {
AssertEqual,
Fs(Fs),
Json(Json),
Length,
Output,
RandomBoolean,
RandomFloat,
RandomFrom,
RandomInteger,
String(StrFunction),
}
impl Callable for BuiltInFunction {
fn name(&self) -> &'static str {
match self {
BuiltInFunction::AssertEqual => "assert_equal",
BuiltInFunction::Fs(fs_function) => fs_function.name(),
BuiltInFunction::Json(json_function) => json_function.name(),
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(),
}
}
fn description(&self) -> &'static str {
match self {
BuiltInFunction::AssertEqual => "assert_equal",
BuiltInFunction::Fs(fs_function) => fs_function.description(),
BuiltInFunction::Json(json_function) => json_function.description(),
BuiltInFunction::Length => "length",
BuiltInFunction::Output => "output",
BuiltInFunction::RandomBoolean => "boolean",
BuiltInFunction::RandomFloat => "float",
BuiltInFunction::RandomFrom => "from",
BuiltInFunction::RandomInteger => "integer",
BuiltInFunction::String(string_function) => string_function.description(),
}
}
fn r#type(&self) -> Type {
match self {
BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None),
BuiltInFunction::Fs(fs_function) => fs_function.r#type(),
BuiltInFunction::Json(json_function) => json_function.r#type(),
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(),
}
}
fn call(
&self,
arguments: &[Value],
_source: &str,
context: &Context,
) -> Result<Value, RuntimeError> {
match self {
BuiltInFunction::AssertEqual => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let left = arguments.get(0).unwrap();
let right = arguments.get(1).unwrap();
if left == right {
Ok(Value::Enum(EnumInstance::new(
Identifier::new("Result"),
Identifier::new("Ok"),
Some(Value::none()),
)))
} else {
Err(RuntimeError::AssertEqualFailed {
left: left.clone(),
right: right.clone(),
})
}
}
BuiltInFunction::Fs(fs_function) => fs_function.call(arguments, _source, context),
BuiltInFunction::Json(json_function) => json_function.call(arguments, _source, context),
BuiltInFunction::Length => {
RuntimeError::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.inner().len()
} else if let Ok(str) = value.as_string() {
str.chars().count()
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedCollection {
actual: value.clone(),
},
));
};
Ok(Value::Integer(length as i64))
}
BuiltInFunction::Output => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
println!("{value}");
Ok(Value::none())
}
BuiltInFunction::RandomBoolean => {
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
Ok(Value::Boolean(random()))
}
BuiltInFunction::RandomFloat => {
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
Ok(Value::Float(random()))
}
BuiltInFunction::RandomFrom => {
RuntimeError::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 => {
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
Ok(Value::Integer(random()))
}
BuiltInFunction::String(string_function) => {
string_function.call(arguments, _source, 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,591 +0,0 @@
use enum_iterator::Sequence;
use serde::{Deserialize, Serialize};
use crate::{error::RuntimeError, Context, EnumInstance, Identifier, List, Type, Value};
use super::Callable;
pub fn string_functions() -> impl Iterator<Item = StrFunction> {
enum_iterator::all()
}
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum StrFunction {
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 Callable for StrFunction {
fn name(&self) -> &'static str {
match self {
StrFunction::AsBytes => "as_bytes",
StrFunction::EndsWith => "ends_with",
StrFunction::Find => "find",
StrFunction::Insert => "insert",
StrFunction::IsAscii => "is_ascii",
StrFunction::IsEmpty => "is_empty",
StrFunction::Lines => "lines",
StrFunction::Matches => "matches",
StrFunction::Parse => "parse",
StrFunction::Remove => "remove",
StrFunction::ReplaceRange => "replace_range",
StrFunction::Retain => "retain",
StrFunction::Split => "split",
StrFunction::SplitAt => "split_at",
StrFunction::SplitInclusive => "split_inclusive",
StrFunction::SplitN => "split_n",
StrFunction::SplitOnce => "split_once",
StrFunction::SplitTerminator => "split_terminator",
StrFunction::SplitWhitespace => "split_whitespace",
StrFunction::StartsWith => "starts_with",
StrFunction::StripPrefix => "strip_prefix",
StrFunction::ToLowercase => "to_lowercase",
StrFunction::ToUppercase => "to_uppercase",
StrFunction::Trim => "trim",
StrFunction::TrimEnd => "trim_end",
StrFunction::TrimEndMatches => "trim_end_matches",
StrFunction::TrimMatches => "trim_matches",
StrFunction::TrimStart => "trim_start",
StrFunction::TrimStartMatches => "trim_start_matches",
StrFunction::Truncate => "truncate",
}
}
fn description(&self) -> &'static str {
match self {
StrFunction::AsBytes => "TODO",
StrFunction::EndsWith => "TODO",
StrFunction::Find => "TODO",
StrFunction::Insert => "TODO",
StrFunction::IsAscii => "TODO",
StrFunction::IsEmpty => "TODO",
StrFunction::Lines => "TODO",
StrFunction::Matches => "TODO",
StrFunction::Parse => "TODO",
StrFunction::Remove => "TODO",
StrFunction::ReplaceRange => "TODO",
StrFunction::Retain => "TODO",
StrFunction::Split => "TODO",
StrFunction::SplitAt => "TODO",
StrFunction::SplitInclusive => "TODO",
StrFunction::SplitN => "TODO",
StrFunction::SplitOnce => "TODO",
StrFunction::SplitTerminator => "TODO",
StrFunction::SplitWhitespace => "TODO",
StrFunction::StartsWith => "TODO",
StrFunction::StripPrefix => "TODO",
StrFunction::ToLowercase => "TODO",
StrFunction::ToUppercase => "TODO",
StrFunction::Trim => "TODO",
StrFunction::TrimEnd => "TODO",
StrFunction::TrimEndMatches => "TODO",
StrFunction::TrimMatches => "TODO",
StrFunction::TrimStart => "TODO",
StrFunction::TrimStartMatches => "TODO",
StrFunction::Truncate => "TODO",
}
}
fn r#type(&self) -> Type {
match self {
StrFunction::AsBytes => Type::function(vec![Type::String], Type::list(Type::Integer)),
StrFunction::EndsWith => {
Type::function(vec![Type::String, Type::String], Type::Boolean)
}
StrFunction::Find => Type::function(
vec![Type::String, Type::String],
Type::option(Some(Type::Integer)),
),
StrFunction::Insert => Type::function(
vec![Type::String, Type::Integer, Type::String],
Type::String,
),
StrFunction::IsAscii => Type::function(vec![Type::String], Type::Boolean),
StrFunction::IsEmpty => Type::function(vec![Type::String], Type::Boolean),
StrFunction::Lines => Type::function(vec![Type::String], Type::list(Type::String)),
StrFunction::Matches => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::Parse => Type::function(vec![Type::String], Type::Any),
StrFunction::Remove => Type::function(
vec![Type::String, Type::Integer],
Type::option(Some(Type::String)),
),
StrFunction::ReplaceRange => Type::function(
vec![Type::String, Type::list(Type::Integer), Type::String],
Type::String,
),
StrFunction::Retain => Type::function(
vec![
Type::String,
Type::function(vec![Type::String], Type::Boolean),
],
Type::String,
),
StrFunction::Split => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::SplitAt => {
Type::function(vec![Type::String, Type::Integer], Type::list(Type::String))
}
StrFunction::SplitInclusive => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::SplitN => Type::function(
vec![Type::String, Type::Integer, Type::String],
Type::list(Type::String),
),
StrFunction::SplitOnce => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::SplitTerminator => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::SplitWhitespace => {
Type::function(vec![Type::String], Type::list(Type::String))
}
StrFunction::StartsWith => {
Type::function(vec![Type::String, Type::String], Type::Boolean)
}
StrFunction::StripPrefix => Type::function(
vec![Type::String, Type::String],
Type::option(Some(Type::String)),
),
StrFunction::ToLowercase => Type::function(vec![Type::String], Type::String),
StrFunction::ToUppercase => Type::function(vec![Type::String], Type::String),
StrFunction::Truncate => {
Type::function(vec![Type::String, Type::Integer], Type::String)
}
StrFunction::Trim => Type::function(vec![Type::String], Type::String),
StrFunction::TrimEnd => Type::function(vec![Type::String], Type::String),
StrFunction::TrimEndMatches => {
Type::function(vec![Type::String, Type::String], Type::String)
}
StrFunction::TrimMatches => {
Type::function(vec![Type::String, Type::String], Type::String)
}
StrFunction::TrimStart => Type::function(vec![Type::String], Type::String),
StrFunction::TrimStartMatches => {
Type::function(vec![Type::String, Type::String], Type::String)
}
}
}
fn call(
&self,
arguments: &[Value],
_source: &str,
_context: &Context,
) -> Result<Value, RuntimeError> {
let value = match self {
StrFunction::AsBytes => {
RuntimeError::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))
}
StrFunction::EndsWith => {
RuntimeError::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))
}
StrFunction::Find => {
RuntimeError::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| Value::Integer(index as i64));
if let Some(index) = find {
Value::Enum(EnumInstance::new(
Identifier::new("Option"),
Identifier::new("Some"),
Some(index),
))
} else {
Value::Enum(EnumInstance::new(
Identifier::new("Option"),
Identifier::new("None"),
Some(Value::none()),
))
}
}
StrFunction::IsAscii => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
Value::Boolean(string.is_ascii())
}
StrFunction::IsEmpty => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
Value::Boolean(string.is_empty())
}
StrFunction::Insert => {
RuntimeError::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)
}
StrFunction::Lines => {
RuntimeError::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))
}
StrFunction::Matches => {
RuntimeError::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))
}
StrFunction::Parse => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
if let Ok(integer) = string.parse::<i64>() {
Value::Integer(integer)
} else if let Ok(float) = string.parse::<f64>() {
Value::Float(float)
} else {
Value::none()
}
}
StrFunction::Remove => {
RuntimeError::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()
}
}
StrFunction::ReplaceRange => {
RuntimeError::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[0].as_integer()? as usize;
let end = range[1].as_integer()? as usize;
let pattern = arguments.get(2).unwrap().as_string()?;
string.replace_range(start..end, pattern);
Value::String(string)
}
StrFunction::Retain => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
todo!();
// 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)
}
StrFunction::Split => {
RuntimeError::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))
}
StrFunction::SplitAt => {
RuntimeError::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()),
]))
}
StrFunction::SplitInclusive => {
RuntimeError::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))
}
StrFunction::SplitN => {
RuntimeError::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))
}
StrFunction::SplitOnce => {
RuntimeError::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()),
]))
});
if let Some(sections) = sections {
Value::some(sections)
} else {
Value::none()
}
}
StrFunction::SplitTerminator => {
RuntimeError::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))
}
StrFunction::SplitWhitespace => {
RuntimeError::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))
}
StrFunction::StartsWith => {
RuntimeError::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))
}
StrFunction::StripPrefix => {
RuntimeError::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()));
if let Some(value) = stripped {
Value::Enum(EnumInstance::new(
Identifier::new("Option"),
Identifier::new("Some"),
Some(value),
))
} else {
Value::none()
}
}
StrFunction::ToLowercase => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let lowercase = string.to_lowercase();
Value::string(lowercase)
}
StrFunction::ToUppercase => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let uppercase = string.to_uppercase();
Value::string(uppercase)
}
StrFunction::Trim => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let trimmed = arguments.first().unwrap().as_string()?.trim().to_string();
Value::string(trimmed)
}
StrFunction::TrimEnd => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let trimmed = arguments
.first()
.unwrap()
.as_string()?
.trim_end()
.to_string();
Value::string(trimmed)
}
StrFunction::TrimEndMatches => {
RuntimeError::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)
}
StrFunction::TrimMatches => {
RuntimeError::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)
}
StrFunction::TrimStart => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let trimmed = arguments
.first()
.unwrap()
.as_string()?
.trim_start()
.to_string();
Value::string(trimmed)
}
StrFunction::TrimStartMatches => {
RuntimeError::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)
}
StrFunction::Truncate => {
RuntimeError::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,51 +0,0 @@
use std::sync::{Arc, OnceLock};
use enum_iterator::{all, Sequence};
use crate::Identifier;
pub fn all_built_in_identifiers() -> impl Iterator<Item = BuiltInIdentifier> {
all()
}
static OPTION: OnceLock<Identifier> = OnceLock::new();
static NONE: OnceLock<Identifier> = OnceLock::new();
static SOME: OnceLock<Identifier> = OnceLock::new();
static RESULT: OnceLock<Identifier> = OnceLock::new();
static OK: OnceLock<Identifier> = OnceLock::new();
static ERROR: OnceLock<Identifier> = OnceLock::new();
#[derive(Sequence, Debug)]
pub enum BuiltInIdentifier {
Option,
None,
Some,
Result,
Ok,
Error,
}
impl BuiltInIdentifier {
pub fn get(&self) -> &Identifier {
match self {
BuiltInIdentifier::Option => {
OPTION.get_or_init(|| Identifier::from_raw_parts(Arc::new("Option".to_string())))
}
BuiltInIdentifier::None => {
NONE.get_or_init(|| Identifier::from_raw_parts(Arc::new("None".to_string())))
}
BuiltInIdentifier::Some => {
SOME.get_or_init(|| Identifier::from_raw_parts(Arc::new("Some".to_string())))
}
BuiltInIdentifier::Result => {
RESULT.get_or_init(|| Identifier::from_raw_parts(Arc::new("Result".to_string())))
}
BuiltInIdentifier::Ok => {
OK.get_or_init(|| Identifier::from_raw_parts(Arc::new("Ok".to_string())))
}
BuiltInIdentifier::Error => {
ERROR.get_or_init(|| Identifier::from_raw_parts(Arc::new("Error".to_string())))
}
}
}
}

View File

@ -1,56 +0,0 @@
use std::sync::OnceLock;
use enum_iterator::{all, Sequence};
use crate::{
error::rw_lock_error::RwLockError, Context, EnumDefinition, Identifier, Type, TypeDefinition,
};
static OPTION: OnceLock<Result<TypeDefinition, RwLockError>> = OnceLock::new();
static RESULT: OnceLock<Result<TypeDefinition, RwLockError>> = OnceLock::new();
pub fn all_built_in_type_definitions() -> impl Iterator<Item = BuiltInTypeDefinition> {
all()
}
#[derive(Sequence)]
pub enum BuiltInTypeDefinition {
Option,
Result,
}
impl BuiltInTypeDefinition {
pub fn name(&self) -> &'static str {
match self {
BuiltInTypeDefinition::Option => "Option",
BuiltInTypeDefinition::Result => "Result",
}
}
pub fn get(&self, _context: &Context) -> &Result<TypeDefinition, RwLockError> {
match self {
BuiltInTypeDefinition::Option => OPTION.get_or_init(|| {
let definition = TypeDefinition::Enum(EnumDefinition::new(
Identifier::new(self.name()),
vec![
(Identifier::new("Some"), vec![Type::Any]),
(Identifier::new("None"), Vec::with_capacity(0)),
],
));
Ok(definition)
}),
BuiltInTypeDefinition::Result => RESULT.get_or_init(|| {
let definition = TypeDefinition::Enum(EnumDefinition::new(
Identifier::new(self.name()),
vec![
(Identifier::new("Ok"), vec![Type::Any]),
(Identifier::new("Error"), vec![Type::Any]),
],
));
Ok(definition)
}),
}
}
}

View File

@ -1,29 +0,0 @@
use std::sync::OnceLock;
use crate::{Identifier, Type};
static OPTION: OnceLock<Type> = OnceLock::new();
pub enum BuiltInType {
Option(Option<Type>),
}
impl BuiltInType {
pub fn name(&self) -> &'static str {
match self {
BuiltInType::Option(_) => "Option",
}
}
pub fn get(&self) -> &Type {
match self {
BuiltInType::Option(content_type) => OPTION.get_or_init(|| {
if let Some(content_type) = content_type {
Type::custom(Identifier::new("Option"), vec![content_type.clone()])
} else {
Type::custom(Identifier::new("Option"), Vec::with_capacity(0))
}
}),
}
}
}

View File

@ -1,180 +0,0 @@
use std::{env::args, sync::OnceLock};
use enum_iterator::{all, Sequence};
use serde::{Deserialize, Serialize};
use crate::{
built_in_functions::{fs::fs_functions, json::json_functions, str::string_functions, Callable},
BuiltInFunction, EnumInstance, Function, Identifier, List, Map, Value,
};
static ARGS: OnceLock<Value> = OnceLock::new();
static FS: OnceLock<Value> = OnceLock::new();
static JSON: OnceLock<Value> = OnceLock::new();
static NONE: OnceLock<Value> = OnceLock::new();
static RANDOM: OnceLock<Value> = OnceLock::new();
static STR: OnceLock<Value> = OnceLock::new();
/// Returns the entire built-in value API.
pub fn all_built_in_values() -> impl Iterator<Item = BuiltInValue> {
all()
}
/// A variable with a hard-coded key that is globally available.
#[derive(Sequence, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BuiltInValue {
/// The arguments used to launch the current program.
Args,
/// Create an error if two values are not equal.
AssertEqual,
/// File system tools.
Fs,
/// JSON format tools.
Json,
/// Get the length of a collection.
Length,
/// The absence of a value.
None,
/// Print a value to stdout.
Output,
/// Random value generators.
Random,
/// String utilities.
Str,
}
impl BuiltInValue {
/// Returns the hard-coded key used to identify the value.
pub fn name(&self) -> &'static str {
match self {
BuiltInValue::Args => "args",
BuiltInValue::AssertEqual => "assert_equal",
BuiltInValue::Fs => "fs",
BuiltInValue::Json => "json",
BuiltInValue::Length => BuiltInFunction::Length.name(),
BuiltInValue::None => "None",
BuiltInValue::Output => "output",
BuiltInValue::Random => "random",
BuiltInValue::Str => "str",
}
}
/// Returns a brief description of the value's features.
///
/// This is used by the shell when suggesting completions.
pub fn description(&self) -> &'static str {
match self {
BuiltInValue::Args => "The command line arguments sent to this program.",
BuiltInValue::AssertEqual => "Error if the two values are not equal.",
BuiltInValue::Fs => "File and directory tools.",
BuiltInValue::Json => "JSON formatting tools.",
BuiltInValue::Length => BuiltInFunction::Length.description(),
BuiltInValue::None => "The absence of a value.",
BuiltInValue::Output => "output",
BuiltInValue::Random => "random",
BuiltInValue::Str => "string",
}
}
/// Returns the value by creating it or, if it has already been accessed, retrieving it from its
/// [OnceLock][].
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))
})
.clone(),
BuiltInValue::AssertEqual => {
Value::Function(Function::BuiltIn(BuiltInFunction::AssertEqual))
}
BuiltInValue::Fs => FS
.get_or_init(|| {
let mut fs_map = Map::new();
for fs_function in fs_functions() {
let key = fs_function.name();
let value =
Value::Function(Function::BuiltIn(BuiltInFunction::Fs(fs_function)));
fs_map.set(Identifier::new(key), value);
}
Value::Map(fs_map)
})
.clone(),
BuiltInValue::Json => JSON
.get_or_init(|| {
let mut json_map = Map::new();
for json_function in json_functions() {
let key = json_function.name();
let value = Value::Function(Function::BuiltIn(BuiltInFunction::Json(
json_function,
)));
json_map.set(Identifier::new(key), value);
}
Value::Map(json_map)
})
.clone(),
BuiltInValue::Length => Value::Function(Function::BuiltIn(BuiltInFunction::Length)),
BuiltInValue::None => NONE
.get_or_init(|| {
Value::Enum(EnumInstance::new(
Identifier::new("Option"),
Identifier::new("None"),
None,
))
})
.clone(),
BuiltInValue::Output => Value::Function(Function::BuiltIn(BuiltInFunction::Output)),
BuiltInValue::Random => RANDOM
.get_or_init(|| {
let mut random_map = Map::new();
for built_in_function in [
BuiltInFunction::RandomBoolean,
BuiltInFunction::RandomFloat,
BuiltInFunction::RandomFrom,
BuiltInFunction::RandomInteger,
] {
let identifier = Identifier::new(built_in_function.name());
let value = Value::Function(Function::BuiltIn(built_in_function));
random_map.set(identifier, value);
}
Value::Map(random_map)
})
.clone(),
BuiltInValue::Str => STR
.get_or_init(|| {
let mut str_map = Map::new();
for string_function in string_functions() {
let identifier = Identifier::new(string_function.name());
let value = Value::Function(Function::BuiltIn(BuiltInFunction::String(
string_function,
)));
str_map.set(identifier, value);
}
Value::Map(str_map)
})
.clone(),
}
}
}

View File

@ -1,395 +0,0 @@
//! A garbage-collecting execution context that stores variables and type data
//! during the [Interpreter][crate::Interpreter]'s abstraction and execution
//! process.
//!
//! ## Setting values
//!
//! When data is stored in a context, it can be accessed by dust source code.
//! This allows you to insert values and type definitions before any code is
//! interpreted.
//!
//! ```
//! # use dust_lang::*;
//! let context = Context::default();
//!
//! context.set_value(
//! "foobar".into(),
//! Value::String("FOOBAR".to_string())
//! ).unwrap();
//!
//! interpret_with_context("output foobar", context);
//!
//! // Stdout: "FOOBAR"
//! ```
//!
//! ## Built-in values and type definitions
//!
//! When looking up values and definitions, the Context will try to use one that
//! has been explicitly set. If nothing is found, it will then check the built-
//! in values and type definitions for a match. This means that the user can
//! override the built-ins.
//!
//! ## Garbage Collection
//!
//! To disable garbage collection, run a Context in AllowGarbage mode.
//!
//! ```
//! # use dust_lang::*;
//! let context = Context::new(ContextMode::AllowGarbage);
//! ```
//!
//!
//! Every item stored in a Context has a counter attached to it. You must use
//! [Context::add_allowance][] to let the Context know not to drop the value.
//! Every time you use [Context::get_value][] it checks the number of times it
//! has been used and compares it to the number of allowances. If the limit
//! has been reached, the value will be removed from the context and can no
//! longer be found.
mod usage_counter;
mod value_data;
pub use usage_counter::UsageCounter;
pub use value_data::ValueData;
use std::{
cmp::Ordering,
collections::BTreeMap,
fmt::Display,
sync::{Arc, RwLock, RwLockReadGuard},
};
use crate::{
built_in_type_definitions::all_built_in_type_definitions, built_in_values::all_built_in_values,
error::rw_lock_error::RwLockError, Identifier, Type, TypeDefinition, Value,
};
#[derive(Clone, Debug, PartialEq)]
pub enum ContextMode {
AllowGarbage,
RemoveGarbage,
}
/// An execution context stores that variable and type data during the
/// [Interpreter]'s abstraction and execution process.
///
/// See the [module-level docs][self] for more info.
#[derive(Clone, Debug)]
pub struct Context {
mode: ContextMode,
inner: Arc<RwLock<BTreeMap<Identifier, (ValueData, UsageCounter)>>>,
}
impl Context {
/// Return a new, empty Context.
pub fn new(mode: ContextMode) -> Self {
Self {
mode,
inner: Arc::new(RwLock::new(BTreeMap::new())),
}
}
/// Return a lock guard to the inner BTreeMap.
pub fn inner(
&self,
) -> Result<RwLockReadGuard<BTreeMap<Identifier, (ValueData, UsageCounter)>>, RwLockError> {
Ok(self.inner.read()?)
}
/// Create a new context with all of the data from an existing context.
pub fn with_variables_from(other: &Context) -> Result<Context, RwLockError> {
let mut new_variables = BTreeMap::new();
for (identifier, (value_data, counter)) in other.inner.read()?.iter() {
let (allowances, _runtime_uses) = counter.get_counts()?;
let new_counter = UsageCounter::with_counts(allowances, 0);
new_variables.insert(identifier.clone(), (value_data.clone(), new_counter));
}
Ok(Context {
mode: other.mode.clone(),
inner: Arc::new(RwLock::new(new_variables)),
})
}
/// Modify a context to take the functions and type definitions of another.
///
/// In the case of the conflict, the inherited value will override the previous
/// value.
pub fn inherit_from(&self, other: &Context) -> Result<(), RwLockError> {
let mut self_variables = self.inner.write()?;
for (identifier, (value_data, counter)) in other.inner.read()?.iter() {
let (allowances, _runtime_uses) = counter.get_counts()?;
let new_counter = UsageCounter::with_counts(allowances, 0);
if let ValueData::Value(value) = value_data {
if value.is_function() {
self_variables.insert(identifier.clone(), (value_data.clone(), new_counter));
}
} else if let ValueData::TypeHint(r#type) = value_data {
if r#type.is_function() {
self_variables.insert(identifier.clone(), (value_data.clone(), new_counter));
}
} else if let ValueData::TypeDefinition(_) = value_data {
self_variables.insert(identifier.clone(), (value_data.clone(), new_counter));
}
}
Ok(())
}
/// Modify a context to take all the information of another.
///
/// In the case of the conflict, the inherited value will override the previous
/// value.
///
/// ```
/// # use dust_lang::*;
/// let first_context = Context::default();
/// let second_context = Context::default();
///
/// second_context.set_value(
/// "Foo".into(),
/// Value::String("Bar".to_string())
/// );
///
/// first_context.inherit_all_from(&second_context).unwrap();
///
/// assert_eq!(first_context, second_context);
/// ```
pub fn inherit_all_from(&self, other: &Context) -> Result<(), RwLockError> {
let mut self_variables = self.inner.write()?;
for (identifier, (value_data, _counter)) in other.inner.read()?.iter() {
self_variables.insert(
identifier.clone(),
(value_data.clone(), UsageCounter::new()),
);
}
Ok(())
}
/// Increment the number of allowances a variable has. Return a boolean
/// representing whether or not the variable was found.
pub fn add_allowance(&self, identifier: &Identifier) -> Result<bool, RwLockError> {
if let Some((_value_data, counter)) = self.inner.read()?.get(identifier) {
log::debug!("Adding allowance for {identifier}.");
counter.add_allowance()?;
Ok(true)
} else {
Ok(false)
}
}
/// Get a [Value] from the context.
pub fn get_value(&self, identifier: &Identifier) -> Result<Option<Value>, RwLockError> {
let (value, counter) =
if let Some((value_data, counter)) = self.inner.read()?.get(identifier) {
if let ValueData::Value(value) = value_data {
(value.clone(), counter.clone())
} else {
return Ok(None);
}
} else {
for built_in_value in all_built_in_values() {
if built_in_value.name() == identifier.inner().as_ref() {
return Ok(Some(built_in_value.get().clone()));
}
}
return Ok(None);
};
counter.add_runtime_use()?;
log::debug!("Adding runtime use for {identifier}.");
let (allowances, runtime_uses) = counter.get_counts()?;
if self.mode == ContextMode::RemoveGarbage && allowances == runtime_uses {
self.unset(identifier)?;
}
Ok(Some(value))
}
/// Get a [Type] from the context.
///
/// If the key matches a stored [Value], its type will be returned. It if
/// matches a type hint, the type hint will be returned.
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, RwLockError> {
if let Some((value_data, _counter)) = self.inner.read()?.get(identifier) {
match value_data {
ValueData::Value(value) => return Ok(Some(value.r#type()?)),
ValueData::TypeHint(r#type) => return Ok(Some(r#type.clone())),
ValueData::TypeDefinition(_) => todo!(),
}
}
for built_in_value in all_built_in_values() {
if built_in_value.name() == identifier.inner().as_ref() {
return Ok(Some(built_in_value.get().r#type()?));
}
}
Ok(None)
}
/// Get a [TypeDefinition] from the context.
///
/// This will also return a built-in type definition if one matches the key.
/// See the [module-level docs][self] for more info.
pub fn get_definition(
&self,
identifier: &Identifier,
) -> Result<Option<TypeDefinition>, RwLockError> {
if let Some((value_data, _counter)) = self.inner.read()?.get(identifier) {
if let ValueData::TypeDefinition(definition) = value_data {
return Ok(Some(definition.clone()));
}
}
for built_in_definition in all_built_in_type_definitions() {
if built_in_definition.name() == identifier.inner().as_ref() {
return Ok(Some(built_in_definition.get(self).clone()?));
}
}
Ok(None)
}
/// Set a value to a key.
pub fn set_value(&self, key: Identifier, value: Value) -> Result<(), RwLockError> {
let mut map = self.inner.write()?;
let old_data = map.remove(&key);
if let Some((_, old_counter)) = old_data {
map.insert(key, (ValueData::Value(value), old_counter));
} else {
map.insert(key, (ValueData::Value(value), UsageCounter::new()));
}
Ok(())
}
/// Set a type hint.
///
/// This allows the interpreter to check a value's type before the value
/// actually exists by predicting what the abstract tree will produce.
pub fn set_type(&self, key: Identifier, r#type: Type) -> Result<(), RwLockError> {
self.inner
.write()?
.insert(key, (ValueData::TypeHint(r#type), UsageCounter::new()));
Ok(())
}
/// Set a type definition.
///
/// This allows defined types (i.e. structs and enums) to be instantiated
/// later while running the interpreter using this context.
pub fn set_definition(
&self,
key: Identifier,
definition: TypeDefinition,
) -> Result<(), RwLockError> {
self.inner.write()?.insert(
key,
(ValueData::TypeDefinition(definition), UsageCounter::new()),
);
Ok(())
}
/// Remove a key-value pair.
pub fn unset(&self, key: &Identifier) -> Result<(), RwLockError> {
log::debug!("Dropping variable {key}.");
self.inner.write()?.remove(key);
Ok(())
}
}
impl Default for Context {
fn default() -> Self {
Context::new(ContextMode::RemoveGarbage)
}
}
impl Eq for Context {}
impl PartialEq for Context {
fn eq(&self, other: &Self) -> bool {
let self_variables = self.inner().unwrap();
let other_variables = other.inner().unwrap();
if self_variables.len() != other_variables.len() {
return false;
}
for ((left_key, left_value_data), (right_key, right_value_data)) in
self_variables.iter().zip(other_variables.iter())
{
if left_key != right_key || left_value_data != right_value_data {
return false;
}
}
true
}
}
impl PartialOrd for Context {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Context {
fn cmp(&self, other: &Self) -> Ordering {
let left = self.inner().unwrap();
let right = other.inner().unwrap();
left.cmp(&right)
}
}
impl Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{{")?;
for (identifier, value_data) in self.inner.read().unwrap().iter() {
writeln!(f, "{identifier} {value_data:?}")?;
}
writeln!(f, "}}")
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn drops_variables() {
let context = Context::default();
interpret_with_context(
"
x = 1
y = 2
z = x + y
",
context.clone(),
)
.unwrap();
assert_eq!(context.inner.read().unwrap().len(), 1);
}
}

View File

@ -1,74 +0,0 @@
use std::{
cmp::Ordering,
sync::{Arc, RwLock},
};
use crate::error::rw_lock_error::RwLockError;
#[derive(Clone, Debug)]
pub struct UsageCounter(Arc<RwLock<UsageCounterInner>>);
impl UsageCounter {
pub fn new() -> UsageCounter {
UsageCounter(Arc::new(RwLock::new(UsageCounterInner {
allowances: 0,
runtime_uses: 0,
})))
}
pub fn with_counts(allowances: usize, runtime_uses: usize) -> UsageCounter {
UsageCounter(Arc::new(RwLock::new(UsageCounterInner {
allowances,
runtime_uses,
})))
}
pub fn get_counts(&self) -> Result<(usize, usize), RwLockError> {
let inner = self.0.read()?;
Ok((inner.allowances, inner.runtime_uses))
}
pub fn add_allowance(&self) -> Result<(), RwLockError> {
self.0.write()?.allowances += 1;
Ok(())
}
pub fn add_runtime_use(&self) -> Result<(), RwLockError> {
self.0.write()?.runtime_uses += 1;
Ok(())
}
}
impl Eq for UsageCounter {}
impl PartialEq for UsageCounter {
fn eq(&self, other: &Self) -> bool {
let left = self.0.read().unwrap();
let right = other.0.read().unwrap();
*left == *right
}
}
impl PartialOrd for UsageCounter {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for UsageCounter {
fn cmp(&self, other: &Self) -> Ordering {
let left = self.0.read().unwrap();
let right = other.0.read().unwrap();
left.cmp(&right)
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
struct UsageCounterInner {
pub allowances: usize,
pub runtime_uses: usize,
}

View File

@ -1,8 +0,0 @@
use crate::{Type, TypeDefinition, Value};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum ValueData {
Value(Value),
TypeHint(Type),
TypeDefinition(TypeDefinition),
}

View File

@ -1,110 +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.
mod runtime_error;
pub(crate) mod rw_lock_error;
mod syntax_error;
mod validation_error;
use colored::Colorize;
pub use runtime_error::RuntimeError;
pub use syntax_error::SyntaxError;
pub use validation_error::ValidationError;
use tree_sitter::LanguageError;
use std::fmt::{self, Formatter};
#[derive(Debug, PartialEq)]
pub enum Error {
Syntax(SyntaxError),
Validation(ValidationError),
Runtime(RuntimeError),
ParserCancelled,
Language(LanguageError),
}
impl Error {
/// Create a pretty error report with `lyneate`.
///
/// The `source` argument should be the full source code document that was
/// used to create this error.
pub fn create_report(&self, source: &str) -> String {
match self {
Error::Syntax(syntax_error) => {
let report = syntax_error.create_report(source);
format!(
"{}\n{}\n{report}",
"Syntax Error".bold().yellow().underline(),
"Dust does not recognize this syntax.".dimmed()
)
}
Error::Validation(validation_error) => {
let report = validation_error.create_report(source);
format!(
"{}\n{}\n{report}",
"Validation Error".bold().yellow().underline(),
"Dust prevented the program from running.".dimmed()
)
}
Error::Runtime(runtime_error) => {
let report = runtime_error.create_report(source);
format!(
"{}\n{}\n{report}",
"Runtime Error".bold().red().underline(),
"This error occured while the program was running.".dimmed()
)
}
Error::ParserCancelled => todo!(),
Error::Language(_) => todo!(),
}
}
}
impl From<SyntaxError> for Error {
fn from(error: SyntaxError) -> Self {
Error::Syntax(error)
}
}
impl From<ValidationError> for Error {
fn from(error: ValidationError) -> Self {
Error::Validation(error)
}
}
impl From<RuntimeError> for Error {
fn from(error: RuntimeError) -> Self {
Error::Runtime(error)
}
}
impl From<LanguageError> for Error {
fn from(error: LanguageError) -> Self {
Error::Language(error)
}
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use Error::*;
match self {
Syntax(error) => write!(f, "{error}"),
Validation(error) => write!(f, "{error}"),
Runtime(error) => write!(f, "{error}"),
ParserCancelled => write!(f, "Parsing was cancelled because the parser took too long."),
Language(_error) => write!(f, "Parser failed to load language grammar."),
}
}
}

View File

@ -1,193 +0,0 @@
use std::{
fmt::{self, Debug, Display, Formatter},
io,
num::ParseFloatError,
string::FromUtf8Error,
sync::PoisonError,
time,
};
use lyneate::Report;
use crate::{SourcePosition, Type, Value};
use super::{rw_lock_error::RwLockError, ValidationError};
#[derive(Debug, PartialEq)]
pub enum RuntimeError {
/// The 'assert' macro did not resolve successfully.
AssertEqualFailed {
left: Value,
right: Value,
},
/// The 'assert' macro did not resolve successfully.
AssertFailed {
assertion: Value,
},
/// The attempted conversion is impossible.
ConversionImpossible {
from: Type,
to: Type,
position: SourcePosition,
},
Csv(String),
Io(String),
Reqwest(String),
Json(String),
SystemTime(String),
Toml(toml::de::Error),
/// Failed to read or write a map.
///
/// See the [MapError] docs for more info.
RwLock(RwLockError),
ParseFloat(ParseFloatError),
Utf8(FromUtf8Error),
/// A built-in function was called with the wrong amount of arguments.
ExpectedBuiltInFunctionArgumentAmount {
function_name: String,
expected: usize,
actual: usize,
},
ValidationFailure(ValidationError),
}
impl RuntimeError {
pub fn create_report(&self, source: &str) -> String {
let messages = match self {
RuntimeError::AssertEqualFailed {
left: expected,
right: actual,
} => {
vec![(
0..source.len(),
format!("\"assert_equal\" failed. {} != {}", expected, actual),
(200, 0, 0),
)]
}
RuntimeError::AssertFailed { assertion: _ } => todo!(),
RuntimeError::ConversionImpossible { from, to, position } => vec![(
position.start_byte..position.end_byte,
format!("Cannot convert from {from} to {to}."),
(255, 64, 112),
)],
RuntimeError::Csv(_) => todo!(),
RuntimeError::Io(_) => todo!(),
RuntimeError::Reqwest(_) => todo!(),
RuntimeError::Json(_) => todo!(),
RuntimeError::SystemTime(_) => todo!(),
RuntimeError::Toml(_) => todo!(),
RuntimeError::RwLock(_) => todo!(),
RuntimeError::ParseFloat(_) => todo!(),
RuntimeError::Utf8(_) => todo!(),
RuntimeError::ExpectedBuiltInFunctionArgumentAmount {
function_name: _,
expected: _,
actual: _,
} => todo!(),
RuntimeError::ValidationFailure(_) => todo!(),
};
Report::new_byte_spanned(source, messages).display_str()
}
pub fn expect_argument_amount(
function_name: &str,
expected: usize,
actual: usize,
) -> Result<(), Self> {
if expected == actual {
Ok(())
} else {
Err(RuntimeError::ExpectedBuiltInFunctionArgumentAmount {
function_name: function_name.to_string(),
expected,
actual,
})
}
}
}
impl From<ValidationError> for RuntimeError {
fn from(error: ValidationError) -> Self {
RuntimeError::ValidationFailure(error)
}
}
impl From<csv::Error> for RuntimeError {
fn from(error: csv::Error) -> Self {
RuntimeError::Csv(error.to_string())
}
}
impl From<io::Error> for RuntimeError {
fn from(error: std::io::Error) -> Self {
RuntimeError::Io(error.to_string())
}
}
impl From<reqwest::Error> for RuntimeError {
fn from(error: reqwest::Error) -> Self {
RuntimeError::Reqwest(error.to_string())
}
}
impl From<serde_json::Error> for RuntimeError {
fn from(error: serde_json::Error) -> Self {
RuntimeError::Json(error.to_string())
}
}
impl From<time::SystemTimeError> for RuntimeError {
fn from(error: time::SystemTimeError) -> Self {
RuntimeError::SystemTime(error.to_string())
}
}
impl From<toml::de::Error> for RuntimeError {
fn from(error: toml::de::Error) -> Self {
RuntimeError::Toml(error)
}
}
impl From<ParseFloatError> for RuntimeError {
fn from(error: ParseFloatError) -> Self {
RuntimeError::ParseFloat(error)
}
}
impl From<FromUtf8Error> for RuntimeError {
fn from(error: FromUtf8Error) -> Self {
RuntimeError::Utf8(error)
}
}
impl From<RwLockError> for RuntimeError {
fn from(error: RwLockError) -> Self {
RuntimeError::RwLock(error)
}
}
impl<T> From<PoisonError<T>> for RuntimeError {
fn from(_: PoisonError<T>) -> Self {
RuntimeError::RwLock(RwLockError)
}
}
impl Display for RuntimeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}

View File

@ -1,30 +0,0 @@
use std::{
fmt::{self, Debug, Display, Formatter},
sync::PoisonError,
};
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct RwLockError;
impl Display for RwLockError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Map error: failed to acquire a read/write lock because another thread has panicked."
)
}
}
impl Debug for RwLockError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl<T> From<PoisonError<T>> for RwLockError {
fn from(_: PoisonError<T>) -> Self {
RwLockError
}
}

View File

@ -1,126 +0,0 @@
use std::fmt::{self, Display, Formatter};
use colored::Colorize;
use lyneate::Report;
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::SourcePosition;
use super::rw_lock_error::RwLockError;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum SyntaxError {
/// Invalid user input.
InvalidSource {
expected: String,
actual: String,
position: SourcePosition,
},
RwLock(RwLockError),
UnexpectedSyntaxNode {
expected: String,
actual: String,
position: SourcePosition,
},
}
impl SyntaxError {
pub fn create_report(&self, source: &str) -> String {
let messages = match self {
SyntaxError::InvalidSource { position, .. } => self
.to_string()
.split_inclusive(".")
.map(|message_part| {
(
position.start_byte..position.end_byte,
message_part.to_string(),
(255, 200, 100),
)
})
.collect(),
SyntaxError::RwLock(_) => todo!(),
SyntaxError::UnexpectedSyntaxNode { position, .. } => {
vec![(
position.start_byte..position.end_byte,
self.to_string(),
(255, 200, 100),
)]
}
};
Report::new_byte_spanned(source, messages).display_str()
}
pub fn expect_syntax_node(expected: &str, actual: SyntaxNode) -> Result<(), SyntaxError> {
log::trace!("Converting {} to abstract node", actual.kind());
if expected == actual.kind() {
Ok(())
} else if actual.is_error() {
Err(SyntaxError::InvalidSource {
expected: expected.to_owned(),
actual: actual.kind().to_string(),
position: SourcePosition::from(actual.range()),
})
} else {
Err(SyntaxError::UnexpectedSyntaxNode {
expected: expected.to_string(),
actual: actual.kind().to_string(),
position: SourcePosition::from(actual.range()),
})
}
}
}
impl From<RwLockError> for SyntaxError {
fn from(error: RwLockError) -> Self {
SyntaxError::RwLock(error)
}
}
impl Display for SyntaxError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
SyntaxError::InvalidSource {
expected,
actual,
position,
} => {
let actual = if actual == "ERROR" {
"unrecognized characters"
} else {
actual
};
write!(
f,
"Invalid syntax from ({}, {}) to ({}, {}). Exected {} but found {}.",
position.start_row,
position.start_column,
position.end_row,
position.end_column,
expected.bold().green(),
actual.bold().red(),
)
}
SyntaxError::RwLock(_) => todo!(),
SyntaxError::UnexpectedSyntaxNode {
expected,
actual,
position,
} => {
write!(
f,
"Interpreter Error. Tried to parse {actual} as {expected} from ({}, {}) to ({}, {}).",
position.start_row,
position.start_column,
position.end_row,
position.end_column,
)
}
}
}
}

View File

@ -1,274 +0,0 @@
use std::fmt::{self, Display, Formatter};
use colored::Colorize;
use lyneate::Report;
use serde::{Deserialize, Serialize};
use crate::{Identifier, SourcePosition, Type, TypeDefinition, Value};
use super::rw_lock_error::RwLockError;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ValidationError {
/// Two value are incompatible for addition.
CannotAdd {
left: Value,
right: Value,
position: SourcePosition,
},
/// Two value are incompatible for subtraction.
CannotSubtract {
left: Value,
right: Value,
position: SourcePosition,
},
/// Two value are incompatible for multiplication.
CannotMultiply {
left: Value,
right: Value,
position: SourcePosition,
},
/// Two value are incompatible for dividing.
CannotDivide {
left: Value,
right: Value,
position: SourcePosition,
},
/// The attempted conversion is impossible.
ConversionImpossible {
initial_type: Type,
target_type: 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,
},
ExpectedMap {
actual: Value,
},
ExpectedFunction {
actual: Value,
},
/// A string, list, map or table value was expected.
ExpectedCollection {
actual: Value,
},
/// A built-in 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,
position: SourcePosition,
},
/// A function was called with the wrong amount of arguments.
ExpectedFunctionArgumentMinimum {
minumum_expected: usize,
actual: usize,
position: SourcePosition,
},
/// Failed to read or write a map.
///
/// See the [MapError] docs for more info.
RwLock(RwLockError),
TypeCheck {
expected: Type,
actual: Type,
position: SourcePosition,
},
TypeCheckExpectedFunction {
actual: Type,
position: SourcePosition,
},
/// Failed to find a value with this key.
VariableIdentifierNotFound(Identifier),
/// Failed to find a type definition with this key.
TypeDefinitionNotFound(Identifier),
/// Failed to find an enum definition with this key.
ExpectedEnumDefintion {
actual: TypeDefinition,
},
/// Failed to find a struct definition with this key.
ExpectedStructDefintion {
actual: TypeDefinition,
},
}
impl ValidationError {
pub fn create_report(&self, source: &str) -> String {
let messages = match self {
ValidationError::CannotAdd {
left: _,
right: _,
position,
} => vec![
((
position.start_byte..position.end_byte,
format!(""),
(255, 159, 64),
)),
],
ValidationError::CannotSubtract {
left: _,
right: _,
position: _,
} => todo!(),
ValidationError::CannotMultiply {
left: _,
right: _,
position: _,
} => todo!(),
ValidationError::CannotDivide {
left: _,
right: _,
position: _,
} => todo!(),
ValidationError::ConversionImpossible {
initial_type: _,
target_type: _,
} => todo!(),
ValidationError::ExpectedString { actual: _ } => todo!(),
ValidationError::ExpectedInteger { actual: _ } => todo!(),
ValidationError::ExpectedFloat { actual: _ } => todo!(),
ValidationError::ExpectedNumber { actual: _ } => todo!(),
ValidationError::ExpectedNumberOrString { actual: _ } => todo!(),
ValidationError::ExpectedBoolean { actual: _ } => todo!(),
ValidationError::ExpectedList { actual: _ } => todo!(),
ValidationError::ExpectedMinLengthList {
minimum_len: _,
actual_len: _,
} => todo!(),
ValidationError::ExpectedFixedLenList {
expected_len: _,
actual: _,
} => todo!(),
ValidationError::ExpectedMap { actual: _ } => todo!(),
ValidationError::ExpectedFunction { actual: _ } => todo!(),
ValidationError::ExpectedCollection { actual: _ } => todo!(),
ValidationError::ExpectedBuiltInFunctionArgumentAmount {
function_name: _,
expected: _,
actual: _,
} => todo!(),
ValidationError::ExpectedFunctionArgumentAmount {
expected: _,
actual: _,
position: _,
} => todo!(),
ValidationError::ExpectedFunctionArgumentMinimum {
minumum_expected: _,
actual: _,
position: _,
} => todo!(),
ValidationError::RwLock(_) => todo!(),
ValidationError::TypeCheck {
expected,
actual,
position,
} => vec![(
position.start_byte..position.end_byte,
format!(
"Type {} is incompatible with {}.",
actual.to_string().bold().red(),
expected.to_string().bold().green()
),
(200, 200, 200),
)],
ValidationError::TypeCheckExpectedFunction {
actual: _,
position: _,
} => todo!(),
ValidationError::VariableIdentifierNotFound(_) => todo!(),
ValidationError::TypeDefinitionNotFound(_) => todo!(),
ValidationError::ExpectedEnumDefintion { actual: _ } => todo!(),
ValidationError::ExpectedStructDefintion { actual: _ } => todo!(),
};
Report::new_byte_spanned(source, messages).display_str()
}
pub fn expect_argument_amount(
function_name: &str,
expected: usize,
actual: usize,
) -> Result<(), Self> {
if expected == actual {
Ok(())
} else {
Err(ValidationError::ExpectedBuiltInFunctionArgumentAmount {
function_name: function_name.to_string(),
expected,
actual,
})
}
}
}
impl From<RwLockError> for ValidationError {
fn from(_error: RwLockError) -> Self {
ValidationError::RwLock(RwLockError)
}
}
impl Display for ValidationError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}

View File

@ -1,157 +0,0 @@
//! Tools to interpret dust source code.
//!
//! This module has three tools to run Dust code.
//!
//! - [interpret] is the simplest way to run Dust code inside of an application or library
//! - [interpret_with_context] allows you to set variables on the execution context
//! - [Interpreter] is an advanced tool that can parse, validate, run and format Dust code
//!
//! # Examples
//!
//! Run some Dust and get the result.
//!
//! ```rust
//! # use dust_lang::*;
//! assert_eq!(
//! interpret("1 + 2 + 3"),
//! Ok(Value::Integer(6))
//! );
//! ```
//!
//! Create a custom context with variables you can use in your code.
//!
//! ```rust
//! # use dust_lang::*;
//! let context = Context::default();
//!
//! context.set_value("one".into(), 1.into()).unwrap();
//! context.set_value("two".into(), 2.into()).unwrap();
//! context.set_value("three".into(), 3.into()).unwrap();
//!
//! let dust_code = "four = 4; one + two + three + four";
//!
//! assert_eq!(
//! interpret_with_context(dust_code, context),
//! Ok(Value::Integer(10))
//! );
//! ```
use tree_sitter::{Parser, Tree as SyntaxTree};
use crate::{language, AbstractTree, Context, ContextMode, Error, Format, Root, Value};
/// Interpret the given source code. Returns the value of last statement or the
/// first error encountered.
///
/// See the [module-level docs][self] for more info.
pub fn interpret(source: &str) -> Result<Value, Error> {
interpret_with_context(source, Context::new(ContextMode::RemoveGarbage))
}
/// Interpret the given source code with the given context.
///
/// See the [module-level docs][self] for more info.
pub fn interpret_with_context(source: &str, context: Context) -> Result<Value, Error> {
let mut interpreter = Interpreter::new(context);
let value = interpreter.run(source)?;
Ok(value)
}
/// A source code interpreter for the Dust language.
///
/// The interpreter's most important functions are used to parse dust source
/// code, verify it is safe and run it. They are written in a way that forces
/// them to be used safely: each step in this process contains the prior
/// steps, meaning that the same code is always used to create the syntax tree,
/// abstract tree and final evaluation. This avoids a critical logic error.
///
/// ```
/// # use dust_lang::*;
/// let context = Context::default();
/// let mut interpreter = Interpreter::new(context);
/// let result = interpreter.run("2 + 2");
///
/// assert_eq!(result, Ok(Value::Integer(4)));
/// ```
pub struct Interpreter {
parser: Parser,
context: Context,
}
impl Interpreter {
/// Create a new interpreter with the given context.
pub fn new(context: Context) -> Self {
let mut parser = Parser::new();
parser
.set_language(language())
.expect("Language version is incompatible with tree sitter version.");
parser.set_logger(Some(Box::new(|_log_type, message| {
log::trace!("{}", message)
})));
Interpreter { parser, context }
}
/// Generate a syntax tree from the source. Returns an error if the the
/// parser is cancelled for taking too long. The syntax tree may contain
/// error nodes, which represent syntax errors.
///
/// Tree sitter is designed to be run on every keystroke, so this is
/// generally a lightweight function to call.
pub fn parse(&mut self, source: &str) -> Result<SyntaxTree, Error> {
if let Some(tree) = self.parser.parse(source, None) {
Ok(tree)
} else {
Err(Error::ParserCancelled)
}
}
/// Check the source for errors and generate an abstract tree.
///
/// The order in which this function works is:
///
/// - parse the source into a syntax tree
/// - generate an abstract tree from the source and syntax tree
/// - check the abstract tree for errors
pub fn validate(&mut self, source: &str) -> Result<Root, Error> {
let syntax_tree = self.parse(source)?;
let abstract_tree = Root::from_syntax(syntax_tree.root_node(), source, &self.context)?;
abstract_tree.validate(source, &self.context)?;
Ok(abstract_tree)
}
/// Run the source, returning the final statement's value or first error.
///
/// This function [parses][Self::parse], [validates][Self::validate] and
/// [runs][Root::run] using the same source code.
pub fn run(&mut self, source: &str) -> Result<Value, Error> {
let final_value = self.validate(source)?.run(source, &self.context)?;
Ok(final_value)
}
/// Return an s-expression displaying a syntax tree of the source or an
/// error.
pub fn syntax_tree(&mut self, source: &str) -> Result<String, Error> {
Ok(self.parse(source)?.root_node().to_sexp())
}
/// Return a formatted version of the source.
pub fn format(&mut self, source: &str) -> Result<String, Error> {
let mut formatted_output = String::new();
self.validate(source)?.format(&mut formatted_output, 0);
Ok(formatted_output)
}
}
impl Default for Interpreter {
fn default() -> Self {
Interpreter::new(Context::default())
}
}

View File

@ -1,47 +0,0 @@
//! The Dust library is used to parse, format and run dust source code.
//!
//! See the [interpret] module for more information.
//!
//! You can use this library externally by calling either of the "interpret"
//! functions or by constructing your own Interpreter.
pub use crate::{
abstract_tree::*, built_in_functions::BuiltInFunction, context::*, error::Error, interpret::*,
value::*,
};
pub use tree_sitter::Node as SyntaxNode;
pub mod abstract_tree;
pub mod built_in_functions;
pub mod built_in_identifiers;
pub mod built_in_type_definitions;
pub mod built_in_types;
pub mod built_in_values;
pub mod context;
pub mod error;
pub mod interpret;
pub 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() }
}
#[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,407 +0,0 @@
//! Command line interface for the dust programming language.
use clap::{Parser, Subcommand};
use colored::Colorize;
use crossterm::event::{KeyCode, KeyModifiers};
use nu_ansi_term::{Color, Style};
use reedline::{
default_emacs_keybindings, ColumnarMenu, Completer, DefaultHinter, EditCommand, Emacs, Prompt,
Reedline, ReedlineEvent, ReedlineMenu, Signal, Span, SqliteBackedHistory, Suggestion,
};
use std::{borrow::Cow, fs::read_to_string, io::Write, path::PathBuf, process::Command};
use dust_lang::{
built_in_values::all_built_in_values, Context, ContextMode, Error, Interpreter, Value,
ValueData,
};
/// 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>,
/// 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 { path: String },
}
fn main() {
env_logger::Builder::from_env("DUST_LOG")
.format(|buffer, record| {
let args = record.args();
let log_level = record.level().to_string().bold();
let timestamp = buffer.timestamp_seconds().to_string().dimmed();
writeln!(buffer, "[{log_level} {timestamp}] {args}")
})
.init();
let args = Args::parse();
let context = Context::new(ContextMode::AllowGarbage);
if args.path.is_none() && args.command.is_none() {
let run_shell_result = run_shell(context);
match run_shell_result {
Ok(_) => {}
Err(error) => eprintln!("{error}"),
}
return;
}
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 { path }) = args.cli_command {
let source = read_to_string(path).unwrap();
let syntax_tree_sexp = interpreter.syntax_tree(&source).unwrap();
println!("{syntax_tree_sexp}");
return;
}
if let Some(CliCommand::Format) = args.cli_command {
let formatted = interpreter.format(&source).unwrap();
println!("{formatted}");
return;
}
let eval_result = interpreter.run(&source);
match eval_result {
Ok(value) => {
if !value.is_none() {
println!("{value}")
}
}
Err(error) => eprintln!("{}", error.create_report(&source)),
}
}
// struct DustHighlighter {
// context: Context,
// }
// impl DustHighlighter {
// fn new(context: Context) -> Self {
// Self { context }
// }
// }
// const HIGHLIGHT_TERMINATORS: [char; 8] = [' ', ':', '(', ')', '{', '}', '[', ']'];
// impl Highlighter for DustHighlighter {
// fn highlight(&self, line: &str, _cursor: usize) -> reedline::StyledText {
// let mut styled = StyledText::new();
// for word in line.split_inclusive(&HIGHLIGHT_TERMINATORS) {
// let mut word_is_highlighted = false;
// for key in self.context.inner().unwrap().keys() {
// if key == &word {
// styled.push((Style::new().bold(), word.to_string()));
// }
// word_is_highlighted = true;
// }
// for built_in_value in built_in_values() {
// if built_in_value.name() == word {
// styled.push((Style::new().bold(), word.to_string()));
// }
// word_is_highlighted = true;
// }
// if word_is_highlighted {
// let final_char = word.chars().last().unwrap();
// if HIGHLIGHT_TERMINATORS.contains(&final_char) {
// let mut terminator_style = Style::new();
// terminator_style.foreground = Some(Color::Cyan);
// styled.push((terminator_style, final_char.to_string()));
// }
// } else {
// styled.push((Style::new(), word.to_string()));
// }
// }
// styled
// }
// }
struct StarshipPrompt {
left: String,
right: String,
}
impl StarshipPrompt {
fn new() -> Self {
Self {
left: String::new(),
right: String::new(),
}
}
fn reload(&mut self) {
let run_starship_left = Command::new("starship").arg("prompt").output();
let run_starship_right = Command::new("starship")
.args(["prompt", "--right"])
.output();
let left_prompt = if let Ok(output) = &run_starship_left {
String::from_utf8_lossy(&output.stdout).trim().to_string()
} else {
">".to_string()
};
let right_prompt = if let Ok(output) = &run_starship_right {
String::from_utf8_lossy(&output.stdout).trim().to_string()
} else {
"".to_string()
};
self.left = left_prompt;
self.right = right_prompt;
}
}
impl Prompt for StarshipPrompt {
fn render_prompt_left(&self) -> Cow<str> {
Cow::Borrowed(&self.left)
}
fn render_prompt_right(&self) -> Cow<str> {
Cow::Borrowed(&self.right)
}
fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<str> {
Cow::Borrowed(" ")
}
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
Cow::Borrowed("")
}
fn render_prompt_history_search_indicator(
&self,
_history_search: reedline::PromptHistorySearch,
) -> Cow<str> {
Cow::Borrowed("")
}
}
pub struct DustCompleter {
context: Context,
}
impl DustCompleter {
fn new(context: Context) -> Self {
DustCompleter { context }
}
}
impl Completer for DustCompleter {
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let mut suggestions = Vec::new();
let last_word = if let Some(word) = line.rsplit([' ', ':']).next() {
word
} else {
line
};
if let Ok(path) = PathBuf::try_from(last_word) {
if let Ok(read_dir) = path.read_dir() {
for entry in read_dir {
if let Ok(entry) = entry {
let description = if let Ok(file_type) = entry.file_type() {
if file_type.is_dir() {
"directory"
} else if file_type.is_file() {
"file"
} else if file_type.is_symlink() {
"symlink"
} else {
"unknown"
}
} else {
"unknown"
};
suggestions.push(Suggestion {
value: entry.path().to_string_lossy().to_string(),
description: Some(description.to_string()),
extra: None,
span: Span::new(pos - last_word.len(), pos),
append_whitespace: false,
});
}
}
}
}
for built_in_value in all_built_in_values() {
let name = built_in_value.name();
let description = built_in_value.description();
if built_in_value.name().contains(last_word) {
suggestions.push(Suggestion {
value: name.to_string(),
description: Some(description.to_string()),
extra: None,
span: Span::new(pos - last_word.len(), pos),
append_whitespace: false,
});
}
if let Value::Map(map) = built_in_value.get() {
for (key, value) in map.inner() {
if key.contains(last_word) {
suggestions.push(Suggestion {
value: format!("{name}:{key}"),
description: Some(value.to_string()),
extra: None,
span: Span::new(pos - last_word.len(), pos),
append_whitespace: false,
});
}
}
}
}
for (key, (value_data, _counter)) in self.context.inner().unwrap().iter() {
let value = match value_data {
ValueData::Value(value) => value,
ValueData::TypeHint(_) => continue,
ValueData::TypeDefinition(_) => continue,
};
if key.contains(last_word) {
suggestions.push(Suggestion {
value: key.to_string(),
description: Some(value.to_string()),
extra: None,
span: Span::new(pos - last_word.len(), pos),
append_whitespace: false,
});
}
}
suggestions
}
}
fn run_shell(context: Context) -> Result<(), Error> {
let mut interpreter = Interpreter::new(context.clone());
let mut keybindings = default_emacs_keybindings();
keybindings.add_binding(
KeyModifiers::CONTROL,
KeyCode::Char(' '),
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
);
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Enter,
ReedlineEvent::SubmitOrNewline,
);
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]),
);
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::Multiple(vec![
ReedlineEvent::Menu("context menu".to_string()),
ReedlineEvent::MenuNext,
]),
);
let edit_mode = Box::new(Emacs::new(keybindings));
let history = Box::new(
SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None)
.expect("Error loading history."),
);
let hinter = Box::new(DefaultHinter::default().with_style(Style::new().dimmed()));
let completer = DustCompleter::new(context.clone());
let mut line_editor = Reedline::create()
.with_edit_mode(edit_mode)
.with_history(history)
.with_hinter(hinter)
.use_kitty_keyboard_enhancement(true)
.with_completer(Box::new(completer))
.with_menu(ReedlineMenu::EngineCompleter(Box::new(
ColumnarMenu::default()
.with_name("context menu")
.with_text_style(Style::new().fg(Color::White))
.with_columns(1)
.with_column_padding(10),
)));
let mut prompt = StarshipPrompt::new();
prompt.reload();
loop {
let sig = line_editor.read_line(&prompt);
match sig {
Ok(Signal::Success(buffer)) => {
if buffer.trim().is_empty() {
continue;
}
let run_result = interpreter.run(&buffer);
match run_result {
Ok(value) => {
if !value.is_none() {
println!("{value}")
}
}
Err(error) => println!("{error}"),
}
prompt.reload();
}
Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => {
println!("\nLeaving the Dust shell.");
break;
}
x => {
println!("Unknown event: {:?}", x);
}
}
}
Ok(())
}

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