Compare commits

...

565 Commits

Author SHA1 Message Date
25e3941315 Clean up examples 2024-02-19 21:19:27 -05:00
900de8ca4b Edit README; Improve bench script; Optimize 2024-02-19 20:44:26 -05:00
c4b51a1ef9 Change README outline; Fix bench script 2024-02-19 19:23:20 -05:00
939b7464c6 Use GitHub theme for example pics 2024-02-19 18:15:03 -05:00
64fb20c45e Use GitHub theme for example pic 2024-02-19 18:05:55 -05:00
e3b55092b3 Increment cargo version 2024-02-19 17:59:16 -05:00
bd4983b821 Write README; Use GitHub theme for example pics 2024-02-19 17:57:25 -05:00
a1500bf262 Write README 2024-02-19 17:22:14 -05:00
1585145ff4 Write docs; Update logging and error messages 2024-02-19 17:04:13 -05:00
fb3cd6e6da Add image to README 2024-02-19 15:35:27 -05:00
69347ad435 Update grammar and highlight queries 2024-02-19 15:26:49 -05:00
ca72fe04f1 Start new example; Start new syntax features 2024-02-19 15:04:33 -05:00
eaf26fec5e Begin reqriting README 2024-02-19 11:24:54 -05:00
0eac67eb3a Pass enum tests 2024-02-19 11:13:04 -05:00
37fd722fa6 Fix garbage collection bug 2024-02-18 16:43:47 -05:00
255843cb3b Fix type checking bugs 2024-02-18 15:52:47 -05:00
927a2cfbf9 Fix tests 2024-02-18 15:44:57 -05:00
88d05f0dc9 Clean up 2024-02-18 15:19:30 -05:00
0805b96809 Add type argument syntax 2024-02-18 15:07:53 -05:00
a5f3127bcf Fix command tests and parsing 2024-02-18 11:38:35 -05:00
01bdaa308d Fix test 2024-02-18 10:59:49 -05:00
dbf9ab0d00 Fix function test 2024-02-18 10:55:54 -05:00
2bbbfa34a4 Roll back changes to match syntax; Fix match tests 2024-02-18 10:53:34 -05:00
979335f497 Modify struct, enum and match syntax 2024-02-18 10:34:59 -05:00
ef12d97895 Fix enum tests 2024-02-18 10:19:38 -05:00
14eedc6a2a Pass enum match test 2024-02-18 08:27:59 -05:00
5559860699 Fix doc tests 2024-02-18 07:14:32 -05:00
3a63d4973d Implement specific map types 2024-02-18 06:48:42 -05:00
52027db6c3 Pass index tests; Begin implementing specific maps 2024-02-18 06:28:31 -05:00
4afc8face8 Partially fix indexes break/return statements 2024-02-18 05:37:15 -05:00
5450f00174 Fix validation bug 2024-02-18 04:57:05 -05:00
a52eadc5ad Pass function tests; Fix recursion 2024-02-18 04:48:45 -05:00
d4a5424ad5 Improve logging 2024-02-18 04:18:19 -05:00
f835f2817f Pass if_else test 2024-02-18 03:49:38 -05:00
86ce1dc3af Pass value tests 2024-02-18 01:50:15 -05:00
ca04103372 Clean up 2024-02-18 00:40:48 -05:00
dab3d2de8e Add test; Make garbage collection work 2024-02-18 00:32:03 -05:00
6c699ec900 Improve context API 2024-02-17 23:43:00 -05:00
4f5ad1e4aa Implement automatic value dropping 2024-02-17 22:02:15 -05:00
d05b5a8628 Run cargo fix 2024-02-16 20:30:58 -05:00
a46d5bb4ea Add fancy validation errors 2024-02-16 20:18:07 -05:00
1094a5662c Simplify errors and make them fancier 2024-02-16 19:57:24 -05:00
fd33f330f7 Clean up errors; Add more pretty errors 2024-02-16 18:54:00 -05:00
bda217135e Simplify errors; Make another pretty error type 2024-02-16 17:56:36 -05:00
ee4f37080e Write function for lyneate integration 2024-02-16 17:28:57 -05:00
7003c37aac Remove redundant check for syntax errors 2024-02-16 17:11:28 -05:00
a53f83f03a Begin making pretty errors with lyneate 2024-02-16 16:49:01 -05:00
4b0910a545 Implement new math interface for Value 2024-02-16 15:37:07 -05:00
c82f631524 Begin new math implementation for Value; Clean up 2024-02-16 15:07:24 -05:00
d27c98e393 Add method to inherit all context data from another 2024-02-16 13:40:55 -05:00
97640c1b9b Fix test 2024-02-16 13:31:35 -05:00
d2e0de0483 Fix function contexts and recursion 2024-02-16 13:23:58 -05:00
7eecb7b070 Fix type checking with None type 2024-02-16 11:36:25 -05:00
1819c7e646 Fix type lookup for built-in values 2024-02-16 11:32:02 -05:00
ee692b360e Implement return for root 2024-02-16 11:23:07 -05:00
8c4b2c9eef Implement block returns 2024-02-16 11:21:36 -05:00
122d81f252 Clean up docs 2024-02-16 11:04:43 -05:00
d8705c5d50 Fix docs 2024-02-16 11:00:27 -05:00
c466096c8d Fix doc tests; Add from impls for Identifier 2024-02-16 10:58:37 -05:00
9f2b0461df Add statment_kind syntax node 2024-02-16 10:55:15 -05:00
1ce2178af5 Move return syntax node to option for statements 2024-02-16 10:38:51 -05:00
172a6fa860 Remove return statement; Add StatementInner 2024-02-16 10:36:16 -05:00
51869f04b6 Add test for root node 2024-02-16 10:23:33 -05:00
50a7a7aca1 Add built-in identifiers 2024-02-15 17:04:34 -05:00
edded5043d Fix infintite loop 2024-02-15 16:30:47 -05:00
5e105177cf Simplify TypeDefinition type 2024-02-15 16:06:47 -05:00
ec074177d5 Clean up new API 2024-02-15 16:02:27 -05:00
c2ba519240 Overhaul built-ins and identifiers 2024-02-15 15:20:29 -05:00
91e94a5adc Clean up 2024-02-15 10:37:10 -05:00
e7f5d66297 Implement custom and built-in types 2024-02-15 10:33:25 -05:00
e1c3e8bc0d Fix test 2024-02-15 07:12:10 -05:00
a6e52e4ee6 Implement matching for enums 2024-02-15 07:04:38 -05:00
85cb641af8 Write docs 2024-02-15 04:18:30 -05:00
933ab3900b Write docs 2024-02-15 04:16:34 -05:00
d9f065fbb6 Write docs 2024-02-15 03:57:13 -05:00
540f59e6d8 Modify enum variant syntax 2024-02-15 02:22:04 -05:00
b8f2fe7eb4 Implement Result type 2024-02-15 02:08:42 -05:00
ed1f139595 Remove option value type and built-in value syntax 2024-02-15 02:02:48 -05:00
4c68bc0260 Add built-in option definition 2024-02-15 01:51:05 -05:00
d3601be44c Fix test 2024-02-15 00:58:14 -05:00
fc3dfc0e03 Implement structs; Modify tests 2024-02-15 00:53:43 -05:00
97319d28b2 Implement custom types; Add test 2024-02-14 22:46:40 -05:00
89a4c09307 Implement basic enum instantiation 2024-02-14 22:38:45 -05:00
b8c54ea8bd Begin implementing enums 2024-02-14 20:53:42 -05:00
4323c50d32 Rework structs; Add enums; Remove yield statements 2024-02-14 20:23:33 -05:00
390d1aa504 Revert "Remove structure from map types"
This reverts commit 5e685d6641.
2024-02-14 19:15:47 -05:00
5e685d6641 Remove structure from map types 2024-02-14 19:07:34 -05:00
c2f0ec28ba Clean up 2024-02-14 18:57:24 -05:00
a23688803c Add test; Pass test by fixing type validation bug 2024-02-14 18:46:05 -05:00
85419c47be Convert maps to structures for advanced type checks 2024-02-13 12:04:02 -05:00
1f5dacad7d Add type check for type conversion; Add test 2024-02-13 10:49:49 -05:00
52493a0b73 Complete string to list conversion with as 2024-02-13 10:36:43 -05:00
18508fa217 Begin As implementation for AbstractTree; Add tests 2024-02-13 10:26:26 -05:00
e9bc16af0d Add syntax for as expressions 2024-02-13 09:19:23 -05:00
3f4c4ff464 Fix type checking bug 2024-02-13 08:10:34 -05:00
3c72e4f988 Rework built-in function arguments; Fix context bug 2024-02-12 18:55:54 -05:00
41a268389c Fix function recursion 2024-02-12 18:15:49 -05:00
bbab728ce9 Add context to function call nodes 2024-02-12 17:55:45 -05:00
daf78919da Move block contexts to loops and function 2024-02-12 16:51:06 -05:00
1e665a6f13 Fix context error 2024-02-12 15:48:43 -05:00
b7e0828ced Make maps multi-threaded again 2024-02-12 15:07:41 -05:00
924b388f2c Fix bug with loop contexts 2024-02-12 14:19:07 -05:00
d243c030e8 Fix Block Debug formatting 2024-02-11 15:26:09 -05:00
b1266df835 Fix function contexts 2024-02-11 14:18:53 -05:00
b5b317df95 Implement block contexts 2024-02-11 14:10:11 -05:00
f2049225fe Implement new context principles 2024-02-11 13:54:27 -05:00
90c0304af5 Implement context 2024-02-10 20:50:49 -05:00
d997bbd08a Continue implementing context 2024-02-10 19:31:47 -05:00
ddd5912248 Begin implementing new Context type 2024-02-10 18:29:11 -05:00
4479f340d7 Add From implementation for RwLockError 2024-02-09 14:23:41 -05:00
9ef82df3a7 Fix type setting bugs 2024-01-31 21:21:42 -05:00
e486413aca Fix type setting bugs; Rename function 2024-01-31 20:52:34 -05:00
9a465cb42d Continue error overhaul 2024-01-31 19:35:27 -05:00
88ca9c5ea4 Implement error overhaul 2024-01-31 19:07:18 -05:00
7f849f13a3 Begin error overhaul 2024-01-31 13:51:48 -05:00
cd4296c941 Add error for float parse failures 2024-01-31 12:18:30 -05:00
699c37d860 Add error for 2024-01-31 12:17:13 -05:00
820c863f7f Overhaul integer, float and range parsing 2024-01-31 12:10:32 -05:00
65afe9dd29 Clean up build script 2024-01-31 10:19:26 -05:00
9c87d70659 Clean up example tests 2024-01-31 10:18:25 -05:00
c5241bb0af Write docs 2024-01-30 18:19:05 -05:00
4cbfdde4a3 Write docs; Refine library API 2024-01-30 18:13:30 -05:00
7c9be2151d Write docs; Add missing docs warning 2024-01-30 14:48:38 -05:00
0ba3ed51e0 Clean up examples 2024-01-30 14:31:22 -05:00
ba0d154962 Write tests; Clean up 2024-01-30 14:11:18 -05:00
93e2a24a25 Add test; Rename "string" built-in to "str" 2024-01-30 13:57:30 -05:00
f85fed941a Fix serde errors 2024-01-30 13:43:18 -05:00
9762112b3c Clean up dependencies 2024-01-30 12:29:55 -05:00
4e8799e750 Clean up depen 2024-01-30 12:29:30 -05:00
7b101c944a Add tests 2024-01-30 11:48:29 -05:00
61e7079a00 Fix ranges 2024-01-30 09:46:49 -05:00
e756c7eac1 Write docs 2024-01-30 07:21:54 -05:00
36dd23a7ab Write docs 2024-01-30 06:22:07 -05:00
b182e3b945 Format document 2024-01-30 03:03:40 -05:00
281732924c Write docs 2024-01-30 01:38:19 -05:00
5adfa716fd Outline language reference 2024-01-30 00:48:50 -05:00
ce0ca17beb Write tests 2024-01-30 00:24:13 -05:00
8a5efa054b Fix example 2024-01-30 00:11:45 -05:00
2588715f98 Clean up examples 2024-01-30 00:01:16 -05:00
7f30097d45 Improve tests; Clean up 2024-01-29 23:57:13 -05:00
da11431edf Edit README 2024-01-29 23:21:54 -05:00
fb1399ab0d Write docs; Add fs built-in functions 2024-01-29 23:18:09 -05:00
ae278e42ad Edit docs 2024-01-29 21:32:56 -05:00
df34c10d47 Add WIP message to language.md 2024-01-29 21:27:56 -05:00
7f9d43e377 Write docs 2024-01-29 21:24:19 -05:00
ed6fad9843 Begin restructuring the docs; Write some docs 2024-01-29 20:28:05 -05:00
0752ebedf2 Refine command implementation; Add tests 2024-01-29 18:19:06 -05:00
df5cf93e58 Allow empty maps; Write tests 2024-01-29 17:36:21 -05:00
bd6ca6a6c1 Fix function context bug 2024-01-28 18:42:27 -05:00
34173c261b Fix recursion 2024-01-28 18:07:28 -05:00
6a9ce76007 Clean up 2024-01-28 17:46:15 -05:00
433306b3e1 Add path completion to shell 2024-01-28 13:45:08 -05:00
80428a3dd7 Add table output 2024-01-28 13:30:57 -05:00
7b9913309d Clean up 2024-01-28 12:14:43 -05:00
f67ef7f26f Add variable to suggestions 2024-01-28 12:07:25 -05:00
3bb9090afa Improve shell ergonomics 2024-01-28 12:04:33 -05:00
a0b38d329b Improve shell 2024-01-26 21:03:54 -05:00
0eee9f0936 Integrate starship 2024-01-26 18:28:37 -05:00
3d21196768 Add JSON functions; Modify CLI prompt 2024-01-26 17:14:57 -05:00
f6a1e641c9 Repair command implementation 2024-01-26 15:23:24 -05:00
c2fc3362c8 Fix command syntax; Modify shell prompt 2024-01-26 15:10:46 -05:00
54790bc0db Fix stack overflow; Clean up 2024-01-25 09:08:39 -05:00
9a65afa083 Fix quoted string parsing for commands 2024-01-25 08:57:55 -05:00
70ad08128c Add command logic 2024-01-25 08:43:21 -05:00
5bdb9f116f Add command and pipe syntax 2024-01-25 08:27:24 -05:00
fe1f007692 Add command syntax; Write docs 2024-01-25 07:10:45 -05:00
58bbbb749e Revert "Add loading binaries as functions"
This reverts commit 8f3d36fc8d.
2024-01-25 04:45:25 -05:00
8f3d36fc8d Add loading binaries as functions 2024-01-25 04:19:45 -05:00
784e4b309d Implement reedline 2024-01-25 02:17:45 -05:00
ac29f0210f Implement reedline crate with highlighting 2024-01-25 01:28:22 -05:00
12f82f7bfd Implement recursion using the "self" variable 2024-01-24 21:40:46 -05:00
52c6c3a507 Implement Range value 2024-01-24 20:11:34 -05:00
f2e7badf4b Show completion hints for built-in values 2024-01-24 19:41:47 -05:00
848a0cadb6 Clean up 2024-01-24 18:59:27 -05:00
363ecf444b Fix main function and bench script 2024-01-24 18:57:36 -05:00
270c2fd1dc Begin implementing range value 2024-01-23 17:35:12 -05:00
9299131024 Run clippy and prettier 2024-01-23 17:10:52 -05:00
e4bd0a51d6 Continue implementing type definition type 2024-01-23 17:03:35 -05:00
6c997c837d Begin adding "new" expressions 2024-01-23 16:06:52 -05:00
42ec57bf82 Refine implementation 2024-01-23 15:46:20 -05:00
bdef5db051 Begin implementing and testing type defintions 2024-01-23 15:35:26 -05:00
6c4efadb10 Add type definitions as a first-class value 2024-01-23 15:20:19 -05:00
ed6e4cfd1a Rename 'type defintion' to 'type specification' 2024-01-23 14:35:57 -05:00
8224f7fe3c Revise tests 2024-01-23 14:08:56 -05:00
4e61c6dd6e Add TODO item 2024-01-23 13:53:40 -05:00
7d0cce6fcb Fix function calls inside of functions 2024-01-22 21:41:18 -05:00
44dc6db377 Clean up 2024-01-22 20:48:52 -05:00
fc7cffcb70 Improve Map type's display implementation 2024-01-22 20:47:44 -05:00
1ae9dd67a7 Clean up 2024-01-22 20:45:46 -05:00
7642b23553 Remove useless function call "name" argument 2024-01-17 15:12:37 -05:00
f04adfc661 Use type definitions for type checks 2024-01-17 14:50:39 -05:00
4a42f51580 Reimplement type setting for type check system 2024-01-17 14:45:34 -05:00
5fada12165 Clean up 2024-01-17 12:48:51 -05:00
74dd455ae4 Refine Map interface for stability 2024-01-17 10:21:00 -05:00
c736e3be8f Move syntax tree CLI flag to a command 2024-01-13 13:39:30 -05:00
3e1765e810 Add simple logging 2024-01-13 13:30:50 -05:00
9538caf330 Clean up 2024-01-10 15:07:27 -05:00
c75538c064 Clean up 2024-01-10 15:03:52 -05:00
c4908dc00d Implement custom type 2024-01-10 14:25:35 -05:00
b7db177bd2 Fix variable context bugs 2024-01-09 20:38:40 -05:00
d8850b2d3c Fix assignment type check bug 2024-01-08 10:25:01 -05:00
e6acb8cbb9 Implement function purity 2024-01-06 11:17:08 -05:00
f89e94cc33 Pass format tests 2024-01-06 10:40:25 -05:00
7d7b96d76f Improve formatting; Remove string mutability 2024-01-06 10:13:47 -05:00
14d967b659 Improve formatting 2024-01-06 08:53:31 -05:00
8737175df0 Implement formatting 2024-01-06 08:11:09 -05:00
731bf1cb98 Write formatting tests; Improve formatting output 2024-01-06 05:29:38 -05:00
a52b17930e Implement basic formatting 2024-01-06 05:00:36 -05:00
9cee46cfe5 Implement structured maps 2024-01-06 03:47:54 -05:00
dcb0133a4b Reorganize tests 2024-01-06 02:26:51 -05:00
9417d0d160 Write tests 2024-01-06 02:18:30 -05:00
a4013fa26d Remove gui bin 2024-01-06 01:38:55 -05:00
86d2e6aaf4 Implement structure value 2024-01-06 01:05:13 -05:00
3cbd3bbf3c Improve error interface 2024-01-05 23:33:51 -05:00
45384fb394 Adjust test 2024-01-05 22:40:58 -05:00
bb53331b65 Add new means of reporting type check errors 2024-01-05 22:26:37 -05:00
d4487117eb Revert changes to map type 2024-01-05 20:02:29 -05:00
ff6cc707d2 Implement new type checking 2024-01-03 19:58:07 -05:00
4e861620ce Fix identifier bug 2024-01-03 15:36:03 -05:00
ab149ce010 Fix CLI error and example 2024-01-03 15:25:53 -05:00
d1ac97507b Merge branch 'main' of ssh://git.jeffa.io:22022/jeff/dust 2024-01-03 11:14:52 -05:00
8e95b4af75 Fix README 2024-01-03 15:05:46 +00:00
a9b73ce82b Increment cargo version 2024-01-01 13:41:13 -05:00
f5306be3dd Finish adding string functions 2024-01-01 13:39:29 -05:00
96f323979e Continue adding string functions 2024-01-01 13:26:56 -05:00
5aa65af3ad Implement more string functions 2024-01-01 13:12:41 -05:00
20e0ec0e3d Change string type to be passed by reference 2024-01-01 12:39:03 -05:00
525c87bf0f Implement string functions 2024-01-01 10:51:49 -05:00
ab0dacd0f2 Continue adding string functions 2024-01-01 10:31:53 -05:00
a8ed942c13 Improve list display 2024-01-01 10:11:45 -05:00
799875a55d Continue adding string functions 2024-01-01 09:58:18 -05:00
ab4c8922b1 Implement string functions 2024-01-01 09:39:59 -05:00
c2d919957e Begin adding string functions 2024-01-01 08:52:25 -05:00
976cb7de3f Implement new built-in values 2024-01-01 07:46:47 -05:00
f136cafb41 Implement collection type 2024-01-01 05:20:11 -05:00
ae66e2a211 Implement new built-in values 2024-01-01 04:59:27 -05:00
2f0ec91c08 Add index expressions to fix parsing bug 2023-12-31 23:38:09 -05:00
346ff1c0da Improve index parsing 2023-12-31 21:46:45 -05:00
5be7b9b73a Begin new std implementation 2023-12-31 21:24:46 -05:00
49159d379f Remove experimental std directory 2023-12-31 20:14:44 -05:00
415c9863e6 Improve GUI 2023-12-31 19:46:23 -05:00
b72d11b500 Set default binary for cargo 2023-12-31 18:22:27 -05:00
128ddc385c Add test for match example 2023-12-31 18:14:00 -05:00
32127a6cda Fix type check bug 2023-12-31 18:10:42 -05:00
d3a9fb3a0e Add function return type check; Clean up 2023-12-31 16:46:21 -05:00
0bec02344e Merge branch 'main' into gui 2023-12-31 15:31:16 -05:00
83a7007446 Improve value multiplication and division 2023-12-31 15:30:13 -05:00
dbe52e1ad7 Fix type check bug 2023-12-31 14:17:43 -05:00
a39d879c40 Implement return statements at root level 2023-12-31 14:09:03 -05:00
a3a2df552d Implement return statements 2023-12-31 14:04:10 -05:00
3ae7456758 Manually catch up to main branch 2023-12-31 11:54:19 -05:00
318825d1b1 Merge branch 'main' into gui 2023-12-31 11:49:58 -05:00
0fa0a026f8 Improve error output; Add syntax error check 2023-12-31 11:46:56 -05:00
2fee80843d Improve identifier regex 2023-12-31 11:03:33 -05:00
12e00bfc42 Remove return and use statements; Clean up 2023-12-31 09:47:20 -05:00
bf79526764 Use new syntax for None values 2023-12-31 09:41:00 -05:00
f78070ca47 Merge branch 'main' into gui 2023-12-31 09:15:55 -05:00
f4c2bfa657 Use new syntax for None values 2023-12-31 09:14:43 -05:00
9323548375 Merge branch 'main' into gui 2023-12-31 08:40:31 -05:00
d2def28751 Add tests covering for loops 2023-12-31 08:36:39 -05:00
9a35dc5ec9 Fix parsing bug; Extend GUI 2023-12-30 12:02:58 -05:00
6cb84a664a Remove TUI 2023-12-30 10:56:56 -05:00
fc1d1c9ee9 Merge branch 'main' into gui 2023-12-30 10:45:38 -05:00
02b30d3730 Improve function Display implementation 2023-12-30 10:23:00 -05:00
9d94cb9af4 Restart TUI 2023-12-30 09:29:33 -05:00
7ea6283650 Begin writing GUI 2023-12-30 02:04:39 -05:00
dec9e70e4f Fix bench script; Update highlight queries 2023-12-30 00:39:49 -05:00
49a219f764 Move TUI project; Increment cargo version 2023-12-29 23:57:09 -05:00
e57d3f6e60 Write README 2023-12-29 22:58:05 -05:00
507082209f Apply clippy suggestions and fixes 2023-12-29 22:39:50 -05:00
42e0ef366f Clean up 2023-12-29 22:28:10 -05:00
1d26b0b418 Clean up 2023-12-29 22:26:37 -05:00
f486d87976 Allow wrapping expressions in parentheses 2023-12-29 21:53:26 -05:00
f3921ba87c Revise function and yield syntax 2023-12-29 21:15:03 -05:00
55de33ceb7 Revise function syntax 2023-12-29 20:14:03 -05:00
e10429e1e9 Extend function expression to values and indexes 2023-12-29 19:22:41 -05:00
17fa708739 Add FunctionExpression to fix syntax bug 2023-12-29 18:59:15 -05:00
4a8242621d Increment cargo version 2023-12-29 16:28:36 -05:00
43ee989eec Improve Interpreter API 2023-12-29 16:27:13 -05:00
3c729bea6e Improve Map interface; Clean up 2023-12-29 14:52:51 -05:00
37a9a37c72 Fix type check bug 2023-12-29 14:35:52 -05:00
93ba04d35c Increment cargo version 2023-12-29 14:02:56 -05:00
6be9204123 Improve Intepreter API for shell use 2023-12-29 14:01:54 -05:00
049c28795b Fix type check bug 2023-12-29 18:29:16 +00:00
efefd704f7 Write README; Fix bench script 2023-12-26 23:30:33 -05:00
226c1e806f Increment Cargo version 2023-12-26 20:23:06 -05:00
34db948c6e Implement option type 2023-12-26 20:05:19 -05:00
9dfaf1420c Implement option value 2023-12-26 19:33:19 -05:00
20a6e707c5 Clean up 2023-12-26 18:36:10 -05:00
a27b33dd36 Fix type check error 2023-12-26 17:52:44 -05:00
2bcb5f59f7 Implement Option and None value types 2023-12-26 17:19:12 -05:00
8369477346 Fix function context error 2023-12-22 16:12:41 -05:00
364ce9cb33 Fix async/function bug; Remove Table; Add packages 2023-12-22 15:02:22 -05:00
afa937a697 Add type checks for maps 2023-12-20 18:36:42 -05:00
d2dcc665bb Add type definitions to maps 2023-12-20 18:29:18 -05:00
8a7f05acda Add bench script 2023-12-18 15:35:04 -05:00
1946b24531 Increment cargo version 2023-12-17 19:23:48 -05:00
3bfedec5d6 Refine type checking and function calling 2023-12-17 19:06:36 -05:00
70f0c6b887 Fix list type checking 2023-12-16 21:26:07 -05:00
500a579910 Update grammar; Fix built-in function type checks 2023-12-16 21:15:36 -05:00
3096cf5959 Fix function parsing 2023-12-16 20:42:19 -05:00
6cb2df55f7 Clean up and improve tests 2023-12-16 20:17:38 -05:00
ece75d7b9c Improve testing with an error method 2023-12-16 19:47:23 -05:00
9a4196fb2a Fix list add-assign type checking 2023-12-16 19:40:14 -05:00
3b7e75c41c Add sh function; Improve type check error output 2023-12-15 17:54:11 -05:00
5beda4695d Update README 2023-12-15 17:33:48 -05:00
fa7fb57600 Add new tests for type checking 2023-12-15 17:27:29 -05:00
ce4d366bab Implement type checking for functions and indexes 2023-12-13 15:47:41 -05:00
b91e23fef3 Finish function syntax 2023-12-12 18:21:16 -05:00
e1a7c3ff72 Improve highlight queries 2023-12-11 11:17:37 -05:00
06da345333 Implement match logic and syntax 2023-12-11 10:19:45 -05:00
99338dedc5 Fix map deserialization; Clean up; Improve errors 2023-12-10 13:47:05 -05:00
744290f0d4 Write README 2023-12-09 19:05:36 -05:00
aada1c72d6 Fix list type checking 2023-12-09 18:50:17 -05:00
0452243c08 Expand type checking 2023-12-09 17:55:47 -05:00
833a830b30 Expand type checking to map contexts 2023-12-09 17:15:41 -05:00
0fb787bb72 Write docs 2023-12-06 14:13:22 -05:00
b064d23719 Implement type system; Modify use; Write docs 2023-12-06 13:48:38 -05:00
984b66b0aa Implement new function syntax 2023-12-05 17:40:22 -05:00
ed4dd6a819 Improve internal API 2023-12-05 17:08:22 -05:00
d6c679c0b3 Implement new function syntax 2023-12-05 16:42:11 -05:00
7f1b53aabe Continue implementing type checks 2023-12-02 02:34:23 -05:00
9181c319b8 Write README 2023-12-02 00:16:00 -05:00
62959be020 Add types for built-in functions 2023-12-01 23:50:26 -05:00
9fd02a2118 Continue fixing tests and implementing types 2023-12-01 23:47:15 -05:00
07b1efd369 Make fixes for function changes 2023-12-01 23:20:33 -05:00
31979364eb Remove function_declaration module 2023-12-01 22:54:25 -05:00
ae05e942f2 Add types for built-in functions 2023-12-01 22:16:50 -05:00
50abe9765a Implement random_float and random_boolean 2023-11-30 11:07:52 -05:00
a0c648b33a Simplifiy syntax for function calls 2023-11-30 11:05:09 -05:00
99dd189328 Implement from_json and to_json 2023-11-30 10:10:03 -05:00
9b693ba41b Implement assert_equal 2023-11-30 10:00:40 -05:00
21099a4092 Clean up 2023-11-30 09:48:56 -05:00
e90a4d7353 Clean up 2023-11-30 09:30:58 -05:00
0ee26dcf0d Implement function declarations 2023-11-30 09:30:25 -05:00
57b06df9c2 Clean up 2023-11-30 05:40:39 -05:00
3dc78a7066 Implement runtime type checking 2023-11-30 02:09:55 -05:00
8826d08392 Implement list type checks 2023-11-30 00:57:15 -05:00
b6422a438b Implement parsing and runtime checks 2023-11-29 22:54:46 -05:00
5f960021b1 Implement type equality 2023-11-29 22:02:55 -05:00
081d349783 Continue type check implementation 2023-11-29 20:59:58 -05:00
bc2615a1ed Begin changes for new type definitions 2023-11-29 19:23:42 -05:00
5ffb797b5f Clean up 2023-11-28 19:36:10 -05:00
fc898e28e2 Begin converting to new built-in API 2023-11-28 19:18:04 -05:00
a1f3dcb107 Clean up 2023-11-28 18:28:07 -05:00
b46dfc5791 Create new built-in function API 2023-11-28 17:54:17 -05:00
5d68b7b156 Do not ignore generated tree sitter files 2023-11-28 13:53:33 -05:00
07e598e766 Increment cargo version 2023-11-28 13:45:45 -05:00
40a42cf5e9 Clean up 2023-11-28 12:24:17 -05:00
34191e95f9 Ignore generated tree sitter files 2023-11-28 12:19:19 -05:00
c412836487 Change map syntax 2023-11-28 11:01:38 -05:00
43d46cb289 Add type check error; Add parameter syntax 2023-11-28 10:29:42 -05:00
2bd4ccb40d Implement type checking 2023-11-27 17:53:12 -05:00
25852efcd6 Continue type check implementation 2023-11-27 15:02:08 -05:00
f0635bf330 Improve error output 2023-11-27 10:32:25 -05:00
0646d010c5 Add type checking 2023-11-27 10:27:44 -05:00
ab769b4b2a Add any type 2023-11-27 09:36:17 -05:00
8db95b237c Begin new type checking system 2023-11-21 13:42:47 -05:00
60ba9853ed Clean up; Fix read function 2023-11-20 12:49:20 -05:00
9b53485519 Write README 2023-11-17 20:46:18 -05:00
83390b53a7 Implement use statement; Rework value generation 2023-11-17 20:10:07 -05:00
97447d6d8b Fix examples; Clean up 2023-11-16 02:57:50 -05:00
c4dd68c293 Pass tests 2023-11-16 02:11:47 -05:00
ff836b4f0a Start std library; Write README; Change map syntax 2023-11-16 01:59:48 -05:00
ee87d322db Add benchmarks to README 2023-11-15 22:33:58 -05:00
7445ebec34 Clean up 2023-11-15 21:52:49 -05:00
a21aa5e37b Fix find loops and index syntax 2023-11-15 21:35:40 -05:00
2876f50822 Implement yield logic; Reform yield syntax 2023-11-15 21:13:14 -05:00
274891d96e Add yield syntax 2023-11-15 20:46:45 -05:00
1d6b49ba25 Add yield syntax 2023-11-14 22:26:32 -05:00
60013f34da Set edition to 2021 2023-11-14 22:01:17 -05:00
d4aac2c729 Implement parallel find loop 2023-11-14 21:24:47 -05:00
98ea049229 Complete index assignment 2023-11-14 21:03:52 -05:00
a804a85b1f Begin adding index assignment 2023-11-14 20:41:57 -05:00
0b14ab5832 Add index assignment syntax 2023-11-14 20:00:57 -05:00
9ec06997c5 Function call indexing works 2023-11-14 19:38:19 -05:00
781d475794 Add tree option to CLI args 2023-11-14 19:37:19 -05:00
364fed810b Improve error handling 2023-11-14 19:31:04 -05:00
ec1f059d16 Improve function call syntax 2023-11-14 19:22:26 -05:00
567c20a5bc Improve index syntax 2023-11-14 18:56:44 -05:00
c86f61e1cd Modify index syntax; Clean up 2023-11-13 15:41:55 -05:00
e9e4e92f68 Remove yield 2023-11-12 13:20:41 -05:00
f0fb16607c Simplify grammar structure 2023-11-10 20:44:03 -05:00
71c169a1cf Add yield to grammar 2023-11-10 18:44:56 -05:00
020ebd8833 Clean up 2023-11-10 16:24:19 -05:00
9828d9c643 Clean up 2023-11-06 23:20:59 -05:00
6006de13e5 Clean up 2023-11-06 19:16:49 -05:00
f0cb4631ab Begin adding yield; Clean up 2023-11-06 19:10:12 -05:00
2d85a3ee2b Improve soundness of Map type 2023-11-05 13:54:29 -05:00
a3db9cb9f2 Clean up; Complete async 2023-11-04 06:02:27 -04:00
cedf0a8c65 Implement async statment 2023-11-03 23:42:10 -04:00
8ca97300d3 Clean up 2023-11-03 18:04:45 -04:00
d1b116cc35 Begin completing syntax revision 2023-10-31 18:18:39 -04:00
ea4ffb492c Revise syntax 2023-10-31 16:25:13 -04:00
df7cd0e972 Revise language syntax 2023-10-31 15:21:13 -04:00
42f0834d80 Continue syntax revision 2023-10-31 13:04:22 -04:00
e582f3cad3 Continue syntax revision 2023-10-31 09:31:10 -04:00
02cded4af4 Continue syntax revision 2023-10-31 06:21:21 -04:00
47f50931d9 Continue syntax revision 2023-10-31 05:51:37 -04:00
0c37e5e3a6 Continue syntax revisions 2023-10-31 03:17:58 -04:00
42339e1171 Begin syntax revision 2023-10-31 01:09:29 -04:00
9c565e810e Use async function to read file input; Clean up 2023-10-30 18:30:41 -04:00
3bb4d3c7ec Increment cargo version 2023-10-30 17:11:52 -04:00
9f571a0bfb Implement list, map and string indexing 2023-10-30 17:11:06 -04:00
4eabd61ea8 Increment cargo version 2023-10-30 15:49:09 -04:00
b6b427b2f2 Clean up 2023-10-30 15:48:43 -04:00
c721164d99 Remove unwrap errors 2023-10-30 15:42:06 -04:00
f33eef9c5a Prepare for new version 2023-10-29 19:31:06 -04:00
c2a5f5e972 Prepare for new version 2023-10-28 10:28:43 -04:00
674d3c91f9 Fix transform loops 2023-10-26 22:35:59 -04:00
335fc2e454 Clean up 2023-10-26 21:23:39 -04:00
25778cc480 Add reference counting for list values 2023-10-26 18:03:59 -04:00
a5390c5150 Add reference counting for map values 2023-10-26 16:00:06 -04:00
86499367fc Rename VariableMap to Map 2023-10-25 16:44:50 -04:00
3e45c198aa Clean up tests and docs 2023-10-25 16:41:51 -04:00
51e93e5992 Clean up 2023-10-25 15:12:57 -04:00
ec6df112b7 Improve list errors; Begin use statement 2023-10-25 14:46:58 -04:00
7b403ea92c Improve example 2023-10-25 14:46:19 -04:00
745e56e4b5 Expand and improve examples 2023-10-25 14:41:23 -04:00
8a38790f57 Write examples and highlight queries 2023-10-23 20:45:47 -04:00
72af839102 Clean up 2023-10-23 17:36:11 -04:00
8348b6ce85 Add example; Fix add assignment 2023-10-23 16:12:43 -04:00
9dfe5689e4 Implement add assignment for strings 2023-10-23 15:25:22 -04:00
7342b383dc Fix dot notation; Add corpus tests; Clean up 2023-10-23 15:01:00 -04:00
c0bafe577e Implement string concatenation 2023-10-22 17:04:14 -04:00
ecdac6fab5 Implement download tool 2023-10-22 16:50:09 -04:00
bd83ccd301 Implement workdir tool 2023-10-22 16:41:37 -04:00
b327068e17 Implement columns and rows tools 2023-10-22 16:33:27 -04:00
c6ef9ad57f Implement colu 2023-10-22 16:32:55 -04:00
d527b3c9c0 Fix README example 2023-10-22 16:17:00 -04:00
4f7e1c48ca Update README 2023-10-22 15:52:33 -04:00
88747e6fe5 Clean up 2023-10-22 15:24:10 -04:00
f9a4496473 Increment cargo version 2023-10-22 15:05:43 -04:00
b08a1c5f9a Fix errors 2023-10-22 14:56:41 -04:00
b5e659f09f Implement random tools 2023-10-22 14:48:34 -04:00
3d143cc64c Add debug statement 2023-10-22 14:33:08 -04:00
0cbf4e8385 Clean up corpus test 2023-10-22 14:29:07 -04:00
b64ebbbd18 Clean up examples; Implement type tool 2023-10-22 14:27:18 -04:00
fa6af4026b Implementing insert 2023-10-22 14:10:16 -04:00
75f16a3afe Fix README example; Begin implementing insert 2023-10-22 13:55:56 -04:00
7fef56f90d Implement select with where clauses 2023-10-22 13:07:40 -04:00
2066cf7256 Clean up 2023-10-22 12:37:15 -04:00
2e3f9ebee2 Implement to_string tool 2023-10-21 18:31:46 -04:00
13e10cd4a8 Implement command tools 2023-10-21 18:29:46 -04:00
0398988074 Implement json tools 2023-10-21 17:59:19 -04:00
010cbf2447 Implement write tool 2023-10-21 17:51:04 -04:00
3355310d9d Implement remove tool; Remove trash tool 2023-10-21 17:48:14 -04:00
003a082074 Implement read tool 2023-10-21 17:46:08 -04:00
059675bc16 Implement read tool 2023-10-21 17:28:49 -04:00
c176b8882b Implement move tool 2023-10-21 17:14:44 -04:00
5d16f9cfd4 Implement metadata tool 2023-10-21 17:02:23 -04:00
7e2d280921 Implement tools 2023-10-21 16:46:18 -04:00
6f3e62e555 Implement tools 2023-10-21 16:38:20 -04:00
00d40f4525 Add debug statement 2023-10-21 14:27:32 -04:00
f820cc7867 Implement tools 2023-10-21 14:11:07 -04:00
b123b90298 Implement output tool 2023-10-21 13:47:08 -04:00
02b237b11b Add builtin function syntax 2023-10-21 13:19:01 -04:00
3b82c6d900 Replace tools 2023-10-21 13:04:17 -04:00
eefb6e5fb6 Improve error display 2023-10-19 13:52:26 -04:00
d4ad3c8ddd Update tests; Set bin name; Increment version 2023-10-18 22:27:57 -04:00
33bacfc100 Update implementation tests 2023-10-18 22:05:16 -04:00
36a6f5f548 Add remove loop logic; Update examples 2023-10-18 21:50:45 -04:00
3a5987a3d7 Clean up 2023-10-18 19:44:27 -04:00
c7e0cdb571 Correct crate name 2023-10-18 19:38:43 -04:00
834b6743eb Implement table values 2023-10-18 19:28:20 -04:00
7f049f86c5 Remove excess Cargo files 2023-10-18 18:34:55 -04:00
7e322a9bdf Clean up 2023-10-18 18:31:49 -04:00
13289e5a59 Implement find loop logic 2023-10-18 18:30:37 -04:00
b9f47dc77f Implement find syntax 2023-10-18 18:18:41 -04:00
810c7bb6f6 Fix path 2023-10-18 18:18:12 -04:00
d3b8f524d4 Add tree sitter as directory without module 2023-10-18 17:52:56 -04:00
c8b90e36b1 Remove submodule 2023-10-18 17:51:22 -04:00
a5a87d18e5 Correct map serialization 2023-10-18 17:42:36 -04:00
10af8e4af2 Change project structure 2023-10-17 18:25:53 -04:00
7a85cf6f86 Implement filter loop 2023-10-17 17:52:41 -04:00
f4600e858c Increment cargo version 2023-10-17 17:20:23 -04:00
8a544001fd Increment cargo version 2023-10-17 17:18:28 -04:00
c8890b2e9b Increment cargo version 2023-10-17 17:18:05 -04:00
a13b618a28 Increment cargo version 2023-10-17 17:17:51 -04:00
c3d7cfd530 Increment cargo version 2023-10-17 17:17:24 -04:00
fc10bca6ef Increment cargo version 2023-10-17 17:17:05 -04:00
3cd3fdc07d Increment cargo version 2023-10-17 17:15:47 -04:00
6eca29be1a Increment cargo version 2023-10-17 17:14:36 -04:00
f8f45bd4f4 Increment cargo version 2023-10-17 17:13:42 -04:00
cfac01443b Increment cargo version 2023-10-17 17:03:46 -04:00
2b882f1137 Implement transform loop 2023-10-17 16:40:07 -04:00
686f7b435d Write README and examples 2023-10-17 16:21:59 -04:00
3bb825fa58 Improve for loop implementation 2023-10-17 15:31:43 -04:00
173e7a2ee8 Implement for loops 2023-10-17 14:06:02 -04:00
deb03ed1b6 Make random ranges inclusive 2023-10-17 13:28:35 -04:00
35cb8116b6 Clean up 2023-10-17 13:25:40 -04:00
487b3f29a2 Implement sh, bash, fish and zsh tools 2023-10-17 12:58:25 -04:00
f07e20c403 Implement length tool 2023-10-17 12:43:33 -04:00
e95aa1e437 Add async statements 2023-10-17 12:25:47 -04:00
a9ef75dc12 Add async statements 2023-10-16 21:13:58 -04:00
2ccd28bbf4 Add async statements 2023-10-16 16:48:02 -04:00
4e8d320c77 Implement to json; Improve map display 2023-10-14 14:18:13 -04:00
ca4fd34bc1 Implement from_json 2023-10-14 13:52:16 -04:00
d898696d6c Add error messages 2023-10-13 20:16:14 -04:00
96adc4bf77 Fix error message 2023-10-13 20:05:45 -04:00
65bb2c9d84 Improve expression implementation 2023-10-13 19:56:57 -04:00
d043f334db Add more tools 2023-10-13 15:48:48 -04:00
da7d6a8493 Clean up example 2023-10-13 15:20:20 -04:00
7206d60ac9 Add random tools 2023-10-13 13:53:00 -04:00
c6675e87ba Update error variants 2023-10-13 12:26:44 -04:00
f20f69afa4 Update README 2023-10-13 11:37:07 -04:00
2595ac5bfc Update README 2023-10-11 13:10:06 -04:00
fc92513246 Add assert and assert_equal tools 2023-10-11 12:07:30 -04:00
96c6193799 Rewrite example 2023-10-11 11:31:24 -04:00
3fd03325c2 Rewrite example 2023-10-10 18:11:30 -04:00
f5b60ea2ff Rewrite example 2023-10-10 17:21:28 -04:00
ea633fbc59 Reimplement functions 2023-10-10 17:12:38 -04:00
8188aa41a5 Implement ValueNode 2023-10-10 14:12:07 -04:00
9450e6dc96 Implement new control flow syntax 2023-10-10 13:29:11 -04:00
574cff5cc6 Implement new control flow syntax 2023-10-09 17:01:30 -04:00
39692b3bd7 Implement new grammar 2023-10-09 15:54:47 -04:00
fd9a4c04cb Implement function calls 2023-10-07 12:38:21 -04:00
e82dd6736e Begin implementing while loops 2023-10-06 22:45:36 -04:00
12418a3bba Update examples and README 2023-10-06 21:59:01 -04:00
90352dd264 Implement function calls 2023-10-06 21:00:31 -04:00
31e9cb61bb Implement function calls; Remove Time 2023-10-06 17:11:50 -04:00
3e87d8b322 Continue syntax overhaul 2023-10-06 13:32:58 -04:00
059e55c7aa Continue syntax overhaul 2023-10-06 08:17:37 -04:00
a691b1fa34 Continue syntax overhaul 2023-10-06 07:55:14 -04:00
6bab3db5e5 Improve syntax error messages; Add test 2023-10-06 01:03:17 -04:00
3ca7aa077b Clean up 2023-10-06 00:06:21 -04:00
2d8527134d Begin chaning gramma structure 2023-10-05 22:49:36 -04:00
23843b5117 Begin chaning gramma structure 2023-10-05 22:07:54 -04:00
8f0bc386b1 Begin function implementation; Clean up 2023-10-05 14:29:13 -04:00
0359fabf1a Add tree sitter submodule 2023-10-05 09:25:09 -04:00
f30dfe6431 Fix errors 2023-10-05 08:42:23 -04:00
1cfa809340 Change list formatting 2023-10-05 08:20:20 -04:00
9aaa9f1e6d Fix simple value parsing 2023-10-05 08:18:33 -04:00
7fa5dd0f54 Update internal API 2023-10-05 08:03:14 -04:00
5551f40ffd Implement tables 2023-10-02 23:19:01 -04:00
a42b43ed78 Fix tests 2023-10-02 18:22:24 -04:00
a4660cbafb Improve internal API 2023-10-02 17:15:05 -04:00
41483f6b84 Implement lists 2023-10-02 15:19:48 -04:00
e23bf0f887 Implement lists 2023-10-01 13:13:13 -04:00
597d6bd8b8 Improve AST 2023-10-01 12:17:27 -04:00
e9db34be8b Implement assignment 2023-10-01 05:27:02 -04:00
ff60640ff8 Clean up 2023-10-01 05:04:57 -04:00
3b0b9d044e Implement addition 2023-10-01 04:20:29 -04:00
b476818ba3 Implement assignment 2023-10-01 02:43:27 -04:00
1f829d930a Change syntax 2023-09-30 22:59:15 -04:00
f596c6b581 Implement map value 2023-09-30 18:07:12 -04:00
327a2d044b Implement function value 2023-09-30 17:52:37 -04:00
d2d2ea1c57 Clean up and fix tests 2023-09-29 17:57:55 -04:00
dd939db924 Implement list 2023-09-29 16:33:46 -04:00
823bb31305 Remove semicolons from language 2023-09-29 13:03:55 -04:00
8bcf59f216 Rearrange tests; Add new tests 2023-09-29 12:42:37 -04:00
ad429b3563 Add equality operator 2023-09-29 09:17:21 -04:00
b308c1852f Add list support 2023-09-29 08:59:09 -04:00
3182e7c860 Add float support; Write tests 2023-09-29 08:21:38 -04:00
5877884877 Fix test 2023-09-29 07:57:17 -04:00
c9c7eb3173 Improve AST construction and errors 2023-09-29 07:17:11 -04:00
9994919127 Implement subtraction 2023-09-29 06:02:46 -04:00
91745af149 Implement control flow 2023-09-29 05:52:56 -04:00
8250d3cb8e Add example; Implement AST 2023-09-29 05:45:15 -04:00
4907dd9f8f Implement top-level API with docs 2023-09-29 02:53:31 -04:00
cdf79f8763 Implement closed and open statement 2023-09-29 00:16:51 -04:00
4983975e59 Improve addition implementation 2023-09-28 23:53:37 -04:00
adbd69b17c Run addition and subtration operations 2023-09-28 23:29:50 -04:00
589d66a90f Modify library for new parser 2023-09-28 15:58:01 -04:00
5fed0984a7 Set version 2023-09-25 02:39:39 -04:00
176 changed files with 61289 additions and 10673 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
target/
node_modules/

3857
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +1,50 @@
[package]
name = "dust-lang"
version = "0.1.1"
description = "Data-oriented programming language and interactive shell."
authors = ["jeff <dev@jeffa.io.com>"]
description = "General purpose programming language"
version = "0.4.2"
repository = "https://git.jeffa.io/jeff/dust.git"
homepage = "https://git.jeffa.io/jeff/dust"
readme = "README.md"
edition = "2021"
license = "MIT"
edition = "2018"
authors = ["Jeff Anderson"]
default-run = "dust"
[[bin]]
name = "dust"
path = "src/main.rs"
[[bin]]
name = "gui"
[lib]
name = "dust_lib"
path = "src/lib.rs"
[profile.dev]
opt-level = 1
[profile.dev.package."*"]
opt-level = 3
[dependencies]
rand = "0.8.5"
chrono = "0.4.26"
trash = "3.0.3"
rayon = "1.7.0"
serde = { version = "1.0.171", features = ["derive"] }
sysinfo = "0.29.6"
toml = "0.7.6"
toml_edit = "0.19.14"
comfy-table = "7.0.1"
clap = { version = "4.3.19", features = ["derive"] }
git2 = "0.17.2"
clap = { version = "4.4.4", features = ["derive"] }
csv = "1.2.2"
json = "0.12.4"
reqwest = { version = "0.11.18", features = ["blocking", "json"] }
serde_json = "1.0.104"
egui_extras = "0.22.0"
rustyline = { version = "12.0.0", features = ["with-file-history", "derive"] }
ansi_term = "0.12.1"
iced = "0.10.0"
egui = "0.22.0"
eframe = "0.22.0"
env_logger = "0.10.0"
once_cell = "1.18.0"
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"

297
README.md
View File

@ -1,259 +1,100 @@
# Dust
Dust is a data-oriented programming language and interactive shell. Dust can be used as a replacement for a traditional command line shell, as a scripting language and as a tool create or manage data. Dust is expression-based, has first-class functions, lexical scope and lightweight syntax.
High-level programming language with effortless concurrency, automatic memory management, type safety and strict error handling.
A basic dust program:
```dust
output "Hello world!"
```
Dust can do two (or more) things at the same time with effortless concurrency:
```dust
run(
'output "will this one finish first?"',
'output "or will this one?"'
)
```
Dust can do amazing things with data. To load CSV data, isolate a column and render it as a line plot in a GUI window:
```dust
read_file("examples/assets/faithful.csv")
-> from_csv(input)
-> rows(input)
-> transform(input, 'input.1')
-> plot(input)
```
![Dust version of an example from The Rust Programming Language.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/example_0.png)
<!--toc:start-->
- [Dust](#dust)
- [Features](#features)
- [Usage](#usage)
- [Installation](#installation)
- [Contributing](#contributing)
- [The Dust Programming Language](#the-dust-programming-language)
- [Variables and Data Types](#variables-and-data-types)
- [Tools](#tools)
- [Lists](#lists)
- [Maps](#maps)
- [Tables](#tables)
- [The Yield Operator](#the-yield-operator)
- [Functions](#functions)
- [Time](#time)
- [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-->
## Features
- Data visualization: GUI (not TUI) plots, graphs and charts are available from directly within dust. No external tools are needed.
- Powerful tooling: Built-in commands reduce complex tasks to plain, simple code. You can even partition disks or install software.
- Pipelines: Like a pipe in bash, dust features the yield `->` operator.
- Format conversion: Effortlessly convert between dust and formats like JSON, CSV and TOML.
- Structured data: Dust can represent data with more than just strings. Lists, maps and tables are easy to make and manage.
- Developer tools: Dust has a complete tree sitter grammar, allowing syntax highlighting and completion in most code editors.
### Easy to Read and Write
## Usage
Dust has simple, easy-to-learn syntax.
Dust is an experimental project under active development. At this stage, features come and go and the API is always changing. It should not be considered for serious use yet.
To get help with the shell you can use the "help" tool.
```dust
help() # Returns a table will all tool info.
help("random") # Returns a table with info on tools in the specified group.
# The above is simply a shorthand for this:
help() -> where(input, 'tool == "random"')
```js
output('Hello world!')
```
## Installation
### Effortless Concurrency
You must have the default rust toolchain installed and up-to-date. Install [rustup] if it is not already installed. Run `cargo install dust-lang` then run `dust` to start the interactive shell. Use `dust --help` to see the full command line options.
Write multi-threaded code as easily as you would write code for a single thread.
To build from source, clone the repository and run `cargo run` to start the shell. To see other command line options, use `cargo run -- --help`.
## Contributing
Please submit any thoughts or suggestions for this project. To contribute a new command, see the library documentation. Implementation tests are written in dust and are run by a corresponding rust test so dust tests will be run when `cargo test` is called.
## The Dust Programming Language
Dust is a hard fork of [evalexpr]; a simple expression language. Dust's core language features maintain this simplicity. But it can manage large, complex sets of data and perform complicated tasks through commands. It should not take long for a new user to learn the language, especially with the assistance of the shell.
If your editor supports tree sitter, you can use [tree-sitter-dust] for syntax highlighting and completion support. Aside from this guide, the best way to learn dust is to read the examples and tests to get a better idea of what dust can do.
### Variables and Data Types
Variables have two parts: a key and a value. The key is always a text string. The value can be any of the following data types:
- string
- integer
- floating point value
- boolean
- list
- map
- table
- function
- time
- empty
Here are some examples of variables in dust.
```dust
string = "The answer is 42.";
integer = 42;
float = 42.42;
list = (1, 2, string, integer, float);
map.key = "value";
empty = ();
```js
async {
output('Will this one print first?')
output('Or will this one?')
output('Who knows! Each "output" will run in its own thread!')
}
```
### Tools
### Helpful Errors
**Tools** are dust's built-in functions. Some of them can reconfigure your whole system while others do very little. They may accept different inputs, or none at all. For example, commands in the `random` group can be run without input, but the `random_integer` command can optionally take two numbers as in inclusive range.
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.
### Error Handling
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.
```dust
die_roll = random_integer(1, 6);
d20_roll = random_integer(1, 20);
coin_flip = random_boolean();
match io:stdin() {
Result::Ok(input) -> output("We read this input: " + input)
Result::Error(message) -> output("We got this error: " + message)
}
```
```dust
message = "I hate dust.";
replace(message, "hate", "love")
## Installation and Usage
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.
To install from the git repository:
```fish
git clone https://git.jeffa.io/jeff/dust
cd dust
cargo run --release
```
### Lists
To install with cargo:
Lists are sequential collections. They can be built by grouping values with parentheses and separating them with commas. Values can be indexed by their position to access their contents. Lists are used to represent rows in tables and most commands take a list as an argument. Their contents can be indexed using dot notation with an integer.
```dust
list = (true, 41, "Ok");
assert_equal(list.0, true);
the_answer = list.1 + 1;
assert_equal(the_answer, 42);
```fish
cargo install dust-lang
dust
```
### Maps
## Benchmarks
Maps are flexible collections with arbitrary key-value pairs, similar to JSON objects. Under the hood, all of dust's runtime variables are stored in a map, so, as with variables, the key is always a string.
## Development Status
```dust
reminder.message = "Buy milk";
reminder.tags = ("groceries", "home");
json = to_json(reminder);
append(json, "info.txt");
```
### Tables
Tables are strict collections, each row must have a value for each column. Empty cells must be explicitly set to an empty value.
```dust
animals = create_table (
("name", "species", "age"),
(
("rover", "cat", 14),
("spot", "snake", 9),
("bob", "giraffe", 2)
)
);
```
Querying a table is similar to SQL.
```dust
names = select(animals, "name");
youngins = where(animals, 'age < 5');
```
The commands `create_table` and `insert` make sure that all of the memory used to hold the rows is allocated at once, so it is good practice to group your rows together instead of using a call for each row.
```dust
insert(
animals,
(
("eliza", "ostrich", 4),
("pat", "white rhino", 7),
("jim", "walrus", 9)
)
);
assert_equal(count(animals.all), 6);
sorted = sort(animals);
```
### The Yield Operator
Like a pipe in bash, zsh or fish, the yield operator evaluates the expression on the left and passes it as input to the expression on the right. That input is always assigned to the **`input` variable** for that context. These expressions may simply contain a value or they can call a command or function that returns a value.
```dust
"Hello dust!" -> output(input)
```
This can be useful when working on the command line but to make a script easier to read or to avoid fetching the same resource multiple times, we can also declare variables. You should use `->` and variables together to write efficient, elegant scripts.
```dust
json = download("https://api.sampleapis.com/futurama/characters");
from_json(json)
-> select(input, "name");
-> input.4
```
### Functions
Functions are first-class values in dust, so they are assigned to variables like any other value. The function body is wrapped in single parentheses. To call a function, it's just like calling a command: simply pass it an argument or use an empty set of parentheses to pass an empty value.
In the function bod, the **`input` variable** represents whatever value is passed to the function when called.
```dust
say_hi = 'output "hi"';
add_one = 'input + 1';
say_hi();
assert_equal(add_one(3), 4);
```
This function simply passes the input to the shell's standard output.
```dust
print = 'output(input)';
```
Because functions are stored in variables, we can use collections like maps to
organize them.
```dust
math.add = 'input.0 + input.1';
math.subtract = 'input.0 - input.1';
assert_equal(math.add(2, 2), 4);
assert_equal(math.subtract(100, 1), 99);
```
### Time
Dust can record, parse and convert time values. Dust can parse TOML datetime
values or can create time values using commands.
```dust
dob = from_toml("1979-05-27T07:32:00-08:00")
output "Date of birth = " + local(dob);
```
```dust
time = now();
output "Universal time is " + utc(time);
output "Local time is " + local(time);
```
[dnf]: https://dnf.readthedocs.io/en/latest/index.html
[evalexpr]: https://github.com/ISibboI/evalexpr
[rustup]: https://rustup.rs
Currently, Dust is being prepared for version 1.0. Until then, there may be breaking changes to the language and CLI.

17
build.rs Normal file
View File

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

BIN
docs/assets/debugging.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/assets/example_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/assets/type_error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

515
docs/language.md Normal file
View File

@ -0,0 +1,515 @@
# 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

View File

@ -1,273 +0,0 @@
"Index", "Eruption length (mins)","Eruption wait (mins)"
1, 3.600, 79
2, 1.800, 54
3, 3.333, 74
4, 2.283, 62
5, 4.533, 85
6, 2.883, 55
7, 4.700, 88
8, 3.600, 85
9, 1.950, 51
10, 4.350, 85
11, 1.833, 54
12, 3.917, 84
13, 4.200, 78
14, 1.750, 47
15, 4.700, 83
16, 2.167, 52
17, 1.750, 62
18, 4.800, 84
19, 1.600, 52
20, 4.250, 79
21, 1.800, 51
22, 1.750, 47
23, 3.450, 78
24, 3.067, 69
25, 4.533, 74
26, 3.600, 83
27, 1.967, 55
28, 4.083, 76
29, 3.850, 78
30, 4.433, 79
31, 4.300, 73
32, 4.467, 77
33, 3.367, 66
34, 4.033, 80
35, 3.833, 74
36, 2.017, 52
37, 1.867, 48
38, 4.833, 80
39, 1.833, 59
40, 4.783, 90
41, 4.350, 80
42, 1.883, 58
43, 4.567, 84
44, 1.750, 58
45, 4.533, 73
46, 3.317, 83
47, 3.833, 64
48, 2.100, 53
49, 4.633, 82
50, 2.000, 59
51, 4.800, 75
52, 4.716, 90
53, 1.833, 54
54, 4.833, 80
55, 1.733, 54
56, 4.883, 83
57, 3.717, 71
58, 1.667, 64
59, 4.567, 77
60, 4.317, 81
61, 2.233, 59
62, 4.500, 84
63, 1.750, 48
64, 4.800, 82
65, 1.817, 60
66, 4.400, 92
67, 4.167, 78
68, 4.700, 78
69, 2.067, 65
70, 4.700, 73
71, 4.033, 82
72, 1.967, 56
73, 4.500, 79
74, 4.000, 71
75, 1.983, 62
76, 5.067, 76
77, 2.017, 60
78, 4.567, 78
79, 3.883, 76
80, 3.600, 83
81, 4.133, 75
82, 4.333, 82
83, 4.100, 70
84, 2.633, 65
85, 4.067, 73
86, 4.933, 88
87, 3.950, 76
88, 4.517, 80
89, 2.167, 48
90, 4.000, 86
91, 2.200, 60
92, 4.333, 90
93, 1.867, 50
94, 4.817, 78
95, 1.833, 63
96, 4.300, 72
97, 4.667, 84
98, 3.750, 75
99, 1.867, 51
100, 4.900, 82
101, 2.483, 62
102, 4.367, 88
103, 2.100, 49
104, 4.500, 83
105, 4.050, 81
106, 1.867, 47
107, 4.700, 84
108, 1.783, 52
109, 4.850, 86
110, 3.683, 81
111, 4.733, 75
112, 2.300, 59
113, 4.900, 89
114, 4.417, 79
115, 1.700, 59
116, 4.633, 81
117, 2.317, 50
118, 4.600, 85
119, 1.817, 59
120, 4.417, 87
121, 2.617, 53
122, 4.067, 69
123, 4.250, 77
124, 1.967, 56
125, 4.600, 88
126, 3.767, 81
127, 1.917, 45
128, 4.500, 82
129, 2.267, 55
130, 4.650, 90
131, 1.867, 45
132, 4.167, 83
133, 2.800, 56
134, 4.333, 89
135, 1.833, 46
136, 4.383, 82
137, 1.883, 51
138, 4.933, 86
139, 2.033, 53
140, 3.733, 79
141, 4.233, 81
142, 2.233, 60
143, 4.533, 82
144, 4.817, 77
145, 4.333, 76
146, 1.983, 59
147, 4.633, 80
148, 2.017, 49
149, 5.100, 96
150, 1.800, 53
151, 5.033, 77
152, 4.000, 77
153, 2.400, 65
154, 4.600, 81
155, 3.567, 71
156, 4.000, 70
157, 4.500, 81
158, 4.083, 93
159, 1.800, 53
160, 3.967, 89
161, 2.200, 45
162, 4.150, 86
163, 2.000, 58
164, 3.833, 78
165, 3.500, 66
166, 4.583, 76
167, 2.367, 63
168, 5.000, 88
169, 1.933, 52
170, 4.617, 93
171, 1.917, 49
172, 2.083, 57
173, 4.583, 77
174, 3.333, 68
175, 4.167, 81
176, 4.333, 81
177, 4.500, 73
178, 2.417, 50
179, 4.000, 85
180, 4.167, 74
181, 1.883, 55
182, 4.583, 77
183, 4.250, 83
184, 3.767, 83
185, 2.033, 51
186, 4.433, 78
187, 4.083, 84
188, 1.833, 46
189, 4.417, 83
190, 2.183, 55
191, 4.800, 81
192, 1.833, 57
193, 4.800, 76
194, 4.100, 84
195, 3.966, 77
196, 4.233, 81
197, 3.500, 87
198, 4.366, 77
199, 2.250, 51
200, 4.667, 78
201, 2.100, 60
202, 4.350, 82
203, 4.133, 91
204, 1.867, 53
205, 4.600, 78
206, 1.783, 46
207, 4.367, 77
208, 3.850, 84
209, 1.933, 49
210, 4.500, 83
211, 2.383, 71
212, 4.700, 80
213, 1.867, 49
214, 3.833, 75
215, 3.417, 64
216, 4.233, 76
217, 2.400, 53
218, 4.800, 94
219, 2.000, 55
220, 4.150, 76
221, 1.867, 50
222, 4.267, 82
223, 1.750, 54
224, 4.483, 75
225, 4.000, 78
226, 4.117, 79
227, 4.083, 78
228, 4.267, 78
229, 3.917, 70
230, 4.550, 79
231, 4.083, 70
232, 2.417, 54
233, 4.183, 86
234, 2.217, 50
235, 4.450, 90
236, 1.883, 54
237, 1.850, 54
238, 4.283, 77
239, 3.950, 79
240, 2.333, 64
241, 4.150, 75
242, 2.350, 47
243, 4.933, 86
244, 2.900, 63
245, 4.583, 85
246, 3.833, 82
247, 2.083, 57
248, 4.367, 82
249, 2.133, 67
250, 4.350, 74
251, 2.200, 54
252, 4.450, 83
253, 3.567, 73
254, 4.500, 73
255, 4.150, 88
256, 3.817, 80
257, 3.917, 71
258, 4.450, 83
259, 2.000, 56
260, 4.283, 79
261, 4.767, 78
262, 4.533, 84
263, 1.850, 58
264, 4.250, 83
265, 1.983, 43
266, 2.250, 60
267, 4.750, 75
268, 4.117, 81
269, 2.150, 46
270, 4.417, 90
271, 1.817, 46
272, 4.467, 74
1 Index Eruption length (mins) Eruption wait (mins)
2 1 3.600 79
3 2 1.800 54
4 3 3.333 74
5 4 2.283 62
6 5 4.533 85
7 6 2.883 55
8 7 4.700 88
9 8 3.600 85
10 9 1.950 51
11 10 4.350 85
12 11 1.833 54
13 12 3.917 84
14 13 4.200 78
15 14 1.750 47
16 15 4.700 83
17 16 2.167 52
18 17 1.750 62
19 18 4.800 84
20 19 1.600 52
21 20 4.250 79
22 21 1.800 51
23 22 1.750 47
24 23 3.450 78
25 24 3.067 69
26 25 4.533 74
27 26 3.600 83
28 27 1.967 55
29 28 4.083 76
30 29 3.850 78
31 30 4.433 79
32 31 4.300 73
33 32 4.467 77
34 33 3.367 66
35 34 4.033 80
36 35 3.833 74
37 36 2.017 52
38 37 1.867 48
39 38 4.833 80
40 39 1.833 59
41 40 4.783 90
42 41 4.350 80
43 42 1.883 58
44 43 4.567 84
45 44 1.750 58
46 45 4.533 73
47 46 3.317 83
48 47 3.833 64
49 48 2.100 53
50 49 4.633 82
51 50 2.000 59
52 51 4.800 75
53 52 4.716 90
54 53 1.833 54
55 54 4.833 80
56 55 1.733 54
57 56 4.883 83
58 57 3.717 71
59 58 1.667 64
60 59 4.567 77
61 60 4.317 81
62 61 2.233 59
63 62 4.500 84
64 63 1.750 48
65 64 4.800 82
66 65 1.817 60
67 66 4.400 92
68 67 4.167 78
69 68 4.700 78
70 69 2.067 65
71 70 4.700 73
72 71 4.033 82
73 72 1.967 56
74 73 4.500 79
75 74 4.000 71
76 75 1.983 62
77 76 5.067 76
78 77 2.017 60
79 78 4.567 78
80 79 3.883 76
81 80 3.600 83
82 81 4.133 75
83 82 4.333 82
84 83 4.100 70
85 84 2.633 65
86 85 4.067 73
87 86 4.933 88
88 87 3.950 76
89 88 4.517 80
90 89 2.167 48
91 90 4.000 86
92 91 2.200 60
93 92 4.333 90
94 93 1.867 50
95 94 4.817 78
96 95 1.833 63
97 96 4.300 72
98 97 4.667 84
99 98 3.750 75
100 99 1.867 51
101 100 4.900 82
102 101 2.483 62
103 102 4.367 88
104 103 2.100 49
105 104 4.500 83
106 105 4.050 81
107 106 1.867 47
108 107 4.700 84
109 108 1.783 52
110 109 4.850 86
111 110 3.683 81
112 111 4.733 75
113 112 2.300 59
114 113 4.900 89
115 114 4.417 79
116 115 1.700 59
117 116 4.633 81
118 117 2.317 50
119 118 4.600 85
120 119 1.817 59
121 120 4.417 87
122 121 2.617 53
123 122 4.067 69
124 123 4.250 77
125 124 1.967 56
126 125 4.600 88
127 126 3.767 81
128 127 1.917 45
129 128 4.500 82
130 129 2.267 55
131 130 4.650 90
132 131 1.867 45
133 132 4.167 83
134 133 2.800 56
135 134 4.333 89
136 135 1.833 46
137 136 4.383 82
138 137 1.883 51
139 138 4.933 86
140 139 2.033 53
141 140 3.733 79
142 141 4.233 81
143 142 2.233 60
144 143 4.533 82
145 144 4.817 77
146 145 4.333 76
147 146 1.983 59
148 147 4.633 80
149 148 2.017 49
150 149 5.100 96
151 150 1.800 53
152 151 5.033 77
153 152 4.000 77
154 153 2.400 65
155 154 4.600 81
156 155 3.567 71
157 156 4.000 70
158 157 4.500 81
159 158 4.083 93
160 159 1.800 53
161 160 3.967 89
162 161 2.200 45
163 162 4.150 86
164 163 2.000 58
165 164 3.833 78
166 165 3.500 66
167 166 4.583 76
168 167 2.367 63
169 168 5.000 88
170 169 1.933 52
171 170 4.617 93
172 171 1.917 49
173 172 2.083 57
174 173 4.583 77
175 174 3.333 68
176 175 4.167 81
177 176 4.333 81
178 177 4.500 73
179 178 2.417 50
180 179 4.000 85
181 180 4.167 74
182 181 1.883 55
183 182 4.583 77
184 183 4.250 83
185 184 3.767 83
186 185 2.033 51
187 186 4.433 78
188 187 4.083 84
189 188 1.833 46
190 189 4.417 83
191 190 2.183 55
192 191 4.800 81
193 192 1.833 57
194 193 4.800 76
195 194 4.100 84
196 195 3.966 77
197 196 4.233 81
198 197 3.500 87
199 198 4.366 77
200 199 2.250 51
201 200 4.667 78
202 201 2.100 60
203 202 4.350 82
204 203 4.133 91
205 204 1.867 53
206 205 4.600 78
207 206 1.783 46
208 207 4.367 77
209 208 3.850 84
210 209 1.933 49
211 210 4.500 83
212 211 2.383 71
213 212 4.700 80
214 213 1.867 49
215 214 3.833 75
216 215 3.417 64
217 216 4.233 76
218 217 2.400 53
219 218 4.800 94
220 219 2.000 55
221 220 4.150 76
222 221 1.867 50
223 222 4.267 82
224 223 1.750 54
225 224 4.483 75
226 225 4.000 78
227 226 4.117 79
228 227 4.083 78
229 228 4.267 78
230 229 3.917 70
231 230 4.550 79
232 231 4.083 70
233 232 2.417 54
234 233 4.183 86
235 234 2.217 50
236 235 4.450 90
237 236 1.883 54
238 237 1.850 54
239 238 4.283 77
240 239 3.950 79
241 240 2.333 64
242 241 4.150 75
243 242 2.350 47
244 243 4.933 86
245 244 2.900 63
246 245 4.583 85
247 246 3.833 82
248 247 2.083 57
249 248 4.367 82
250 249 2.133 67
251 250 4.350 74
252 251 2.200 54
253 252 4.450 83
254 253 3.567 73
255 254 4.500 73
256 255 4.150 88
257 256 3.817 80
258 257 3.917 71
259 258 4.450 83
260 259 2.000 56
261 260 4.283 79
262 261 4.767 78
263 262 4.533 84
264 263 1.850 58
265 264 4.250 83
266 265 1.983 43
267 266 2.250 60
268 267 4.750 75
269 268 4.117 81
270 269 2.150 46
271 270 4.417 90
272 271 1.817 46
273 272 4.467 74

7883
examples/assets/jq_data.json Normal file

File diff suppressed because it is too large Load Diff

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 }
]

19
examples/async.ds Normal file
View File

@ -0,0 +1,19 @@
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

@ -0,0 +1,17 @@
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.'
}
}

View File

@ -0,0 +1,20 @@
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])

53
examples/clue_solver.ds Normal file
View File

@ -0,0 +1,53 @@
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,114 +0,0 @@
# transform
list = (1, 2, 3);
test = transform(list, 'input + 1');
assert_equal((2, 3, 4), test);
# string
test = string(42);
assert_equal("42", test);
test = string(42.42);
assert_equal("42.42", test);
test = string(false);
assert_equal("false", test);
# create_table
table = create_table(
("text", "num"),
(
("foo", 1),
("bar", 2)
)
);
# rows
test = rows(table);
assert_equal(
(
("foo", 1),
("bar", 2)
),
test
);
# insert
test = insert(
table,
(
("foo", 1),
("bar", 2)
)
);
assert_equal(
create_table(
("text", "num"),
(
("foo", 1),
("bar", 2),
("foo", 1),
("bar", 2)
)
),
test
);
# select
table = create_table(
("text", "number", "bool"),
(
("a", 1, true),
("b", 2, true),
("a", 3, true)
)
);
test_table = create_table(
("text", "bool"),
(
("a", true),
("b", true),
("a", true)
)
);
assert_equal(select(table, ("text", "bool")), test_table);
test_table = create_table(
("text", "number", "bool"),
(
("a", 1, true),
("a", 3, true)
)
);
assert_equal(where(table, 'text == "a"'), test_table);
# count
table = create_table(
("text", "number", "bool"),
(
("a", 1, true),
("b", 2, true),
("a", 3, true)
)
);
test = count(table);
assert_equal(3, test);
test = count("123");
assert_equal(3, test);
test = count(1, 2, 3);
assert_equal(3, test);
map.x.z.y = 1;
test = count(map);
assert_equal(1, test);

View File

@ -1,4 +0,0 @@
dob = from_toml("1979-05-27T07:32:00-08:00");
toml = to_toml(dob);
assert_equal(toml, "1979-05-27T07:32:00-08:00");

10
examples/download.ds Normal file
View File

@ -0,0 +1,10 @@
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,4 +0,0 @@
raw_data = download("https://api.sampleapis.com/futurama/cast");
data = from_json(raw_data);
assert_equal("Billy West", data.0.name);

9
examples/fibonacci.ds Normal file
View File

@ -0,0 +1,9 @@
fib = (i <int>) <int> {
if i <= 1 {
1
} else {
fib(i - 1) + fib(i - 2)
}
}
fib(8)

18
examples/fizzbuzz.ds Normal file
View File

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

26
examples/guessing_game.ds Normal file
View File

@ -0,0 +1,26 @@
# This is a Dust version of an example from the Rust Book.
#
# https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
output("Guess the number.")
secret_number = int:random_range(0..=100)
loop {
output("Please input your guess.")
input = io:stdin():expect("Failed to read line.")
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
}
}
}

View File

@ -1,6 +0,0 @@
# This will read a CSV file and display it as a line plot in a GUI window.
read_file("examples/assets/faithful.csv")
-> from_csv(input)
-> rows(input)
-> transform(input, 'input.1')
-> plot(input);

1
examples/hello_world.ds Normal file
View File

@ -0,0 +1 @@
output('Hello, world!')

12
examples/jq_data.ds Normal file
View File

@ -0,0 +1,12 @@
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

View File

@ -1,12 +0,0 @@
# Lists are created by grouping items in partheses and separating them with
# commas.
numbers = (1, 2, 3);
# To access the values in a list, use an integer as an index.
x = numbers.0;
y = numbers.1;
z = numbers.2;
assert_equal(x + y, z);

13
examples/random.ds Normal file
View File

@ -0,0 +1,13 @@
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,9 +0,0 @@
foo = "bar";
func = 'foo';
assert_equal("bar", func());
foo = "xyz";
assert_equal("xyz", func());

19
examples/sea_creatures.ds Normal file
View File

@ -0,0 +1,19 @@
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

@ -1,29 +0,0 @@
table = create_table(
("text", "number", "bool"),
(
("a", 1, true),
("b", 2, true),
("a", 3, true)
)
);
test_table = create_table(
("text", "bool"),
(
("a", true),
("b", true),
("a", true)
)
);
assert_equal(select(table, ("text", "bool")), test_table);
test_table = create_table(
("text", "number", "bool"),
(
("a", 1, true),
("a", 3, true)
)
);
assert_equal(where(table, 'text == "a"'), test_table);

View File

@ -1,13 +0,0 @@
# Dust is data-oriented, so variables are declared with minimal syntax. A
# single character, the assignment operator (`=`), sets a variable.
x = 1;
y = "hello dust!";
z = 42.0;
list = (3, 2, x);
big_list = (x, y, z, list);
map.x = "foobar";
function = '
message = "I am a function!";
output message;
';

35
scripts/bench.fish Executable file
View File

@ -0,0 +1,35 @@
#!/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'"

8
scripts/build_debug.fish Executable file
View File

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

8
scripts/build_release.fish Executable file
View File

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

9
scripts/test.fish Executable file
View File

@ -0,0 +1,9 @@
#!/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

131
src/abstract_tree/as.rs Normal file
View File

@ -0,0 +1,131 @@
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

@ -0,0 +1,186 @@
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

@ -0,0 +1,62 @@
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("-="),
}
}
}

170
src/abstract_tree/block.rs Normal file
View File

@ -0,0 +1,170 @@
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

@ -0,0 +1,76 @@
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

@ -0,0 +1,92 @@
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

@ -0,0 +1,70 @@
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

@ -0,0 +1,120 @@
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),
}
}
}

153
src/abstract_tree/for.rs Normal file
View File

@ -0,0 +1,153 @@
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

@ -0,0 +1,202 @@
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

@ -0,0 +1,91 @@
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

@ -0,0 +1,179 @@
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

@ -0,0 +1,167 @@
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

@ -0,0 +1,160 @@
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);
}
}
}

134
src/abstract_tree/index.rs Normal file
View File

@ -0,0 +1,134 @@
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

@ -0,0 +1,96 @@
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

@ -0,0 +1,98 @@
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

@ -0,0 +1,95 @@
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

@ -0,0 +1,93 @@
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

@ -0,0 +1,117 @@
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!()
}
}

145
src/abstract_tree/match.rs Normal file
View File

@ -0,0 +1,145 @@
//! 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

@ -0,0 +1,64 @@
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!()
}
}

74
src/abstract_tree/math.rs Normal file
View File

@ -0,0 +1,74 @@
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

@ -0,0 +1,69 @@
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);
}
}

178
src/abstract_tree/mod.rs Normal file
View File

@ -0,0 +1,178 @@
//! 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(" ");
}
}
}

45
src/abstract_tree/new.rs Normal file
View File

@ -0,0 +1,45 @@
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

@ -0,0 +1,202 @@
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

@ -0,0 +1,120 @@
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!()
}
}

394
src/abstract_tree/type.rs Normal file
View File

@ -0,0 +1,394 @@
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

@ -0,0 +1,73 @@
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

@ -0,0 +1,56 @@
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

@ -0,0 +1,357 @@
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

@ -0,0 +1,64 @@
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,168 +0,0 @@
//! Command line interface for the whale programming language.
use clap::Parser;
use rustyline::{
completion::FilenameCompleter,
error::ReadlineError,
highlight::Highlighter,
hint::{Hint, Hinter, HistoryHinter},
history::DefaultHistory,
Completer, Context, Editor, Helper, Validator,
};
use std::{borrow::Cow, fs::read_to_string};
use dust_lib::{eval, eval_with_context, Value, VariableMap, TOOL_LIST};
/// Command-line arguments to be parsed.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Whale source code to evaluate.
#[arg(short, long)]
command: Option<String>,
/// Location of the file to run.
path: Option<String>,
}
fn main() {
let args = Args::parse();
if args.path.is_none() && args.command.is_none() {
return run_cli_shell();
}
let eval_result = if let Some(path) = args.path {
let file_contents = read_to_string(path).unwrap();
eval(&file_contents)
} else if let Some(command) = args.command {
eval(&command)
} else {
Ok(Value::Empty)
};
match eval_result {
Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"),
}
}
#[derive(Helper, Completer, Validator)]
struct DustReadline {
#[rustyline(Completer)]
completer: FilenameCompleter,
tool_hints: Vec<ToolHint>,
#[rustyline(Hinter)]
_hinter: HistoryHinter,
}
impl DustReadline {
fn new() -> Self {
Self {
completer: FilenameCompleter::new(),
_hinter: HistoryHinter {},
tool_hints: TOOL_LIST
.iter()
.map(|tool| ToolHint {
display: tool.info().identifier.to_string() + "()",
complete_to: tool.info().identifier.len(),
})
.collect(),
}
}
}
struct ToolHint {
display: String,
complete_to: usize,
}
impl Hint for ToolHint {
fn display(&self) -> &str {
&self.display
}
fn completion(&self) -> Option<&str> {
if self.complete_to > 0 {
Some(&self.display[..self.complete_to])
} else {
None
}
}
}
impl ToolHint {
fn suffix(&self, strip_chars: usize) -> ToolHint {
ToolHint {
display: self.display[strip_chars..].to_string(),
complete_to: self.complete_to.saturating_sub(strip_chars),
}
}
}
impl Hinter for DustReadline {
type Hint = ToolHint;
fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<Self::Hint> {
if line.is_empty() || pos < line.len() {
return None;
}
self.tool_hints.iter().find_map(|tool_hint| {
if tool_hint.display.starts_with(line) {
Some(tool_hint.suffix(pos))
} else {
None
}
})
}
}
impl Highlighter for DustReadline {
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
let highlighted = ansi_term::Colour::Red.paint(hint).to_string();
Cow::Owned(highlighted)
}
}
fn run_cli_shell() {
let mut context = VariableMap::new();
let mut rl: Editor<DustReadline, DefaultHistory> = Editor::new().unwrap();
rl.set_helper(Some(DustReadline::new()));
if rl.load_history("target/history.txt").is_err() {
println!("No previous history.");
}
loop {
let readline = rl.readline("* ");
match readline {
Ok(line) => {
let line = line.as_str();
rl.add_history_entry(line).unwrap();
let eval_result = eval_with_context(line, &mut context);
match eval_result {
Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"),
}
}
Err(ReadlineError::Interrupted) => {
break;
}
Err(ReadlineError::Eof) => {
break;
}
Err(error) => eprintln!("{error}"),
}
}
rl.save_history("target/history.txt").unwrap();
}

View File

@ -1,88 +0,0 @@
use dust_lib::eval;
use iced::widget::{column, container, text_input, Column};
use iced::{executor, Application, Command, Element, Settings, Theme};
use once_cell::sync::Lazy;
static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
pub fn main() -> iced::Result {
DustGui::run(Settings::default())
}
struct DustGui {
text_buffer: String,
results: Vec<String>,
}
impl Application for DustGui {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
(
DustGui {
text_buffer: String::new(),
results: Vec::new(),
},
Command::none(),
)
}
fn title(&self) -> String {
"Dust".to_string()
}
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
match message {
Message::TextInput(input) => {
self.text_buffer = input;
Command::none()
}
Message::Evaluate => {
let eval_result = eval(&self.text_buffer);
match eval_result {
Ok(result) => self.results.push(result.to_string()),
Err(error) => self.results.push(error.to_string()),
}
Command::batch(vec![])
}
}
}
fn view(&self) -> Element<'_, Self::Message, iced::Renderer<Self::Theme>> {
let input = text_input("What needs to be done?", &self.text_buffer)
.id(INPUT_ID.clone())
.on_input(Message::TextInput)
.on_submit(Message::Evaluate)
.padding(15)
.size(30);
let result_display: Column<Message> = {
let mut text_widgets = Vec::new();
for _result in &self.results {
// text_widgets.push(text(result).style().into());
}
text_widgets.reverse();
Column::with_children(text_widgets)
};
container(column![input, result_display]).into()
}
}
#[derive(Debug, Clone)]
enum Message {
TextInput(String),
Evaluate,
}

View File

@ -0,0 +1,59 @@
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

@ -0,0 +1,77 @@
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

@ -0,0 +1,198 @@
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

@ -0,0 +1,591 @@
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

@ -0,0 +1,51 @@
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

@ -0,0 +1,56 @@
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)
}),
}
}
}

29
src/built_in_types.rs Normal file
View File

@ -0,0 +1,29 @@
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))
}
}),
}
}
}

180
src/built_in_values.rs Normal file
View File

@ -0,0 +1,180 @@
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(),
}
}
}

395
src/context/mod.rs Normal file
View File

@ -0,0 +1,395 @@
//! 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

@ -0,0 +1,74 @@
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

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

View File

@ -1,633 +0,0 @@
//! Error and Result types.
//!
//! To deal with errors from dependencies, either create a new error variant
//! or use the MacroFailure variant if the error can only occur inside a macro.
use crate::{
operator::Operator, token::PartialToken, value::value_type::ValueType, value::Value, Node,
ToolInfo,
};
use std::{fmt, io, time::SystemTimeError};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// Dust's internal type checking failed to identify a type mismatch. This should never happen, /// the error prompts the user to report the bug.
TypeCheckFailure {
tool_info: ToolInfo<'static>,
argument: Value,
},
/// The 'assert' macro did not resolve successfully.
AssertEqualFailed {
expected: Value,
actual: Value,
},
/// The 'assert' macro did not resolve successfully.
AssertFailed,
/// A row was inserted to a table with the wrong amount of values.
WrongColumnAmount {
expected: usize,
actual: usize,
},
/// An operator was called with the wrong amount of arguments.
ExpectedOperatorArgumentAmount {
expected: usize,
actual: usize,
},
/// A function was called with the wrong amount of arguments.
ExpectedFunctionArgumentAmount {
identifier: String,
expected: usize,
actual: usize,
},
/// A function was called with the wrong amount of arguments.
ExpectedAtLeastFunctionArgumentAmount {
identifier: String,
minimum: usize,
actual: usize,
},
ExpectedString {
actual: Value,
},
ExpectedInt {
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,
},
ExpectedFixedLenList {
expected_len: usize,
actual: Value,
},
ExpectedEmpty {
actual: Value,
},
ExpectedMap {
actual: Value,
},
ExpectedTable {
actual: Value,
},
ExpectedFunction {
actual: Value,
},
/// A string, list, map or table value was expected.
ExpectedCollection {
actual: Value,
},
/// Tried to append a child to a leaf node.
/// Leaf nodes cannot have children.
AppendedToLeafNode(Node),
/// Tried to append a child to a node such that the precedence of the child
/// is not higher. This error should never occur. If it does, please file a
/// bug report.
PrecedenceViolation,
/// A `VariableIdentifier` operation did not find its value in the context.
VariableIdentifierNotFound(String),
/// A `FunctionIdentifier` operation did not find its value in the context.
FunctionIdentifierNotFound(String),
/// A value has the wrong type.
/// Only use this if there is no other error that describes the expected and
/// provided types in more detail.
TypeError {
/// The expected types.
expected: &'static [ValueType],
/// The actual value.
actual: Value,
},
/// A macro or function was called with the wrong type of input.
MacroArgumentType {
/// The macro that was called.
macro_info: ToolInfo<'static>,
/// The actual value.
actual: Value,
},
/// An operator is used with a wrong combination of types.
WrongTypeCombination {
/// The operator that whose evaluation caused the error.
operator: Operator,
/// The types that were used in the operator causing it to fail.
actual: Vec<ValueType>,
},
/// An opening brace without a matching closing brace was found.
UnmatchedLBrace,
/// A closing brace without a matching opening brace was found.
UnmatchedRBrace,
/// Left of an opening brace or right of a closing brace is a token that does not expect the brace next to it.
/// For example, writing `4(5)` would yield this error, as the `4` does not have any operands.
MissingOperatorOutsideOfBrace,
/// A `PartialToken` is unmatched, such that it cannot be combined into a full `Token`.
/// This happens if for example a single `=` is found, surrounded by whitespace.
/// It is not a token, but it is part of the string representation of some tokens.
UnmatchedPartialToken {
/// The unmatched partial token.
first: PartialToken,
/// The token that follows the unmatched partial token and that cannot be matched to the partial token, or `None`, if `first` is the last partial token in the stream.
second: Option<PartialToken>,
},
/// An addition operation performed by Rust failed.
AdditionError {
/// The first argument of the addition.
augend: Value,
/// The second argument of the addition.
addend: Value,
},
/// A subtraction operation performed by Rust failed.
SubtractionError {
/// The first argument of the subtraction.
minuend: Value,
/// The second argument of the subtraction.
subtrahend: Value,
},
/// A negation operation performed by Rust failed.
NegationError {
/// The argument of the negation.
argument: Value,
},
/// A multiplication operation performed by Rust failed.
MultiplicationError {
/// The first argument of the multiplication.
multiplicand: Value,
/// The second argument of the multiplication.
multiplier: Value,
},
/// A division operation performed by Rust failed.
DivisionError {
/// The first argument of the division.
dividend: Value,
/// The second argument of the division.
divisor: Value,
},
/// A modulation operation performed by Rust failed.
ModulationError {
/// The first argument of the modulation.
dividend: Value,
/// The second argument of the modulation.
divisor: Value,
},
/// A regular expression could not be parsed
InvalidRegex {
/// The invalid regular expression
regex: String,
/// Failure message from the regex engine
message: String,
},
/// A modification was attempted on a `Context` that does not allow modifications.
ContextNotMutable,
/// An escape sequence within a string literal is illegal.
IllegalEscapeSequence(String),
/// This context does not allow enabling builtin functions.
BuiltinFunctionsCannotBeEnabled,
/// This context does not allow disabling builtin functions.
BuiltinFunctionsCannotBeDisabled,
/// The function failed due to an external error.
MacroFailure(String),
/// A custom error explained by its message.
CustomMessage(String),
}
impl From<csv::Error> for Error {
fn from(value: csv::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<json::Error> for Error {
fn from(value: json::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<git2::Error> for Error {
fn from(value: git2::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<reqwest::Error> for Error {
fn from(value: reqwest::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<SystemTimeError> for Error {
fn from(value: SystemTimeError) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<trash::Error> for Error {
fn from(value: trash::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<toml::de::Error> for Error {
fn from(value: toml::de::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl Error {
pub(crate) fn expect_operator_argument_amount(actual: usize, expected: usize) -> Result<()> {
if actual == expected {
Ok(())
} else {
Err(Error::ExpectedOperatorArgumentAmount { expected, actual })
}
}
pub(crate) fn expect_function_argument_amount(
identifier: &str,
actual: usize,
expected: usize,
) -> Result<()> {
if actual == expected {
Ok(())
} else {
Err(Error::ExpectedFunctionArgumentAmount {
identifier: identifier.to_string(),
expected,
actual,
})
}
}
pub(crate) fn expected_minimum_function_argument_amount(
identifier: &str,
actual: usize,
minimum: usize,
) -> Result<()> {
if actual >= minimum {
Ok(())
} else {
Err(Error::ExpectedAtLeastFunctionArgumentAmount {
identifier: identifier.to_string(),
minimum,
actual,
})
}
}
pub fn type_error(actual: Value, expected: &'static [ValueType]) -> Self {
Error::TypeError { actual, expected }
}
pub fn wrong_type_combination(operator: Operator, actual: Vec<ValueType>) -> Self {
Error::WrongTypeCombination { operator, actual }
}
pub fn expected_string(actual: Value) -> Self {
Error::ExpectedString { actual }
}
pub fn expected_int(actual: Value) -> Self {
Error::ExpectedInt { actual }
}
pub fn expected_float(actual: Value) -> Self {
Error::ExpectedFloat { actual }
}
pub fn expected_number(actual: Value) -> Self {
Error::ExpectedNumber { actual }
}
pub fn expected_number_or_string(actual: Value) -> Self {
Error::ExpectedNumberOrString { actual }
}
pub fn expected_boolean(actual: Value) -> Self {
Error::ExpectedBoolean { actual }
}
pub fn expected_list(actual: Value) -> Self {
Error::ExpectedList { actual }
}
pub fn expected_fixed_len_list(expected_len: usize, actual: Value) -> Self {
Error::ExpectedFixedLenList {
expected_len,
actual,
}
}
pub fn expected_empty(actual: Value) -> Self {
Error::ExpectedEmpty { actual }
}
pub fn expected_map(actual: Value) -> Self {
Error::ExpectedMap { actual }
}
pub fn expected_table(actual: Value) -> Self {
Error::ExpectedTable { actual }
}
pub fn expected_function(actual: Value) -> Self {
Error::ExpectedFunction { actual }
}
pub fn expected_collection(actual: Value) -> Self {
Error::ExpectedCollection { actual }
}
pub(crate) fn unmatched_partial_token(
first: PartialToken,
second: Option<PartialToken>,
) -> Self {
Error::UnmatchedPartialToken { first, second }
}
pub(crate) fn addition_error(augend: Value, addend: Value) -> Self {
Error::AdditionError { augend, addend }
}
pub(crate) fn subtraction_error(minuend: Value, subtrahend: Value) -> Self {
Error::SubtractionError {
minuend,
subtrahend,
}
}
pub(crate) fn negation_error(argument: Value) -> Self {
Error::NegationError { argument }
}
pub(crate) fn multiplication_error(multiplicand: Value, multiplier: Value) -> Self {
Error::MultiplicationError {
multiplicand,
multiplier,
}
}
pub(crate) fn division_error(dividend: Value, divisor: Value) -> Self {
Error::DivisionError { dividend, divisor }
}
pub(crate) fn modulation_error(dividend: Value, divisor: Value) -> Self {
Error::ModulationError { dividend, divisor }
}
/// Constructs `EvalexprError::InvalidRegex(regex)`
pub fn invalid_regex(regex: String, message: String) -> Self {
Error::InvalidRegex { regex, message }
}
}
/// Returns `Ok(())` if the given value is a string or a numeric.
pub fn expect_number_or_string(actual: &Value) -> Result<()> {
match actual {
Value::String(_) | Value::Float(_) | Value::Integer(_) => Ok(()),
_ => Err(Error::expected_number_or_string(actual.clone())),
}
}
/// Returns `Ok(())` if the given value is a String, List, Map or Table.
pub fn _expect_collection(actual: &Value) -> Result<()> {
match actual {
Value::String(_) | Value::List(_) | Value::Map(_) | Value::Table(_) => Ok(()),
_ => Err(Error::expected_collection(actual.clone())),
}
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
TypeCheckFailure { tool_info, argument } => write!(f, "Type check failure. This is a bug with the tool or with Dust's internal type checking. Please report this bug and include this error message.\nToolInfo = {tool_info:?}\nargument = {argument}"),
AssertEqualFailed {expected, actual } => write!(f, "Equality assertion failed. {expected} does not equal {actual}."),
AssertFailed => write!(f, "Assertion failed. A false value was passed to \"assert\"."),
ExpectedOperatorArgumentAmount { expected, actual } => write!(
f,
"An operator expected {} arguments, but got {}.",
expected, actual
),
ExpectedFunctionArgumentAmount {
expected,
actual,
identifier,
} => write!(
f,
"{identifier} expected {expected} arguments, but got {actual}.",
),
ExpectedAtLeastFunctionArgumentAmount {
minimum,
actual,
identifier,
} => write!(
f,
"{identifier} expected a minimum of {minimum} arguments, but got {actual}.",
),
ExpectedString { actual } => {
write!(f, "Expected a Value::String, but got {:?}.", actual)
}
ExpectedInt { actual } => write!(f, "Expected a Value::Int, but got {:?}.", actual),
ExpectedFloat { actual } => write!(f, "Expected a Value::Float, but got {:?}.", actual),
ExpectedNumber { actual } => write!(
f,
"Expected a Value::Float or Value::Int, but got {:?}.",
actual
),
ExpectedNumberOrString { actual } => write!(
f,
"Expected a Value::Number or a Value::String, but got {:?}.",
actual
),
ExpectedBoolean { actual } => {
write!(f, "Expected a Value::Boolean, but got {:?}.", actual)
}
ExpectedList { actual } => write!(f, "Expected a Value::Tuple, but got {:?}.", actual),
ExpectedFixedLenList {
expected_len,
actual,
} => write!(
f,
"Expected a Value::Tuple of len {}, but got {:?}.",
expected_len, actual
),
ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual),
ExpectedMap { actual } => write!(f, "Expected a Value::Map, but got {:?}.", actual),
ExpectedTable { actual } => write!(f, "Expected a Value::Table, but got {:?}.", actual),
ExpectedFunction { actual } => {
write!(f, "Expected Value::Function, but got {:?}.", actual)
}
ExpectedCollection { actual } => {
write!(
f,
"Expected a string, list, map or table, but got {:?}.",
actual
)
}
AppendedToLeafNode(node) => write!(f, "Syntax error at \"{node}\"."),
PrecedenceViolation => write!(
f,
"Tried to append a node to another node with higher precedence."
),
VariableIdentifierNotFound(identifier) => write!(
f,
"Variable identifier is not bound to anything by context: {:?}.",
identifier
),
FunctionIdentifierNotFound(identifier) => write!(
f,
"Function identifier is not bound to anything by context: {:?}.",
identifier
),
TypeError { expected, actual } => {
write!(
f,
"Type Error. The value {actual} is not one of the following: {expected:?}.",
)
}
WrongTypeCombination { operator, actual } => write!(
f,
"The operator {:?} was called with a wrong combination of types: {:?}",
operator, actual
),
UnmatchedLBrace => write!(f, "Found an unmatched opening parenthesis '('."),
UnmatchedRBrace => write!(f, "Found an unmatched closing parenthesis ')'."),
MissingOperatorOutsideOfBrace { .. } => write!(
f,
"Found an opening parenthesis that is preceded by something that does not take \
any arguments on the right, or found a closing parenthesis that is succeeded by \
something that does not take any arguments on the left."
),
UnmatchedPartialToken { first, second } => {
if let Some(second) = second {
write!(
f,
"Found a partial token '{}' that should not be followed by '{}'.",
first, second
)
} else {
write!(
f,
"Found a partial token '{}' that should be followed by another partial \
token.",
first
)
}
}
AdditionError { augend, addend } => write!(f, "Error adding {} + {}", augend, addend),
SubtractionError {
minuend,
subtrahend,
} => write!(f, "Error subtracting {} - {}", minuend, subtrahend),
NegationError { argument } => write!(f, "Error negating -{}", argument),
MultiplicationError {
multiplicand,
multiplier,
} => write!(f, "Error multiplying {} * {}", multiplicand, multiplier),
DivisionError { dividend, divisor } => {
write!(f, "Error dividing {} / {}", dividend, divisor)
}
ModulationError { dividend, divisor } => {
write!(f, "Error modulating {} % {}", dividend, divisor)
}
InvalidRegex { regex, message } => write!(
f,
"Regular expression {:?} is invalid: {:?}",
regex, message
),
ContextNotMutable => write!(f, "Cannot manipulate context"),
BuiltinFunctionsCannotBeEnabled => {
write!(f, "This context does not allow enabling builtin functions")
}
BuiltinFunctionsCannotBeDisabled => {
write!(f, "This context does not allow disabling builtin functions")
}
IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string),
MacroFailure(message) => write!(f, "Function failure: {}", message),
CustomMessage(message) => write!(f, "Error: {}", message),
WrongColumnAmount { expected, actual } => write!(
f,
"Wrong number of columns for this table. Expected {expected}, found {actual}."
),
MacroArgumentType {
macro_info,
actual,
} => write!(
f,
"Wrong argument of type {:?} was passed to {}. Expected one of the following types: {:?}.",
actual.value_type(),
macro_info.identifier,
macro_info.inputs
),
}
}
}

110
src/error/mod.rs Normal file
View File

@ -0,0 +1,110 @@
//! 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."),
}
}
}

193
src/error/runtime_error.rs Normal file
View File

@ -0,0 +1,193 @@
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

@ -0,0 +1,30 @@
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
}
}

126
src/error/syntax_error.rs Normal file
View File

@ -0,0 +1,126 @@
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

@ -0,0 +1,274 @@
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,61 +0,0 @@
//! The top level of Dust's API with functions in interpret Dust code.
use crate::{token, tree, Result, Value, VariableMap};
/// Evaluate the given expression string.
///
/// # Examples
///
/// ```rust
/// # use dust_lib::*;
/// assert_eq!(eval("1 + 2 + 3"), Ok(Value::from(6)));
/// ```
///
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
pub fn eval(string: &str) -> Result<Value> {
let mut context = VariableMap::new();
eval_with_context(string, &mut context)
}
/// Evaluate the given expression string with the given context.
///
/// # Examples
///
/// ```rust
/// # use dust_lib::*;
/// let mut context = VariableMap::new();
/// context.set_value("one".into(), 1.into()).unwrap(); // Do proper error handling here
/// context.set_value("two".into(), 2.into()).unwrap(); // Do proper error handling here
/// context.set_value("three".into(), 3.into()).unwrap(); // Do proper error handling here
/// assert_eq!(eval_with_context("one + two + three", &mut context), Ok(Value::from(6)));
/// ```
pub fn eval_with_context(input: &str, context: &mut VariableMap) -> Result<Value> {
let without_comments = input
.lines()
.map(|line| {
let split = line.split_once('#');
if let Some((code, _comment)) = split {
code
} else {
line
}
})
.collect::<String>();
let split = without_comments.split_once("->");
if let Some((left, right)) = split {
let left_result = tree::tokens_to_operator_tree(token::tokenize(left)?)?
.eval_with_context_mut(context)?;
context.set_value("input", left_result)?;
let right_result = eval_with_context(right, context)?;
Ok(right_result)
} else {
tree::tokens_to_operator_tree(token::tokenize(&without_comments)?)?
.eval_with_context_mut(context)
}
}

157
src/interpret.rs Normal file
View File

@ -0,0 +1,157 @@
//! 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,28 +1,47 @@
//! The Dust library is used to implement the Dust language, `src/main.rs` implements the command
//! line binary.
//! The Dust library is used to parse, format and run dust source code.
//!
//! Using this library is simple and straightforward, see the [inferface] module for instructions on
//! interpreting Dust code. Most of the language's features are implemented in the [tools] module.
#![forbid(unsafe_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::{
error::*,
interface::*,
operator::Operator,
token::PartialToken,
tools::{Tool, ToolInfo, TOOL_LIST},
tree::Node,
value::{
function::Function, table::Table, time::Time, value_type::ValueType,
variable_map::VariableMap, Value,
},
abstract_tree::*, built_in_functions::BuiltInFunction, context::*, error::Error, interpret::*,
value::*,
};
pub mod tools;
pub use tree_sitter::Node as SyntaxNode;
mod error;
mod interface;
mod operator;
mod token;
mod tree;
mod value;
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");
}
}

407
src/main.rs Normal file
View File

@ -0,0 +1,407 @@
//! 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(())
}

View File

@ -1,547 +0,0 @@
use std::fmt::{self, Display, Formatter};
use crate::{error::*, value::Value, Result, VariableMap};
/// An enum that represents operators in the operator tree.
#[derive(Debug, PartialEq, Clone)]
pub enum Operator {
/// A root node in the operator tree.
/// The whole expression is stored under a root node, as well as each subexpression surrounded by parentheses.
RootNode,
/// A binary addition operator.
Add,
/// A binary subtraction operator.
Sub,
/// A unary negation operator.
Neg,
/// A binary multiplication operator.
Mul,
/// A binary division operator.
Div,
/// A binary modulo operator.
Mod,
/// A binary exponentiation operator.
Exp,
/// A binary equality comparator.
Eq,
/// A binary inequality comparator.
Neq,
/// A binary greater-than comparator.
Gt,
/// A binary lower-than comparator.
Lt,
/// A binary greater-than-or-equal comparator.
Geq,
/// A binary lower-than-or-equal comparator.
Leq,
/// A binary logical and operator.
And,
/// A binary logical or operator.
Or,
/// A binary logical not operator.
Not,
/// A binary assignment operator.
Assign,
/// A binary add-assign operator.
AddAssign,
/// A binary subtract-assign operator.
SubAssign,
/// A binary multiply-assign operator.
MulAssign,
/// A binary divide-assign operator.
DivAssign,
/// A binary modulo-assign operator.
ModAssign,
/// A binary exponentiate-assign operator.
ExpAssign,
/// A binary and-assign operator.
AndAssign,
/// A binary or-assign operator.
OrAssign,
/// An n-ary tuple constructor.
Tuple,
/// An n-ary subexpression chain.
Chain,
/// A constant value.
Const {
/** The value of the constant. */
value: Value,
},
/// A write to a variable identifier.
VariableIdentifierWrite {
/// The identifier of the variable.
identifier: String,
},
/// A read from a variable identifier.
VariableIdentifierRead {
/// The identifier of the variable.
identifier: String,
},
/// A function identifier.
FunctionIdentifier {
/// The identifier of the function.
identifier: String,
},
}
impl Operator {
pub(crate) fn value(value: Value) -> Self {
Operator::Const { value }
}
pub(crate) fn variable_identifier_write(identifier: String) -> Self {
Operator::VariableIdentifierWrite { identifier }
}
pub(crate) fn variable_identifier_read(identifier: String) -> Self {
Operator::VariableIdentifierRead { identifier }
}
pub(crate) fn function_identifier(identifier: String) -> Self {
Operator::FunctionIdentifier { identifier }
}
/// Returns the precedence of the operator.
/// A high precedence means that the operator has priority to be deeper in the tree.
pub(crate) const fn precedence(&self) -> i32 {
use crate::operator::Operator::*;
match self {
RootNode => 200,
Add | Sub => 95,
Neg => 110,
Mul | Div | Mod => 100,
Exp => 120,
Eq | Neq | Gt | Lt | Geq | Leq => 80,
And => 75,
Or => 70,
Not => 110,
Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign
| AndAssign | OrAssign => 50,
Tuple => 40,
Chain => 0,
Const { .. } => 200,
VariableIdentifierWrite { .. } | VariableIdentifierRead { .. } => 200,
FunctionIdentifier { .. } => 190,
}
}
/// Returns true if chains of operators with the same precedence as this one should be evaluated left-to-right,
/// and false if they should be evaluated right-to-left.
/// Left-to-right chaining has priority if operators with different order but same precedence are chained.
pub(crate) const fn is_left_to_right(&self) -> bool {
use crate::operator::Operator::*;
!matches!(self, Assign | FunctionIdentifier { .. })
}
/// Returns true if chains of this operator should be flattened into one operator with many arguments.
pub(crate) const fn is_sequence(&self) -> bool {
use crate::operator::Operator::*;
matches!(self, Tuple | Chain)
}
/// True if this operator is a leaf, meaning it accepts no arguments.
// Make this a const fn as soon as whatever is missing gets stable (issue #57563)
pub(crate) fn is_leaf(&self) -> bool {
self.max_argument_amount() == Some(0)
}
/// Returns the maximum amount of arguments required by this operator.
pub(crate) const fn max_argument_amount(&self) -> Option<usize> {
use crate::operator::Operator::*;
match self {
Add | Sub | Mul | Div | Mod | Exp | Eq | Neq | Gt | Lt | Geq | Leq | And | Or
| Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign
| AndAssign | OrAssign => Some(2),
Tuple | Chain => None,
Not | Neg | RootNode => Some(1),
Const { .. } => Some(0),
VariableIdentifierWrite { .. } | VariableIdentifierRead { .. } => Some(0),
FunctionIdentifier { .. } => Some(1),
}
}
/// Returns true if this operator is unary, i.e. it requires exactly one argument.
pub(crate) fn is_unary(&self) -> bool {
self.max_argument_amount() == Some(1) && *self != Operator::RootNode
}
/// Evaluates the operator with the given arguments and context.
pub(crate) fn eval(&self, arguments: &[Value], context: &VariableMap) -> Result<Value> {
use crate::operator::Operator::*;
match self {
RootNode => {
if let Some(first) = arguments.first() {
Ok(first.clone())
} else {
Ok(Value::Empty)
}
}
Add => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
expect_number_or_string(&arguments[0])?;
expect_number_or_string(&arguments[1])?;
if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) {
let mut result = String::with_capacity(a.len() + b.len());
result.push_str(a);
result.push_str(b);
Ok(Value::String(result))
} else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
let result = a.checked_add(b);
if let Some(result) = result {
Ok(Value::Integer(result))
} else {
Err(Error::addition_error(
arguments[0].clone(),
arguments[1].clone(),
))
}
} else if let (Ok(a), Ok(b)) = (arguments[0].as_number(), arguments[1].as_number())
{
Ok(Value::Float(a + b))
} else {
Err(Error::wrong_type_combination(
self.clone(),
vec![
arguments.get(0).unwrap().into(),
arguments.get(1).unwrap().into(),
],
))
}
}
Sub => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
arguments[0].as_number()?;
arguments[1].as_number()?;
if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
let result = a.checked_sub(b);
if let Some(result) = result {
Ok(Value::Integer(result))
} else {
Err(Error::subtraction_error(
arguments[0].clone(),
arguments[1].clone(),
))
}
} else {
Ok(Value::Float(
arguments[0].as_number()? - arguments[1].as_number()?,
))
}
}
Neg => {
Error::expect_operator_argument_amount(arguments.len(), 1)?;
arguments[0].as_number()?;
if let Ok(a) = arguments[0].as_int() {
let result = a.checked_neg();
if let Some(result) = result {
Ok(Value::Integer(result))
} else {
Err(Error::negation_error(arguments[0].clone()))
}
} else {
Ok(Value::Float(-arguments[0].as_number()?))
}
}
Mul => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
arguments[0].as_number()?;
arguments[1].as_number()?;
if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
let result = a.checked_mul(b);
if let Some(result) = result {
Ok(Value::Integer(result))
} else {
Err(Error::multiplication_error(
arguments[0].clone(),
arguments[1].clone(),
))
}
} else {
Ok(Value::Float(
arguments[0].as_number()? * arguments[1].as_number()?,
))
}
}
Div => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
arguments[0].as_number()?;
arguments[1].as_number()?;
if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
let result = a.checked_div(b);
if let Some(result) = result {
Ok(Value::Integer(result))
} else {
Err(Error::division_error(
arguments[0].clone(),
arguments[1].clone(),
))
}
} else {
Ok(Value::Float(
arguments[0].as_number()? / arguments[1].as_number()?,
))
}
}
Mod => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
arguments[0].as_number()?;
arguments[1].as_number()?;
if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
let result = a.checked_rem(b);
if let Some(result) = result {
Ok(Value::Integer(result))
} else {
Err(Error::modulation_error(
arguments[0].clone(),
arguments[1].clone(),
))
}
} else {
Ok(Value::Float(
arguments[0].as_number()? % arguments[1].as_number()?,
))
}
}
Exp => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
arguments[0].as_number()?;
arguments[1].as_number()?;
Ok(Value::Float(
arguments[0].as_number()?.powf(arguments[1].as_number()?),
))
}
Eq => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
Ok(Value::Boolean(arguments[0] == arguments[1]))
}
Neq => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
Ok(Value::Boolean(arguments[0] != arguments[1]))
}
Gt => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
expect_number_or_string(&arguments[0])?;
expect_number_or_string(&arguments[1])?;
if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) {
Ok(Value::Boolean(a > b))
} else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
Ok(Value::Boolean(a > b))
} else {
Ok(Value::Boolean(
arguments[0].as_number()? > arguments[1].as_number()?,
))
}
}
Lt => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
expect_number_or_string(&arguments[0])?;
expect_number_or_string(&arguments[1])?;
if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) {
Ok(Value::Boolean(a < b))
} else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
Ok(Value::Boolean(a < b))
} else {
Ok(Value::Boolean(
arguments[0].as_number()? < arguments[1].as_number()?,
))
}
}
Geq => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
expect_number_or_string(&arguments[0])?;
expect_number_or_string(&arguments[1])?;
if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) {
Ok(Value::Boolean(a >= b))
} else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
Ok(Value::Boolean(a >= b))
} else {
Ok(Value::Boolean(
arguments[0].as_number()? >= arguments[1].as_number()?,
))
}
}
Leq => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
expect_number_or_string(&arguments[0])?;
expect_number_or_string(&arguments[1])?;
if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) {
Ok(Value::Boolean(a <= b))
} else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
Ok(Value::Boolean(a <= b))
} else {
Ok(Value::Boolean(
arguments[0].as_number()? <= arguments[1].as_number()?,
))
}
}
And => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
let a = arguments[0].as_boolean()?;
let b = arguments[1].as_boolean()?;
Ok(Value::Boolean(a && b))
}
Or => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
let a = arguments[0].as_boolean()?;
let b = arguments[1].as_boolean()?;
Ok(Value::Boolean(a || b))
}
Not => {
Error::expect_operator_argument_amount(arguments.len(), 1)?;
let a = arguments[0].as_boolean()?;
Ok(Value::Boolean(!a))
}
Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign
| AndAssign | OrAssign => Err(Error::ContextNotMutable),
Tuple => Ok(Value::List(arguments.into())),
Chain => {
if arguments.is_empty() {
return Err(Error::expect_operator_argument_amount(0, 1).unwrap_err());
}
Ok(arguments.last().cloned().unwrap_or(Value::Empty))
}
Const { value } => {
Error::expect_operator_argument_amount(arguments.len(), 0)?;
Ok(value.clone())
}
VariableIdentifierWrite { identifier } => {
Error::expect_operator_argument_amount(arguments.len(), 0)?;
Ok(identifier.clone().into())
}
VariableIdentifierRead { identifier } => {
Error::expect_operator_argument_amount(arguments.len(), 0)?;
if let Some(value) = context.get_value(identifier)? {
Ok(value)
} else {
Err(Error::VariableIdentifierNotFound(identifier.clone()))
}
}
FunctionIdentifier { identifier } => {
Error::expect_operator_argument_amount(arguments.len(), 1)?;
let arguments = &arguments[0];
context.call_function(identifier, arguments)
}
}
}
/// Evaluates the operator with the given arguments and mutable context.
pub(crate) fn eval_mut(&self, arguments: &[Value], context: &mut VariableMap) -> Result<Value> {
use crate::operator::Operator::*;
match self {
Assign => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
let target = arguments[0].as_string()?;
context.set_value(target, arguments[1].clone())?;
Ok(Value::Empty)
}
AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign | AndAssign
| OrAssign => {
Error::expect_operator_argument_amount(arguments.len(), 2)?;
let target = arguments[0].as_string()?;
let left_value = Operator::VariableIdentifierRead {
identifier: target.clone(),
}
.eval(&Vec::new(), context)?;
let arguments = vec![left_value, arguments[1].clone()];
let result = match self {
AddAssign => Operator::Add.eval(&arguments, context),
SubAssign => Operator::Sub.eval(&arguments, context),
MulAssign => Operator::Mul.eval(&arguments, context),
DivAssign => Operator::Div.eval(&arguments, context),
ModAssign => Operator::Mod.eval(&arguments, context),
ExpAssign => Operator::Exp.eval(&arguments, context),
AndAssign => Operator::And.eval(&arguments, context),
OrAssign => Operator::Or.eval(&arguments, context),
_ => unreachable!(
"Forgot to add a match arm for an assign operation: {}",
self
),
}?;
context.set_value(target, result)?;
Ok(Value::Empty)
}
_ => self.eval(arguments, context),
}
}
}
impl Display for Operator {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use crate::operator::Operator::*;
match self {
RootNode => Ok(()),
Add => write!(f, "+"),
Sub => write!(f, "-"),
Neg => write!(f, "-"),
Mul => write!(f, "*"),
Div => write!(f, "/"),
Mod => write!(f, "%"),
Exp => write!(f, "^"),
Eq => write!(f, "=="),
Neq => write!(f, "!="),
Gt => write!(f, ">"),
Lt => write!(f, "<"),
Geq => write!(f, ">="),
Leq => write!(f, "<="),
And => write!(f, "&&"),
Or => write!(f, "||"),
Not => write!(f, "!"),
Assign => write!(f, " = "),
AddAssign => write!(f, " += "),
SubAssign => write!(f, " -= "),
MulAssign => write!(f, " *= "),
DivAssign => write!(f, " /= "),
ModAssign => write!(f, " %= "),
ExpAssign => write!(f, " ^= "),
AndAssign => write!(f, " &&= "),
OrAssign => write!(f, " ||= "),
Tuple => write!(f, ", "),
Chain => write!(f, "; "),
Const { value } => write!(f, "{}", value),
VariableIdentifierWrite { identifier } | VariableIdentifierRead { identifier } => {
write!(f, "{}", identifier)
}
FunctionIdentifier { identifier } => write!(f, "{}", identifier),
}
}
}

View File

@ -1,562 +0,0 @@
use std::fmt::{self, Display, Formatter};
use crate::error::{Error, Result};
#[derive(Clone, PartialEq, Debug)]
pub enum Token {
// Arithmetic
Plus,
Minus,
Star,
Slash,
Percent,
Hat,
// Logic
Eq,
Neq,
Gt,
Lt,
Geq,
Leq,
And,
Or,
Not,
// Precedence
LBrace,
RBrace,
// Assignment
Assign,
PlusAssign,
MinusAssign,
StarAssign,
SlashAssign,
PercentAssign,
HatAssign,
AndAssign,
OrAssign,
// Special
Comma,
Semicolon,
Yield(String, String),
// Values, Variables and Functions
Identifier(String),
Float(f64),
Int(i64),
Boolean(bool),
String(String),
Function(String),
}
/// A partial token is an input character whose meaning depends on the characters around it.
#[derive(Clone, Debug, PartialEq)]
pub enum PartialToken {
/// A partial token that unambiguously maps to a single token.
Token(Token),
/// A partial token that is a literal.
Literal(String),
/// A plus character '+'.
Plus,
/// A minus character '-'.
Minus,
/// A star character '*'.
Star,
/// A slash character '/'.
Slash,
/// A percent character '%'.
Percent,
/// A hat character '^'.
Hat,
/// A whitespace character, e.g. ' '.
Whitespace,
/// An equal-to character '='.
Eq,
/// An exclamation mark character '!'.
ExclamationMark,
/// A greater-than character '>'.
Gt,
/// A lower-than character '<'.
Lt,
/// An ampersand character '&'.
Ampersand,
/// A vertical bar character '|'.
VerticalBar,
}
// Make this a const fn as soon as is_whitespace and to_string get stable (issue #57563)
fn char_to_partial_token(c: char) -> PartialToken {
match c {
'+' => PartialToken::Plus,
'-' => PartialToken::Minus,
'*' => PartialToken::Star,
'/' => PartialToken::Slash,
'%' => PartialToken::Percent,
'^' => PartialToken::Hat,
'(' => PartialToken::Token(Token::LBrace),
')' => PartialToken::Token(Token::RBrace),
',' => PartialToken::Token(Token::Comma),
';' => PartialToken::Token(Token::Semicolon),
'=' => PartialToken::Eq,
'!' => PartialToken::ExclamationMark,
'>' => PartialToken::Gt,
'<' => PartialToken::Lt,
'&' => PartialToken::Ampersand,
'|' => PartialToken::VerticalBar,
c => {
if c.is_whitespace() {
PartialToken::Whitespace
} else {
PartialToken::Literal(c.to_string())
}
}
}
}
impl Token {
#[cfg(not(tarpaulin_include))]
pub(crate) const fn is_leftsided_value(&self) -> bool {
match self {
Token::Plus => false,
Token::Minus => false,
Token::Star => false,
Token::Slash => false,
Token::Percent => false,
Token::Hat => false,
Token::Eq => false,
Token::Neq => false,
Token::Gt => false,
Token::Lt => false,
Token::Geq => false,
Token::Leq => false,
Token::And => false,
Token::Or => false,
Token::Not => false,
Token::LBrace => true,
Token::RBrace => false,
Token::Comma => false,
Token::Semicolon => false,
Token::Yield(_, _) => false,
Token::Assign => false,
Token::PlusAssign => false,
Token::MinusAssign => false,
Token::StarAssign => false,
Token::SlashAssign => false,
Token::PercentAssign => false,
Token::HatAssign => false,
Token::AndAssign => false,
Token::OrAssign => false,
Token::Identifier(_) => true,
Token::Float(_) => true,
Token::Int(_) => true,
Token::Boolean(_) => true,
Token::String(_) => true,
Token::Function(_) => true,
}
}
#[cfg(not(tarpaulin_include))]
pub(crate) const fn is_rightsided_value(&self) -> bool {
match self {
Token::Plus => false,
Token::Minus => false,
Token::Star => false,
Token::Slash => false,
Token::Percent => false,
Token::Hat => false,
Token::Eq => false,
Token::Neq => false,
Token::Gt => false,
Token::Lt => false,
Token::Geq => false,
Token::Leq => false,
Token::And => false,
Token::Or => false,
Token::Not => false,
Token::LBrace => false,
Token::RBrace => true,
Token::Comma => false,
Token::Semicolon => false,
Token::Yield(_, _) => false,
Token::Assign => false,
Token::PlusAssign => false,
Token::MinusAssign => false,
Token::StarAssign => false,
Token::SlashAssign => false,
Token::PercentAssign => false,
Token::HatAssign => false,
Token::AndAssign => false,
Token::OrAssign => false,
Token::Identifier(_) => true,
Token::Float(_) => true,
Token::Int(_) => true,
Token::Boolean(_) => true,
Token::String(_) => true,
Token::Function(_) => true,
}
}
#[cfg(not(tarpaulin_include))]
pub(crate) fn is_assignment(&self) -> bool {
use Token::*;
matches!(
self,
Assign
| PlusAssign
| MinusAssign
| StarAssign
| SlashAssign
| PercentAssign
| HatAssign
| AndAssign
| OrAssign
)
}
}
/// Parses an escape sequence within a string literal.
fn parse_escape_sequence<Iter: Iterator<Item = char>>(iter: &mut Iter) -> Result<char> {
match iter.next() {
Some('"') => Ok('"'),
Some('\\') => Ok('\\'),
Some(c) => Err(Error::IllegalEscapeSequence(format!("\\{}", c))),
None => Err(Error::IllegalEscapeSequence("\\".to_string())),
}
}
/// Parses a string value from the given character iterator.
///
/// The first character from the iterator is interpreted as first character of the string.
/// The string is terminated by a double quote `"`.
/// Occurrences of `"` within the string can be escaped with `\`.
/// The backslash needs to be escaped with another backslash `\`.
fn parse_string_literal<Iter: Iterator<Item = char>>(mut iter: &mut Iter) -> Result<PartialToken> {
let mut result = String::new();
while let Some(c) = iter.next() {
match c {
'"' => break,
'\\' => result.push(parse_escape_sequence(&mut iter)?),
c => result.push(c),
}
}
Ok(PartialToken::Token(Token::String(result)))
}
fn parse_function<Iter: Iterator<Item = char>>(mut iter: &mut Iter) -> Result<PartialToken> {
let mut result = String::new();
while let Some(c) = iter.next() {
match c {
'\'' => break,
'\\' => result.push(parse_escape_sequence(&mut iter)?),
c => result.push(c),
}
}
Ok(PartialToken::Token(Token::Function(result)))
}
/// Converts a string to a vector of partial tokens.
fn str_to_partial_tokens(string: &str) -> Result<Vec<PartialToken>> {
let mut result = Vec::new();
let mut iter = string.chars().peekable();
while let Some(c) = iter.next() {
if c == '"' {
result.push(parse_string_literal(&mut iter)?);
} else if c == '\'' {
result.push(parse_function(&mut iter)?)
} else {
let partial_token = char_to_partial_token(c);
let if_let_successful =
if let (Some(PartialToken::Literal(last)), PartialToken::Literal(literal)) =
(result.last_mut(), &partial_token)
{
last.push_str(literal);
true
} else {
false
};
if !if_let_successful {
result.push(partial_token);
}
}
}
Ok(result)
}
/// Resolves all partial tokens by converting them to complex tokens.
fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> Result<Vec<Token>> {
let mut result = Vec::new();
while !tokens.is_empty() {
let first = tokens[0].clone();
let second = tokens.get(1).cloned();
let third = tokens.get(2).cloned();
let mut cutoff = 2;
result.extend(
match first {
PartialToken::Token(token) => {
cutoff = 1;
Some(token)
}
PartialToken::Plus => match second {
Some(PartialToken::Eq) => Some(Token::PlusAssign),
_ => {
cutoff = 1;
Some(Token::Plus)
}
},
PartialToken::Minus => match second {
Some(PartialToken::Eq) => Some(Token::MinusAssign),
_ => {
cutoff = 1;
Some(Token::Minus)
}
},
PartialToken::Star => match second {
Some(PartialToken::Eq) => Some(Token::StarAssign),
_ => {
cutoff = 1;
Some(Token::Star)
}
},
PartialToken::Slash => match second {
Some(PartialToken::Eq) => Some(Token::SlashAssign),
_ => {
cutoff = 1;
Some(Token::Slash)
}
},
PartialToken::Percent => match second {
Some(PartialToken::Eq) => Some(Token::PercentAssign),
_ => {
cutoff = 1;
Some(Token::Percent)
}
},
PartialToken::Hat => match second {
Some(PartialToken::Eq) => Some(Token::HatAssign),
_ => {
cutoff = 1;
Some(Token::Hat)
}
},
PartialToken::Literal(literal) => {
cutoff = 1;
if let Ok(number) = parse_dec_or_hex(&literal) {
Some(Token::Int(number))
} else if let Ok(number) = literal.parse::<f64>() {
Some(Token::Float(number))
} else if let Ok(boolean) = literal.parse::<bool>() {
Some(Token::Boolean(boolean))
} else {
// If there are two tokens following this one, check if the next one is
// a plus or a minus. If so, then attempt to parse all three tokens as a
// scientific notation number of the form `<coefficient>e{+,-}<exponent>`,
// for example [Literal("10e"), Minus, Literal("3")] => "1e-3".parse().
match (second, third) {
(Some(second), Some(third))
if second == PartialToken::Minus
|| second == PartialToken::Plus =>
{
if let Ok(number) =
format!("{}{}{}", literal, second, third).parse::<f64>()
{
cutoff = 3;
Some(Token::Float(number))
} else {
Some(Token::Identifier(literal.to_string()))
}
}
_ => Some(Token::Identifier(literal.to_string())),
}
}
}
PartialToken::Whitespace => {
cutoff = 1;
None
}
PartialToken::Eq => match second {
Some(PartialToken::Eq) => Some(Token::Eq),
_ => {
cutoff = 1;
Some(Token::Assign)
}
},
PartialToken::ExclamationMark => match second {
Some(PartialToken::Eq) => Some(Token::Neq),
_ => {
cutoff = 1;
Some(Token::Not)
}
},
PartialToken::Gt => match second {
Some(PartialToken::Eq) => Some(Token::Geq),
_ => {
cutoff = 1;
Some(Token::Gt)
}
},
PartialToken::Lt => match second {
Some(PartialToken::Eq) => Some(Token::Leq),
_ => {
cutoff = 1;
Some(Token::Lt)
}
},
PartialToken::Ampersand => match second {
Some(PartialToken::Ampersand) => match third {
Some(PartialToken::Eq) => {
cutoff = 3;
Some(Token::AndAssign)
}
_ => Some(Token::And),
},
_ => return Err(Error::unmatched_partial_token(first, second)),
},
PartialToken::VerticalBar => match second {
Some(PartialToken::VerticalBar) => match third {
Some(PartialToken::Eq) => {
cutoff = 3;
Some(Token::OrAssign)
}
_ => Some(Token::Or),
},
_ => return Err(Error::unmatched_partial_token(first, second)),
},
}
.into_iter(),
);
tokens = &tokens[cutoff..];
}
Ok(result)
}
impl Display for Token {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use self::Token::*;
match self {
Plus => write!(f, "+"),
Minus => write!(f, "-"),
Star => write!(f, "*"),
Slash => write!(f, "/"),
Percent => write!(f, "%"),
Hat => write!(f, "^"),
// Logic
Eq => write!(f, "=="),
Neq => write!(f, "!="),
Gt => write!(f, ">"),
Lt => write!(f, "<"),
Geq => write!(f, ">="),
Leq => write!(f, "<="),
And => write!(f, "&&"),
Or => write!(f, "||"),
Not => write!(f, "!"),
// Precedence
LBrace => write!(f, "("),
RBrace => write!(f, ")"),
// Assignment
Assign => write!(f, "="),
PlusAssign => write!(f, "+="),
MinusAssign => write!(f, "-="),
StarAssign => write!(f, "*="),
SlashAssign => write!(f, "/="),
PercentAssign => write!(f, "%="),
HatAssign => write!(f, "^="),
AndAssign => write!(f, "&&="),
OrAssign => write!(f, "||="),
// Special
Comma => write!(f, ","),
Semicolon => write!(f, ";"),
// Values => write!(f, ""), Variables and Functions
Identifier(identifier) => identifier.fmt(f),
Float(float) => float.fmt(f),
Int(int) => int.fmt(f),
Boolean(boolean) => boolean.fmt(f),
String(string) => fmt::Debug::fmt(string, f),
Function(string) => write!(f, "'{string}'"),
Yield(_, _) => todo!(),
}
}
}
impl Display for PartialToken {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use self::PartialToken::*;
match self {
Token(token) => token.fmt(f),
Literal(literal) => literal.fmt(f),
Whitespace => write!(f, " "),
Plus => write!(f, "+"),
Minus => write!(f, "-"),
Star => write!(f, "*"),
Slash => write!(f, "/"),
Percent => write!(f, "%"),
Hat => write!(f, "^"),
Eq => write!(f, "="),
ExclamationMark => write!(f, "!"),
Gt => write!(f, ">"),
Lt => write!(f, "<"),
Ampersand => write!(f, "&"),
VerticalBar => write!(f, "|"),
}
}
}
pub(crate) fn tokenize(string: &str) -> Result<Vec<Token>> {
partial_tokens_to_tokens(&str_to_partial_tokens(string)?)
}
fn parse_dec_or_hex(literal: &str) -> std::result::Result<i64, std::num::ParseIntError> {
if let Some(literal) = literal.strip_prefix("0x") {
literal.parse()
} else {
literal.parse()
}
}
#[cfg(test)]
mod tests {
use crate::token::{tokenize, Token};
#[test]
fn assignment_lhs_is_identifier() {
let tokens = tokenize("a = 1").unwrap();
assert_eq!(
tokens.as_slice(),
[
Token::Identifier("a".to_string()),
Token::Assign,
Token::Int(1)
]
);
}
}

View File

@ -1,416 +0,0 @@
//! Macros for collection values: strings, lists, maps and tables.
//!
//! Tests for this module are written in Dust and can be found at tests/collections.ds.
use crate::{Error, Result, Table, Tool, ToolInfo, Value, ValueType, VariableMap};
pub struct Sort;
impl Tool for Sort {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "sort",
description: "Apply default ordering to a list or table.",
group: "collections",
inputs: vec![ValueType::List, ValueType::Table],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
if let Ok(mut list) = argument.as_list().cloned() {
list.sort();
Ok(Value::List(list))
} else if let Ok(mut table) = argument.as_table().cloned() {
table.sort();
Ok(Value::Table(table))
} else {
Err(crate::Error::ExpectedList {
actual: argument.clone(),
})
}
}
}
pub struct Transform;
impl Tool for Transform {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "transform",
description: "Alter a list by calling a function on each value.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::List,
ValueType::Function,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?.as_list()?;
let list = argument[0].as_list()?;
let function = argument[1].as_function()?;
let mut mapped_list = Vec::with_capacity(list.len());
for value in list {
let mut context = VariableMap::new();
context.set_value("input", value.clone())?;
let mapped_value = function.run_with_context(&mut context)?;
mapped_list.push(mapped_value);
}
Ok(Value::List(mapped_list))
}
}
pub struct String;
impl Tool for String {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "string",
description: "Stringify a value.",
group: "collections",
inputs: vec![
ValueType::String,
ValueType::Function,
ValueType::Float,
ValueType::Integer,
ValueType::Boolean,
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
let string = match argument {
Value::String(string) => string.clone(),
Value::Function(function) => function.to_string(),
Value::Float(float) => float.to_string(),
Value::Integer(integer) => integer.to_string(),
Value::Boolean(boolean) => boolean.to_string(),
_ => return self.fail(argument),
};
Ok(Value::String(string))
}
}
pub struct Replace;
impl Tool for Replace {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "replace",
description: "Replace all occurences of a substring in a string.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::String,
ValueType::String,
ValueType::String,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?.as_list()?;
let target = argument[0].as_string()?;
let to_remove = argument[1].as_string()?;
let replacement = argument[2].as_string()?;
let result = target.replace(to_remove, replacement);
Ok(Value::String(result))
}
}
pub struct Count;
impl Tool for Count {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "count",
description: "Return the number of items in a collection.",
group: "collections",
inputs: vec![
ValueType::String,
ValueType::List,
ValueType::Map,
ValueType::Table,
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
let len = match argument {
Value::String(string) => string.chars().count(),
Value::List(list) => list.len(),
Value::Map(map) => map.len(),
Value::Table(table) => table.len(),
Value::Function(_)
| Value::Float(_)
| Value::Integer(_)
| Value::Boolean(_)
| Value::Time(_)
| Value::Empty => return self.fail(argument),
};
Ok(Value::Integer(len as i64))
}
}
pub struct CreateTable;
impl Tool for CreateTable {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "create_table",
description: "Define a new table with a list of column names and list of rows.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::ListOf(Box::new(ValueType::String)),
ValueType::ListOf(Box::new(ValueType::List)),
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?.as_list()?;
let column_name_inputs = argument[0].as_list()?;
let mut column_names = Vec::with_capacity(column_name_inputs.len());
for name in column_name_inputs {
column_names.push(name.as_string()?.clone());
}
let column_count = column_names.len();
let rows = argument[1].as_list()?;
let mut table = Table::new(column_names);
for row in rows {
let row = row.as_fixed_len_list(column_count)?;
table.insert(row.clone()).unwrap();
}
Ok(Value::Table(table))
}
}
pub struct Rows;
impl Tool for Rows {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "rows",
description: "Extract a table's rows as a list.",
group: "collections",
inputs: vec![ValueType::Table],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
if let Value::Table(table) = argument {
let rows = table
.rows()
.iter()
.map(|row| Value::List(row.clone()))
.collect();
Ok(Value::List(rows))
} else {
self.fail(argument)
}
}
}
pub struct Insert;
impl Tool for Insert {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "insert",
description: "Add new rows to a table.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::Table,
ValueType::ListOf(Box::new(ValueType::List)),
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_list()?;
let new_rows = argument[1].as_list()?;
let mut table = argument[0].as_table()?.clone();
table.reserve(new_rows.len());
for row in new_rows {
let row = row.as_list()?.clone();
table.insert(row)?;
}
Ok(Value::Table(table))
}
}
pub struct Select;
impl Tool for Select {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "select",
description: "Extract one or more values based on their key.",
group: "collections",
inputs: vec![
ValueType::ListExact(vec![ValueType::Table, ValueType::String]),
ValueType::ListExact(vec![
ValueType::Table,
ValueType::ListOf(Box::new(ValueType::String)),
]),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let arguments = argument.as_fixed_len_list(2)?;
let collection = &arguments[0];
if let Value::List(list) = collection {
let mut selected = Vec::new();
let index = arguments[1].as_int()?;
let value = list.get(index as usize);
if let Some(value) = value {
selected.push(value.clone());
return Ok(Value::List(selected));
} else {
return Ok(Value::List(selected));
}
}
let mut column_names = Vec::new();
if let Value::List(columns) = &arguments[1] {
for column in columns {
let name = column.as_string()?;
column_names.push(name.clone());
}
} else if let Value::String(column) = &arguments[1] {
column_names.push(column.clone());
} else {
todo!()
};
if let Value::Map(map) = collection {
let mut selected = VariableMap::new();
for (key, value) in map.inner() {
if column_names.contains(key) {
selected.set_value(key, value.clone())?;
}
}
return Ok(Value::Map(selected));
}
if let Value::Table(table) = collection {
let selected = table.select(&column_names);
return Ok(Value::Table(selected));
}
todo!()
}
}
pub struct ForEach;
impl Tool for ForEach {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "for_each",
description: "Run an operation on every item in a collection.",
group: "collections",
inputs: vec![],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_list()?;
Error::expected_minimum_function_argument_amount(
self.info().identifier,
2,
argument.len(),
)?;
let table = argument[0].as_table()?;
let columns = argument[1].as_list()?;
let mut column_names = Vec::new();
for column in columns {
let name = column.as_string()?;
column_names.push(name.clone());
}
let selected = table.select(&column_names);
Ok(Value::Table(selected))
}
}
pub struct Where;
impl Tool for Where {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "where",
description: "Keep rows matching a predicate.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::Table,
ValueType::Function,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?.as_list()?;
let table = &argument[0].as_table()?;
let function = argument[1].as_function()?;
let mut context = VariableMap::new();
let mut new_table = Table::new(table.column_names().clone());
for row in table.rows() {
for (column_index, cell) in row.iter().enumerate() {
let column_name = table.column_names().get(column_index).unwrap();
context.set_value(column_name, cell.clone())?;
}
let keep_row = function.run_with_context(&mut context)?.as_boolean()?;
if keep_row {
new_table.insert(row.clone())?;
}
}
Ok(Value::Table(new_table))
}
}

View File

@ -1,119 +0,0 @@
use std::process::Command;
use crate::{Result, Tool, ToolInfo, Value, ValueType};
pub struct Sh;
impl Tool for Sh {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "sh",
description: "Pass input to the Bourne Shell.",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new("sh").arg("-c").arg(argument).spawn()?.wait()?;
Ok(Value::Empty)
}
}
pub struct Bash;
impl Tool for Bash {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "bash",
description: "Pass input to the Bourne Again Shell.",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new("bash")
.arg("-c")
.arg(argument)
.spawn()?
.wait()?;
Ok(Value::Empty)
}
}
pub struct Fish;
impl Tool for Fish {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "fish",
description: "Pass input to the fish shell.",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new("fish")
.arg("-c")
.arg(argument)
.spawn()?
.wait()?;
Ok(Value::Empty)
}
}
pub struct Zsh;
impl Tool for Zsh {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "zsh",
description: "Pass input to the Z shell.",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new("zsh")
.arg("-c")
.arg(argument)
.spawn()?
.wait()?;
Ok(Value::Empty)
}
}
pub struct Raw;
impl Tool for Raw {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "raw",
description: "Run input as a command without a shell",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new(argument).spawn()?.wait()?;
Ok(Value::Empty)
}
}

View File

@ -1,171 +0,0 @@
//! Convert values to and from data formats like JSON and TOML.
use crate::{Result, Table, Tool, ToolInfo, Value, ValueType};
pub struct FromToml;
impl Tool for FromToml {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "from_toml",
description: "Create a value from a TOML string.",
group: "data",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
let value = toml::from_str(&argument)?;
Ok(value)
}
}
pub struct FromJson;
impl Tool for FromJson {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "from_json",
description: "Get a whale value from a JSON string.",
group: "data",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
let value = serde_json::from_str(argument)?;
Ok(value)
}
}
pub struct ToJson;
impl Tool for ToJson {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "to_json",
description: "Create a JSON string from a whale value.",
group: "data",
inputs: vec![ValueType::Any],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let json = serde_json::to_string(argument)?;
Ok(Value::String(json))
}
}
pub struct FromCsv;
impl Tool for FromCsv {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "from_csv",
description: "Create a whale value from a CSV string.",
group: "data",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let csv = argument.as_string()?;
let mut reader = csv::Reader::from_reader(csv.as_bytes());
let headers = reader
.headers()?
.iter()
.map(|header| header.trim().trim_matches('"').to_string())
.collect();
let mut table = Table::new(headers);
for result in reader.records() {
let row = result?
.iter()
.map(|column| {
let column = column.trim().trim_matches('"').trim_matches('\'');
if let Ok(integer) = column.parse::<i64>() {
Value::Integer(integer)
} else if let Ok(float) = column.parse::<f64>() {
Value::Float(float)
} else {
Value::String(column.to_string())
}
})
.collect();
table.insert(row)?;
}
Ok(Value::Table(table))
}
}
pub struct ToCsv;
impl Tool for ToCsv {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "to_csv",
description: "Convert a value to a string of comma-separated values.",
group: "data",
inputs: vec![ValueType::Any],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let mut buffer = Vec::new();
let mut writer = csv::Writer::from_writer(&mut buffer);
match argument {
Value::String(string) => {
writer.write_record([string])?;
}
Value::Float(float) => {
writer.write_record(&[float.to_string()])?;
}
Value::Integer(integer) => {
writer.write_record(&[integer.to_string()])?;
}
Value::Boolean(boolean) => {
writer.write_record(&[boolean.to_string()])?;
}
Value::List(list) => {
let string_list = list.iter().map(|value| value.to_string());
writer.write_record(string_list)?;
}
Value::Empty => {}
Value::Map(map) => {
writer.write_record(map.inner().keys())?;
writer.write_record(map.inner().values().map(|value| value.to_string()))?;
}
Value::Table(table) => {
writer.write_record(table.column_names())?;
for row in table.rows() {
let row_string = row.iter().map(|value| value.to_string());
writer.write_record(row_string)?;
}
}
Value::Function(_) => todo!(),
Value::Time(time) => {
writer.write_record(&[time.to_string()])?;
}
}
writer.flush()?;
Ok(Value::String(
String::from_utf8_lossy(writer.get_ref()).to_string(),
))
}
}

View File

@ -1,123 +0,0 @@
use std::process::Command;
use sysinfo::{DiskExt, System, SystemExt};
use crate::{Result, Table, Tool, ToolInfo, Value, ValueType};
pub struct ListDisks;
impl Tool for ListDisks {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "list_disks",
description: "List all block devices.",
group: "disks",
inputs: vec![ValueType::Empty],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
argument.as_empty()?;
let mut sys = System::new_all();
sys.refresh_all();
let mut disk_table = Table::new(vec![
"name".to_string(),
"kind".to_string(),
"file system".to_string(),
"mount point".to_string(),
"total space".to_string(),
"available space".to_string(),
"is removable".to_string(),
]);
for disk in sys.disks() {
let name = disk.name().to_string_lossy().to_string();
let kind = disk.kind();
let file_system = String::from_utf8_lossy(disk.file_system()).to_string();
let mount_point = disk.mount_point().to_str().unwrap().to_string();
let total_space = disk.total_space() as i64;
let available_space = disk.available_space() as i64;
let is_removable = disk.is_removable();
let row = vec![
Value::String(name),
Value::String(format!("{kind:?}")),
Value::String(file_system),
Value::String(mount_point),
Value::Integer(total_space),
Value::Integer(available_space),
Value::Boolean(is_removable),
];
disk_table.insert(row)?;
}
Ok(Value::Table(disk_table))
}
}
pub struct Partition;
impl Tool for Partition {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "partition",
description: "Partition a disk, clearing its content.",
group: "disks",
inputs: vec![ValueType::Map],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_map()?;
let path = argument
.get_value("path")?
.unwrap_or(Value::Empty)
.as_string()?
.clone();
let label = argument
.get_value("label")?
.unwrap_or(Value::Empty)
.as_string()?
.clone();
let name = argument
.get_value("name")?
.unwrap_or(Value::Empty)
.as_string()?
.clone();
let filesystem = argument
.get_value("filesystem")?
.unwrap_or(Value::Empty)
.as_string()?
.clone();
let range = argument
.get_value("range")?
.unwrap_or(Value::Empty)
.as_list()?
.clone();
if range.len() != 2 {
return Err(crate::Error::ExpectedFixedLenList {
expected_len: 2,
actual: Value::List(range),
});
}
let range_start = range[0].as_string()?;
let range_end = range[1].as_string()?;
let script = format!(
"sudo parted {path} mklabel {label} mkpart {name} {filesystem} {range_start} {range_end}"
);
Command::new("fish")
.arg("-c")
.arg(&script)
.spawn()?
.wait()?;
Ok(Value::Empty)
}
}

View File

@ -1,487 +0,0 @@
//! Dust commands for managing files and directories.
use std::{
fs::{self, OpenOptions},
io::{Read, Write as IoWrite},
path::PathBuf,
};
use crate::{Error, Result, Table, Time, Tool, ToolInfo, Value, ValueType};
pub struct Append;
impl Tool for Append {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "append",
description: "Append data to a file.",
group: "filesystem",
inputs: vec![ValueType::ListExact(vec![
ValueType::String,
ValueType::Any,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let arguments = argument.as_fixed_len_list(2)?;
let path = arguments[0].as_string()?;
let content = arguments[1].as_string()?;
let mut file = OpenOptions::new().append(true).open(path)?;
file.write_all(content.as_bytes())?;
Ok(Value::Empty)
}
}
pub struct CreateDir;
impl Tool for CreateDir {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "create_dir",
description: "Create one or more directories.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = argument.as_string()?;
fs::create_dir_all(path)?;
Ok(Value::Empty)
}
}
pub struct FileMetadata;
impl Tool for FileMetadata {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "file_metadata",
description: "Get metadata for files.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path_string = argument.as_string()?;
let metadata = PathBuf::from(path_string).metadata()?;
let created = metadata.accessed()?.elapsed()?.as_secs() / 60;
let accessed = metadata.accessed()?.elapsed()?.as_secs() / 60;
let modified = metadata.modified()?.elapsed()?.as_secs() / 60;
let read_only = metadata.permissions().readonly();
let size = metadata.len();
let mut file_table = Table::new(vec![
"path".to_string(),
"size".to_string(),
"created".to_string(),
"accessed".to_string(),
"modified".to_string(),
"read only".to_string(),
]);
file_table.insert(vec![
Value::String(path_string.clone()),
Value::Integer(size as i64),
Value::Integer(created as i64),
Value::Integer(accessed as i64),
Value::Integer(modified as i64),
Value::Boolean(read_only),
])?;
Ok(Value::Table(file_table))
}
}
pub struct ReadDir;
impl Tool for ReadDir {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "read_dir",
description: "Read the content of a directory.",
group: "filesystem",
inputs: vec![ValueType::String, ValueType::Empty],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = if let Ok(path) = argument.as_string() {
path
} else if argument.is_empty() {
"."
} else {
return Err(Error::TypeError {
expected: &[ValueType::Empty, ValueType::String],
actual: argument.clone(),
});
};
let dir = fs::read_dir(path)?;
let mut file_table = Table::new(vec![
"path".to_string(),
"size".to_string(),
"created".to_string(),
"accessed".to_string(),
"modified".to_string(),
"read only".to_string(),
]);
for entry in dir {
let entry = entry?;
let file_type = entry.file_type()?;
let file_name = if file_type.is_dir() {
let name = entry.file_name().into_string().unwrap_or_default();
format!("{name}/")
} else {
entry.file_name().into_string().unwrap_or_default()
};
let metadata = entry.path().metadata()?;
let created_timestamp = metadata.accessed()?;
let created = Time::from(created_timestamp);
let accessed_timestamp = metadata.accessed()?;
let accessed = Time::from(accessed_timestamp);
let modified_timestamp = metadata.modified()?;
let modified = Time::from(modified_timestamp);
let read_only = metadata.permissions().readonly();
let size = metadata.len();
file_table.insert(vec![
Value::String(file_name),
Value::Integer(size as i64),
Value::Time(created),
Value::Time(accessed),
Value::Time(modified),
Value::Boolean(read_only),
])?;
}
Ok(Value::Table(file_table))
}
}
pub struct ReadFile;
impl Tool for ReadFile {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "read_file",
description: "Read file contents.",
group: "filesystem",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = argument.as_string()?;
let mut contents = String::new();
OpenOptions::new()
.read(true)
.create(false)
.open(path)?
.read_to_string(&mut contents)?;
Ok(Value::String(contents))
}
}
pub struct RemoveDir;
impl Tool for RemoveDir {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "remove_dir",
description: "Remove directories.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = argument.as_string()?;
fs::remove_dir(path)?;
Ok(Value::Empty)
}
}
pub struct MoveDir;
impl Tool for MoveDir {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "move_dir",
description: "Move a directory to a new path.",
group: "filesystem",
inputs: vec![ValueType::ListExact(vec![
ValueType::String,
ValueType::String,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_list()?;
Error::expect_function_argument_amount(self.info().identifier, argument.len(), 2)?;
let current_path = argument[0].as_string()?;
let target_path = argument[1].as_string()?;
let file_list = ReadDir.run(&Value::String(current_path.clone()))?;
for path in file_list.as_list()? {
let path = PathBuf::from(path.as_string()?);
let new_path = PathBuf::from(&target_path).join(&path);
if path.is_file() {
fs::copy(&path, target_path)?;
}
if path.is_symlink() && path.symlink_metadata()?.is_file() {
fs::copy(&path, new_path)?;
}
}
Ok(Value::Empty)
}
}
pub struct Trash;
impl Tool for Trash {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "trash",
description: "Move a file or directory to the trash.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = argument.as_string()?;
trash::delete(path)?;
Ok(Value::Empty)
}
}
pub struct Write;
impl Tool for Write {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "write",
description: "Write data to a file.",
group: "filesystem",
inputs: vec![ValueType::ListExact(vec![
ValueType::String,
ValueType::Any,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let strings = argument.as_list()?;
Error::expect_function_argument_amount(self.info().identifier, strings.len(), 2)?;
let path = strings.first().unwrap().as_string()?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)?;
for content in &strings[1..] {
let content = content.to_string();
file.write_all(content.as_bytes())?;
}
Ok(Value::Empty)
}
}
pub struct RemoveFile;
impl Tool for RemoveFile {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "remove_file",
description: "Permanently delete a file.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
if let Ok(path) = argument.as_string() {
fs::remove_file(path)?;
return Ok(Value::Empty);
}
if let Ok(path_list) = argument.as_list() {
for path in path_list {
let path = path.as_string()?;
fs::remove_file(path)?;
}
return Ok(Value::Empty);
}
Err(Error::expected_string(argument.clone()))
}
}
pub struct Watch;
impl Tool for Watch {
fn info(&self) -> crate::ToolInfo<'static> {
crate::ToolInfo {
identifier: "watch",
description: "Wait until a file changes.",
group: "filesystem",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
let path = PathBuf::from(argument);
let modified_old = path.metadata()?.modified()?;
let wait_time = loop {
let modified_new = path.metadata()?.modified()?;
if modified_old != modified_new {
break modified_new
.duration_since(modified_old)
.unwrap_or_default()
.as_millis() as i64;
}
};
Ok(Value::Integer(wait_time))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_dir() {
let path = PathBuf::from("./target/create_dir/");
let path_value = Value::String(path.to_string_lossy().to_string());
let _ = std::fs::remove_file(&path);
CreateDir.run(&path_value).unwrap();
assert!(path.is_dir());
}
#[test]
fn create_dir_nested() {
let path = PathBuf::from("./target/create_dir/nested");
let path_value = Value::String(path.to_string_lossy().to_string());
let _ = std::fs::remove_file(&path);
CreateDir.run(&path_value).unwrap();
assert!(path.is_dir());
}
#[test]
fn write() {
let path = PathBuf::from("./target/write.txt");
let path_value = Value::String(path.to_string_lossy().to_string());
let message = "hiya".to_string();
let message_value = Value::String(message.clone());
let _ = std::fs::remove_file(&path);
Write
.run(&Value::List(vec![path_value, message_value]))
.unwrap();
assert!(path.is_file());
}
#[test]
fn append() {
let path = PathBuf::from("./target/append.txt");
let path_value = Value::String(path.to_string_lossy().to_string());
let message = "hiya".to_string();
let message_value = Value::String(message.clone());
let _ = std::fs::remove_file(&path);
Write
.run(&Value::List(vec![
path_value.clone(),
message_value.clone(),
]))
.unwrap();
Append
.run(&Value::List(vec![path_value, message_value]))
.unwrap();
let read = fs::read_to_string(&path).unwrap();
assert_eq!("hiyahiya", read);
}
#[test]
fn read_file() {
let path = PathBuf::from("./target/read_file.txt");
let path_value = Value::String(path.to_string_lossy().to_string());
let message = "hiya".to_string();
let message_value = Value::String(message.clone());
let _ = std::fs::remove_file(&path);
Write
.run(&Value::List(vec![path_value.clone(), message_value]))
.unwrap();
let test = ReadFile.run(&path_value).unwrap();
let read = fs::read_to_string(&path).unwrap();
assert_eq!(test, Value::String(read));
}
#[test]
fn remove_file() {
let path = PathBuf::from("./target/remove_file.txt");
let path_value = Value::String(path.to_string_lossy().to_string());
let _ = std::fs::File::create(&path);
RemoveFile.run(&path_value).unwrap();
assert!(!path.exists());
}
}

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