Compare commits

...

394 commits

Author SHA1 Message Date
Erin a21fe4de57 Update Cargo lock 2023-11-30 16:55:40 +01:00
Erin 7aad9b83f0 Release bump 2023-05-19 00:25:14 +02:00
Erin 1bcd5ce9b0 Fixed BF code parsing 2023-05-18 23:49:54 +02:00
Erin 89c4fe68d2 Updated dependencies. 2023-05-18 20:33:58 +02:00
Erin 1d91e68b05 shorten file 2023-01-04 15:30:03 +01:00
Erin acd7cad29e Fix clippy complaining when used instead of cargo check with RA 2022-11-04 00:58:23 +01:00
Erin 4dfddc2cdb initial vars - &mut self 2022-09-21 00:28:57 +02:00
Erin e936585112 fmt??? 2022-09-19 20:58:50 +02:00
Erin 61fbd3ee28 Host Interface initial vars support 2022-09-19 20:58:32 +02:00
Erin 2690c65c21 stuff 2022-09-14 21:57:02 +02:00
Erin fd4845ac8d Bumped version 2022-09-14 21:52:17 +02:00
Erin 9803a393d4 Fixed #13 2022-09-14 21:50:04 +02:00
Erin a9fdff0663 fmt + clippy 2022-09-14 21:46:24 +02:00
Erin d004955c4e Fixed issue #12 2022-09-14 21:46:17 +02:00
Erin e674052942 thing 2022-08-18 14:58:46 +02:00
Erin cbb7152890 No need for the associated type :D 2022-07-02 12:55:10 +02:00
Erin 1781a66c3a Revert "Added emoji support for identifiers"
This reverts commit 167a5077df.
2022-07-02 12:47:16 +02:00
Erin 18943f184d cycle detecion on cart print - no longer stack overflow 2022-07-02 01:17:31 +02:00
Erin 167a5077df Added emoji support for identifiers 2022-07-02 00:47:09 +02:00
Erin 5c0e89e38e Invalid Token error + fixed forgotten refactor 2022-07-02 00:30:51 +02:00
Erin f2d3561508 Error comments + renaming 2022-07-02 00:27:38 +02:00
Erin 63af15cc94 EOF -> EOI 2022-07-02 00:22:40 +02:00
Erin 24c5ada994 Bumped AbleScript version
+ chore: update
+ clap minor update
+ change repo URLs
2022-07-02 00:19:44 +02:00
Erin 83b25b2b89 Implemented host interface 2022-07-02 00:17:29 +02:00
Erin e883010580 Well, Able and Eval functios can now change loops flow! 2022-07-01 23:12:59 +02:00
Erin 472b41cf16 Implemented finally 2022-07-01 22:56:22 +02:00
Erin 844ccd731a chore: cargo update 2022-06-23 19:00:11 +02:00
Erin 49e36b0a21 Value Refactoring 2022-06-17 20:58:37 +02:00
Erin 43c9293e4f Dependencies update 2022-06-07 23:32:25 +02:00
Erin 7140082ff6 Parser cleanup 2022-06-06 23:30:08 +02:00
Erin 84f54f12d6 Changed dim syntax 2022-06-03 00:10:19 +02:00
Erin 2c7596c871 ADDED VERY IMPORTANT CHANGE 2022-06-02 23:56:32 +02:00
Erin c0a2857122 Changed hopback to and again (credits: Evrey#6086) 2022-05-17 19:03:02 +02:00
Erin bb3de6db14 chore: cargo update 2022-05-17 18:04:09 +02:00
Alex Bethel 507f700c44 Merge branch 'v0.4.0-fixes' 2022-05-09 17:58:36 -06:00
Alex Bethel ea802fbdf9 Fix AbleScript lib doc comment 2022-05-09 17:56:28 -06:00
Erin 7ec0b0b39d Replaced break with enough (credits: Evrey#6086) 2022-05-06 23:52:43 +02:00
Erin 3d745e6082 Bumped version to 0.4.0 2022-05-06 21:22:57 +02:00
Erin e4114ed2e8 changed undefined -> cart coercion 2022-05-06 21:12:55 +02:00
Erin ed1b3bfb43 Implemented all coercions for undefined 2022-05-06 21:08:16 +02:00
Erin d97d940d23 Evaluating nonextant variable results into Undefined 2022-05-06 20:20:52 +02:00
Erin bda92cc2f7 Added Undefined type 2022-05-06 20:19:35 +02:00
Erin aa29a1fc86 {} -> () 2022-05-06 17:30:41 +02:00
Erin 86dfc6d1f7 Removed arity checking and arity error 2022-05-06 17:28:25 +02:00
Erin 28a5415207 Renamed few errors 2022-05-06 17:25:50 +02:00
Erin 766eb799c9 Changed error variant name to go by conventions 2022-05-05 00:39:13 +02:00
Erin edb03dec94 get_bit returns error kind which makes the error spanned 2022-05-05 00:36:13 +02:00
Erin 7b5a9aed89 Fixed zero division and zero assertion errors when [] / 0 2022-05-04 23:15:12 +02:00
Erin 947c2552b1 fmt + clippy 2022-05-03 23:19:10 +02:00
Erin 6ad0ab5e29 Added non-newline print. 2022-05-03 23:13:17 +02:00
Erin fd97655e04 Merge pull request 'T-Dark letter cases implemented' (#10) from feature/tdark-cases into master
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/10
2022-05-03 21:03:42 +00:00
Erin 1a61c66e10 T-Dark letter cases implemented 2022-05-02 23:40:42 +02:00
Erin ab76516624 extracted t-dark substitution to separate function 2022-05-02 23:12:32 +02:00
Erin b76e890fb7 Modified Str -> Int coercion 2022-04-26 00:18:11 +02:00
Erin 85226fbfbb Implemented key extraction 2022-04-25 14:56:35 +02:00
able e895ef775f Merge pull request 'Added String character escaping by dozenal " delimited unicode values' (#9) from feature/string-unicode-escapes into master
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/9
2022-04-25 12:48:07 +00:00
Erin 19744aa63a Added escapes test 2022-04-24 23:47:51 +02:00
Erin c6f4aaef24 Added some comments 2022-04-24 23:33:11 +02:00
Erin 90f8137b0d Implemented string unicode escapes 2022-04-24 23:29:15 +02:00
Erin 842c121901 sus 2022-04-24 22:01:31 +02:00
Erin c51a332f60 Merge pull request 'Changed the way how Melo-ing variables works' (#8) from improvements/melo into master
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/8
2022-04-23 11:54:22 +00:00
Erin db3b56d798 Double Melo causes variable deletion 2022-04-22 21:14:53 +02:00
Erin 5ccc181f47 Rc is got by reference by default 2022-04-22 21:09:03 +02:00
Erin 95e790d8e8 Reimplemented Melo
- Variable made into enum of `ValueRef or Melo`
- Melo-ing variable causes dropping of the Rc, decreasing reference count
2022-04-22 21:06:34 +02:00
Erin 3916b20b59 clearer comment 2022-04-18 22:09:56 +02:00
Erin 5aada3c09e Made Parser private and made parse function
Parser had only consturctor and parse functions public.

Likely, there would be just a creation of parser followed by .parse(),
and now this action is performed in `parse` function.
2022-04-18 22:09:41 +02:00
Erin 98b72b14e2 Renamed init function in parser 2022-04-18 22:01:35 +02:00
Erin 60fb95cb13 Added crate-level docs for ablescript 2022-04-18 21:59:57 +02:00
Erin ee6105dd91 chore: fmt 2022-04-18 21:43:32 +02:00
Erin f2340dd493 fixed comments and naming 2022-04-18 21:43:19 +02:00
Erin ea13c79fc4 replaced if with unless 2022-04-18 21:42:26 +02:00
Erin 6dd32793bb Chars are no longer special case of identifiers 2022-04-18 21:04:19 +02:00
Erin 0eb2698242 Expr::Literal contains Literal type which contains only possible types instead of any Value 2022-04-18 20:56:11 +02:00
Erin eac86b5e0b Changed variable declaration / assignment and equals syntax:
- `dim <ident> [value];` is now used for declaration
- `<value> =: <assignable>` is used for assignments
- As token `=` doesn't cause any ambiguity now, it can be used for equals operation
2022-04-18 20:34:08 +02:00
Erin ba019d3b7e Release binaries are now stripped 2022-04-16 22:11:13 +02:00
Erin c1bc0e8a96 No underscore! 2022-04-16 22:06:37 +02:00
Erin 35caa085f8 fmt 2022-04-12 22:15:53 +02:00
Erin de3b6dc048 version bump 2022-04-12 14:17:20 +02:00
Erin cee4a80584 dependency update + fmt 2022-04-12 14:16:34 +02:00
Erin 60505735c9 removed != in favour of ain't 2022-04-08 17:44:35 +02:00
Erin 89326fd344 Removed ! for ain't 2022-04-08 17:39:14 +02:00
Alex Bethel 19fa04f979 cargo fmt 2022-04-02 12:42:40 -06:00
Erin 45007e7d2b removed \ from idents 2022-04-02 20:39:48 +02:00
Erin ec48508ba7 changed line order 2022-04-02 14:13:49 +02:00
Erin 0b4db65ca3 This isn't AbleScript, we can just negate expressions :D 2022-04-02 13:44:29 +02:00
Erin eae118a78e Small change 2022-04-02 01:42:13 +02:00
Erin 3b829d7e55 Merge branch 'refactoring/ast-spans' 2022-04-02 01:37:33 +02:00
Erin b46c178692 Removed Booleans
We have Abooleans (where Always = true, Never = false), so why to duplicate stuff?

This PR was lying around unmerged for quite some time, merged without review.
2022-04-01 23:37:10 +00:00
Erin 38fe23d2b5 Removed ident type, fixed tests 2022-04-02 01:34:25 +02:00
Erin db7fc4d592 Generalised Spanned items 2022-04-02 01:22:46 +02:00
Erin d890d7cfff Simplified base 55 2022-04-02 01:06:47 +02:00
Erin a3d2922f7e fixed tests 2022-03-30 21:03:17 +02:00
Erin c1c8f17c8d Improved type coercions 2022-03-30 20:56:59 +02:00
Erin 6f663839a7 Removed booleans 2022-03-30 20:55:05 +02:00
Erin 86e1391ea2 Used checked next in require 2022-03-20 02:20:55 +01:00
Erin 5a5403a8df Changed way of String lexing 2022-03-13 13:18:51 +01:00
Erin c567341775 Added support for idents starting with _ 2022-03-12 23:26:39 +01:00
Erin 070f3b2f0e fixed spelling of ain't 2022-03-02 11:36:08 +01:00
Erin 9d6f5740b1 \ is valid part of identifiers 2022-03-01 23:07:23 +01:00
Erin 4f56411375 Renamed dir 2022-03-01 22:17:01 +01:00
Erin 5f842dc8d9 There, we are now, we do not need those. 2022-03-01 22:15:27 +01:00
Erin 0ec54ecc51 Introduced newtype for Rc<RefCell<Value>> 2022-03-01 22:13:49 +01:00
Erin 3f4cc58b87 Satisfied most of THE ALLMIGHTY CLIPPY's requests. 2022-03-01 21:53:58 +01:00
Erin b975bc52d0 Bool, Abool and Nil values are no longer tokens 2022-03-01 18:57:03 +01:00
Erin 567665a314 fixed strings 2022-02-24 21:53:43 +01:00
Erin a23dac3ce0 Rust 2021
- Consts map generated using iterators
2022-02-23 21:47:07 +01:00
able 81f713a5e2 Merge pull request 'You know what could be cursed? Using Rust's block comment syntax as String delimiters!' (#5) from change/string-delimiters into master
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/5
2022-02-23 14:11:14 +00:00
Erin cb6fcceb79 Fixed tests and examples 2022-02-22 22:49:56 +01:00
Erin f55f9e0512 Changed string delimiters
You know what could be cursed? Using Rust's block comment syntax as String delimiters!
2022-02-22 22:39:03 +01:00
Erin d1f18be279 Removed unused tokens from Lexer
Uh, they were lying there with no use for 9 months!
2022-02-22 22:31:05 +01:00
Erin 0615091b3c added simple history support 2022-02-14 00:17:55 +01:00
Erin 43ae772894 Exposed variables to public API 2022-02-14 00:12:03 +01:00
Erin edce9dcc91 Add from_value variable constructor 2022-02-14 00:11:37 +01:00
Erin ca4a9aba4c added new_with_vars 2022-02-14 00:08:48 +01:00
Erin 0134448472 Merge pull request 'change/isize-as-int' (#4) from change/isize-as-int into master
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/4
2022-02-13 22:17:13 +00:00
Erin 43c51f2c96 Merge branch 'master' into change/isize-as-int 2022-02-13 22:17:00 +00:00
Erin 0d00aba6ce Merge pull request 'Implemented built-in functios' (#3) from feature/builtins into master
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/3
2022-02-13 22:16:43 +00:00
Erin e79c98f2f6 Fixed all errors 2022-02-13 00:58:50 +01:00
Erin 5d29c8cf87 fixed base 55 2022-02-13 00:55:51 +01:00
Erin 8c867c7899 Value::Int is isize 2022-02-13 00:55:19 +01:00
Erin 5b320259f6 Changed Built-in functio -> integer coercion 2022-02-13 00:37:17 +01:00
Erin a9a9cc71e1 Implemented builtin functio division 2022-02-13 00:30:40 +01:00
Erin 3338ec94c2 implemented subtraction for built-in functios 2022-02-13 00:25:56 +01:00
Erin d634692f12 Implemented not for built-in functios 2022-02-13 00:07:23 +01:00
Erin a1f6d1ae5f Implemented Display for built-ins 2022-02-12 22:52:14 +01:00
Erin 06ec25e717 Implemented coercions for built-in functios 2022-02-12 22:10:30 +01:00
Erin 405f88e394 Implemented built-in functios
- Missing coercions and operations
2022-02-12 21:14:55 +01:00
Erin 09617b2a89 Length by empty square braces
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/2
2022-01-28 20:03:31 +00:00
Erin 68e5e7cf41 Functio -> Int more cursed 2022-01-22 21:02:10 +01:00
Erin 7cf9433462 Cart -> Int more cursed 2022-01-22 20:52:09 +01:00
Erin 2191e126f0 Implemented [] as length operator 2022-01-22 20:48:21 +01:00
Erin 8e55872f10 Implemented Len in parser 2022-01-22 20:37:44 +01:00
Alex Bethel 1b84093eab Merge branch 'master' of ssh://git.ablecorp.us:20/AbleScript/able-script 2021-12-29 17:08:05 -06:00
Alex Bethel 912437f10c Add martens owo 2021-12-29 17:06:46 -06:00
Erin 88ef782cea Toggled Fat LTO on 2021-12-14 23:57:03 +01:00
Alex Bethel 7a34105c3e Add version number to ablescript_cli deps 2021-12-14 16:53:18 -06:00
Alex Bethel 0451e71104 Release 0.2
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/1
2021-12-14 22:31:52 +00:00
Erin 4b5ad3c462 Merge branch 'master' into feature/coercions 2021-12-14 23:30:07 +01:00
Alex Bethel b6413f6219 Merge branch 'feature/coercions' of ssh://git.ablecorp.us:20/AbleScript/able-script into feature/coercions 2021-12-14 16:16:44 -06:00
Alex Bethel 20b1408179 Implement function chain division 2021-12-14 16:16:28 -06:00
Erin 573df02ba6 Fixed T-Dark blocks in blocks 2021-12-14 23:16:19 +01:00
Erin c1471bcdb7 Removed clone 2021-12-14 23:05:21 +01:00
Erin 8302324d0f Merge branch 'feature/coercions' of https://git.ablecorp.us/AbleScript/able-script into feature/coercions 2021-12-14 23:02:57 +01:00
Erin 6c836125b3 Renamed stuff 2021-12-14 23:02:55 +01:00
Alex Bethel cf289a82d7 Fix carts.able formatting 2021-12-14 15:57:04 -06:00
Alex Bethel 57939a87b0 Arity-based functio deinterlacing 2021-12-14 15:56:40 -06:00
Erin db95d2f718 🚗 Implemented ordered functio chaining 2021-12-14 21:45:44 +01:00
Erin 872a16578f Removed a feature which disallowed comments in functios 2021-12-14 21:10:58 +01:00
Erin a9d9c7aa27 Implemented subtraction for functio chains... maybe? 2021-12-09 17:51:03 +01:00
Erin 6430387119 Implemented coercions when subtracting able functios 2021-12-09 17:18:37 +01:00
Erin c92cb7e818 Who cares about the content, we have 256 commits!
And I readded a thing
2021-12-09 00:38:36 +01:00
Erin 6d48dca547 sync 2021-12-09 00:35:43 +01:00
Erin 61ee6b3597 Git things beyond your comprehension mortals 2021-12-09 00:34:41 +01:00
Erin 3bfe600103 Implemented halfway of functio sub 2021-12-09 00:33:45 +01:00
Alex Bethel 0b703173f8 Function division 2021-12-08 16:33:06 -07:00
Erin 3c98caf2d6 Made Clippy happy (so he will not kill us in sleep) 2021-12-08 22:56:12 +01:00
Alex Bethel 3cdca1666a Use b-string instead of array of b-chars 2021-12-08 14:39:04 -07:00
Erin 20c6b4c7cf Implemented Abool -> Functio 2021-12-07 22:58:41 +01:00
Alex Bethel 3044ef91e3 Better BF function conversions 2021-12-07 14:27:45 -07:00
Alex Bethel ffcd0000c2 I made a cursed thing 😂 2021-12-07 14:24:58 -07:00
Erin f5e4be4d67 Implemented Functio to Aboolean 2021-12-07 21:57:37 +01:00
Erin 904d647031 huh??? 2021-12-07 21:20:24 +01:00
Erin 6889c3f3b2 Fmt! 2021-12-07 21:20:16 +01:00
Erin 65bb29d401 Function negation 2021-12-07 21:18:45 +01:00
Erin 3a59501217 Revert "Merge branch 'fix/unused-code'"
This reverts commit 6ee12a7c91, reversing
changes made to 706d39e860.
2021-12-07 20:54:23 +01:00
Alex Bethel 6ee12a7c91 Merge branch 'fix/unused-code' 2021-11-27 17:20:54 -06:00
Alex Bethel 2f31e7c3d5 Fix unused code warnings 2021-11-27 11:15:29 -06:00
Alex Bethel 6cd2b8bd44 Avoid trailing comma in cart printout 2021-11-27 11:11:03 -06:00
Alex Bethel 711b8e1c03 Slightly better ExecEnv::assign
A little more idiomatic, avoid some borrow checker clumsiness, add
comments.
2021-11-27 11:02:41 -06:00
Alex Bethel 9727a9b577 Assign read syntax 2021-11-05 17:09:53 -06:00
Alex Bethel 6849f7f296 Coerce indexing assignments into non-carts 2021-11-05 16:18:07 -06:00
Erin 5d6ac150ab Renamed semi_terminated to semicolon_terminated 2021-10-23 23:20:45 +02:00
Alex Bethel 5634a8ad2b Get cart assignments working 2021-10-23 15:08:10 -06:00
Alex Bethel 5db6eebdc0 Almost get cart assignments working 2021-10-23 14:17:17 -06:00
Erin ae1416e329 Read in AST takes Assignable instead of Ident 2021-10-23 21:53:21 +02:00
Alex Bethel 706d39e860 Merge branch 'feature/line_continuations' 2021-10-23 12:46:05 -06:00
Erin bcd446c5d1 Moved Assignable creation functions 2021-10-21 20:51:24 +02:00
Erin 0f198a2554 Merge pull request #43 from T-Dark0/T-Dark_fix_brainfuck_overflows
Fixed integer overflows. Why did I even write `checked_add` in the first place?
2021-10-19 23:37:47 +02:00
Erin 3e8b80af2a Removed in_the_past_this_used_to_crash_but_not_anymore test 2021-10-19 23:35:46 +02:00
T-Dark 6779d7c898 Fixed integer overflows. Why did I even write in the first place? 2021-10-19 17:29:53 +01:00
Erin 610ba7fc40 Change Cart AssignableKind to Index. 2021-10-13 13:20:23 +02:00
Alex Bethel 31fe3f53be Sort of fix cart assignments 2021-10-12 14:33:23 -06:00
Erin cdf4c5a308 improvement 2021-10-12 22:22:33 +02:00
Erin 4c6dd311a7 Added Assignable support in parser / AST 2021-10-12 22:14:20 +02:00
Alex Bethel 6eb97c6032 Add statement continuation in REPL in CLI
Typing ```
var foo = [
``` into the REPL now causes it to prompt you to complete the
statement rather than just printing an "unexpected EOF" error.
2021-10-09 22:31:14 -06:00
Erin e47e739e02 Generalised some lexer functions 2021-10-04 23:03:23 +02:00
Erin 71c5738a6e Renamed Iden to Ident 2021-10-04 23:00:18 +02:00
Alex Bethel e8349bf0d9 Improve consistency of code & comments
Changed all `&str.to_string()` into `&str.to_owned()`, and made the
grammar of the `Value::into_*` comments uniform.
2021-09-04 10:54:53 -06:00
Erin a288db985d Variable name shortened 2021-09-02 18:36:25 +02:00
Erin 4d1e85a295 Fixed division
x/0 is now x/42 instead of 42 which makes it more confusing
2021-09-02 18:35:33 +02:00
Erin 113554428f Clippy 2021-09-01 17:46:17 +02:00
Erin 82c09e5b1b All operations except functio ones are implemented
- Aboolean logic
- Cart division/multiplication
2021-08-31 00:41:37 +02:00
Erin 14e9eb5c6b Implemented negation for most types 2021-08-30 23:19:25 +02:00
Alex Bethel 6f8a49b711 Add parsing negative numbers 2021-08-30 15:03:29 -06:00
Erin 82239be160 Not is now lexing and parsing correctly 2021-08-30 22:55:31 +02:00
Erin 92f2c62d59 Removed And and Or ops because they aren't cursed enough 2021-08-30 22:18:09 +02:00
Erin 49ba66de07 Most of operations are implemented 2021-08-30 22:14:13 +02:00
Erin 58f0645b54 fmt + docs 2021-08-29 00:34:08 +02:00
Erin 31296ec0b8 Used body length for AbleFunctio to i32 instead of AST length 2021-08-29 00:28:45 +02:00
Erin 7a275556c7 Revert "Eval errors are now correctly spanned"
This reverts commit 2f5b954b7c.
2021-08-29 00:26:40 +02:00
Erin 975655a96e Merge branch 'feature/coercions-and-ops' of https://github.com/ablecorp/able-script into feature/coercions-and-ops 2021-08-29 00:24:24 +02:00
Erin 2f5b954b7c Eval errors are now correctly spanned 2021-08-29 00:23:59 +02:00
Erin 3ce85a11ee Eval errors are now correctly spanned 2021-08-29 00:16:15 +02:00
Erin a8c603164c AbleFunctio's body length is now by it's string representation 2021-08-29 00:10:44 +02:00
Erin e30eef32b5 Eval's integer representation is now its length
- consistency with other types
2021-08-29 00:07:26 +02:00
Erin 1c8722ba99 Added placeholders for And + Or
and used placeholders for -, * and / in interpret
2021-08-28 23:59:04 +02:00
Erin c66616ee2c Fixed add overflows 2021-08-28 23:55:36 +02:00
Erin e8f08a90d5 Display carts sorted 2021-08-28 23:52:58 +02:00
Erin 4c74e3bef3 Implement custom PartialEq for Value 2021-08-28 23:43:59 +02:00
Erin 8b57cbe5a5 Most coercions implemented 2021-08-28 23:27:35 +02:00
Erin 2c36b2a5f7 Require space after owo for making comments. 2021-08-23 18:58:28 +02:00
Alex Bethel 4c3d44dfd8 Fix panics on invalid indexes 2021-08-16 15:06:40 -06:00
Able 92134d68d5 Merge pull request #39 from AbleCorp/0.2.0 2021-08-15 19:48:37 -05:00
Erin 3297605bf2 Changed license file to MIT from Github template 2021-08-15 00:59:44 +02:00
Able b3b2d1a8ef Merge pull request #38 from AbleCorp/enhancement/relicensing 2021-08-11 13:36:03 -05:00
Erin c70e77f717 Relicensed to MIT 2021-08-11 20:32:12 +02:00
Erin aa854a067b Restructured project (#37)
* Separation to two crates
- `ablescript`: library, language
- `ablescript_cli` - cli: repl and executor for files

* Added lints (back) to library
- unsafe_code and unwrap_used are forbidden
2021-08-10 21:32:12 +02:00
Alex Bethel a2d240f471 Format .able test files
Unified mixed 2-space and 4-space indentation into uniform 3-space
indentation.
2021-08-09 14:41:02 -06:00
Alex Bethel fd9054d3d3 Functio & cart comparisons and hashing 2021-08-07 16:33:28 -06:00
Erin 3686b3b608 Added carts example 2021-08-01 18:47:13 +02:00
Erin 83a4f67121 Implemented callable expressions in parser 2021-08-01 18:36:09 +02:00
Erin 3df3cbbf6d Implemented callable expressions in AST and Interpret 2021-08-01 18:30:07 +02:00
Erin 0c000337d2 fmt 2021-08-01 18:28:59 +02:00
Erin 25a3c14145 Changed to correct naming conventions 2021-07-29 23:26:43 +02:00
Erin b451503a27 Obeyed clippy 2021-07-29 23:22:38 +02:00
Erin e3cfaaf32c removed unused enum variant 2021-07-29 23:04:04 +02:00
Erin 241f2261d1 Added history (session based) 2021-07-29 22:48:36 +02:00
Erin eb91aa96fa Added carts tests 2021-07-29 22:39:28 +02:00
Alex Bethel 3cd1115f5d Merge branch 'feature/carts' of github.com:AbleCorp/able-script into feature/carts 2021-07-29 13:30:11 -05:00
Alex Bethel 0bca9ff79c Get carts sort of working
still a bunch of `todo`s and needs a lot of work, but I'm hungry and
going on lunch break :)
2021-07-29 13:29:23 -05:00
Erin a6f86b7e26 Bumped version to 0.2.0 2021-07-29 19:18:06 +02:00
Erin 603a244b78 Nest the loop inside a match arm 2021-07-29 19:17:10 +02:00
Erin 0d497bccdd Added cart parsing 2021-07-29 18:58:11 +02:00
Erin b53cc1e768 Not fails on cart contruction parsing, does nothing. 2021-07-29 18:17:08 +02:00
Erin bf2e73a5d1 Extracted construction of carts to separate function 2021-07-27 12:14:11 +02:00
Erin e7bd81e12a Implemented cart indexing parsing 2021-07-27 12:09:36 +02:00
Erin ab1515cdb0 Added arrow token 2021-07-27 11:52:43 +02:00
Erin e847a76c6d Added cart support into AST 2021-07-27 11:51:05 +02:00
Alex Bethel 282a9a6a07 Fix broken type_errors unit test 2021-07-22 22:27:17 -05:00
Alex Bethel 0b0a8942de Remove latent debug println for BFF input 2021-07-22 22:11:55 -05:00
Alex Bethel dcc0c0b00f Merge branch 'master' of github.com:AbleCorp/able-script 2021-07-22 22:09:40 -05:00
Alex Bethel 501d182fb4 Use coercion in BFF argument passing 2021-07-22 22:08:04 -05:00
Alex Bethel 50f9bfa4c6 Eliminate type errors
Hooray, we've just made a whole class of errors impossible! :D
2021-07-16 19:03:27 -05:00
Alex Bethel 821eeae322 Silently fail when calling a non-function
The most cursed possible way to deal with this error.
2021-07-16 19:02:51 -05:00
Alex Bethel d3242484fc Make integer coercion infallible 2021-07-16 18:56:45 -05:00
Erin 0c795741e8 Reduced parser boilerplate 2021-07-15 21:39:01 +02:00
Alex Bethel 141220fb38 Fix function call type error message 2021-07-15 14:24:07 -05:00
Alex Bethel 3d9f847862 Make consts accessible from AbleScript code 2021-07-13 14:22:06 -05:00
Alex Bethel a225105f09 Prettier error handling 2021-07-13 13:54:27 -05:00
Alex Bethel 8818536717 Revert "Update README.md"
This reverts commit e02edbbd16.

It is, in fact, still better than Javascript.
2021-07-01 17:17:48 -05:00
Alex Bethel bc7427014d Add more info to Cargo.toml 2021-06-30 16:45:44 -05:00
Alex Bethel 1e8314b1a1 Merge branch 'feature/read' 2021-06-19 17:34:19 -05:00
Alex Bethel c1cc54be75 Merge 'feature/forty-two' into master 2021-06-19 17:32:12 -05:00
Alex Bethel eee173060c Implement read semantics in interpreter
Also added a test file `iotest.able` to just read data and print
it (the `cat` of AbleScript I guess).
2021-06-18 16:05:50 -05:00
Erin 3edbca51d5 Implemented read in Parser 2021-06-18 20:28:53 +02:00
Alex Bethel 430f0c2b90 Forbid use of unwrap w/ clippy 2021-06-16 10:36:52 -05:00
Alex Bethel 4011997e60 Clippy conformance 2021-06-16 10:35:06 -05:00
Alex Bethel cdb4a22443 Add --debug option for printing AST 2021-06-16 10:29:27 -05:00
Alex Bethel 098137082e Better error messages, remove debug AST printing 2021-06-16 10:25:55 -05:00
Alex Bethel 2e5799d9e6 Remove quotes around rickroll string
They're not necessary, because the `rickroll` file is already included
as a string.
2021-06-15 11:47:39 -05:00
Alex Bethel 9965d97713 Fix ignored unit tests in interpret.rs 2021-06-15 11:46:01 -05:00
Alex Bethel d1064802ec Fix unit tests 2021-06-15 11:29:52 -05:00
Alex Bethel 8aa6fc502a rustfmt: whoops, let's put a semicolon there
maybe I should have actually looked at what rustfmt produced before I
just blindly accepted it and pushed it onto master.
2021-06-15 10:26:44 -05:00
Alex Bethel 7bf3cc6f5d rustfmt: break long line 2021-06-15 10:24:50 -05:00
Alex Bethel ae0955209c Use pass-by-reference for variables, pass-by-value for expressions 2021-06-15 09:51:00 -05:00
Alex Bethel 66d4f4f82d Implement most of pass-by-reference
We need some parser changes to make it work fully: function arguments
are now identifiers rather than expressions.
2021-06-15 09:51:00 -05:00
Alex Bethel 4b0aa1602b Lack of else statements is intentional 2021-06-15 09:51:00 -05:00
Alex Bethel d80c138735 Improve pass-by-reference test 2021-06-15 09:51:00 -05:00
Alex Bethel d59b75fdb3 Add pass-by-reference test (doesn't work yet)
Also rename the "hello_world.able" test to "hello-world.able" for
consistency.
2021-06-15 09:51:00 -05:00
Alex Bethel 75938f27c0 Implement functio declaration & calling 2021-06-15 09:51:00 -05:00
Able c2de1f6411 Merge pull request #33 from HTG-YT/master 2021-06-15 04:39:07 -05:00
HTG-YT aa72945010 comply with the move of rickroll 2021-06-14 20:53:27 +08:00
HTG-YT 26df745dee move rickroll to src/ folder 2021-06-14 20:52:38 +08:00
HTG-YT 953128887b Update rickroll 2021-06-14 09:42:38 +08:00
HTG-YT b526d5c575 move rickroll content into a file 2021-06-14 09:42:23 +08:00
HTG-YT 712e6cef67 Create rickroll 2021-06-14 09:41:20 +08:00
HTG-YT 72e48d1b86 rustfmt: add trailing comma 2021-06-14 09:40:42 +08:00
HTG-YT 327466cddc Merge branch 'AbleCorp:master' into master 2021-06-14 09:40:00 +08:00
Erin 93289e02f1 Added build staus badge 2021-06-13 18:43:09 +02:00
Erin 232595c1be Added CI config
Rust Template
2021-06-13 18:14:41 +02:00
Erin f62bab110f Update README.md 2021-06-13 18:12:21 +02:00
HTG-YT 227fa8e35b handle Token::Rickroll in parser 2021-06-13 13:24:09 +08:00
HTG-YT fa0d3968f5 Update interpret.rs 2021-06-13 13:22:30 +08:00
HTG-YT 3646c5e32e handle StmtKind::Rickroll 2021-06-13 13:22:01 +08:00
HTG-YT 286a12b28e add StmtKind::Rickroll 2021-06-13 13:11:51 +08:00
HTG-YT 855383faac add rickroll lexeme in lexer 2021-06-13 13:11:02 +08:00
Alex Bethel da9ff64486 Make arithmetic errors evaluate to 42 2021-06-12 10:48:44 -05:00
Alex Bethel 92e0a22e24 Integrate const.rs as consts 2021-06-12 10:45:25 -05:00
Alex Bethel 976518a251 Reimplement BF function declaration semantics 2021-06-12 09:26:21 -05:00
Erin ff04c34cf6 Better comment 2021-06-11 19:44:53 +02:00
Erin 183c584e96 Removed todo when unknown token 2021-06-11 18:34:34 +02:00
Erin 3d7c89634e Fixed invalid spanning of UnexpectedToken in parse_expr 2021-06-11 18:31:25 +02:00
Erin 3bcef28843 BF code is now Vec<u8> 2021-06-11 18:10:11 +02:00
Erin 711ad27e00 Basic BfFunctio support 2021-06-11 17:52:47 +02:00
Alex Bethel 41316f5696 Re-enable assignment semantics 2021-06-11 10:05:48 -05:00
Erin 6f16d2594b Added variable assignment to parser
- Fixed parse error, that `rlyeh` returned HopBack
2021-06-11 16:59:40 +02:00
Erin 851f04c653 Merge pull request #29 from HTG-YT/interpreter-fixes
add `interessant`, `nevergonnagiveyouup` and `funny` constants
2021-06-11 09:33:48 +02:00
HTG-YT dd28ff985f add nevergonnagiveyouup constant 2021-06-11 15:26:30 +08:00
HTG-YT 77749bc32b add interessant and funny constants 2021-06-11 15:19:27 +08:00
Alex Bethel 7d7c1e4a1b Fix interpreter variable persistence bug 2021-06-08 19:21:33 -05:00
Alex Bethel dc5ef63fef Better error handling in REPL
We have much better spanned expression support, so now we get to show
it off!
2021-06-07 20:08:38 -05:00
Alex Bethel a0c1f93392 Remove 0..0 placeholder from unexpected_eof() 2021-06-07 20:03:26 -05:00
Alex Bethel ce20611c07 Fix all 0..0 span placeholders in interpret.rs 2021-06-07 19:57:44 -05:00
Alex Bethel d8f462e0b5 Fix unit tests 2021-06-07 17:35:49 -05:00
Alex Bethel addda8fc87 Remove giant comment block left in by mistake 2021-06-07 16:28:27 -05:00
Alex Bethel 426aee6a2b Repair interpreter after parser changes 2021-06-07 16:20:20 -05:00
Erin 903b1a3809 Added T-Dark test 2021-06-07 23:06:13 +02:00
Erin 8bd61a4a65 Added tests, Bugfix
- Originally, it spanned from operator (bug)
2021-06-07 22:58:28 +02:00
Erin 1f78cb0ee1 Added missing mut 2021-06-07 22:24:43 +02:00
Erin 902d8b914c Unified span terminology
- Add more derivations, because it's required by another parts of project
2021-06-07 22:21:21 +02:00
Erin f893e24aed Changed position terminology 2021-06-07 21:28:24 +02:00
Erin 2084ebeed1 Removed unwraps, added Rlyeh 2021-06-07 11:07:50 +02:00
Erin b7464f6ad3 Implement function calls
- And printing
- Revised terminology in Lexer
- Control flow
2021-06-07 11:00:06 +02:00
Erin b6bce72b94 Added logical operators 2021-06-07 09:17:30 +02:00
Erin 97f0271534 Binary operator boilerplate reduction 2021-06-07 09:17:18 +02:00
Erin a5a048728c Added basic parsing
- Expressions
- If, Functio
2021-06-07 00:09:45 +02:00
Erin b04bd77672 Initial chthulic error impl in Lexer
- See discussions #17
2021-06-06 23:15:11 +02:00
Erin 93065e7dc9 AST revamp
- Unified terminology of span in error.rs
2021-06-06 23:13:48 +02:00
Erin 6fd95f3cc2 Updated AST 2021-06-06 21:09:18 +02:00
Erin afe6588c05 Removed custom Lexer, reorganised Token definition
- As peeking will not be involved in parsing, it was removed
2021-06-06 20:28:13 +02:00
Erin 2c15f3dc17 Deleted old parser 2021-06-06 20:05:18 +02:00
Alex Bethel 4bda25c9f3 Correct spelling of "occurred" 2021-06-05 08:50:20 -05:00
Alex Bethel 6c2d5cc84a Prettier error handling 2021-06-04 18:56:26 -05:00
Able 5d230431e0 Merge pull request #28 from AlexBethel/bf-functio 2021-06-02 20:02:53 -05:00
Alex Bethel 528de718dc Clean up eval_expr some more
I keep going back and forth on how I want this block to look :P
Anyway, this is *probably* its final-ish form, until the parser gets
re-written.
2021-06-02 18:41:20 -05:00
Alex Bethel ce02aebd91 Use impl Value rather than trait BfWriter
It probably makes more sense to be writing values with
`Value::bf_write` than to go for the complexity of using a trait to
allow `Writer::write_value` (which also looks ambiguous because it's
not immediately clear that a generic name like "write_value" relates
to BF or AbleScript).
2021-06-02 18:20:30 -05:00
Alex Bethel 326d0511e7 Add Brainfuck functio interpretation
BF functios can now be declared and called from AbleScript code.
Function parameters supplied from AbleScript are serialized manually
into byte sequences using the `BfWriter` trait, and are available from
BF code using the input operator, `,`.

At the moment, BF functios simply write output to stdout, and are
therefore incapable of communicating results to the rest of the
program; this might change in the future if we can get something close
to pass-by-reference working.
2021-06-02 15:29:31 -05:00
Alex Bethel 07195d4cf6 Fix panic on arithmetic error
Divide by zero and add, subtract, or multiply with overflow are all
caught now and reported as an ArithmeticError, rather than causing the
interpreter to panic.
2021-05-30 13:32:29 -05:00
Alex Bethel acfd81ead2 More thorough unit tests 2021-05-30 13:32:29 -05:00
Alex Bethel 344a11084e Add some unit tests to interpret.rs
Currently `overflow_should_not_panic` and `variable_persistence` both
fail due to bugs in the interpreter.
2021-05-30 13:32:29 -05:00
Able 90773eea3d Merge pull request #27 from AlexBethel/master 2021-05-29 17:14:25 -05:00
Alex Bethel 099b9e23d6 Make base-55 encoding & decoding match
Changed "U"'s encoding to -210, so now when the README claims "U =
-210", it's actually accurate :)
2021-05-29 14:24:46 -05:00
Alex Bethel 585bf2e19f Use single-letter identifiers as base-55 digits 2021-05-29 10:45:39 -05:00
Able 7b21eaf48a Merge pull request #26 from Seppel3210/patch-1 2021-05-28 05:10:43 -05:00
Seppel3210 d52c775ed1 Change "*" versions to explicit versions
Change "*" versions to explicit versions in Cargo.toml
2021-05-28 09:30:16 +02:00
Able e02edbbd16 Update README.md 2021-05-27 17:51:08 -05:00
Able 18973333b0 Merge pull request #24 from AlexBethel/master 2021-05-25 23:06:08 -05:00
Alex Bethel c906366e3f Improve name & documentation accuracy
Renamed ControlFlow -> HaltStatus because that's what the enum really
is -- a status on why something halted. Also reviewed `interpret.rs`'s
documentation and fixed a few things that were out of date.
2021-05-25 21:55:02 -05:00
Alexander Bethel ec81ead3ea Implement break and hopback statements 2021-05-25 21:22:38 -05:00
Alexander Bethel 669f379700 Better abstractions, implement scoping rules 2021-05-25 13:26:01 -05:00
Alexander Bethel 109c77eeb2 Implement more statements
Added variable declaration, `if` statements, `loop` statements,
variable assignment, and variable banning to go along with
printing (which was already implemented). We still need function
declarations, brainfuck declarations, function calls, and the control
flow operators "break" and "hopback".
2021-05-24 10:50:26 -05:00
Able c27837a807 Slightly more curse 2021-05-24 01:18:36 -05:00
Able 84016d3dcd Merge pull request #22 from AlexBethel/master 2021-05-21 12:29:03 -05:00
Alexander Bethel b625a71711 Allow abool -> bool coercion
The expression `sometimes & true` now evaluates to `true` 50% of the
time and false 50% of the time, rather than throwing a type error
because `sometimes` is not a bool.
2021-05-21 12:25:37 -05:00
Alexander Bethel ea211fc3b0 Make ablescript -f run interpreter
`ablescript -f foo.able` will now both parse and interpret `foo.able`,
rather than just parsing it.
2021-05-20 18:24:18 -05:00
Alexander Bethel eccc00ff81 Implement basic interpreter
Added code for interpreting parsed AbleScript expressions and
statements, and hooked it up to the REPL.
2021-05-20 18:18:01 -05:00
Able 0ad680cadd Merge pull request #18 from T-Dark0/tdark-brainfuck 2021-05-15 20:34:29 -05:00
tdark 1b195dc955 Added support for specifying a tape size limit 2021-05-15 18:39:49 +02:00
tdark 7b5ae34bfd Implemented brainfuck interpreter 2021-05-15 18:10:50 +02:00
Able c0b7bd2f34 Update README.md 2021-05-14 10:52:16 -05:00
Able 396650ae95 Update README.md 2021-05-11 13:39:41 -05:00
able 87139c4448 consider using able brand products 2021-05-05 07:33:40 -05:00
able 9b7367353d Brain fuck work 2021-05-04 22:23:17 -05:00
able 57d86b9f92 README changes 2021-05-04 21:29:51 -05:00
able bdf27c83da Minor Changes 2021-05-03 19:33:21 -05:00
Able 143ae613d8 Merge pull request #15 from erindesu/master
Fixed some incompatible old code
2021-05-03 18:41:23 -05:00
Erin 72cd540728 Tidy up 2021-05-03 23:02:19 +02:00
Erin 798c1807db Obeyed our paperclip overlord + fmt 2021-05-03 21:36:32 +02:00
Erin 30eedbc854 Fixed some incompatible old code
- Added assignment support
- Reduced boilerplate
- Removed `else`
2021-05-03 21:35:43 +02:00
Erin 29bf01935c Variable assignment implemented 2021-05-03 09:54:27 +02:00
Able e8a2b87c6e Merge pull request #14 from erindesu/master 2021-05-03 02:17:38 -05:00
Erin 8c6f415ac9 Fixed #13 2021-05-03 08:47:52 +02:00
Able e080fc65e3 Merge pull request #12 from erindesu/master 2021-05-02 12:49:30 -05:00
Erin 051f6e781f Added tests and loop flow 2021-05-02 18:12:51 +02:00
Erin ecce080378 Added T-Dark block
- obeyed clippy
2021-05-02 17:38:12 +02:00
Erin 5a8dd5051f Parser production ready
- TODO: T-Dark block
- cargo fmt
- Obeyed our clippy overlord
2021-05-02 16:48:33 +02:00
Erin c90d242b0f Parser implements examples
- Function call is now stmt (muhehe)
2021-05-02 15:43:25 +02:00
Erin 18a1343e11 Added basic math operations
- cargo fmt
2021-05-02 00:39:08 +02:00
Erin 3794fd3c8f Divided token types
- Item = Expr | Stmt
- Added REPL (AST dump)
- Removed operator parsing because it was horrible and I need to redo it
2021-05-01 13:44:58 +02:00
Erin eef7ec16fa Tidy up 2021-04-29 19:19:35 +02:00
Erin ca60f818eb Implemented function calls 2021-04-29 18:50:51 +02:00
Erin 17a32a8df7 Bugfix: Peeking
- Fixed `PeekableLexer` next to not-be passtrough to iterator
- Made error handling depend on state of Option
2021-04-29 09:47:29 +02:00
Erin e45afeac5e Added Peekable Lexer
- Added wrapper for Lexer with `peek()` method
- Renamed `token` module to `lexer` as it more describe it's function
- Started work on operator flow
2021-04-28 22:52:19 +02:00
Able b4d3f0c9b6 Merge pull request #10 from erindesu/master
Reimplemented parser
2021-04-27 09:26:29 -05:00
Erin f3779deeb5 Added parsing of conditionals 2021-04-27 13:48:56 +02:00
Erin 3f9e6b72cc Made parser to throw error when unexpected EOF 2021-04-27 11:57:11 +02:00
Erin 39a8bf6a54 Redone original parser
= implemented original features
2021-04-27 11:49:07 +02:00
Erin d2160a3a4a Added testing for base55, new identifier lexing 2021-04-27 11:09:19 +02:00
Erin 3b8ce34c2b Continue work on parser, improved lexer
- Added literal parsing (improved lexing)
- Revised error handling
2021-04-27 10:51:39 +02:00
Erin cc4ec803c4 Starting work on parser improvements
- Parser should parse single expressions
2021-04-26 10:44:42 +02:00
Able 19762e940a Merge pull request #9 from erindesu/master 2021-04-20 10:28:52 -05:00
Erin e020fc4267 Implemented abool
- Tidied up code
2021-04-18 23:40:41 +02:00
Erin 7e0c881130 Added function/variable parsing
- Added block support
- TODO: Tidy it up
2021-04-18 22:33:55 +02:00
Erin 48dd930872 Initial parser work 2021-04-18 16:39:43 +02:00
able bc1eb42af4 variable things added 2021-04-13 18:01:19 -05:00
Able 83c8595a8f Merge pull request #8 from erindesu/master 2021-04-13 11:04:17 -05:00
Erin 1db4bd74cf clippy + fmt 2021-04-13 17:43:54 +02:00
Erin 81ff84ad73 Improved Scanner 2021-04-13 17:40:20 +02:00
able d13d72ad89 Adding a melo example 2021-04-12 22:19:44 -05:00
Able a26058587a Merge pull request #6 from erindesu/master
Added tokenization
2021-04-12 20:11:41 -05:00
Erin 968a65b1fb Added tokenization
- Added tokenization
- Modified `Token` definition for make it compatible with Logos
- And also obeyed our paperclip overlord and changed all names to be complaint with Rust conventions
2021-04-12 20:20:45 +02:00
able 1abd802b58 Merge branch 'master' of https://github.com/AbleTheAbove/able-lang
merge
2021-04-11 22:40:32 -05:00
Able 4235106434 Merge pull request #2 from erindesu/patch-1 2021-04-11 18:03:34 -05:00
Erin 0d35f7f0a5 Add ANSWER const 2021-04-12 01:02:08 +02:00
38 changed files with 4661 additions and 282 deletions

657
Cargo.lock generated
View file

@ -3,94 +3,529 @@
version = 3
[[package]]
name = "able-script"
version = "0.1.0"
name = "ablescript"
version = "0.5.4"
dependencies = [
"logos",
"rand",
]
[[package]]
name = "ablescript_cli"
version = "0.5.4"
dependencies = [
"ablescript",
"clap",
"rustyline",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
name = "anstream"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
dependencies = [
"winapi",
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "atty"
version = "0.2.14"
name = "anstyle"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
dependencies = [
"hermit-abi",
"libc",
"winapi",
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
]
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.33.3"
version = "4.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
"clap_builder",
"clap_derive",
]
[[package]]
name = "hermit-abi"
version = "0.1.18"
name = "clap_builder"
version = "4.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "clipboard-win"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "error-code"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "fd-lock"
version = "3.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
dependencies = [
"cfg-if",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "libredox"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
dependencies = [
"bitflags 2.4.1",
"libc",
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "logos"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-codegen"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68"
dependencies = [
"beef",
"fnv",
"proc-macro2",
"quote",
"regex-syntax",
"syn",
]
[[package]]
name = "logos-derive"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e"
dependencies = [
"logos-codegen",
]
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
]
[[package]]
name = "libc"
version = "0.2.93"
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "strsim"
version = "0.8.0"
name = "proc-macro2"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-width",
"unicode-ident",
]
[[package]]
name = "unicode-width"
version = "0.1.8"
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "vec_map"
version = "0.8.2"
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "rustix"
version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "rustyline"
version = "11.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfc8644681285d1fb67a467fb3021bfea306b99b4146b166a1fe3ada965eece"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"clipboard-win",
"dirs-next",
"fd-lock",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"scopeguard",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"winapi",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "smallvec"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "str-buf"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
@ -113,3 +548,135 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"

View file

@ -1,10 +1,6 @@
[package]
name = "able-script"
version = "0.1.0"
authors = ["able <abl3theabove@gmail.com>"]
edition = "2018"
[workspace]
members = ["ablescript", "ablescript_cli"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap="*"
[profile.release]
lto = true
strip = true

26
LICENSE
View file

@ -1,7 +1,21 @@
You can do whatever you want with this software assuming you meet the following conditions:
1) Answer me why?
2) Never attribute me
MIT License
You can do whatever you want with this license assuming you meet the following conditions:
1) attribute me
2) I (AbleTheAbove#3819 on discord) own you
Copyright (c) 2021 AbleCorp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,4 +1,19 @@
# Ablescript
# AbleScript
![Build Status](https://img.shields.io/github/workflow/status/AbleCorp/able-script/Rust)
![Lines of code](https://img.shields.io/tokei/lines/github/abletheabove/able-script)
![Discord](https://img.shields.io/discord/831368967385120810)
Bravely going where some languages have gone before.
[Able Script Book](https://ablecorp.us/able-script-the-book)
Still better than Javascript ://
## About
Ablescript is designed to be bad.
## Key Features
- Brain Fuck functions
- No floats
- U is equal to -210
- bannable variables
- fuck you
- constants

View file

@ -1,4 +0,0 @@
functio hello(words){
words print;
}
hello("wonk");

View file

@ -1,2 +0,0 @@
var hello = "world";
hello print;

14
ablescript/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "ablescript"
version = "0.5.4"
authors = ["AbleScript Developers"]
edition = "2021"
description = "The best programming language"
license = "MIT"
documentation = "https://ablecorp.us/able-script-the-book/"
repository = "https://git.ablecorp.us/AbleScript/able-script"
[dependencies]
logos = "0.13"
rand = "0.8"

216
ablescript/src/ast.rs Normal file
View file

@ -0,0 +1,216 @@
//! AbleScript's Abstract Syntax tree
//!
//! Statements are the type which is AST made of, as they
//! express an effect.
//!
//! Expressions are just operations and they cannot be
//! used as statements. Functions in AbleScript are in fact
//! just plain subroutines and they do not return any value,
//! so their calls are statements.
use crate::{base_55::char2num, value::Value};
use std::{fmt::Debug, hash::Hash};
type Span = std::ops::Range<usize>;
#[derive(Clone)]
pub struct Spanned<T> {
pub item: T,
pub span: Span,
}
impl<T> Spanned<T> {
pub fn new(item: T, span: Span) -> Self {
Self { item, span }
}
}
impl<T: Debug> Debug for Spanned<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
write!(f, "{:#?} @ {:?}", self.item, self.span)
} else {
write!(f, "{:?} @ {:?}", self.item, self.span)
}
}
}
impl<T: PartialEq> PartialEq for Spanned<T> {
fn eq(&self, other: &Self) -> bool {
self.item == other.item
}
}
impl<T: Hash> Hash for Spanned<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.item.hash(state);
}
}
#[derive(Debug, PartialEq, Clone, Hash)]
pub struct Assignable {
pub ident: Spanned<String>,
pub kind: AssignableKind,
}
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum AssignableKind {
Variable,
Index { indices: Vec<Spanned<Expr>> },
}
pub struct InvalidAssignable;
impl Assignable {
pub fn from_expr(expr: Spanned<Expr>) -> Result<Assignable, InvalidAssignable> {
match expr.item {
Expr::Variable(ident) => Ok(Assignable {
ident: Spanned::new(ident, expr.span),
kind: AssignableKind::Variable,
}),
Expr::Index { expr, index } => Self::from_index(*expr, *index),
_ => Err(InvalidAssignable),
}
}
fn from_index(
mut buf: Spanned<Expr>,
index: Spanned<Expr>,
) -> Result<Assignable, InvalidAssignable> {
let mut indices = vec![index];
let ident = loop {
match buf.item {
Expr::Variable(ident) => break ident,
Expr::Index { expr, index } => {
indices.push(*index);
buf = *expr;
}
_ => return Err(InvalidAssignable),
}
};
indices.reverse();
Ok(Assignable {
ident: Spanned::new(ident, buf.span),
kind: AssignableKind::Index { indices },
})
}
}
pub type Block = Vec<Spanned<Stmt>>;
/// A syntactic unit expressing an effect.
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum Stmt {
// Control flow
Unless {
cond: Spanned<Expr>,
body: Block,
},
Loop {
body: Block,
},
Enough,
AndAgain,
Dim {
ident: Spanned<String>,
init: Option<Spanned<Expr>>,
},
Assign {
assignable: Assignable,
value: Spanned<Expr>,
},
Functio {
ident: Spanned<String>,
params: Vec<Spanned<String>>,
body: Block,
},
BfFunctio {
ident: Spanned<String>,
tape_len: Option<Spanned<Expr>>,
code: Vec<u8>,
},
Call {
expr: Spanned<Expr>,
args: Vec<Spanned<Expr>>,
},
Print {
expr: Spanned<Expr>,
newline: bool,
},
Read(Assignable),
Melo(Spanned<String>),
Finally(Block),
Rlyeh,
Rickroll,
}
/// Expression is parse unit which do not cause any effect,
/// like math and logical operations or values.
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum Expr {
BinOp {
lhs: Box<Spanned<Expr>>,
rhs: Box<Spanned<Expr>>,
kind: BinOpKind,
},
Aint(Box<Spanned<Expr>>),
Literal(Literal),
Cart(Vec<(Spanned<Expr>, Spanned<Expr>)>),
Index {
expr: Box<Spanned<Expr>>,
index: Box<Spanned<Expr>>,
},
Len(Box<Spanned<Expr>>),
Keys(Box<Spanned<Expr>>),
Variable(String),
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum Literal {
Char(char),
Int(isize),
Str(String),
}
impl From<Literal> for Value {
fn from(lit: Literal) -> Self {
match lit {
Literal::Char(c) => Self::Int(char2num(c)),
Literal::Int(i) => Self::Int(i),
Literal::Str(s) => Self::Str(s),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum BinOpKind {
Add,
Subtract,
Multiply,
Divide,
Greater,
Less,
Equal,
NotEqual,
}
impl BinOpKind {
pub fn from_token(t: crate::lexer::Token) -> Result<Self, crate::error::ErrorKind> {
use crate::lexer::Token;
match t {
Token::Plus => Ok(Self::Add),
Token::Minus => Ok(Self::Subtract),
Token::Star => Ok(Self::Multiply),
Token::FwdSlash => Ok(Self::Divide),
Token::GreaterThan => Ok(Self::Greater),
Token::LessThan => Ok(Self::Less),
Token::Equals => Ok(Self::Equal),
Token::Aint => Ok(Self::NotEqual),
t => Err(crate::error::ErrorKind::UnexpectedToken(t)),
}
}
}

23
ablescript/src/base_55.rs Normal file
View file

@ -0,0 +1,23 @@
pub const fn char2num(c: char) -> isize {
match c {
' ' => 0,
// NOTE(Able): Why does it jump to 53 here? MY REASONS ARE BEYOND YOUR UNDERSTANDING MORTAL
'/' => 53,
'\\' => 54,
'.' => 55,
'U' => -210, // Backwards compatibility
'A'..='Z' => -(c as isize) + 64,
'a'..='z' => (c as isize) - 96,
_ => 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn str_to_base55() {
let chrs: Vec<isize> = "AbleScript".chars().map(char2num).collect();
assert_eq!(chrs, &[-1, 2, 12, 5, -19, 3, 18, 9, 16, 20]);
}
}

438
ablescript/src/brian.rs Normal file
View file

@ -0,0 +1,438 @@
//! A brainfuck interpreter capable of executing arbitrary code, with arbitrary inputs and outputs.
//!
//! If you just want to execute some simple brainfuck, check the [`interpret_with_io`] function.
//!
//! To construct the interpreter, use the [`from_ascii`] or [`from_ascii_with_input_buffer`] methods
//! (or their variants that take a maximum tape size). The latter grants access to
//! the method [`add_input`], which allows for the addition of input while the interpreter is running.
//!
//! [`from_ascii`]: Interpreter::from_ascii
//! [`from_ascii_with_input_buffer`]: Interpreter::from_ascii_with_input_buffer
//! [`add_input`]: Interpreter::add_input
//!
//! Finally, to run the interpreter, you can use the [`advance`], [`advance_until_io`], or [`interpret_with_output`] methods.
//!
//! [`advance`]: Interpreter::advance
//! [`advance_until_io`]: Interpreter::advance_until_io
//! [`interpret_with_output`]: Interpreter::interpret_with_output
#![deny(missing_docs)]
// Putting this here because we still don't use the entire capabilities of this module. ~~Alex
#![allow(dead_code)]
use std::{
collections::VecDeque,
error::Error,
fmt::Display,
io::{Read, Write},
};
// NOTE(Able): This is the brain fuck interface
/// The default limit for the tape size. This is the value used by methods that don't take it as a parameter
pub const DEFAULT_TAPE_SIZE_LIMIT: usize = 30_000;
/// Mappings from integers to BF instructions
pub const INSTRUCTION_MAPPINGS: &[u8] = b"[]+-,.<>";
#[derive(Debug, Clone, PartialEq, Eq)]
/// A brainfuck interpreter. Read the [module level documentation](self) for more
pub struct Interpreter<'a, I> {
code: &'a [u8],
instr_ptr: usize,
tape: Vec<i8>,
data_ptr: usize,
tape_size_limit: usize,
input: I,
}
impl<'a> Interpreter<'a, InputBuffer> {
/// Construct an `Interpreter` from an ASCII string of code with an empty input buffer
/// This methods sets the tape size limit to [its default value](DEFAULT_TAPE_SIZE_LIMIT)
pub fn from_ascii_with_input_buffer(code: &'a [u8]) -> Self {
Self::from_ascii_with_input_buffer_and_tape_limit(code, DEFAULT_TAPE_SIZE_LIMIT)
}
/// Construct an `Interpreter` from an ASCII string of code with an empty input buffer,
/// setting the tape size limit to the specified value
pub fn from_ascii_with_input_buffer_and_tape_limit(
code: &'a [u8],
tape_size_limit: usize,
) -> Self {
Self {
code,
instr_ptr: 0,
tape: Vec::new(),
data_ptr: 0,
tape_size_limit,
input: InputBuffer(VecDeque::new()),
}
}
/// Add a byte to the input buffer of this interpreter
pub fn add_input(&mut self, input: i8) {
self.input.0.push_back(input);
}
}
impl<'a, I: BootlegRead> Interpreter<'a, I> {
/// Construct an interpreter from an ASCII string of code, a source of input bytes, and a tape size limit
pub fn from_ascii_with_tape_limit(code: &'a [u8], input: I, tape_size_limit: usize) -> Self {
Self {
code,
instr_ptr: 0,
tape: Vec::new(),
data_ptr: 0,
tape_size_limit,
input,
}
}
/// Constructs an interpreter from an ASCII string of code, a source of input bytes, and [the default tape size limit](DEFAULT_TAPE_SIZE_LIMIT)
pub fn from_ascii(code: &'a [u8], input: I) -> Self {
Self::from_ascii_with_tape_limit(code, input, DEFAULT_TAPE_SIZE_LIMIT)
}
/// Advance the interpreter by one instruction.
/// A return value of Ok(None) indicates succesful termination of the interpreter
pub fn advance(&mut self) -> Result<Option<Status>, ProgramError> {
let &opcode = match self.code.get(self.instr_ptr) {
Some(opcode) => opcode,
None => return Ok(None),
};
match opcode {
b'>' => self.data_ptr += 1,
b'<' => {
self.data_ptr = self
.data_ptr
.checked_sub(1)
.ok_or(ProgramError::DataPointerUnderflow)?;
}
b'+' => {
let val = self
.get_or_resize_tape_mut()
.ok_or(ProgramError::TapeSizeExceededLimit)?;
*val = val.wrapping_add(1)
}
b'-' => {
let val = self
.get_or_resize_tape_mut()
.ok_or(ProgramError::TapeSizeExceededLimit)?;
*val = val.wrapping_sub(1)
}
b'.' => {
self.instr_ptr += 1;
return Ok(Some(Status::Output(self.get_at_data_ptr())));
}
b',' => match self.input.bootleg_read() {
Ok(Some(num)) => {
let cell = self
.get_or_resize_tape_mut()
.ok_or(ProgramError::TapeSizeExceededLimit)?;
*cell = num;
}
Ok(None) => return Ok(Some(Status::NeedsInput)),
Err(_) => return Err(ProgramError::InputReadError),
},
b'[' => {
if self.get_at_data_ptr() == 0 {
self.instr_ptr = self
.get_matching_closing_bracket(self.instr_ptr)
.ok_or(ProgramError::UnmatchedOpeningBracket)?
//Instruction pointer will be incremented by 1 after the match
}
}
b']' => {
if self.get_at_data_ptr() != 0 {
self.instr_ptr = self
.get_matching_opening_bracket(self.instr_ptr)
.ok_or(ProgramError::UnmatchedClosingBracket)?
//Instruction pointer will be incremented by 1 after the match
}
}
_ => {} //brainfuck treats all characters it doesn't understand as comments
}
self.instr_ptr += 1;
Ok(Some(Status::Continue))
}
/// Advances the interpreter until the next IO operation. See [`advance`](Interpreter::advance)
pub fn advance_until_io(&mut self) -> Result<Option<IoStatus>, ProgramError> {
while let Some(status) = self.advance()? {
match status {
Status::NeedsInput => return Ok(Some(IoStatus::NeedsInput)),
Status::Output(out) => return Ok(Some(IoStatus::Output(out))),
Status::Continue => continue,
}
}
Ok(None)
}
/// Executes the interpreter until it halts, writing all return values to the provided `Write` type.
/// For more granular control, use [`advance`](Interpreter::advance)
pub fn interpret_with_output<O: Write>(&mut self, mut output: O) -> Result<(), InterpretError> {
while let Some(status) = self.advance_until_io()? {
match status {
IoStatus::NeedsInput => return Err(InterpretError::EndOfInput),
IoStatus::Output(out) => match output.write(&[out as u8]) {
Ok(0) => return Err(InterpretError::OutputBufferFull),
Ok(_) => continue,
Err(_) => return Err(InterpretError::OutputWriteError),
},
}
}
Ok(())
}
fn get_or_resize_tape_mut(&mut self) -> Option<&mut i8> {
if self.data_ptr > self.tape_size_limit {
return None;
}
if self.data_ptr >= self.tape.len() {
self.tape.resize(self.data_ptr + 1, 0);
}
Some(&mut self.tape[self.data_ptr])
}
fn get_at_data_ptr(&self) -> i8 {
//No need to resize the tape to read: if the tape doesn't extend that far already, it holds a value of 0
self.tape.get(self.data_ptr).copied().unwrap_or(0)
}
fn get_matching_closing_bracket(&mut self, opening: usize) -> Option<usize> {
self.code[opening..]
.iter()
.zip(opening..)
.scan(0, |counter, (char, index)| {
match char {
b'[' => *counter += 1,
b']' => *counter -= 1,
_ => {}
};
Some((*counter, index))
})
.find_map(|(counter, index)| (counter == 0).then_some(index))
}
fn get_matching_opening_bracket(&mut self, closing: usize) -> Option<usize> {
self.code[..closing + 1]
.iter()
.zip(0..closing + 1)
.rev()
.scan(0, |counter, (char, index)| {
match char {
b']' => *counter += 1,
b'[' => *counter -= 1,
_ => {}
};
Some((*counter, index))
})
.find_map(|(counter, index)| (counter == 0).then_some(index))
}
}
/// A convenience function for interpreting brainfuck code with a given input and output source.
/// For more information, consult [the module level documentation](self)
pub fn interpret_with_io<I: BootlegRead, O: Write>(
code: &[u8],
input: I,
output: O,
) -> Result<(), InterpretError> {
Interpreter::from_ascii(code, input).interpret_with_output(output)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
///The result of advancing the interpreter by one step, assuming it didn't terminate
pub enum Status {
NeedsInput,
Output(i8),
Continue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// The result of advancing the interpreter until the next IO operation, assuming it didn't terminate
pub enum IoStatus {
NeedsInput,
Output(i8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// An error that occurred while the interpreter was advancing
pub enum ProgramError {
DataPointerUnderflow,
InputReadError,
UnmatchedOpeningBracket,
UnmatchedClosingBracket,
TapeSizeExceededLimit,
}
impl Display for ProgramError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
ProgramError::DataPointerUnderflow => "data pointer underflow",
ProgramError::InputReadError => "input read error",
ProgramError::UnmatchedOpeningBracket => "unmatched `[`",
ProgramError::UnmatchedClosingBracket => "unmatched `]`",
ProgramError::TapeSizeExceededLimit => "tape size exceeded",
}
)
}
}
impl Error for ProgramError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// An error that occurred while the interpreter was being run start-to-end all in one go
pub enum InterpretError {
ProgramError(ProgramError),
EndOfInput,
OutputBufferFull,
OutputWriteError,
}
impl Display for InterpretError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InterpretError::ProgramError(e) => write!(f, "program error: {}", e),
InterpretError::EndOfInput => write!(f, "unexpected end of input"),
InterpretError::OutputBufferFull => write!(f, "output buffer full"),
InterpretError::OutputWriteError => write!(f, "output write error"),
}
}
}
impl Error for InterpretError {}
impl From<ProgramError> for InterpretError {
fn from(e: ProgramError) -> Self {
InterpretError::ProgramError(e)
}
}
/// A bootlegged version of the standard library's read trait, so as to allow the interpreter to be generic over any `Read`
/// type, as well as over an input buffer.
pub trait BootlegRead {
type Error;
fn bootleg_read(&mut self) -> Result<Option<i8>, Self::Error>;
}
impl<T: Read> BootlegRead for T {
type Error = std::io::Error;
fn bootleg_read(&mut self) -> Result<Option<i8>, Self::Error> {
let mut buffer = [0];
match self.read(&mut buffer) {
Ok(0) => Ok(None),
Ok(_) => Ok(Some(buffer[0] as i8)),
Err(e) => Err(e),
}
}
}
/// A wrapper around a `VecDeque`, to be able to implement `BootlegRead` for it
struct InputBuffer(VecDeque<i8>);
impl BootlegRead for InputBuffer {
type Error = std::convert::Infallible;
fn bootleg_read(&mut self) -> Result<Option<i8>, Self::Error> {
Ok(self.0.pop_front())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adder() {
let mut interpreter = Interpreter {
code: b"[->+<]", //Source: https://en.wikipedia.org/wiki/Brainfuck
instr_ptr: 0,
tape: vec![10, 5],
data_ptr: 0,
tape_size_limit: DEFAULT_TAPE_SIZE_LIMIT,
input: std::io::empty(),
};
while let Some(status) = interpreter.advance_until_io().expect("Unexpected error") {
match status {
IoStatus::NeedsInput => panic!("Requested input in an IO-less program"),
IoStatus::Output(_) => panic!("Produced output in an IO-less program"),
}
}
assert_eq!(interpreter.tape, vec![0, 15]);
}
#[test]
fn hello_world() {
let mut interpreter = Interpreter::from_ascii(
b"++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.",
std::io::empty(),
);
let mut string = Vec::new();
interpreter
.interpret_with_output(&mut string)
.expect("Failed to write to output buffer");
assert_eq!(string, b"Hello World!\n");
}
#[test]
fn with_input_buffer() {
let mut interpreter = Interpreter::from_ascii_with_input_buffer(b"+++++.>,[-<->].");
let output = match interpreter
.advance_until_io()
.expect("Unexpected error")
.expect("Unexpected termination")
{
IoStatus::NeedsInput => panic!("Unexpected input request"),
IoStatus::Output(out) => out,
};
assert_eq!(
interpreter.advance_until_io(),
Ok(Some(IoStatus::NeedsInput))
);
interpreter.add_input(output);
assert_eq!(
interpreter.advance_until_io(),
Ok(Some(IoStatus::Output(0)))
);
assert_eq!(interpreter.advance_until_io(), Ok(None));
}
#[test]
fn hit_tape_size_limit() {
let mut interpreter =
Interpreter::from_ascii_with_tape_limit(b"+>+>+>+>+>", std::io::empty(), 1);
let result = interpreter.interpret_with_output(std::io::sink());
assert_eq!(
result,
Err(InterpretError::ProgramError(
ProgramError::TapeSizeExceededLimit
))
);
}
#[test]
fn positive_integer_overflow() {
interpret_with_io(b"+[+]", std::io::empty(), std::io::sink()).unwrap();
}
#[test]
fn negative_integer_overflow() {
interpret_with_io(b"-", std::io::empty(), std::io::sink()).unwrap();
}
}

43
ablescript/src/consts.rs Normal file
View file

@ -0,0 +1,43 @@
//! Number constants.
use crate::value::{Value, Variable};
use std::collections::HashMap;
pub const ANSWER: isize = 42;
/// Initialize a HashMap between the constant names and values
/// accessible from within AbleScript.
pub fn ablescript_consts() -> HashMap<String, Variable> {
use Value::*;
[
("TAU", Int(6)), // Circumference / radius
("PI", Int(3)), // Deprecated, do not use
("EULER", Int(3)), // Mathematical constant e
("MASS", Int(70)), // @Kev#6900's weight in kilograms
("PHI", Int(2)), // Golden ratio
("WUA", Int(1)), // 1
("EULERS_CONSTANT", Int(0)), // ???
("GRAVITY", Int(10)), // Earth surface gravity, m/s
("RNG", Int(12)), // Kixiron#5289 Randomly rolled dice
("STD_RNG", Int(4)), // The standard random number is 4 (https://xkcd.com/221/)
("INF", Int(isize::max_value())), // The biggest number
("INTERESSANT", Int(114514)), // HTGAzureX1212.#5959 intéressant number
("FUNNY", Int(69)), // HTGAzureX1212.#5959 funny number
(
// Never gonna let you down
"NEVERGONNAGIVEYOUUP",
Str("1452251871514141792252515212116".to_owned()),
),
("OCTOTHORPE", Str("#".to_owned())), // It's an octothorpe
("AMOGUS", Str("".to_owned())), // Amogus
("ANSWER", Int(ANSWER)),
("nul", Nul),
("always", Abool(crate::value::Abool::Always)),
("sometimes", Abool(crate::value::Abool::Sometimes)),
("never", Abool(crate::value::Abool::Never)),
]
.into_iter()
.map(|(name, value)| (name.to_owned(), Variable::from_value(value)))
.collect()
}

93
ablescript/src/error.rs Normal file
View file

@ -0,0 +1,93 @@
use crate::{brian::InterpretError, lexer::Token};
use std::{fmt::Display, io, ops::Range};
#[derive(Debug)]
pub struct Error {
pub kind: ErrorKind,
pub span: Range<usize>,
}
#[derive(Debug)]
pub enum ErrorKind {
/// Parser expected token, but none was available
UnexpectedEoi,
/// Parser encountered unknown token
InvalidToken,
/// Parser expected certain token, but other one appeared
UnexpectedToken(Token),
/// Attempted to assign to undefined variable
UnknownVariable(String),
/// Attempted to access banned variable
MeloVariable(String),
/// Breaking / re-starting loop outside loop
LoopOpOutsideLoop,
/// Rlyeh was executed but host interface's exit
/// doesn't exit the program
NonExitingRlyeh(i32),
/// Missing left-hand side expression in binary expression
MissingLhs,
/// Error when executing BF code
Brian(InterpretError),
/// IO Error
Io(io::Error),
}
impl Error {
pub fn new(kind: ErrorKind, span: Range<usize>) -> Self {
Self { kind, span }
}
/// Create an UnexpectedEoi error, where the EOI occurs at the
/// given index in the input.
pub fn unexpected_eoi(index: usize) -> Self {
Self::new(ErrorKind::UnexpectedEoi, index..index)
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Error at range {}-{}: {}",
self.span.start, self.span.end, self.kind
)
}
}
impl std::error::Error for Error {}
impl Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::UnexpectedEoi => write!(f, "unexpected end of input"),
ErrorKind::InvalidToken => write!(f, "invalid token"),
ErrorKind::UnexpectedToken(Token::Melo) => write!(f, "unexpected marten"),
ErrorKind::UnexpectedToken(token) => write!(f, "unexpected token {:?}", token),
ErrorKind::UnknownVariable(name) => write!(f, "unknown identifier \"{}\"", name),
ErrorKind::MeloVariable(name) => write!(f, "banned variable \"{}\"", name),
ErrorKind::LoopOpOutsideLoop => write!(
f,
"unable to perform loop operation (enough or and enough) outside a loop"
),
&ErrorKind::NonExitingRlyeh(code) => write!(f, "program exited with code {code}"),
ErrorKind::Brian(err) => write!(f, "brainfuck error: {}", err),
// TODO: give concrete numbers here.
ErrorKind::MissingLhs => write!(f, "missing expression before binary operation"),
ErrorKind::Io(err) => write!(f, "I/O error: {}", err),
}
}
}
impl From<io::Error> for ErrorKind {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}

View file

@ -0,0 +1,53 @@
use crate::value::Variable;
use std::collections::HashMap;
/// Host Environment Interface
pub trait HostInterface {
/// Initial variables for a stack frame
fn initial_vars(&mut self) -> HashMap<String, Variable>;
/// Print a string
fn print(&mut self, string: &str, new_line: bool) -> std::io::Result<()>;
/// Read a byte
fn read_byte(&mut self) -> std::io::Result<u8>;
/// This function should exit the program with specified code.
///
/// For cases where exit is not desired, just let the function return
/// and interpreter will terminate with an error.
fn exit(&mut self, code: i32);
}
/// Standard [HostInterface] implementation
#[derive(Clone, Copy, Default)]
pub struct Standard;
impl HostInterface for Standard {
fn initial_vars(&mut self) -> HashMap<String, Variable> {
HashMap::default()
}
fn print(&mut self, string: &str, new_line: bool) -> std::io::Result<()> {
use std::io::Write;
let mut stdout = std::io::stdout();
stdout.write_all(string.as_bytes())?;
if new_line {
stdout.write_all(b"\n")?;
}
Ok(())
}
fn read_byte(&mut self) -> std::io::Result<u8> {
use std::io::Read;
let mut buf = [0];
std::io::stdin().read_exact(&mut buf)?;
Ok(buf[0])
}
fn exit(&mut self, code: i32) {
std::process::exit(code);
}
}

812
ablescript/src/interpret.rs Normal file
View file

@ -0,0 +1,812 @@
//! Expression evaluator and statement interpreter.
//!
//! To interpret a piece of AbleScript code, you first need to
//! construct an [ExecEnv], which is responsible for storing the stack
//! of local variable and function definitions accessible from an
//! AbleScript snippet. You can then call [ExecEnv::eval_stmts] to
//! evaluate or execute any number of expressions or statements.
#![deny(missing_docs)]
use crate::{
ast::{Assignable, AssignableKind, Block, Expr, Spanned, Stmt},
consts::ablescript_consts,
error::{Error, ErrorKind},
host_interface::HostInterface,
value::{Functio, Value, ValueRef, Variable},
};
use rand::random;
use std::{
cmp::Ordering,
collections::{HashMap, VecDeque},
mem::take,
ops::Range,
};
/// An environment for executing AbleScript code.
pub struct ExecEnv<H> {
/// The stack, ordered such that `stack[stack.len() - 1]` is the
/// top-most (newest) stack frame, and `stack[0]` is the
/// bottom-most (oldest) stack frame.
stack: Vec<Scope>,
/// The `read` statement maintains a buffer of up to 7 bits,
/// because input comes from the operating system 8 bits at a time
/// (via stdin) but gets delivered to AbleScript 3 bits at a time
/// (via the `read` statement). We store each of those bits as
/// booleans to facilitate easy manipulation.
read_buf: VecDeque<bool>,
/// Interface to interact with the host interface
host_interface: H,
/// Vector of blocks to be executed at the end of the program
finalisers: Vec<Block>,
}
/// A set of visible variable and function definitions in a single
/// stack frame.
struct Scope {
/// The mapping from variable names to values.
variables: HashMap<String, Variable>,
}
impl<H> Default for ExecEnv<H>
where
H: Default + HostInterface,
{
fn default() -> Self {
Self::with_host_interface(H::default())
}
}
impl Default for Scope {
fn default() -> Self {
Self {
variables: ablescript_consts(),
}
}
}
/// The reason a successful series of statements halted.
enum HaltStatus {
/// We ran out of statements to execute.
Finished,
/// An `enough` statement occurred at the given span, and was not
/// caught by a `loop` statement up to this point.
Enough(Range<usize>),
/// A `and again` statement occurred at the given span, and was not
/// caught by a `loop` statement up to this point.
AndAgain(Range<usize>),
}
/// The number of bits the `read` statement reads at once from
/// standard input.
pub const READ_BITS: u8 = 3;
impl<H: HostInterface> ExecEnv<H> {
/// Create a new Scope with no predefined variable definitions or
/// other information.
pub fn with_host_interface(mut host_interface: H) -> Self {
Self {
stack: vec![
Default::default(),
Scope {
variables: host_interface.initial_vars(),
},
],
read_buf: Default::default(),
finalisers: vec![],
host_interface,
}
}
/// Create a new Scope with predefined variables
pub fn new_with_vars<I>(mut host_interface: H, vars: I) -> Self
where
I: IntoIterator<Item = (String, Variable)>,
{
Self {
stack: vec![
Scope {
variables: ablescript_consts().into_iter().chain(vars).collect(),
},
Scope {
variables: host_interface.initial_vars(),
},
],
read_buf: Default::default(),
finalisers: vec![],
host_interface,
}
}
/// Execute a set of Statements in the root stack frame. Return an
/// error if one or more of the Stmts failed to evaluate, or if a
/// `enough` or `and again` statement occurred at the top level.
pub fn eval_stmts(&mut self, stmts: &[Spanned<Stmt>]) -> Result<(), Error> {
match self.eval_stmts_hs(stmts, false)? {
HaltStatus::Finished => Ok(()),
HaltStatus::Enough(span) | HaltStatus::AndAgain(span) => Err(Error {
// It's an error to issue a `enough` outside of a
// `loop` statement.
kind: ErrorKind::LoopOpOutsideLoop,
span,
}),
}?;
while !self.finalisers.is_empty() {
for block in std::mem::take(&mut self.finalisers) {
self.eval_stmts_hs(&block, true)?;
}
}
Ok(())
}
/// The same as `eval_stmts`, but report "enough" and "and again"
/// exit codes as normal conditions in a HaltStatus enum, and
/// create a new stack frame if `stackframe` is true.
///
/// `interpret`-internal code should typically prefer this
/// function over `eval_stmts`.
fn eval_stmts_hs(
&mut self,
stmts: &[Spanned<Stmt>],
stackframe: bool,
) -> Result<HaltStatus, Error> {
let init_depth = self.stack.len();
if stackframe {
self.stack.push(Default::default());
}
let mut final_result = Ok(HaltStatus::Finished);
for stmt in stmts {
final_result = self.eval_stmt(stmt);
if !matches!(final_result, Ok(HaltStatus::Finished)) {
break;
}
}
if stackframe {
self.stack.pop();
}
// Invariant: stack size must have net 0 change.
debug_assert_eq!(self.stack.len(), init_depth);
final_result
}
/// Evaluate an Expr, returning its value or an error.
fn eval_expr(&self, expr: &Spanned<Expr>) -> Result<Value, Error> {
use crate::ast::BinOpKind::*;
use crate::ast::Expr::*;
Ok(match &expr.item {
BinOp { lhs, rhs, kind } => {
let lhs = self.eval_expr(lhs)?;
let rhs = self.eval_expr(rhs)?;
match kind {
Add => lhs + rhs,
Subtract => lhs - rhs,
Multiply => lhs * rhs,
Divide => lhs / rhs,
Greater => Value::Abool((lhs > rhs).into()),
Less => Value::Abool((lhs < rhs).into()),
Equal => Value::Abool((lhs == rhs).into()),
NotEqual => Value::Abool((lhs != rhs).into()),
}
}
Aint(expr) => !self.eval_expr(expr)?,
Literal(lit) => lit.clone().into(),
Expr::Cart(members) => Value::Cart(
members
.iter()
.map(|(value, key)| {
self.eval_expr(value).and_then(|value| {
self.eval_expr(key).map(|key| (key, ValueRef::new(value)))
})
})
.collect::<Result<HashMap<_, _>, _>>()?,
),
Index { expr, index } => {
let value = self.eval_expr(expr)?;
let index = self.eval_expr(index)?;
value
.into_cart()
.get(&index)
.map(|x| x.borrow().clone())
.unwrap_or(Value::Nul)
}
Len(expr) => Value::Int(self.eval_expr(expr)?.length()),
Keys(expr) => Value::Cart(
self.eval_expr(expr)?
.into_cart()
.into_keys()
.enumerate()
.map(|(i, k)| (Value::Int(i as isize + 1), ValueRef::new(k)))
.collect(),
),
// TODO: not too happy with constructing an artificial
// Ident here.
Variable(name) => {
self.get_var_value(&Spanned::new(name.to_owned(), expr.span.clone()))?
}
})
}
/// Perform the action indicated by a statement.
fn eval_stmt(&mut self, stmt: &Spanned<Stmt>) -> Result<HaltStatus, Error> {
match &stmt.item {
Stmt::Print { expr, newline } => {
let value = self.eval_expr(expr)?;
self.host_interface
.print(&value.to_string(), *newline)
.map_err(|e| Error::new(e.into(), stmt.span.clone()))?;
}
Stmt::Dim { ident, init } => {
let init = match init {
Some(e) => self.eval_expr(e)?,
None => Value::Nul,
};
self.decl_var(&ident.item, init);
}
Stmt::Functio {
ident,
params,
body,
} => {
self.decl_var(
&ident.item,
Value::Functio(Functio::Able {
params: params.iter().map(|ident| ident.item.to_owned()).collect(),
body: body.to_owned(),
}),
);
}
Stmt::BfFunctio {
ident,
tape_len,
code,
} => {
self.decl_var(
&ident.item,
Value::Functio(Functio::Bf {
instructions: code.to_owned(),
tape_len: tape_len
.as_ref()
.map(|tape_len| {
self.eval_expr(tape_len).map(|v| v.into_isize() as usize)
})
.unwrap_or(Ok(crate::brian::DEFAULT_TAPE_SIZE_LIMIT))?,
}),
);
}
Stmt::Unless { cond, body } => {
if !self.eval_expr(cond)?.into_abool().to_bool() {
return self.eval_stmts_hs(body, true);
}
}
Stmt::Call { expr, args } => {
let func = self.eval_expr(expr)?.into_functio();
return self.fn_call(func, args, &stmt.span);
}
Stmt::Loop { body } => loop {
let res = self.eval_stmts_hs(body, true)?;
match res {
HaltStatus::Finished => (),
HaltStatus::Enough(_) => break,
HaltStatus::AndAgain(_) => continue,
}
},
Stmt::Assign { assignable, value } => {
self.assign(assignable, self.eval_expr(value)?)?;
}
Stmt::Enough => {
return Ok(HaltStatus::Enough(stmt.span.clone()));
}
Stmt::AndAgain => {
return Ok(HaltStatus::AndAgain(stmt.span.clone()));
}
Stmt::Melo(ident) => match self.get_var_mut(ident)? {
var @ Variable::Ref(_) => *var = Variable::Melo,
Variable::Melo => {
for s in &mut self.stack {
if s.variables.remove(&ident.item).is_some() {
break;
}
}
}
},
Stmt::Finally(block) => self.finalisers.push(block.clone()),
Stmt::Rlyeh => {
// Maybe print a creepy error message or something
// here at some point. ~~Alex
let code = random();
self.host_interface.exit(code);
return Err(Error::new(
ErrorKind::NonExitingRlyeh(code),
stmt.span.clone(),
));
}
Stmt::Rickroll => {
self.host_interface
.print(include_str!("rickroll"), false)
.map_err(|e| Error::new(e.into(), stmt.span.clone()))?;
}
Stmt::Read(assignable) => {
let mut value = 0;
for _ in 0..READ_BITS {
value <<= 1;
value += self
.get_bit()
.map_err(|e| Error::new(e, stmt.span.clone()))?
as isize;
}
self.assign(assignable, Value::Int(value))?;
}
}
Ok(HaltStatus::Finished)
}
/// Assign a value to an Assignable.
fn assign(&mut self, dest: &Assignable, value: Value) -> Result<(), Error> {
match dest.kind {
AssignableKind::Variable => {
self.get_var_rc_mut(&dest.ident)?.replace(value);
}
AssignableKind::Index { ref indices } => {
let mut cell = self.get_var_rc_mut(&dest.ident)?.clone();
for index in indices {
let index = self.eval_expr(index)?;
let next_cell = match &mut *cell.borrow_mut() {
Value::Cart(c) => {
// cell is a cart, so we can do simple
// indexing.
if let Some(x) = c.get(&index) {
// cell[index] exists, get a shared
// reference to it.
ValueRef::clone(x)
} else {
// cell[index] does not exist, so we
// insert an empty cart by default
// instead.
let next_cell = ValueRef::new(Value::Cart(Default::default()));
c.insert(index, ValueRef::clone(&next_cell));
next_cell
}
}
x => {
// cell is not a cart; `take` it, convert
// it into a cart, and write the result
// back into it.
let mut cart = take(x).into_cart();
let next_cell = ValueRef::new(Value::Cart(Default::default()));
cart.insert(index, ValueRef::clone(&next_cell));
*x = Value::Cart(cart);
next_cell
}
};
cell = next_cell;
}
cell.replace(value);
}
}
Ok(())
}
/// Call a function with the given arguments (i.e., actual
/// parameters). If the function invocation fails for some reason,
/// report the error at `span`.
fn fn_call(
&mut self,
func: Functio,
args: &[Spanned<Expr>],
span: &Range<usize>,
) -> Result<HaltStatus, Error> {
// Arguments that are ExprKind::Variable are pass by
// reference; all other expressions are pass by value.
let args = args
.iter()
.map(|arg| {
if let Expr::Variable(name) = &arg.item {
self.get_var_rc_mut(&Spanned::new(name.to_owned(), arg.span.clone()))
.cloned()
} else {
self.eval_expr(arg).map(ValueRef::new)
}
})
.collect::<Result<Vec<_>, Error>>()?;
self.fn_call_with_values(func, &args, span)
}
fn fn_call_with_values(
&mut self,
func: Functio,
args: &[ValueRef],
span: &Range<usize>,
) -> Result<HaltStatus, Error> {
match func {
Functio::Bf {
instructions,
tape_len,
} => {
let mut input: Vec<u8> = vec![];
for arg in args {
arg.borrow().bf_write(&mut input);
}
let mut output = vec![];
crate::brian::Interpreter::from_ascii_with_tape_limit(
&instructions,
&input as &[_],
tape_len,
)
.interpret_with_output(&mut output)
.map_err(|e| Error {
kind: ErrorKind::Brian(e),
span: span.to_owned(),
})?;
match String::from_utf8(output) {
Ok(string) => self.host_interface.print(&string, false),
Err(e) => self
.host_interface
.print(&format!("{:?}", e.as_bytes()), true),
}
.map_err(|e| Error::new(e.into(), span.clone()))?;
Ok(HaltStatus::Finished)
}
Functio::Able { params, body } => {
self.stack.push(Default::default());
for (param, arg) in params.iter().zip(args.iter()) {
self.decl_var_shared(param, arg.to_owned());
}
let res = self.eval_stmts_hs(&body, false);
self.stack.pop();
res
}
Functio::Builtin(b) => {
b.call(args).map_err(|e| Error::new(e, span.clone()))?;
Ok(HaltStatus::Finished)
}
Functio::Chain { functios, kind } => {
use crate::value::functio::FunctioChainKind;
let (left_functio, right_functio) = *functios;
Ok(
match match kind {
FunctioChainKind::Equal => {
let (l, r) = args.split_at(args.len() / 2);
(
self.fn_call_with_values(left_functio, l, span)?,
self.fn_call_with_values(right_functio, r, span)?,
)
}
FunctioChainKind::ByArity => {
let (l, r) = Self::deinterlace(
args,
(left_functio.arity(), right_functio.arity()),
);
(
self.fn_call_with_values(left_functio, &l, span)?,
self.fn_call_with_values(right_functio, &r, span)?,
)
}
} {
(s, HaltStatus::Finished) => s,
(HaltStatus::Finished, s) => s,
(_, r) => r,
},
)
}
Functio::Eval(code) => self.eval_stmts_hs(&crate::parser::parse(&code)?, false),
}
}
fn deinterlace(args: &[ValueRef], arities: (usize, usize)) -> (Vec<ValueRef>, Vec<ValueRef>) {
let n_alternations = usize::min(arities.0, arities.1);
let (extra_l, extra_r) = match Ord::cmp(&arities.0, &arities.1) {
Ordering::Less => (0, arities.1 - arities.0),
Ordering::Equal => (0, 0),
Ordering::Greater => (arities.0 - arities.1, 0),
};
(
args.chunks(2)
.take(n_alternations)
.map(|chunk| ValueRef::clone(&chunk[0]))
.chain(
args.get(2 * n_alternations..)
.iter()
.copied()
.flatten()
.map(ValueRef::clone)
.take(extra_l),
)
.collect(),
args.chunks(2)
.take(n_alternations)
.flat_map(|chunk| chunk.get(1))
.map(ValueRef::clone)
.chain(
args.get(2 * n_alternations..)
.iter()
.copied()
.flatten()
.map(ValueRef::clone)
.take(extra_r),
)
.collect(),
)
}
/// Get a single bit from the bit buffer, or refill it from
/// standard input if it is empty.
fn get_bit(&mut self) -> Result<bool, ErrorKind> {
const BITS_PER_BYTE: u8 = 8;
if self.read_buf.is_empty() {
let byte = self.host_interface.read_byte()?;
for n in (0..BITS_PER_BYTE).rev() {
self.read_buf.push_back(((byte >> n) & 1) != 0);
}
}
Ok(self
.read_buf
.pop_front()
.expect("We just pushed to the buffer if it was empty"))
}
/// Get the value of a variable. Throw an error if the variable is
/// inaccessible or banned.
fn get_var_value(&self, name: &Spanned<String>) -> Result<Value, Error> {
// Search for the name in the stack from top to bottom.
match self
.stack
.iter()
.rev()
.find_map(|scope| scope.variables.get(&name.item))
{
Some(Variable::Ref(r)) => Ok(r.borrow().clone()),
Some(Variable::Melo) => Err(Error {
kind: ErrorKind::MeloVariable(name.item.to_owned()),
span: name.span.clone(),
}),
None => Ok(Value::Undefined),
}
}
/// Get a mutable reference to a variable.
fn get_var_mut(&mut self, name: &Spanned<String>) -> Result<&mut Variable, Error> {
// This function has a lot of duplicated code with `get_var`,
// which I feel like is a bad sign...
match self
.stack
.iter_mut()
.rev()
.find_map(|scope| scope.variables.get_mut(&name.item))
{
Some(var) => Ok(var),
None => Err(Error {
kind: ErrorKind::UnknownVariable(name.item.to_owned()),
span: name.span.clone(),
}),
}
}
/// Get an reference to an Rc'd pointer to the value of a variable. Throw an error
/// if the variable is inaccessible or banned.
fn get_var_rc_mut(&mut self, name: &Spanned<String>) -> Result<&mut ValueRef, Error> {
match self.get_var_mut(name)? {
Variable::Ref(r) => Ok(r),
Variable::Melo => Err(Error {
kind: ErrorKind::MeloVariable(name.item.to_owned()),
span: name.span.clone(),
}),
}
}
/// Declare a new variable, with the given initial value.
fn decl_var(&mut self, name: &str, value: Value) {
self.decl_var_shared(name, ValueRef::new(value));
}
/// Declare a new variable, with the given shared initial value.
fn decl_var_shared(&mut self, name: &str, value: ValueRef) {
self.stack
.iter_mut()
.last()
.expect("Declaring variable on empty stack")
.variables
.insert(name.to_owned(), Variable::Ref(value));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
ast::{Expr, Literal},
host_interface::Standard,
};
#[test]
fn basic_expression_test() {
// Check that 2 + 2 = 4.
let env = ExecEnv::<Standard>::default();
assert_eq!(
env.eval_expr(&Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(2)),
span: 1..1,
}),
rhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(2)),
span: 1..1,
}),
kind: crate::ast::BinOpKind::Add,
},
span: 1..1
})
.unwrap(),
Value::Int(4)
)
}
#[test]
fn type_coercions() {
// The sum of an integer and an aboolean causes an aboolean
// coercion.
let env = ExecEnv::<Standard>::default();
assert_eq!(
env.eval_expr(&Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(2)),
span: 1..1,
}),
rhs: Box::new(Spanned {
item: Expr::Variable("always".to_owned()),
span: 1..1,
}),
kind: crate::ast::BinOpKind::Add,
},
span: 1..1
})
.unwrap(),
Value::Int(3)
);
}
#[test]
fn overflow_should_not_panic() {
// Integer overflow should throw a recoverable error instead
// of panicking.
let env = ExecEnv::<Standard>::default();
assert_eq!(
env.eval_expr(&Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(isize::MAX)),
span: 1..1,
}),
rhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(1)),
span: 1..1,
}),
kind: crate::ast::BinOpKind::Add,
},
span: 1..1
})
.unwrap(),
Value::Int(-9223372036854775808)
);
// And the same for divide by zero.
assert_eq!(
env.eval_expr(&Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(84)),
span: 1..1,
}),
rhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(0)),
span: 1..1,
}),
kind: crate::ast::BinOpKind::Divide,
},
span: 1..1
})
.unwrap(),
Value::Int(2)
);
}
// From here on out, I'll use this function to parse and run
// expressions, because writing out abstract syntax trees by hand
// takes forever and is error-prone.
fn eval(env: &mut ExecEnv<Standard>, src: &str) -> Result<Value, Error> {
// We can assume there won't be any syntax errors in the
// interpreter tests.
let ast = crate::parser::parse(src).unwrap();
env.eval_stmts(&ast).map(|()| Value::Nul)
}
#[test]
fn variable_decl_and_assignment() {
// Functions have no return values, so use some
// pass-by-reference hacks to detect the correct
// functionality.
let mut env = ExecEnv::<Standard>::default();
// Declaring and reading from a variable.
eval(&mut env, "foo dim 32; bar dim foo + 1;").unwrap();
assert_eq!(
env.get_var_value(&Spanned {
item: "bar".to_owned(),
span: 1..1,
})
.unwrap(),
Value::Int(33)
);
// Assigning an existing variable.
eval(&mut env, "/*hi*/ =: foo;").unwrap();
assert_eq!(
env.get_var_value(&Spanned {
item: "foo".to_owned(),
span: 1..1,
})
.unwrap(),
Value::Str("hi".to_owned())
);
// But variable assignment should be illegal when the variable
// hasn't been declared in advance.
eval(&mut env, "bar + 1 =: invalid;").unwrap_err();
}
#[test]
fn scope_visibility_rules() {
// Declaration and assignment of variables declared in an `if`
// statement should have no effect on those declared outside
// of it.
let mut env = ExecEnv::<Standard>::default();
eval(
&mut env,
"foo dim 1; 2 =: foo; unless (never) { foo dim 3; 4 =: foo; }",
)
.unwrap();
assert_eq!(
env.get_var_value(&Spanned {
item: "foo".to_owned(),
span: 1..1,
})
.unwrap(),
Value::Int(2)
);
}
}

144
ablescript/src/lexer.rs Normal file
View file

@ -0,0 +1,144 @@
use logos::{Lexer, Logos};
#[derive(Logos, Debug, PartialEq, Eq, Clone)]
#[logos(skip r"[ \t\n\f]+")]
#[logos(skip r"owo .*")]
#[rustfmt::skip]
pub enum Token {
// Symbols
#[token("(")] LeftParen,
#[token(")")] RightParen,
#[token("[")] LeftBracket,
#[token("]")] RightBracket,
#[token("{")] LeftCurly,
#[token("}")] RightCurly,
#[token(";")] Semicolon,
#[token(",")] Comma,
// Operators
#[token("+")] Plus,
#[token("-")] Minus,
#[token("*")] Star,
#[token("/")] FwdSlash,
#[token("=:")] Assign,
#[token("<=")] Arrow,
// Logical operators
#[token("<")] LessThan,
#[token(">")] GreaterThan,
#[token("=")] Equals,
#[token("ain't")] Aint,
// Keywords
#[token("functio")] Functio,
#[token("bff")] Bff,
#[token("dim")] Dim,
#[token("print")] Print,
#[token("read")] Read,
#[token("melo")] Melo,
#[token("T-Dark")] TDark,
// Control flow keywords
#[token("unless")] Unless,
#[token("loop")] Loop,
#[token("enough")] Enough,
#[token("and again")] AndAgain,
#[token("finally")] Finally,
#[token("rlyeh")] Rlyeh,
#[token("rickroll")] Rickroll,
// Literals
#[token("/*", get_string)] String(String),
#[regex(r"-?[0-9]+", get_value)] Integer(isize),
#[regex(r"\p{XID_Start}", get_value)] Char(char),
#[regex(r"\p{XID_Start}[\p{XID_Continue}]+", get_ident)]
#[token("and ", |_| "and".to_owned())]
Identifier(String),
}
fn get_value<T: std::str::FromStr>(lexer: &mut Lexer<Token>) -> Option<T> {
lexer.slice().parse().ok()
}
fn get_string(lexer: &mut Lexer<Token>) -> Option<String> {
lexer.bump(lexer.remainder().find("*/")?);
let mut string = String::new();
let mut slice = &lexer.slice()[2..];
while let Some(escape_start) = slice.find('"') {
// Push predeceasing string
string.push_str(slice.get(..escape_start)?);
// Move slice behind escape start delimiter
slice = slice.get(escape_start + 1..)?;
// Get escape end delimiter position and parse string before it to
// a character from it's unicode value (base-12) and push it to string
let escape_end = slice.find('"')?;
string.push(
u32::from_str_radix(slice.get(..escape_end)?, 12)
.ok()
.and_then(char::from_u32)?,
);
// Move slice behind escape end delimiter
slice = slice.get(escape_end + 1..)?;
}
// Push remaining string
string.push_str(slice);
lexer.bump(2);
Some(string)
}
fn get_ident(lexer: &mut Lexer<Token>) -> String {
lexer.slice().to_owned()
}
#[cfg(test)]
mod tests {
use super::Token;
use super::Token::*;
use logos::Logos;
#[test]
fn simple_fn() {
let code = "functio test() { dim var 3; unless (var ain't 3) { var print } }";
let expected = &[
Functio,
Identifier("test".to_owned()),
LeftParen,
RightParen,
LeftCurly,
Dim,
Identifier("var".to_owned()),
Integer(3),
Semicolon,
Unless,
LeftParen,
Identifier("var".to_owned()),
Aint,
Integer(3),
RightParen,
LeftCurly,
Identifier("var".to_owned()),
Print,
RightCurly,
RightCurly,
];
let result: Vec<_> = Token::lexer(code).collect::<Result<_, _>>().unwrap();
assert_eq!(result, expected);
}
#[test]
fn escapes() {
let code = r#"/*»"720B""722B""7195"«*/"#;
let expected = &[Token::String("»にゃぁ«".to_owned())];
let result: Vec<_> = Token::lexer(code).collect::<Result<_, _>>().unwrap();
assert_eq!(result, expected);
}
}

18
ablescript/src/lib.rs Normal file
View file

@ -0,0 +1,18 @@
//! The AbleScript language reference implementation. See
//! <https://git.ablecorp.us/AbleScript/able-script> for more
//! information.
#![forbid(unsafe_code)]
#![cfg_attr(not(test), forbid(clippy::unwrap_used))]
pub mod ast;
pub mod error;
pub mod host_interface;
pub mod interpret;
pub mod parser;
pub mod value;
mod base_55;
mod brian;
mod consts;
mod lexer;

812
ablescript/src/parser.rs Normal file
View file

@ -0,0 +1,812 @@
//! AbleScript Parser
//!
//! Type of this parser is recursive descent
use crate::ast::*;
use crate::error::{Error, ErrorKind};
use crate::lexer::Token;
use logos::{Lexer, Logos};
/// Parser structure which holds lexer and metadata
///
/// Make one using [`Parser::new`] function
struct Parser<'source> {
lexer: Lexer<'source, Token>,
tdark: bool,
}
impl<'source> Parser<'source> {
/// Create a new parser from source code
fn new(source: &'source str) -> Self {
Self {
lexer: Token::lexer(source),
tdark: false,
}
}
/// Start parsing tokens
///
/// Loops trough lexer, parses statements, returns AST
fn parse(&mut self) -> Result<Block, Error> {
let mut ast = vec![];
while let Some(token) = self.lexer.next() {
match token {
// T-Dark block (replace `lang` with `script`)
Ok(Token::TDark) => ast.extend(self.tdark_flow()?),
Ok(token) => ast.push(self.parse_stmt(token)?),
// Invalid token
Err(()) => return Err(Error::new(ErrorKind::InvalidToken, self.lexer.span())),
}
}
Ok(ast)
}
/// Get next item
///
/// If EOF, return Error instead of None
fn checked_next(&mut self) -> Result<Token, Error> {
match self.lexer.next() {
Some(Ok(t)) => Ok(t),
Some(Err(())) => Err(Error::new(ErrorKind::InvalidToken, self.lexer.span())),
None => Err(Error::unexpected_eoi(self.lexer.span().start)),
}
}
/// Parse a token
///
/// This function will route to corresponding flow functions
/// which may advance the lexer iterator
fn parse_stmt(&mut self, token: Token) -> Result<Spanned<Stmt>, Error> {
let start = self.lexer.span().start;
match token {
Token::Unless => self.unless_flow(),
Token::Functio => self.functio_flow(),
Token::Bff => self.bff_flow(),
Token::Melo => self.melo_flow(),
Token::Loop => self.get_block().map(|body| Stmt::Loop { body }),
Token::Enough => self.semicolon_terminated(Stmt::Enough),
Token::AndAgain => self.semicolon_terminated(Stmt::AndAgain),
Token::Finally => self.get_block().map(Stmt::Finally),
Token::Rlyeh => self.semicolon_terminated(Stmt::Rlyeh),
Token::Rickroll => self.semicolon_terminated(Stmt::Rickroll),
Token::Identifier(_)
| Token::String(_)
| Token::Integer(_)
| Token::Char(_)
| Token::Aint
| Token::LeftBracket
| Token::LeftParen => self.value_flow(token),
t => Err(Error {
kind: ErrorKind::UnexpectedToken(t),
span: start..self.lexer.span().end,
}),
}
.map(|stmt| Spanned::new(stmt, start..self.lexer.span().end))
}
/// Require statement to be semicolon terminated
///
/// Utility function for short statements
fn semicolon_terminated(&mut self, stmt_kind: Stmt) -> Result<Stmt, Error> {
self.require(Token::Semicolon)?;
Ok(stmt_kind)
}
/// Require next item to be equal with expected one
fn require(&mut self, required: Token) -> Result<(), Error> {
match self.checked_next()? {
t if t == required => Ok(()),
t => Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
}
}
/// Get an Identifier
fn get_ident(&mut self) -> Result<Spanned<String>, Error> {
match self.checked_next()? {
Token::Identifier(ident) => {
Ok(Spanned::new(self.tdark_subst(ident), self.lexer.span()))
}
t => Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
}
}
/// Parse an expression
///
/// AbleScript strongly separates expressions from statements.
/// Expressions do not have any side effects and the are
/// only mathematial and logical operations or values.
fn parse_expr(
&mut self,
token: Token,
buf: &mut Option<Spanned<Expr>>,
) -> Result<Spanned<Expr>, Error> {
let start = match buf {
Some(e) => e.span.start,
None => self.lexer.span().start,
};
match token {
// Values
Token::Identifier(i) => Ok(Expr::Variable(self.tdark_subst(i))),
Token::Integer(i) => Ok(Expr::Literal(Literal::Int(i))),
Token::String(s) => Ok(Expr::Literal(Literal::Str(self.tdark_subst(s)))),
Token::Char(c) => Ok(Expr::Literal(Literal::Char(c))),
Token::LeftBracket => match buf.take() {
Some(buf) => self.index_flow(buf),
None => self.cart_flow(),
},
// Operations
Token::Aint if buf.is_none() => {
let next = self.checked_next()?;
Ok(Expr::Aint(Box::new(self.parse_expr(next, buf)?)))
}
Token::Plus
| Token::Minus
| Token::Star
| Token::FwdSlash
| Token::Equals
| Token::LessThan
| Token::GreaterThan
| Token::Aint => self.binop_flow(
BinOpKind::from_token(token).map_err(|e| Error::new(e, self.lexer.span()))?,
buf,
),
Token::LeftParen => return self.expr_flow(Token::RightParen),
t => Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
}
.map(|expr| Spanned::new(expr, start..self.lexer.span().end))
}
/// Flow for creating carts
fn cart_flow(&mut self) -> Result<Expr, Error> {
let mut cart = vec![];
let mut buf = None;
match self.checked_next()? {
Token::RightBracket => (),
t => {
buf = Some(self.parse_expr(t, &mut buf)?);
'cart: loop {
let value = loop {
match self.checked_next()? {
Token::Arrow => break buf.take(),
t => buf = Some(self.parse_expr(t, &mut buf)?),
}
}
.ok_or_else(|| {
Error::new(ErrorKind::UnexpectedToken(Token::Arrow), self.lexer.span())
})?;
let key = loop {
match self.checked_next()? {
Token::RightBracket => {
cart.push((
value,
buf.take().ok_or_else(|| {
Error::unexpected_eoi(self.lexer.span().start)
})?,
));
break 'cart;
}
Token::Comma => break buf.take(),
t => buf = Some(self.parse_expr(t, &mut buf)?),
}
}
.ok_or_else(|| Error::unexpected_eoi(self.lexer.span().start))?;
cart.push((value, key));
}
}
}
Ok(Expr::Cart(cart))
}
/// Flow for indexing operations
///
/// Indexing with empty index resolves to length of expression, else it indexes
fn index_flow(&mut self, expr: Spanned<Expr>) -> Result<Expr, Error> {
let mut buf = None;
Ok(loop {
match self.checked_next()? {
Token::RightBracket => match buf {
Some(index) => {
break Expr::Index {
expr: Box::new(expr),
index: Box::new(index),
}
}
None => break Expr::Len(Box::new(expr)),
},
Token::GreaterThan if buf.is_none() => {
self.require(Token::RightBracket)?;
break Expr::Keys(Box::new(expr));
}
token => buf = Some(self.parse_expr(token, &mut buf)?),
}
})
}
/// Flow for operators
///
/// Generates operation from LHS buffer and next expression as RHS
///
/// This is unaware of precedence, as AbleScript do not have it
fn binop_flow(
&mut self,
kind: BinOpKind,
lhs: &mut Option<Spanned<Expr>>,
) -> Result<Expr, Error> {
Ok(Expr::BinOp {
lhs: Box::new(
lhs.take()
.ok_or_else(|| Error::new(ErrorKind::MissingLhs, self.lexer.span()))?,
),
rhs: {
let next = self.checked_next()?;
Box::new(self.parse_expr(next, &mut None)?)
},
kind,
})
}
/// Parse expressions until terminate token
fn expr_flow(&mut self, terminate: Token) -> Result<Spanned<Expr>, Error> {
let mut buf = None;
Ok(loop {
match self.checked_next()? {
t if t == terminate => {
break buf.take().ok_or_else(|| {
Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())
})?
}
t => buf = Some(self.parse_expr(t, &mut buf)?),
}
})
}
/// Parse a list of statements between curly braces
fn get_block(&mut self) -> Result<Block, Error> {
self.require(Token::LeftCurly)?;
let mut block = vec![];
loop {
match self.checked_next()? {
Token::RightCurly => break,
Token::TDark => block.extend(self.tdark_flow()?),
t => block.push(self.parse_stmt(t)?),
}
}
Ok(block)
}
/// Parse T-Dark block
fn tdark_flow(&mut self) -> Result<Block, Error> {
self.tdark = true;
let block = self.get_block();
self.tdark = false;
block
}
/// If Statement parser gets any kind of value (Identifier or Literal)
/// It cannot parse it as it do not parse expressions. Instead of it it
/// will parse it to function call or print statement.
fn value_flow(&mut self, init: Token) -> Result<Stmt, Error> {
let mut buf = Some(self.parse_expr(init, &mut None)?);
Ok(loop {
match self.checked_next()? {
// Print to stdout
Token::Print => {
break Stmt::Print {
expr: buf.take().ok_or_else(|| {
Error::new(ErrorKind::UnexpectedToken(Token::Print), self.lexer.span())
})?,
newline: match self.checked_next()? {
Token::Semicolon => true,
Token::Minus => {
self.require(Token::Semicolon)?;
false
}
token => {
return Err(Error::new(
ErrorKind::UnexpectedToken(token),
self.lexer.span(),
));
}
},
};
}
// Functio call
Token::LeftParen => {
break self.functio_call_flow(buf.take().ok_or_else(|| {
Error::new(
ErrorKind::UnexpectedToken(Token::LeftParen),
self.lexer.span(),
)
})?)?;
}
// Variable declaration
Token::Dim => {
return match buf.take() {
Some(Spanned {
item: Expr::Variable(ident),
span,
}) => Ok(Stmt::Dim {
ident: Spanned::new(ident, span),
init: {
let mut init = None;
loop {
match self.checked_next()? {
Token::Semicolon => break init,
token => init = Some(self.parse_expr(token, &mut init)?),
}
}
},
}),
_ => Err(Error::new(
ErrorKind::UnexpectedToken(Token::Dim),
self.lexer.span(),
)),
}
}
// Variable assignment
Token::Assign => {
return match buf.take() {
Some(expr) => self.assignment_flow(expr),
None => Err(Error::new(
ErrorKind::UnexpectedToken(Token::Assign),
self.lexer.span(),
)),
}
}
// Read input
Token::Read => {
if let Some(Ok(assignable)) = buf.take().map(Assignable::from_expr) {
self.require(Token::Semicolon)?;
break Stmt::Read(assignable);
} else {
return Err(Error::new(
ErrorKind::UnexpectedToken(Token::Read),
self.lexer.span(),
));
}
}
t => buf = Some(self.parse_expr(t, &mut buf)?),
}
})
}
/// Parse Unless flow
///
/// Consists of condition and block, there is no else
fn unless_flow(&mut self) -> Result<Stmt, Error> {
self.require(Token::LeftParen)?;
Ok(Stmt::Unless {
cond: self.expr_flow(Token::RightParen)?,
body: self.get_block()?,
})
}
/// Parse functio flow
///
/// functio $ident (a, b, c) { ... }
fn functio_flow(&mut self) -> Result<Stmt, Error> {
let ident = self.get_ident()?;
self.require(Token::LeftParen)?;
let mut params = vec![];
loop {
match self.checked_next()? {
Token::RightParen => break,
Token::Identifier(i) => {
params.push(Spanned::new(i, self.lexer.span()));
// Require comma (next) or right paren (end) after identifier
match self.checked_next()? {
Token::Comma => continue,
Token::RightParen => break,
t => {
return Err(Error::new(
ErrorKind::UnexpectedToken(t),
self.lexer.span(),
))
}
}
}
t => return Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
}
}
let body = self.get_block()?;
Ok(Stmt::Functio {
ident,
params,
body,
})
}
/// Parse BF function declaration
///
/// `bff $ident ([tapelen]) { ... }`
fn bff_flow(&mut self) -> Result<Stmt, Error> {
let ident = self.get_ident()?;
let tape_len = match self.checked_next()? {
Token::LeftParen => {
let len = Some(self.expr_flow(Token::RightParen)?);
self.require(Token::LeftCurly)?;
len
}
Token::LeftCurly => None,
token => {
return Err(Error::new(
ErrorKind::UnexpectedToken(token),
self.lexer.span(),
))
}
};
let mut code: Vec<u8> = vec![];
loop {
match self.lexer.next() {
Some(Ok(Token::RightCurly)) => break,
Some(_) => code.push(self.lexer.slice().as_bytes()[0]),
None => return Err(Error::unexpected_eoi(self.lexer.span().start)),
}
}
Ok(Stmt::BfFunctio {
ident,
tape_len,
code,
})
}
/// Parse functio call flow
fn functio_call_flow(&mut self, expr: Spanned<Expr>) -> Result<Stmt, Error> {
let mut args = vec![];
let mut buf = None;
loop {
match self.checked_next()? {
// End of argument list
Token::RightParen => {
if let Some(expr) = buf.take() {
args.push(expr)
}
break;
}
// Next argument
Token::Comma => match buf.take() {
Some(expr) => args.push(expr),
// Comma alone
None => {
return Err(Error::new(
ErrorKind::UnexpectedToken(Token::Comma),
self.lexer.span(),
))
}
},
t => buf = Some(self.parse_expr(t, &mut buf)?),
}
}
self.require(Token::Semicolon)?;
Ok(Stmt::Call { expr, args })
}
/// Parse assignment to assignable
fn assignment_flow(&mut self, value: Spanned<Expr>) -> Result<Stmt, Error> {
let ident = self.get_ident()?;
let kind = match self.checked_next()? {
Token::Semicolon => AssignableKind::Variable,
Token::LeftBracket => {
let mut indices = vec![];
loop {
indices.push(self.expr_flow(Token::RightBracket)?);
match self.checked_next()? {
Token::Semicolon => break AssignableKind::Index { indices },
Token::LeftBracket => (),
t => {
return Err(Error::new(
ErrorKind::UnexpectedToken(t),
self.lexer.span(),
))
}
}
}
}
t => return Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
};
Ok(Stmt::Assign {
assignable: Assignable { ident, kind },
value,
})
}
/// Parse Melo flow
fn melo_flow(&mut self) -> Result<Stmt, Error> {
let ident = self.get_ident()?;
self.semicolon_terminated(Stmt::Melo(ident))
}
/// Perform lang -> script substitution if in T-Dark block
fn tdark_subst(&self, mut string: String) -> String {
if self.tdark {
if let Some(pos) = string.to_lowercase().find("lang") {
let range = pos..pos + 4;
let mut count_upper = 0_u8;
string.replace_range(
range.clone(),
&(string[range]
.chars()
.zip("scri".chars())
.map(|(lc, sc)| {
if lc.is_uppercase() {
count_upper += 1;
sc.to_ascii_uppercase()
} else {
sc.to_ascii_lowercase()
}
})
.collect::<String>()
+ match count_upper {
0 | 1 => "pt",
2 if rand::random() => "Pt",
2 => "pT",
_ => "PT",
}),
)
}
}
string
}
}
/// Parse AbleScript code into AST
pub fn parse(source: &str) -> Result<Block, Error> {
Parser::new(source).parse()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_math() {
let code = "1 * (num + 3) / 666 print;";
let expected = &[Spanned {
item: Stmt::Print {
expr: Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(1)),
span: 0..1,
}),
rhs: Box::new(Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Variable("num".to_owned()),
span: 5..6,
}),
rhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(3)),
span: 9..10,
}),
kind: BinOpKind::Add,
},
span: 5..10,
}),
kind: BinOpKind::Multiply,
},
span: 0..11,
}),
rhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(666)),
span: 14..17,
}),
kind: BinOpKind::Divide,
},
span: 0..17,
},
newline: true,
},
span: 0..24,
}];
let ast = Parser::new(code).parse().unwrap();
assert_eq!(ast, expected);
}
#[test]
fn variable_declaration() {
let code = "var dim 42;";
let expected = &[Spanned {
item: Stmt::Dim {
ident: Spanned {
item: "var".to_owned(),
span: 0..3,
},
init: Some(Spanned {
item: Expr::Literal(Literal::Int(42)),
span: 4..6,
}),
},
span: 0..11,
}];
let ast = Parser::new(code).parse().unwrap();
assert_eq!(ast, expected);
}
#[test]
fn unless_flow() {
let code = "unless (never + never) { /*Buy Able products!*/ print; }";
let expected = &[Spanned {
item: Stmt::Unless {
cond: Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Variable("never".to_owned()),
span: 8..13,
}),
rhs: Box::new(Spanned {
item: Expr::Variable("never".to_owned()),
span: 16..21,
}),
kind: BinOpKind::Add,
},
span: 8..21,
},
body: vec![Spanned {
item: Stmt::Print {
expr: Spanned {
item: Expr::Literal(Literal::Str("Buy Able products!".to_owned())),
span: 25..47,
},
newline: true,
},
span: 25..54,
}],
},
span: 0..56,
}];
let ast = Parser::new(code).parse().unwrap();
assert_eq!(ast, expected);
}
#[test]
fn tdark() {
let code = "T-Dark { lang dim /*lang*/ + lang; }";
let expected = &[Spanned {
item: Stmt::Dim {
ident: Spanned {
item: "script".to_owned(),
span: 9..15,
},
init: Some(Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Str("script".to_owned())),
span: 20..26,
}),
rhs: Box::new(Spanned {
item: Expr::Variable("script".to_owned()),
span: 29..33,
}),
kind: BinOpKind::Add,
},
span: 20..33,
}),
},
span: 9..34,
}];
let ast = Parser::new(code).parse().unwrap();
assert_eq!(ast, expected);
}
#[test]
fn cart_construction() {
let code = "[/*able*/ <= 1, /*script*/ <= 3 - 1] print;";
let expected = &[Spanned {
item: Stmt::Print {
expr: Spanned {
item: Expr::Cart(vec![
(
Spanned {
item: Expr::Literal(Literal::Str("able".to_owned())),
span: 1..7,
},
Spanned {
item: Expr::Literal(Literal::Int(1)),
span: 11..12,
},
),
(
Spanned {
item: Expr::Literal(Literal::Str("script".to_owned())),
span: 14..22,
},
Spanned {
item: Expr::BinOp {
kind: BinOpKind::Subtract,
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(3)),
span: 26..27,
}),
rhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(1)),
span: 30..31,
}),
},
span: 26..31,
},
),
]),
span: 0..32,
},
newline: true,
},
span: 0..39,
}];
let ast = Parser::new(code).parse().unwrap();
assert_eq!(ast, expected);
}
#[test]
fn cart_index() {
let code = "[/*able*/ <= /*ablecorp*/][/*ablecorp*/] print;";
let expected = &[Spanned {
item: Stmt::Print {
expr: Spanned {
item: Expr::Index {
expr: Box::new(Spanned {
item: Expr::Cart(vec![(
Spanned {
item: Expr::Literal(Literal::Str("able".to_owned())),
span: 1..7,
},
Spanned {
item: Expr::Literal(Literal::Str("ablecorp".to_owned())),
span: 11..21,
},
)]),
span: 0..22,
}),
index: Box::new(Spanned {
item: Expr::Literal(Literal::Str("ablecorp".to_owned())),
span: 23..33,
}),
},
span: 0..34,
},
newline: true,
},
span: 0..41,
}];
let ast = Parser::new(code).parse().unwrap();
assert_eq!(ast, expected);
}
}

67
ablescript/src/rickroll Normal file
View file

@ -0,0 +1,67 @@
We're no strangers to love
You know the rules and so do I
A full commitments what I'm thinking of
You wouldn't get this from another guy
I just wanna tell you how I'm feeling
Gotta make you understand
Never gonna give you up
Never gonna let you down
Never gonna run around and desert you
Never gonna make you cry
Never gonna say goodbye
Never gonna tell a lie and hurt you
We've known each other for so long
Your heart's been aching but you're too shy to say it
Inside we both know what's been going on
We know the game and we're gonna play it
And if you ask me how I'm feeling
Don't tell me you're too blind to see
Never gonna give you up
Never gonna let you down
Never gonna run around and desert you
Never gonna make you cry
Never gonna say goodbye
Never gonna tell a lie and hurt you
Never gonna give you up
Never gonna let you down
Never gonna run around and desert you
Never gonna make you cry
Never gonna say goodbye
Never gonna tell a lie and hurt you
Never gonna give, never gonna give
(Give you up)
We've known each other for so long
Your heart's been aching but you're too shy to say it
Inside we both know what's been going on
We know the game and we're gonna play it
I just wanna tell you how I'm feeling
Gotta make you understand
Never gonna give you up
Never gonna let you down
Never gonna run around and desert you
Never gonna make you cry
Never gonna say goodbye
Never gonna tell a lie and hurt you
Never gonna give you up
Never gonna let you down
Never gonna run around and desert you
Never gonna make you cry
Never gonna say goodbye
Never gonna tell a lie and hurt you
Never gonna give you up
Never gonna let you down
Never gonna run around and desert you
Never gonna make you cry
Never gonna say goodbye

View file

@ -0,0 +1,279 @@
use super::{functio::FunctioChainKind, Abool, Cart, Functio, Value, ValueRef};
use crate::{brian::INSTRUCTION_MAPPINGS, consts};
use std::collections::HashMap;
impl Value {
/// Coerce a value to an integer.
pub fn into_isize(self) -> isize {
match self {
Value::Nul => consts::ANSWER,
Value::Undefined => rand::random(),
Value::Str(text) => text
.parse()
.unwrap_or_else(|_| text.chars().map(|cr| cr as isize).sum()),
Value::Int(i) => i,
Value::Abool(a) => a as _,
Value::Functio(f) => match f {
Functio::Bf {
instructions,
tape_len,
} => {
instructions.into_iter().map(|x| x as isize).sum::<isize>() * tape_len as isize
}
Functio::Able { params, body } => {
params
.into_iter()
.map(|x| x.bytes().map(|x| x as isize).sum::<isize>())
.sum::<isize>()
+ body.len() as isize
}
Functio::Builtin(b) => (b.fn_addr() + b.arity) as _,
Functio::Chain { functios, kind } => {
let (lf, rf) = *functios;
Value::Functio(lf).into_isize()
+ Value::Functio(rf).into_isize()
* match kind {
FunctioChainKind::Equal => -1,
FunctioChainKind::ByArity => 1,
}
}
Functio::Eval(code) => code.bytes().map(|x| x as isize).sum(),
},
Value::Cart(c) => c
.into_iter()
.map(|(i, v)| i.into_isize() * v.borrow().clone().into_isize())
.sum(),
}
}
/// Coerce a value to an aboolean.
pub fn into_abool(self) -> Abool {
match self {
Value::Nul => Abool::Never,
Value::Undefined => Abool::Sometimes,
Value::Str(s) => match s.to_lowercase().as_str() {
"never" | "no" | "🇳🇴" => Abool::Never,
"sometimes" => Abool::Sometimes,
"always" | "yes" => Abool::Always,
s => (!s.is_empty()).into(),
},
Value::Int(x) => match x.cmp(&0) {
std::cmp::Ordering::Less => Abool::Never,
std::cmp::Ordering::Equal => Abool::Sometimes,
std::cmp::Ordering::Greater => Abool::Always,
},
Value::Abool(a) => a,
Value::Functio(f) => match f {
Functio::Bf {
instructions,
tape_len,
} => Value::Int(
(instructions.iter().map(|x| *x as usize).sum::<usize>() * tape_len) as _,
)
.into_abool(),
Functio::Able { params, body } => {
let str_to_isize =
|x: String| -> isize { x.as_bytes().iter().map(|x| *x as isize).sum() };
let params: isize = params.into_iter().map(str_to_isize).sum();
let body: isize = body
.into_iter()
.map(|x| format!("{:?}", x))
.map(str_to_isize)
.sum();
Value::Int((params + body) % 3 - 1).into_abool()
}
Functio::Builtin(b) => (b.fn_addr() % b.arity == 0).into(),
Functio::Chain { functios, kind } => {
let (lhs, rhs) = *functios;
match kind {
FunctioChainKind::Equal => {
Value::Abool(Value::Functio(lhs).into_abool())
+ Value::Abool(Value::Functio(rhs).into_abool())
}
FunctioChainKind::ByArity => {
Value::Abool(Value::Functio(lhs).into_abool())
* Value::Abool(Value::Functio(rhs).into_abool())
}
}
.into_abool()
}
Functio::Eval(code) => Value::Str(code).into_abool(),
},
Value::Cart(c) => {
if c.is_empty() {
Abool::Never
} else {
Abool::Always
}
}
}
}
/// Coerce a value to a functio.
pub fn into_functio(self) -> Functio {
match self {
Value::Nul | Value::Undefined => Functio::Able {
body: vec![],
params: vec![],
},
Value::Str(s) => Functio::Eval(s),
Value::Int(i) => Functio::Bf {
instructions: {
std::iter::successors(Some(i as usize), |i| {
Some(i / INSTRUCTION_MAPPINGS.len())
})
.take_while(|&i| i != 0)
.map(|i| INSTRUCTION_MAPPINGS[i % INSTRUCTION_MAPPINGS.len()])
.collect()
},
tape_len: crate::brian::DEFAULT_TAPE_SIZE_LIMIT,
},
Value::Abool(a) => Functio::Eval(match a {
Abool::Never => "".to_owned(),
Abool::Sometimes => {
use rand::seq::SliceRandom;
let mut str_chars: Vec<_> = "Buy Able Products!".chars().collect();
str_chars.shuffle(&mut rand::thread_rng());
format!(r#""{}"print;"#, str_chars.iter().collect::<String>())
}
Abool::Always => r#"loop{"Buy Able products!"print;}"#.to_owned(),
}),
Value::Functio(f) => f,
Value::Cart(c) => {
let kind = if let Some(114514) = c
.get(&Value::Str("1452251871514141792252515212116".to_owned()))
.map(|x| x.borrow().to_owned().into_isize())
{
FunctioChainKind::Equal
} else {
FunctioChainKind::ByArity
};
let mut cart_vec = c.iter().collect::<Vec<_>>();
cart_vec.sort_by(|x, y| x.0.partial_cmp(y.0).unwrap_or(std::cmp::Ordering::Less));
cart_vec
.into_iter()
.map(|(_, x)| x.borrow().to_owned().into_functio())
.reduce(|acc, x| Functio::Chain {
functios: Box::new((acc, x)),
kind,
})
.unwrap_or_else(|| Functio::Eval(r#""Buy Able Products!"print;"#.to_owned()))
}
}
}
/// Coerce a value into a cart.
pub fn into_cart(self) -> Cart {
match self {
Value::Nul => HashMap::new(),
Value::Undefined => [(Value::Undefined, ValueRef::new(Value::Undefined))]
.into_iter()
.collect(),
Value::Str(s) => s
.chars()
.enumerate()
.map(|(i, x)| {
(
Value::Int(i as isize + 1),
ValueRef::new(Value::Str(x.to_string())),
)
})
.collect(),
Value::Int(i) => Value::Str(i.to_string()).into_cart(),
Value::Abool(a) => Value::Str(a.to_string()).into_cart(),
Value::Functio(f) => match f {
Functio::Able { params, body } => {
let params: Cart = params
.into_iter()
.enumerate()
.map(|(i, x)| (Value::Int(i as isize + 1), ValueRef::new(Value::Str(x))))
.collect();
let body: Cart = body
.into_iter()
.enumerate()
.map(|(i, x)| {
(
Value::Int(i as isize + 1),
ValueRef::new(Value::Str(format!("{:?}", x))),
)
})
.collect();
let mut cart = HashMap::new();
cart.insert(
Value::Str("params".to_owned()),
ValueRef::new(Value::Cart(params)),
);
cart.insert(
Value::Str("body".to_owned()),
ValueRef::new(Value::Cart(body)),
);
cart
}
Functio::Bf {
instructions,
tape_len,
} => {
let mut cart: Cart = instructions
.into_iter()
.enumerate()
.map(|(i, x)| {
(
Value::Int(i as isize + 1),
ValueRef::new(
char::from_u32(x as u32)
.map(|x| Value::Str(x.to_string()))
.unwrap_or(Value::Nul),
),
)
})
.collect();
cart.insert(
Value::Str("tapelen".to_owned()),
ValueRef::new(Value::Int(tape_len as _)),
);
cart
}
Functio::Builtin(b) => {
let mut cart = HashMap::new();
cart.insert(
Value::Str("addr".to_owned()),
ValueRef::new(Value::Cart(Value::Int(b.fn_addr() as _).into_cart())),
);
cart.insert(
Value::Str("arity".to_owned()),
ValueRef::new(Value::Int(b.arity as _)),
);
cart
}
Functio::Chain { functios, kind } => {
let (lhs, rhs) = *functios;
match kind {
FunctioChainKind::Equal => {
Value::Cart(Value::Functio(lhs).into_cart())
+ Value::Cart(Value::Functio(rhs).into_cart())
}
FunctioChainKind::ByArity => {
Value::Cart(Value::Functio(lhs).into_cart())
* Value::Cart(Value::Functio(rhs).into_cart())
}
}
.into_cart()
}
Functio::Eval(s) => Value::Str(s).into_cart(),
},
Value::Cart(c) => c,
}
}
}

View file

@ -0,0 +1,113 @@
use super::ValueRef;
use crate::ast::Block;
use std::{hash::Hash, rc::Rc};
type BuiltinRc = Rc<dyn Fn(&[ValueRef]) -> Result<(), crate::error::ErrorKind>>;
/// AbleScript Function
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum Functio {
/// BF instructions and a length of the type
///
/// Takes input bytes as parameters
Bf {
instructions: Vec<u8>,
tape_len: usize,
},
/// Regular AbleScript functio
///
/// Consists of parameter name mapping and AST
Able { params: Vec<String>, body: Block },
/// Builtin Rust functio
Builtin(BuiltinFunctio),
/// Chained functio pair
Chain {
functios: Box<(Functio, Functio)>,
kind: FunctioChainKind,
},
/// Code to be parsed and then executed in current scope
Eval(String),
}
impl Functio {
pub fn arity(&self) -> usize {
match self {
Functio::Bf {
instructions: _,
tape_len: _,
} => 0,
Functio::Able { params, body: _ } => params.len(),
Functio::Builtin(b) => b.arity,
Functio::Chain { functios, kind: _ } => functios.0.arity() + functios.1.arity(),
Functio::Eval(_) => 0,
}
}
}
/// Built-in Rust functio
#[derive(Clone)]
pub struct BuiltinFunctio {
pub(super) function: BuiltinRc,
pub(super) arity: usize,
}
impl BuiltinFunctio {
/// Wrap a Rust function into AbleScript's built-in functio
///
/// Arity used for functio chaining, recommend value for variadic
/// functions is the accepted minimum.
pub fn new<F>(f: F, arity: usize) -> Self
where
F: Fn(&[ValueRef]) -> Result<(), crate::error::ErrorKind> + 'static,
{
Self {
function: Rc::new(f),
arity,
}
}
pub fn call(&self, args: &[ValueRef]) -> Result<(), crate::error::ErrorKind> {
(self.function)(args)
}
pub fn fn_addr(&self) -> usize {
Rc::as_ptr(&self.function) as *const () as _
}
}
impl std::fmt::Debug for BuiltinFunctio {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BuiltinFunctio")
.field("function", &"built-in")
.field("arity", &self.arity)
.finish()
}
}
impl PartialEq for BuiltinFunctio {
fn eq(&self, other: &Self) -> bool {
self.fn_addr() == other.fn_addr() && self.arity == other.arity
}
}
impl Hash for BuiltinFunctio {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.fn_addr().hash(state);
self.arity.hash(state);
}
}
/// A method of distributting parameters across functio chain
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub enum FunctioChainKind {
/// All parameters are equally distributed
Equal,
/// Parameters are distributed to members of chain
/// by their arity
ByArity,
}

202
ablescript/src/value/mod.rs Normal file
View file

@ -0,0 +1,202 @@
pub mod functio;
mod coercions;
mod ops;
pub use functio::Functio;
use std::{
cell::{Ref, RefCell, RefMut},
collections::HashMap,
fmt::Display,
hash::Hash,
io::Write,
mem::discriminant,
rc::Rc,
};
pub type Cart = HashMap<Value, ValueRef>;
/// AbleScript Value
#[derive(Debug, Default, Clone)]
pub enum Value {
#[default]
Nul,
Undefined,
Str(String),
Int(isize),
Abool(Abool),
Functio(Functio),
Cart(Cart),
}
impl Hash for Value {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
discriminant(self).hash(state);
match self {
Value::Nul | Value::Undefined => (),
Value::Str(v) => v.hash(state),
Value::Int(v) => v.hash(state),
Value::Abool(v) => v.to_string().hash(state),
Value::Functio(statements) => statements.hash(state),
Value::Cart(_) => self.to_string().hash(state),
}
}
}
impl Value {
/// Write an AbleScript value to a Brainfuck input stream by
/// coercing the value to an integer, then truncating that integer
/// to a single byte, then writing that byte. This should only be
/// called on `Write`rs that cannot fail, e.g., `Vec<u8>`, because
/// any IO errors will cause a panic.
pub fn bf_write(&self, stream: &mut impl Write) {
stream
.write_all(&[self.clone().into_isize() as u8])
.expect("Failed to write to Brainfuck input");
}
}
/// Three-state logic value
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum Abool {
Never = -1,
Sometimes = 0,
Always = 1,
}
impl Abool {
pub fn to_bool(&self) -> bool {
match self {
Self::Always => true,
Self::Sometimes if rand::random() => true,
_ => false,
}
}
}
impl Display for Abool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Abool::Never => write!(f, "never"),
Abool::Sometimes => write!(f, "sometimes"),
Abool::Always => write!(f, "always"),
}
}
}
impl From<bool> for Abool {
fn from(b: bool) -> Self {
if b {
Abool::Always
} else {
Abool::Never
}
}
}
impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Nul => write!(f, "nul"),
Value::Undefined => write!(f, "undefined"),
Value::Str(v) => write!(f, "{}", v),
Value::Int(v) => write!(f, "{}", v),
Value::Abool(v) => write!(f, "{}", v),
Value::Functio(v) => match v {
Functio::Bf {
instructions,
tape_len,
} => {
write!(
f,
"({}) {}",
tape_len,
String::from_utf8(instructions.to_owned())
.expect("Brainfuck functio source should be UTF-8")
)
}
Functio::Able { params, body } => {
write!(
f,
"({}) -> {:?}",
params.join(", "),
// Maybe we should have a pretty-printer for
// statement blocks at some point?
body,
)
}
Functio::Builtin(b) => write!(f, "builtin @ {}", b.fn_addr()),
Functio::Chain { functios, kind } => {
let (a, b) = *functios.clone();
write!(
f,
"{} {} {} ",
Value::Functio(a),
match kind {
functio::FunctioChainKind::Equal => '+',
functio::FunctioChainKind::ByArity => '*',
},
Value::Functio(b)
)
}
Functio::Eval(s) => write!(f, "{}", s),
},
Value::Cart(c) => {
write!(f, "[")?;
let mut cart_vec = c.iter().collect::<Vec<_>>();
cart_vec.sort_by(|x, y| x.0.partial_cmp(y.0).unwrap_or(std::cmp::Ordering::Less));
for (idx, (key, value)) in cart_vec.into_iter().enumerate() {
write!(f, "{}", if idx != 0 { ", " } else { "" },)?;
match &*value.borrow() {
x if std::ptr::eq(x, self) => write!(f, "<cycle>"),
x => write!(f, "{x}"),
}?;
write!(f, " <= {key}")?;
}
write!(f, "]")
}
}
}
}
/// Runtime borrow-checked, counted reference to a [Value]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValueRef(Rc<RefCell<Value>>);
impl ValueRef {
pub fn new(v: Value) -> Self {
Self(Rc::new(RefCell::new(v)))
}
pub fn borrow(&self) -> Ref<Value> {
self.0.borrow()
}
pub fn borrow_mut(&self) -> RefMut<Value> {
self.0.borrow_mut()
}
pub fn replace(&self, v: Value) -> Value {
self.0.replace(v)
}
}
/// AbleScript variable either holding a reference
/// or being banned
#[derive(Debug)]
pub enum Variable {
/// Reference to a value
Ref(ValueRef),
/// Banned variable
Melo,
}
impl Variable {
pub fn from_value(value: Value) -> Self {
Self::Ref(ValueRef::new(value))
}
}

455
ablescript/src/value/ops.rs Normal file
View file

@ -0,0 +1,455 @@
use super::{
functio::{BuiltinFunctio, FunctioChainKind},
Abool, Functio, Value, ValueRef,
};
use crate::consts;
use rand::Rng;
use std::{
collections::HashMap,
ops::{Add, Div, Mul, Not, Sub},
};
impl Add for Value {
type Output = Value;
fn add(self, rhs: Self) -> Self::Output {
match self {
Value::Nul | Value::Undefined => match rhs {
Value::Nul => Value::Nul,
Value::Undefined => Value::Undefined,
Value::Str(_) => Value::Str(self.to_string()) + rhs,
Value::Int(_) => Value::Int(self.into_isize()) + rhs,
Value::Abool(_) => Value::Abool(self.into_abool()) + rhs,
Value::Functio(_) => Value::Functio(self.into_functio()) + rhs,
Value::Cart(_) => Value::Cart(self.into_cart()) + rhs,
},
Value::Str(s) => Value::Str(format!("{s}{rhs}")),
Value::Int(i) => Value::Int(i.wrapping_add(rhs.into_isize())),
Value::Abool(_) => {
Value::Abool(Value::Int(self.into_isize().max(rhs.into_isize())).into_abool())
}
Value::Functio(f) => Value::Functio(Functio::Chain {
functios: Box::new((f, rhs.into_functio())),
kind: FunctioChainKind::Equal,
}),
Value::Cart(c) => {
Value::Cart(c.into_iter().chain(rhs.into_cart().into_iter()).collect())
}
}
}
}
impl Sub for Value {
type Output = Value;
fn sub(self, rhs: Self) -> Self::Output {
match self {
Value::Nul | Value::Undefined => match rhs {
Value::Nul => Value::Nul,
Value::Undefined => Value::Undefined,
Value::Str(_) => Value::Str(self.to_string()) - rhs,
Value::Int(_) => Value::Int(self.into_isize()) - rhs,
Value::Abool(_) => Value::Abool(self.into_abool()) - rhs,
Value::Functio(_) => Value::Functio(self.into_functio()) - rhs,
Value::Cart(_) => Value::Cart(self.into_cart()) - rhs,
},
Value::Str(s) => Value::Str(s.replace(&rhs.to_string(), "")),
Value::Int(i) => Value::Int(i.wrapping_sub(rhs.into_isize())),
Value::Abool(_) => (self.clone() + rhs.clone()) * !(self * rhs),
Value::Functio(f) => Value::Functio(match f {
Functio::Bf {
instructions: lhs_ins,
tape_len: lhs_tl,
} => match rhs.into_functio() {
Functio::Bf {
instructions: rhs_ins,
tape_len: rhs_tl,
} => Functio::Bf {
instructions: lhs_ins
.into_iter()
.zip(rhs_ins.into_iter())
.filter_map(|(l, r)| if l != r { Some(l) } else { None })
.collect(),
tape_len: lhs_tl - rhs_tl,
},
rhs => Functio::Bf {
instructions: lhs_ins
.into_iter()
.zip(Value::Functio(rhs).to_string().bytes())
.filter_map(|(l, r)| if l != r { Some(l) } else { None })
.collect(),
tape_len: lhs_tl,
},
},
Functio::Able {
params: lhs_params,
body: lhs_body,
} => match rhs.into_functio() {
Functio::Able {
params: rhs_params,
body: rhs_body,
} => Functio::Able {
params: lhs_params
.into_iter()
.zip(rhs_params.into_iter())
.filter_map(|(l, r)| if l != r { Some(l) } else { None })
.collect(),
body: lhs_body
.into_iter()
.zip(rhs_body.into_iter())
.filter_map(|(l, r)| if l != r { Some(l) } else { None })
.collect(),
},
rhs => Value::Int(
Value::Functio(Functio::Able {
params: lhs_params,
body: lhs_body,
})
.into_isize()
- Value::Functio(rhs).into_isize(),
)
.into_functio(),
},
Functio::Builtin(b) => {
let arity = b.arity;
let resulting_arity = arity.saturating_sub(rhs.into_isize() as usize);
Functio::Builtin(BuiltinFunctio::new(
move |args| {
b.call(
&args
.iter()
.take(resulting_arity)
.cloned()
.chain(std::iter::repeat_with(|| ValueRef::new(Value::Nul)))
.take(arity)
.collect::<Vec<_>>(),
)
},
resulting_arity,
))
}
Functio::Chain { functios, .. } => {
let rhs = rhs.into_functio();
let (a, b) = *functios;
match (a == rhs, b == rhs) {
(_, true) => a,
(true, _) => b,
(_, _) => (Value::Functio(a) - Value::Functio(rhs)).into_functio(),
}
}
Functio::Eval(lhs_code) => Functio::Eval(lhs_code.replace(
&match rhs.into_functio() {
Functio::Eval(code) => code,
rhs => Value::Functio(rhs).to_string(),
},
"",
)),
}),
Value::Cart(c) => Value::Cart({
let rhs_cart = rhs.into_cart();
c.into_iter()
.filter(|(k, v)| rhs_cart.get(k) != Some(v))
.collect()
}),
}
}
}
impl Mul for Value {
type Output = Value;
fn mul(self, rhs: Self) -> Self::Output {
match self {
Value::Nul | Value::Undefined => match rhs {
Value::Nul => Value::Nul,
Value::Undefined => Value::Undefined,
Value::Str(_) => Value::Str(self.to_string()) * rhs,
Value::Int(_) => Value::Int(self.into_isize()) * rhs,
Value::Abool(_) => Value::Abool(self.into_abool()) * rhs,
Value::Functio(_) => Value::Functio(self.into_functio()) * rhs,
Value::Cart(_) => Value::Cart(self.into_cart()) * rhs,
},
Value::Str(s) => Value::Str(s.repeat(rhs.into_isize() as usize)),
Value::Int(i) => Value::Int(i.wrapping_mul(rhs.into_isize())),
Value::Abool(_) => {
Value::Abool(Value::Int(self.into_isize().min(rhs.into_isize())).into_abool())
}
Value::Functio(f) => Value::Functio(Functio::Chain {
functios: Box::new((f, rhs.into_functio())),
kind: FunctioChainKind::ByArity,
}),
Value::Cart(c) => {
let rhsc = rhs.into_cart();
Value::Cart(
c.into_iter()
.map(|(k, v)| {
if let Some(k) = rhsc.get(&k) {
(k.borrow().clone(), v)
} else {
(k, v)
}
})
.collect(),
)
}
}
}
}
impl Div for Value {
type Output = Value;
fn div(self, rhs: Self) -> Self::Output {
match self {
Value::Nul | Value::Undefined => match rhs {
Value::Nul => Value::Nul,
Value::Undefined => Value::Undefined,
Value::Str(_) => Value::Str(self.to_string()) / rhs,
Value::Int(_) => Value::Int(self.into_isize()) / rhs,
Value::Abool(_) => Value::Abool(self.into_abool()) / rhs,
Value::Functio(_) => Value::Functio(self.into_functio()) / rhs,
Value::Cart(_) => Value::Cart(self.into_cart()) / rhs,
},
Value::Str(s) => Value::Cart(
s.split(&rhs.to_string())
.enumerate()
.map(|(i, x)| {
(
Value::Int(i as isize + 1),
ValueRef::new(Value::Str(x.to_owned())),
)
})
.collect(),
),
Value::Int(i) => Value::Int(i.wrapping_div(match rhs.into_isize() {
0 => consts::ANSWER,
x => x,
})),
Value::Abool(_) => !self + rhs,
Value::Functio(f) => Value::Functio(match f {
Functio::Bf {
instructions,
tape_len,
} => {
let fraction = 1.0 / rhs.into_isize() as f64;
let len = instructions.len();
Functio::Bf {
instructions: instructions
.into_iter()
.take((len as f64 * fraction) as usize)
.collect(),
tape_len,
}
}
Functio::Able { params, body } => {
let fraction = 1.0 / rhs.into_isize() as f64;
let len = body.len();
Functio::Able {
params,
body: body
.into_iter()
.take((len as f64 * fraction) as usize)
.collect(),
}
}
Functio::Builtin(b) => Functio::Builtin(BuiltinFunctio {
arity: b.arity + rhs.into_isize() as usize,
..b
}),
Functio::Chain { functios, kind } => {
let functios = *functios;
Functio::Chain {
functios: Box::new((
(Value::Functio(functios.0) / rhs.clone()).into_functio(),
(Value::Functio(functios.1) / rhs).into_functio(),
)),
kind,
}
}
Functio::Eval(s) => {
let fraction = 1.0 / rhs.into_isize() as f64;
let len = s.len();
Functio::Eval(s.chars().take((len as f64 * fraction) as usize).collect())
}
}),
Value::Cart(c) => {
let cart_len = match c.len() {
0 => return Value::Cart(HashMap::new()),
l => l,
};
let chunk_len = match rhs.into_isize() as usize {
0 => rand::thread_rng().gen_range(1..=cart_len),
l => l,
};
Value::Cart(
c.into_iter()
.collect::<Vec<_>>()
.chunks(cart_len / chunk_len + (cart_len % chunk_len != 0) as usize)
.enumerate()
.map(|(k, v)| {
(
Value::Int(k as isize + 1),
ValueRef::new(Value::Cart(v.iter().cloned().collect())),
)
})
.collect(),
)
}
}
}
}
impl Not for Value {
type Output = Value;
fn not(self) -> Self::Output {
match self {
Value::Nul => Value::Undefined,
Value::Undefined => Value::Nul,
Value::Str(s) => Value::Str(s.chars().rev().collect()),
Value::Int(i) => Value::Int(i.swap_bytes()),
Value::Abool(a) => Value::Abool(match a {
Abool::Never => Abool::Always,
Abool::Sometimes => Abool::Sometimes,
Abool::Always => Abool::Never,
}),
Value::Functio(f) => Value::Functio(match f {
Functio::Bf {
mut instructions,
tape_len,
} => {
instructions.reverse();
Functio::Bf {
instructions,
tape_len,
}
}
Functio::Able {
mut params,
mut body,
} => {
params.reverse();
body.reverse();
Functio::Able { params, body }
}
Functio::Builtin(b) => {
let arity = b.arity;
Functio::Builtin(BuiltinFunctio::new(
move |args| b.call(&args.iter().cloned().rev().collect::<Vec<_>>()),
arity,
))
}
Functio::Chain { functios, kind } => {
let (a, b) = *functios;
Functio::Chain {
functios: Box::new((
(!Value::Functio(b)).into_functio(),
(!Value::Functio(a)).into_functio(),
)),
kind,
}
}
Functio::Eval(code) => Functio::Eval(code.chars().rev().collect()),
}),
Value::Cart(c) => Value::Cart(
c.into_iter()
.map(|(k, v)| (v.borrow().clone(), ValueRef::new(k)))
.collect(),
),
}
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
let other = other.clone();
match self {
Value::Nul => matches!(other, Value::Nul),
Value::Undefined => matches!(other, Value::Undefined),
Value::Str(s) => *s == other.to_string(),
Value::Int(i) => *i == other.into_isize(),
Value::Abool(a) => *a == other.into_abool(),
Value::Functio(f) => *f == other.into_functio(),
Value::Cart(c) => *c == other.into_cart(),
}
}
}
impl Eq for Value {}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use std::cmp::Ordering::*;
let other = other.clone();
match self {
Value::Nul if other == Value::Nul => Some(Equal),
Value::Undefined if other == Value::Undefined => Some(Equal),
Value::Str(s) => Some(s.cmp(&other.to_string())),
Value::Int(i) => Some(i.cmp(&other.into_isize())),
Value::Abool(a) => a.partial_cmp(&other.into_abool()),
Value::Functio(_) => self.clone().into_isize().partial_cmp(&other.into_isize()),
Value::Cart(c) => Some(c.len().cmp(&other.into_cart().len())),
Value::Nul | Value::Undefined => None,
}
}
}
impl Value {
/// Get a length of a value
pub fn length(&self) -> isize {
match self {
Value::Nul => 0,
Value::Undefined => -1,
Value::Str(s) => s.len() as _,
Value::Int(i) => i.count_zeros() as _,
Value::Abool(a) => match a {
Abool::Never => -3,
Abool::Sometimes => {
if rand::thread_rng().gen() {
3
} else {
-3
}
}
Abool::Always => 3,
},
Value::Functio(f) => match f {
// Compares lengths of functions:
// BfFunctio - Sum of lengths of instructions and length of tape
// AbleFunctio - Sum of argument count and body length
// Eval - Length of input code
Functio::Bf {
instructions,
tape_len,
} => (instructions.len() + tape_len) as _,
Functio::Able { params, body } => (params.len() + format!("{:?}", body).len()) as _,
Functio::Builtin(b) => (std::mem::size_of_val(b.function.as_ref()) + b.arity) as _,
Functio::Chain { functios, kind } => {
let (lhs, rhs) = *functios.clone();
match kind {
FunctioChainKind::Equal => {
Value::Int(Value::Functio(lhs).into_isize())
+ Value::Int(Value::Functio(rhs).into_isize())
}
FunctioChainKind::ByArity => {
Value::Int(Value::Functio(lhs).into_isize())
* Value::Int(Value::Functio(rhs).into_isize())
}
}
.into_isize()
}
Functio::Eval(s) => s.len() as _,
},
Value::Cart(c) => c.len() as _,
}
}
}

15
ablescript_cli/Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "ablescript_cli"
version = "0.5.4"
authors = ["AbleScript Developers"]
edition = "2021"
description = "The best programming language"
license = "MIT"
documentation = "https://gblecorp.github.io/able-script-the-book"
repository = "https://git.ablecorp.us/AbleScript/able-script"
[dependencies]
ablescript = { version = "0.5.3", path = "../ablescript" }
clap = { version = "4.2", features = ["derive"] }
rustyline = "11.0"

View file

@ -0,0 +1,55 @@
#![forbid(unsafe_code)]
mod repl;
use ablescript::{interpret::ExecEnv, parser::parse};
use clap::Parser;
use std::{path::PathBuf, process::exit};
fn main() {
// variables::test(); // NOTE(Able): Add this as a test case
let args = Args::parse();
match args.file {
Some(file_path) => {
// Read file
let source = match std::fs::read_to_string(&file_path) {
Ok(s) => s,
Err(e) => {
println!("Failed to read file \"{:?}\": {}", file_path, e);
exit(1)
}
};
// Parse & evaluate
if let Err(e) = parse(&source).and_then(|ast| {
if args.debug {
eprintln!("{:#?}", ast);
}
ExecEnv::<ablescript::host_interface::Standard>::default().eval_stmts(&ast)
}) {
println!(
"Error `{:?}` occurred at span: {:?} = `{:?}`",
e.kind,
e.span.clone(),
&source[e.span]
);
}
}
None => {
println!("Hi [AbleScript {}]", env!("CARGO_PKG_VERSION"));
repl::repl(args.debug);
}
}
}
#[derive(Parser, Debug)]
#[command(name = "AbleScript", version, about)]
struct Args {
/// File to execute
#[arg(short, long)]
file: Option<PathBuf>,
/// Dump AST to console
#[arg(short, long)]
debug: bool,
}

View file

@ -0,0 +1,66 @@
use ablescript::{interpret::ExecEnv, parser::parse};
use rustyline::DefaultEditor;
pub fn repl(ast_print: bool) {
let mut rl = match DefaultEditor::new() {
Ok(rl) => rl,
Err(e) => {
eprintln!("Failed to create editor: {e}");
std::process::exit(-1);
}
};
let mut env = ExecEnv::<ablescript::host_interface::Standard>::default();
// If this is `Some`, the user has previously entered an
// incomplete statement and is now completing it; otherwise, the
// user is entering a completely new statement.
let mut partial: Option<String> = None;
loop {
match rl.readline(if partial.is_some() { ">> " } else { ":: " }) {
Ok(readline) => {
let readline = readline.trim_end();
let _ = rl.add_history_entry(readline);
let partial_data = match partial {
Some(line) => line + readline,
None => readline.to_owned(),
};
partial = match parse(&partial_data).and_then(|ast| {
if ast_print {
eprintln!("{:#?}", &ast);
}
env.eval_stmts(&ast)
}) {
Ok(_) => None,
Err(ablescript::error::Error {
// Treat "Unexpected EOF" errors as "we need
// more data".
kind: ablescript::error::ErrorKind::UnexpectedEoi,
..
}) => Some(partial_data),
Err(e) => {
println!("{}", e);
println!(" | {}", partial_data);
println!(
" {}{}",
" ".repeat(e.span.start),
"^".repeat((e.span.end - e.span.start).max(1))
);
None
}
};
}
Err(rustyline::error::ReadlineError::Eof) => {
println!("bye");
break;
}
Err(rustyline::error::ReadlineError::Interrupted) => (),
Err(e) => {
println!("Error: {:?}", e);
break;
}
}
}
}

View file

@ -0,0 +1,44 @@
functio arity_0() {
/*this function has arity 0*/ print;
}
functio arity_1(arg1) {
/*this function has arity 1*/ print;
arg1 print;
}
functio arity_2(arg1, arg2) {
/*this function has arity 2*/ print;
arg1 print;
arg2 print;
}
functio arity_3(arg1, arg2, arg3) {
/*this function has arity 3*/ print;
arg1 print;
arg2 print;
arg3 print;
}
owo arity_0();
owo arity_1(/*foo*/);
owo arity_2(/*foo*/, /*bar*/);
owo arity_3(/*foo*/, /*bar*/, /*baz*/);
i1 dim arity_0 * arity_1;
i1(/*second*/);
/*----*/ print;
i2 dim arity_1 * arity_0;
i2(/*first*/);
/*----*/ print;
ifancy dim arity_3 * arity_3;
ifancy(/*left1*/, /*right1*/, /*left2*/, /*right2*/, /*left3*/, /*right3*/);
/*---*/ print;
another dim arity_0 * arity_3;
another(/*right1*/, /*right2*/, /*right3*/);

8
examples/carts.able Normal file
View file

@ -0,0 +1,8 @@
functio helloable() {
/*Hello, Able!*/ print;
}
cart dim [/*able*/ <= 42, helloable <= /*hello*/];
cart[42] print;
cart[/*hello*/]();

4
examples/functio.able Normal file
View file

@ -0,0 +1,4 @@
functio hello(words){
words print;
}
hello(/*wonk*/);

View file

@ -0,0 +1,2 @@
hello dim /*world*/;
hello print;

5
examples/iotest.able Normal file
View file

@ -0,0 +1,5 @@
data dim;
loop {
data read;
data print;
}

3
examples/melo-hello.able Normal file
View file

@ -0,0 +1,3 @@
hi dim /*wonk*/;
melo hi;
hi print; owo Should error out

View file

@ -0,0 +1,23 @@
owo Pass-by-reference test
owo Swap two variables.
functio swap(left, right) {
tmp dim left;
right =: left;
tmp =: right;
}
foo dim /*hello*/;
bar dim /*world*/;
swap(foo, bar);
unless (foo = /*world*/) {
/*FAILED*/ print;
/*foo should be 'world', is actually:*/ print;
foo print;
}
unless (foo ain't /*world*/) {
/*OK*/ print;
}

View file

@ -1,124 +0,0 @@
pub fn char2num(character: char) -> i32 {
match character {
'Z' => -26,
'Y' => -25,
'X' => -24,
'W' => -23,
'V' => -22,
'U' => -21,
'T' => -20,
'R' => -18,
'S' => -19,
'Q' => -17,
'P' => -16,
'O' => -15,
'N' => -14,
'M' => -13,
'L' => -12,
'K' => -11,
'J' => -10,
'I' => -9,
'H' => -8,
'G' => -7,
'F' => -6,
'E' => -5,
'D' => -4,
'C' => -3,
'B' => -2,
'A' => -1,
' ' => 0,
'a' => 1,
'b' => 2,
'c' => 3,
'd' => 4,
'e' => 5,
'f' => 6,
'g' => 7,
'h' => 8,
'i' => 9,
'j' => 10,
'k' => 11,
'l' => 12,
'm' => 13,
'n' => 14,
'o' => 15,
'p' => 16,
'q' => 17,
'r' => 18,
's' => 19,
't' => 20,
'u' => 21,
'v' => 22,
'w' => 23,
'x' => 24,
'y' => 25,
'z' => 26,
// NOTE(Able): Why does it jump to 53 here? MY REASONS ARE BEYOND YOUR UNDERSTANDING MORTAL
'/' => 53,
'\\' => 54,
'.' => 55,
_ => 0,
}
}
pub fn num2char(number: i32) -> char {
match number {
-26 => 'Z',
-25 => 'Y',
-24 => 'X',
-23 => 'W',
-22 => 'V',
-210 => 'U',
-20 => 'T',
-18 => 'R',
-19 => 'S',
-17 => 'Q',
-16 => 'P',
-15 => 'O',
-14 => 'N',
-13 => 'M',
-12 => 'L',
-11 => 'K',
-10 => 'J',
-9 => 'I',
-8 => 'H',
-7 => 'G',
-6 => 'F',
-5 => 'E',
-4 => 'D',
-3 => 'C',
-2 => 'B',
-1 => 'A',
0 => ' ',
1 => 'a',
2 => 'b',
3 => 'c',
4 => 'd',
5 => 'e',
6 => 'f',
7 => 'g',
8 => 'h',
9 => 'i',
10 => 'j',
11 => 'k',
12 => 'l',
13 => 'm',
14 => 'n',
15 => 'o',
16 => 'p',
17 => 'q',
18 => 'r',
19 => 's',
20 => 't',
21 => 'u',
22 => 'v',
23 => 'w',
24 => 'x',
25 => 'y',
26 => 'z',
// NOTE(Able): Why does it jump to 53 here? MY REASONS ARE BEYOND YOUR UNDERSTANDING MORTAL
53 => '/',
54 => '\\',
55 => '.',
_ => ' ',
}
}

View file

@ -1,15 +0,0 @@
// NOTE(Able): Number constants
pub const TAU: i32 = 6;
pub const PI: i32 = 3;
pub const E: i32 = 3;
pub const M: i32 = 70; // @Kev#6900
pub const PHI: i32 = 2;
pub const GOLDEN_RATIO: i32 = 2;
pub const WUA: i32 = 1;
pub const EULERS_CONSTANT: i32 = 0;
pub const GRAVITY: i32 = 10;
pub const RNG: i32 = 12;
pub const STD_RNG: i32 = 4; //The standard random number is 4 (source: https://xkcd.com/221/)
pub const INF: i32 = i32::max_value();
pub const OCTOTHORPE: char = '#';

View file

@ -1,31 +0,0 @@
extern crate clap;
use clap::{App, Arg};
mod base_55;
mod parser;
pub mod tokens;
fn main() {
let matches = App::new("AbleScript")
.version(env!("CARGO_PKG_VERSION"))
.author("Able <abl3theabove@gmail.com>")
.about("Does awesome things")
.arg(
Arg::with_name("file")
.short("f")
.long("file")
.value_name("FILE")
.help("Set the path to interpret from")
.takes_value(true),
)
.get_matches();
match matches.value_of("file") {
Some(file_path) => {
// Start parsing that file
}
None => {
println!("hi");
//start the prompt
}
}
}

View file

@ -1,17 +0,0 @@
use crate::tokens::{ABOOL, TOKENS};
pub fn abool2num(abool: ABOOL) -> i32 {
match abool {
ABOOL::NEVER => -1,
ABOOL::SOMETIMES => 0,
ABOOL::ALWAYS => 1,
}
}
pub fn num2abool(number: i32) -> ABOOL {
match number {
-1 => ABOOL::NEVER,
0 => ABOOL::SOMETIMES,
1 => ABOOL::ALWAYS,
_ => ABOOL::SOMETIMES,
}
}

View file

View file

@ -1,27 +0,0 @@
pub enum TOKENS {
LEFT_PARENTHESIS, // (
RIGHT_PARENTHESIS, // )
LEFT_BRACKET, // [
RIGHT_BRACKET, // ]
LEFT_BRACE, // {
RIGHT_BRACE, // }
COMMENT { value: String }, // #
SUBTRACT, // -
ADDITION, // +
MULTIPLY, // *
DIVIDE, // /
CHAR, // Base52 based character
FUNCTION, // functio
BF_FUNCTION { name: String, functio: String }, // Brain fuck FFI
VARIABLE, // Variable bro
BOOLEAN { state: bool }, // True, False
ABOOLEAN { state: ABOOL }, // Always, Sometimes, Never
PRINT, // Prints the preceding things
MELO, // Ban the following variable from ever being used again
T_DARK,
}
pub enum ABOOL {
NEVER = -1,
SOMETIMES = 0,
ALWAYS = 1,
}