Compare commits

..

330 commits

Author SHA1 Message Date
Erin 57bbc44b82 clearer comment 2022-04-18 22:09:56 +02:00
Erin 407c8674a7 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 c1698d9b25 Renamed init function in parser 2022-04-18 22:01:35 +02:00
Erin fd0e7012ac Added crate-level docs for ablescript 2022-04-18 21:59:57 +02:00
Erin 165e7c6b5f chore: fmt 2022-04-18 21:43:32 +02:00
Erin f335302b2a fixed comments and naming 2022-04-18 21:43:19 +02:00
Erin 152c5c0f88 replaced if with unless 2022-04-18 21:42:26 +02:00
Erin b346995bfd Chars are no longer special case of identifiers 2022-04-18 21:04:19 +02:00
Erin f7cba4b2b6 Expr::Literal contains Literal type which contains only possible types instead of any Value 2022-04-18 20:56:11 +02:00
Erin e3d49c9c1f 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 ea931d1f4b Release binaries are now stripped 2022-04-16 22:11:13 +02:00
Erin 5a18642cd6 No underscore! 2022-04-16 22:06:37 +02:00
Erin f1da9b2117 fmt 2022-04-12 22:15:53 +02:00
Erin 6720ecc534 version bump 2022-04-12 14:17:20 +02:00
Erin afac0cdac7 dependency update + fmt 2022-04-12 14:16:34 +02:00
Erin 7a35885282 removed != in favour of ain't 2022-04-08 17:44:35 +02:00
Erin ec1b8200e0 Removed ! for ain't 2022-04-08 17:39:14 +02:00
Alex Bethel 7dfdb35685 cargo fmt 2022-04-02 12:42:40 -06:00
Erin 6dec02c60c removed \ from idents 2022-04-02 20:39:48 +02:00
Erin 3bb8af5b3f changed line order 2022-04-02 14:13:49 +02:00
Erin ac42fdc5ca This isn't AbleScript, we can just negate expressions :D 2022-04-02 13:44:29 +02:00
Erin 306e6cfc8e Small change 2022-04-02 01:42:13 +02:00
Erin f7b18ec6ce Merge branch 'refactoring/ast-spans' 2022-04-02 01:37:33 +02:00
Erin d34d7dd488 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 176467dfc8 Removed ident type, fixed tests 2022-04-02 01:34:25 +02:00
Erin ce2de21d9b Generalised Spanned items 2022-04-02 01:22:46 +02:00
Erin a6ecf782db Simplified base 55 2022-04-02 01:06:47 +02:00
Erin c2e9daf1fc fixed tests 2022-03-30 21:03:17 +02:00
Erin f18742a9b3 Improved type coercions 2022-03-30 20:56:59 +02:00
Erin 8d24ad5528 Removed booleans 2022-03-30 20:55:05 +02:00
Erin 04b44fb01e Used checked next in require 2022-03-20 02:20:55 +01:00
Erin 77c201476b Changed way of String lexing 2022-03-13 13:18:51 +01:00
Erin 8ad10e132b Added support for idents starting with _ 2022-03-12 23:26:39 +01:00
Erin 4d8ee1a0d7 fixed spelling of ain't 2022-03-02 11:36:08 +01:00
Erin e17b67e438 \ is valid part of identifiers 2022-03-01 23:07:23 +01:00
Erin dbbe46fa5e Renamed dir 2022-03-01 22:17:01 +01:00
Erin f2f509ded0 There, we are now, we do not need those. 2022-03-01 22:15:27 +01:00
Erin d7ec6b3658 Introduced newtype for Rc<RefCell<Value>> 2022-03-01 22:13:49 +01:00
Erin 81f433e633 Satisfied most of THE ALLMIGHTY CLIPPY's requests. 2022-03-01 21:53:58 +01:00
Erin a3c0e65f6b Bool, Abool and Nil values are no longer tokens 2022-03-01 18:57:03 +01:00
Erin 55a455d110 fixed strings 2022-02-24 21:53:43 +01:00
Erin a3a0663433 Rust 2021
- Consts map generated using iterators
2022-02-23 21:47:07 +01:00
able d57b692e71 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 392a357c8e Fixed tests and examples 2022-02-22 22:49:56 +01:00
Erin 5f135be37c 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 97981c8772 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 9fa6b57fe5 added simple history support 2022-02-14 00:17:55 +01:00
Erin 6fc1f59e23 Exposed variables to public API 2022-02-14 00:12:03 +01:00
Erin 5f48d20f2a Add from_value variable constructor 2022-02-14 00:11:37 +01:00
Erin 417b3b53b4 added new_with_vars 2022-02-14 00:08:48 +01:00
Erin f63861f59f 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 1d08231a34 Merge branch 'master' into change/isize-as-int 2022-02-13 22:17:00 +00:00
Erin 7e2cba314b 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 a636c5926d Fixed all errors 2022-02-13 00:58:50 +01:00
Erin bf0e64c26a fixed base 55 2022-02-13 00:55:51 +01:00
Erin 7f859081ba Value::Int is isize 2022-02-13 00:55:19 +01:00
Erin b84b86eef7 Changed Built-in functio -> integer coercion 2022-02-13 00:37:17 +01:00
Erin 56c9ccdf38 Implemented builtin functio division 2022-02-13 00:30:40 +01:00
Erin 0ac507e3b3 implemented subtraction for built-in functios 2022-02-13 00:25:56 +01:00
Erin 5d5c66fb3c Implemented not for built-in functios 2022-02-13 00:07:23 +01:00
Erin aa94ae57d8 Implemented Display for built-ins 2022-02-12 22:52:14 +01:00
Erin 49d4699928 Implemented coercions for built-in functios 2022-02-12 22:10:30 +01:00
Erin 1d1919976f Implemented built-in functios
- Missing coercions and operations
2022-02-12 21:14:55 +01:00
Erin 159a462b0a 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 6f5b96b26b Functio -> Int more cursed 2022-01-22 21:02:10 +01:00
Erin a3a9b777ad Cart -> Int more cursed 2022-01-22 20:52:09 +01:00
Erin 3f29fb09cd Implemented [] as length operator 2022-01-22 20:48:21 +01:00
Erin 30ab0cf7da Implemented Len in parser 2022-01-22 20:37:44 +01:00
Alex Bethel a7bba352d5 Merge branch 'master' of ssh://git.ablecorp.us:20/AbleScript/able-script 2021-12-29 17:08:05 -06:00
Alex Bethel 78afee1472 Add martens owo 2021-12-29 17:06:46 -06:00
Erin b9634080b9 Toggled Fat LTO on 2021-12-14 23:57:03 +01:00
Alex Bethel cdc50a847f Add version number to ablescript_cli deps 2021-12-14 16:53:18 -06:00
Alex Bethel 0324c08ce1 Release 0.2
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/1
2021-12-14 22:31:52 +00:00
Erin cb10b9f1d9 Merge branch 'master' into feature/coercions 2021-12-14 23:30:07 +01:00
Alex Bethel f1e964988c 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 ec4a7e2447 Implement function chain division 2021-12-14 16:16:28 -06:00
Erin 007917a217 Fixed T-Dark blocks in blocks 2021-12-14 23:16:19 +01:00
Erin 183f26f415 Removed clone 2021-12-14 23:05:21 +01:00
Erin 6149dfddb6 Merge branch 'feature/coercions' of https://git.ablecorp.us/AbleScript/able-script into feature/coercions 2021-12-14 23:02:57 +01:00
Erin 0da3d58718 Renamed stuff 2021-12-14 23:02:55 +01:00
Alex Bethel b93333262c Fix carts.able formatting 2021-12-14 15:57:04 -06:00
Alex Bethel f890d6248b Arity-based functio deinterlacing 2021-12-14 15:56:40 -06:00
Erin cd902e0036 🚗 Implemented ordered functio chaining 2021-12-14 21:45:44 +01:00
Erin 690c78c9c0 Removed a feature which disallowed comments in functios 2021-12-14 21:10:58 +01:00
Erin 6f193577ab Implemented subtraction for functio chains... maybe? 2021-12-09 17:51:03 +01:00
Erin 7aeb9d525d Implemented coercions when subtracting able functios 2021-12-09 17:18:37 +01:00
Erin d570dfcb6f Who cares about the content, we have 256 commits!
And I readded a thing
2021-12-09 00:38:36 +01:00
Erin d7725b0db7 sync 2021-12-09 00:35:43 +01:00
Erin dc8f72bcf7 Git things beyond your comprehension mortals 2021-12-09 00:34:41 +01:00
Erin 656926387b Implemented halfway of functio sub 2021-12-09 00:33:45 +01:00
Alex Bethel 7225bd3004 Function division 2021-12-08 16:33:06 -07:00
Erin 920ff99a7f Made Clippy happy (so he will not kill us in sleep) 2021-12-08 22:56:12 +01:00
Alex Bethel 8993e78ee6 Use b-string instead of array of b-chars 2021-12-08 14:39:04 -07:00
Erin 66ceb8f8c6 Implemented Abool -> Functio 2021-12-07 22:58:41 +01:00
Alex Bethel 7812058fbf Better BF function conversions 2021-12-07 14:27:45 -07:00
Alex Bethel 8824633aae I made a cursed thing 😂 2021-12-07 14:24:58 -07:00
Erin 07d021f610 Implemented Functio to Aboolean 2021-12-07 21:57:37 +01:00
Erin 1a78eaf8c3 huh??? 2021-12-07 21:20:24 +01:00
Erin 6b5b8998c9 Fmt! 2021-12-07 21:20:16 +01:00
Erin 3ed60f7306 Function negation 2021-12-07 21:18:45 +01:00
Erin 6e70b3d41a Revert "Merge branch 'fix/unused-code'"
This reverts commit b3bbf03e2d, reversing
changes made to 77d75eea62.
2021-12-07 20:54:23 +01:00
Alex Bethel b3bbf03e2d Merge branch 'fix/unused-code' 2021-11-27 17:20:54 -06:00
Alex Bethel da1fcfd861 Fix unused code warnings 2021-11-27 11:15:29 -06:00
Alex Bethel 716e4997c5 Avoid trailing comma in cart printout 2021-11-27 11:11:03 -06:00
Alex Bethel 38a3414e88 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 8a04d9d1e0 Assign read syntax 2021-11-05 17:09:53 -06:00
Alex Bethel 99f84dfd5b Coerce indexing assignments into non-carts 2021-11-05 16:18:07 -06:00
Erin 9712d58962 Renamed semi_terminated to semicolon_terminated 2021-10-23 23:20:45 +02:00
Alex Bethel 2871e95e75 Get cart assignments working 2021-10-23 15:08:10 -06:00
Alex Bethel 7e0daeab29 Almost get cart assignments working 2021-10-23 14:17:17 -06:00
Erin 48d9d1e2e1 Read in AST takes Assignable instead of Ident 2021-10-23 21:53:21 +02:00
Alex Bethel 77d75eea62 Merge branch 'feature/line_continuations' 2021-10-23 12:46:05 -06:00
Erin a82a9d7b79 Moved Assignable creation functions 2021-10-21 20:51:24 +02:00
Erin f82ad34e34 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 06464dfea9 Removed in_the_past_this_used_to_crash_but_not_anymore test 2021-10-19 23:35:46 +02:00
T-Dark 0c755c927f Fixed integer overflows. Why did I even write in the first place? 2021-10-19 17:29:53 +01:00
Erin 3cff0da70a Change Cart AssignableKind to Index. 2021-10-13 13:20:23 +02:00
Alex Bethel 990e2806f1 Sort of fix cart assignments 2021-10-12 14:33:23 -06:00
Erin cce392aaaa improvement 2021-10-12 22:22:33 +02:00
Erin ea79e805f2 Added Assignable support in parser / AST 2021-10-12 22:14:20 +02:00
Alex Bethel 863d03575b 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 1a5d2edee1 Generalised some lexer functions 2021-10-04 23:03:23 +02:00
Erin d229c3d3c3 Renamed Iden to Ident 2021-10-04 23:00:18 +02:00
Alex Bethel 7673b64a71 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 1b9f8a6179 Variable name shortened 2021-09-02 18:36:25 +02:00
Erin fb16cb2ebb 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 23f03e7f67 Clippy 2021-09-01 17:46:17 +02:00
Erin a33a3ddd7f All operations except functio ones are implemented
- Aboolean logic
- Cart division/multiplication
2021-08-31 00:41:37 +02:00
Erin 70288ae409 Implemented negation for most types 2021-08-30 23:19:25 +02:00
Alex Bethel a3b6ae326e Add parsing negative numbers 2021-08-30 15:03:29 -06:00
Erin af2fe717fb Not is now lexing and parsing correctly 2021-08-30 22:55:31 +02:00
Erin bd7209c004 Removed And and Or ops because they aren't cursed enough 2021-08-30 22:18:09 +02:00
Erin 539989e2d0 Most of operations are implemented 2021-08-30 22:14:13 +02:00
Erin e2c7a33a59 fmt + docs 2021-08-29 00:34:08 +02:00
Erin e84c4b2a39 Used body length for AbleFunctio to i32 instead of AST length 2021-08-29 00:28:45 +02:00
Erin d6e99acdc9 Revert "Eval errors are now correctly spanned"
This reverts commit 9b81ccf57c.
2021-08-29 00:26:40 +02:00
Erin 90d0ed7a93 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 9b81ccf57c Eval errors are now correctly spanned 2021-08-29 00:23:59 +02:00
Erin 0cf8576a77 Eval errors are now correctly spanned 2021-08-29 00:16:15 +02:00
Erin c764cda7c7 AbleFunctio's body length is now by it's string representation 2021-08-29 00:10:44 +02:00
Erin f08778ca3e Eval's integer representation is now its length
- consistency with other types
2021-08-29 00:07:26 +02:00
Erin 24b6683b21 Added placeholders for And + Or
and used placeholders for -, * and / in interpret
2021-08-28 23:59:04 +02:00
Erin e95e0744c6 Fixed add overflows 2021-08-28 23:55:36 +02:00
Erin 1b243d142c Display carts sorted 2021-08-28 23:52:58 +02:00
Erin 2010b13168 Implement custom PartialEq for Value 2021-08-28 23:43:59 +02:00
Erin 160ebd649d Most coercions implemented 2021-08-28 23:27:35 +02:00
Erin 1d24082ee8 Require space after owo for making comments. 2021-08-23 18:58:28 +02:00
Alex Bethel 5368dd5209 Fix panics on invalid indexes 2021-08-16 15:06:40 -06:00
Able 06358adafa Merge pull request #39 from AbleCorp/0.2.0 2021-08-15 19:48:37 -05:00
Erin 507189c5f7 Changed license file to MIT from Github template 2021-08-15 00:59:44 +02:00
Able b5bb66f504 Merge pull request #38 from AbleCorp/enhancement/relicensing 2021-08-11 13:36:03 -05:00
Erin 17f5388de8 Relicensed to MIT 2021-08-11 20:32:12 +02:00
Erin 60b63a7699 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 0bec0a5a17 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 22afba44b1 Functio & cart comparisons and hashing 2021-08-07 16:33:28 -06:00
Erin 6de8c17c19 Added carts example 2021-08-01 18:47:13 +02:00
Erin 1f891353df Implemented callable expressions in parser 2021-08-01 18:36:09 +02:00
Erin 3262702465 Implemented callable expressions in AST and Interpret 2021-08-01 18:30:07 +02:00
Erin f4beb85de2 fmt 2021-08-01 18:28:59 +02:00
Erin 82767864ab Changed to correct naming conventions 2021-07-29 23:26:43 +02:00
Erin fa63fda7f8 Obeyed clippy 2021-07-29 23:22:38 +02:00
Erin 35ddf85a92 removed unused enum variant 2021-07-29 23:04:04 +02:00
Erin 281fc9c635 Added history (session based) 2021-07-29 22:48:36 +02:00
Erin d1146824f8 Added carts tests 2021-07-29 22:39:28 +02:00
Alex Bethel d1e0393681 Merge branch 'feature/carts' of github.com:AbleCorp/able-script into feature/carts 2021-07-29 13:30:11 -05:00
Alex Bethel 8570f495f5 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 02b8e02f27 Bumped version to 0.2.0 2021-07-29 19:18:06 +02:00
Erin 754ad496af Nest the loop inside a match arm 2021-07-29 19:17:10 +02:00
Erin 50be7ca556 Added cart parsing 2021-07-29 18:58:11 +02:00
Erin 68d0b8d334 Not fails on cart contruction parsing, does nothing. 2021-07-29 18:17:08 +02:00
Erin 1f8d6a8ec2 Extracted construction of carts to separate function 2021-07-27 12:14:11 +02:00
Erin fa87efa7e8 Implemented cart indexing parsing 2021-07-27 12:09:36 +02:00
Erin 4c735d0928 Added arrow token 2021-07-27 11:52:43 +02:00
Erin 1714629492 Added cart support into AST 2021-07-27 11:51:05 +02:00
Alex Bethel 003d800d6e Fix broken type_errors unit test 2021-07-22 22:27:17 -05:00
Alex Bethel 594968f781 Remove latent debug println for BFF input 2021-07-22 22:11:55 -05:00
Alex Bethel 4fd61b71dd Merge branch 'master' of github.com:AbleCorp/able-script 2021-07-22 22:09:40 -05:00
Alex Bethel afc1d9b18c Use coercion in BFF argument passing 2021-07-22 22:08:04 -05:00
Alex Bethel 5b87ed220a 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 9beb45adf0 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 56623de96a Make integer coercion infallible 2021-07-16 18:56:45 -05:00
ondra05 ffcbdc258b Reduced parser boilerplate 2021-07-15 21:39:01 +02:00
Alex Bethel f6e6f8cea1 Fix function call type error message 2021-07-15 14:24:07 -05:00
Alex Bethel a4d815cf7c Make consts accessible from AbleScript code 2021-07-13 14:22:06 -05:00
Alex Bethel e00df9d5ac Prettier error handling 2021-07-13 13:54:27 -05:00
Alex Bethel 6b4a2469b6 Revert "Update README.md"
This reverts commit 55a0d9923e.

It is, in fact, still better than Javascript.
2021-07-01 17:17:48 -05:00
Alex Bethel 6d49d142f7 Add more info to Cargo.toml 2021-06-30 16:45:44 -05:00
Alex Bethel 53e624fdff Merge branch 'feature/read' 2021-06-19 17:34:19 -05:00
Alex Bethel 09d551075a Merge 'feature/forty-two' into master 2021-06-19 17:32:12 -05:00
Alex Bethel f7ac2a1a43 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 2ec416db97 Implemented read in Parser 2021-06-18 20:28:53 +02:00
Alex Bethel 4b6c3528da Forbid use of unwrap w/ clippy 2021-06-16 10:36:52 -05:00
Alex Bethel b26c0ab639 Clippy conformance 2021-06-16 10:35:06 -05:00
Alex Bethel e45df2efe7 Add --debug option for printing AST 2021-06-16 10:29:27 -05:00
Alex Bethel 151564b718 Better error messages, remove debug AST printing 2021-06-16 10:25:55 -05:00
Alex Bethel ca61561ade 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 90c3e31824 Fix ignored unit tests in interpret.rs 2021-06-15 11:46:01 -05:00
Alex Bethel d0f7052290 Fix unit tests 2021-06-15 11:29:52 -05:00
Alex Bethel c35f3d80ff 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 f02b33d564 rustfmt: break long line 2021-06-15 10:24:50 -05:00
Alex Bethel 0e6bf2b27e Use pass-by-reference for variables, pass-by-value for expressions 2021-06-15 09:51:00 -05:00
Alex Bethel 5ac9117651 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 f06fba1741 Lack of else statements is intentional 2021-06-15 09:51:00 -05:00
Alex Bethel da53ba4040 Improve pass-by-reference test 2021-06-15 09:51:00 -05:00
Alex Bethel 817ca7cf9a 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 e709f398f7 Implement functio declaration & calling 2021-06-15 09:51:00 -05:00
Able a1aa4edc30 Merge pull request #33 from HTG-YT/master 2021-06-15 04:39:07 -05:00
HTG-YT aa33fe88db comply with the move of rickroll 2021-06-14 20:53:27 +08:00
HTG-YT 6a01893f2e move rickroll to src/ folder 2021-06-14 20:52:38 +08:00
HTG-YT 552c793e2f Update rickroll 2021-06-14 09:42:38 +08:00
HTG-YT 087768fcbc move rickroll content into a file 2021-06-14 09:42:23 +08:00
HTG-YT acff3e2139 Create rickroll 2021-06-14 09:41:20 +08:00
HTG-YT 7ceab0d3fd rustfmt: add trailing comma 2021-06-14 09:40:42 +08:00
HTG-YT cbe07d87db Merge branch 'AbleCorp:master' into master 2021-06-14 09:40:00 +08:00
Erin 9912ea4b9b Added build staus badge 2021-06-13 18:43:09 +02:00
ondra05 6e13e9163f Added CI config
Rust Template
2021-06-13 18:14:41 +02:00
ondra05 7ca5917d59 Update README.md 2021-06-13 18:12:21 +02:00
HTG-YT aa5000c6c8 handle Token::Rickroll in parser 2021-06-13 13:24:09 +08:00
HTG-YT 1bba812018 Update interpret.rs 2021-06-13 13:22:30 +08:00
HTG-YT 141feac31c handle StmtKind::Rickroll 2021-06-13 13:22:01 +08:00
HTG-YT f9f891cd20 add StmtKind::Rickroll 2021-06-13 13:11:51 +08:00
HTG-YT 3ffdd15b41 add rickroll lexeme in lexer 2021-06-13 13:11:02 +08:00
Alex Bethel ede6eccc71 Make arithmetic errors evaluate to 42 2021-06-12 10:48:44 -05:00
Alex Bethel 083eb01282 Integrate const.rs as consts 2021-06-12 10:45:25 -05:00
Alex Bethel 1a8f17339e Reimplement BF function declaration semantics 2021-06-12 09:26:21 -05:00
Erin ae3a7b7c8a Better comment 2021-06-11 19:44:53 +02:00
Erin 76e5fb9043 Removed todo when unknown token 2021-06-11 18:34:34 +02:00
Erin 8dbf93caa5 Fixed invalid spanning of UnexpectedToken in parse_expr 2021-06-11 18:31:25 +02:00
Erin f3c459f26e BF code is now Vec<u8> 2021-06-11 18:10:11 +02:00
Erin eecde7635c Basic BfFunctio support 2021-06-11 17:52:47 +02:00
Alex Bethel 2a428e8415 Re-enable assignment semantics 2021-06-11 10:05:48 -05:00
Erin 588f69b710 Added variable assignment to parser
- Fixed parse error, that `rlyeh` returned HopBack
2021-06-11 16:59:40 +02:00
ondra05 0046149c0d 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 6a6658fd75 add nevergonnagiveyouup constant 2021-06-11 15:26:30 +08:00
HTG-YT 6d7fbb3514 add interessant and funny constants 2021-06-11 15:19:27 +08:00
Alex Bethel 0fe7819e28 Fix interpreter variable persistence bug 2021-06-08 19:21:33 -05:00
Alex Bethel 39c5709db7 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 bdb32c4599 Remove 0..0 placeholder from unexpected_eof() 2021-06-07 20:03:26 -05:00
Alex Bethel dfededfe26 Fix all 0..0 span placeholders in interpret.rs 2021-06-07 19:57:44 -05:00
Alex Bethel b6a4ecba29 Fix unit tests 2021-06-07 17:35:49 -05:00
Alex Bethel aedeeb2512 Remove giant comment block left in by mistake 2021-06-07 16:28:27 -05:00
Alex Bethel 3bca3f88a6 Repair interpreter after parser changes 2021-06-07 16:20:20 -05:00
Erin 717d592710 Added T-Dark test 2021-06-07 23:06:13 +02:00
Erin 84f58dab3d Added tests, Bugfix
- Originally, it spanned from operator (bug)
2021-06-07 22:58:28 +02:00
Erin 99ebd71dac Added missing mut 2021-06-07 22:24:43 +02:00
Erin 7b1546387e Unified span terminology
- Add more derivations, because it's required by another parts of project
2021-06-07 22:21:21 +02:00
Erin beffef80c6 Changed position terminology 2021-06-07 21:28:24 +02:00
Erin 17a7f33c0b Removed unwraps, added Rlyeh 2021-06-07 11:07:50 +02:00
Erin 42df59705b Implement function calls
- And printing
- Revised terminology in Lexer
- Control flow
2021-06-07 11:00:06 +02:00
Erin f0cd6cd0ad Added logical operators 2021-06-07 09:17:30 +02:00
Erin 927ad5e955 Binary operator boilerplate reduction 2021-06-07 09:17:18 +02:00
Erin afee5fb82d Added basic parsing
- Expressions
- If, Functio
2021-06-07 00:09:45 +02:00
Erin d66874624b Initial chthulic error impl in Lexer
- See discussions #17
2021-06-06 23:15:11 +02:00
Erin b9d10fae03 AST revamp
- Unified terminology of span in error.rs
2021-06-06 23:13:48 +02:00
Erin e5c6feacb9 Updated AST 2021-06-06 21:09:18 +02:00
Erin bccf5bc74b 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 935ba3b791 Deleted old parser 2021-06-06 20:05:18 +02:00
Alex Bethel 08b4fff20d Correct spelling of "occurred" 2021-06-05 08:50:20 -05:00
Alex Bethel 2cb915dd24 Prettier error handling 2021-06-04 18:56:26 -05:00
Able 723719b9df Merge pull request #28 from AlexBethel/bf-functio 2021-06-02 20:02:53 -05:00
Alex Bethel 98b2fae6f3 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 d80f47716d 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 2c35691ec4 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 b3866eea9e 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 d13c22a473 More thorough unit tests 2021-05-30 13:32:29 -05:00
Alex Bethel 7bfefd3ba1 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 3e94e9e78c Merge pull request #27 from AlexBethel/master 2021-05-29 17:14:25 -05:00
Alex Bethel fcfa83a8a6 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 00e80e9740 Use single-letter identifiers as base-55 digits 2021-05-29 10:45:39 -05:00
Able cedc52024c Merge pull request #26 from Seppel3210/patch-1 2021-05-28 05:10:43 -05:00
Seppel3210 bbfc2351f9 Change "*" versions to explicit versions
Change "*" versions to explicit versions in Cargo.toml
2021-05-28 09:30:16 +02:00
Able 55a0d9923e Update README.md 2021-05-27 17:51:08 -05:00
Able 7379f9650c Merge pull request #24 from AlexBethel/master 2021-05-25 23:06:08 -05:00
Alex Bethel 51db37f1fe 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 7537b4ac98 Implement break and hopback statements 2021-05-25 21:22:38 -05:00
Alexander Bethel e7560f9364 Better abstractions, implement scoping rules 2021-05-25 13:26:01 -05:00
Alexander Bethel 1c2032ab87 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 b37c189da9 Slightly more curse 2021-05-24 01:18:36 -05:00
Able 0f1a106381 Merge pull request #22 from AlexBethel/master 2021-05-21 12:29:03 -05:00
Alexander Bethel e79fc9a005 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 464337bd53 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 e31b8fb00d 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 2febc944e4 Merge pull request #18 from T-Dark0/tdark-brainfuck 2021-05-15 20:34:29 -05:00
tdark 1306c797c8 Added support for specifying a tape size limit 2021-05-15 18:39:49 +02:00
tdark 99758a177b Implemented brainfuck interpreter 2021-05-15 18:10:50 +02:00
Able 7f5576c119 Update README.md 2021-05-14 10:52:16 -05:00
Able 598917bf58 Update README.md 2021-05-11 13:39:41 -05:00
able fafe6cf672 consider using able brand products 2021-05-05 07:33:40 -05:00
able ac88a2e3c6 Brain fuck work 2021-05-04 22:23:17 -05:00
able e2b7239992 README changes 2021-05-04 21:29:51 -05:00
able f98c4d83d9 Minor Changes 2021-05-03 19:33:21 -05:00
Able 785ddda4cf Merge pull request #15 from erindesu/master
Fixed some incompatible old code
2021-05-03 18:41:23 -05:00
Erin 1618262948 Tidy up 2021-05-03 23:02:19 +02:00
Erin 5bdbc2bbb0 Obeyed our paperclip overlord + fmt 2021-05-03 21:36:32 +02:00
Erin 7057c3cfef Fixed some incompatible old code
- Added assignment support
- Reduced boilerplate
- Removed `else`
2021-05-03 21:35:43 +02:00
Erin c0ecf5f2a3 Variable assignment implemented 2021-05-03 09:54:27 +02:00
Able 045ca17b52 Merge pull request #14 from erindesu/master 2021-05-03 02:17:38 -05:00
Erin 4b2d306ccb Fixed #13 2021-05-03 08:47:52 +02:00
Able a0b3f7c34a Merge pull request #12 from erindesu/master 2021-05-02 12:49:30 -05:00
Erin d80b5514ce Added tests and loop flow 2021-05-02 18:12:51 +02:00
Erin f8db60bc7c Added T-Dark block
- obeyed clippy
2021-05-02 17:38:12 +02:00
Erin ecd972e55d Parser production ready
- TODO: T-Dark block
- cargo fmt
- Obeyed our clippy overlord
2021-05-02 16:48:33 +02:00
Erin 4a9b656093 Parser implements examples
- Function call is now stmt (muhehe)
2021-05-02 15:43:25 +02:00
Erin 91bd015e9b Added basic math operations
- cargo fmt
2021-05-02 00:39:08 +02:00
Erin d7cf1b013d 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 c26c4a14bb Tidy up 2021-04-29 19:19:35 +02:00
Erin 5c00446c59 Implemented function calls 2021-04-29 18:50:51 +02:00
Erin 27a8de70fa 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 3e019621b2 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 345bed5f66 Merge pull request #10 from erindesu/master
Reimplemented parser
2021-04-27 09:26:29 -05:00
Erin 00a464321a Added parsing of conditionals 2021-04-27 13:48:56 +02:00
Erin fc8c3a3499 Made parser to throw error when unexpected EOF 2021-04-27 11:57:11 +02:00
Erin 2a0b2952d4 Redone original parser
= implemented original features
2021-04-27 11:49:07 +02:00
Erin 138b9efadc Added testing for base55, new identifier lexing 2021-04-27 11:09:19 +02:00
Erin 83c549607c Continue work on parser, improved lexer
- Added literal parsing (improved lexing)
- Revised error handling
2021-04-27 10:51:39 +02:00
Erin 47400ee2ce Starting work on parser improvements
- Parser should parse single expressions
2021-04-26 10:44:42 +02:00
Able 31c9fb3203 Merge pull request #9 from erindesu/master 2021-04-20 10:28:52 -05:00
Erin f7d1287fd5 Implemented abool
- Tidied up code
2021-04-18 23:40:41 +02:00
Erin 3e2dc5fba9 Added function/variable parsing
- Added block support
- TODO: Tidy it up
2021-04-18 22:33:55 +02:00
Erin 7026711b64 Initial parser work 2021-04-18 16:39:43 +02:00
able 08af75fbda variable things added 2021-04-13 18:01:19 -05:00
Able 35586aa700 Merge pull request #8 from erindesu/master 2021-04-13 11:04:17 -05:00
Erin 4ca017671c clippy + fmt 2021-04-13 17:43:54 +02:00
Erin 406c6b1297 Improved Scanner 2021-04-13 17:40:20 +02:00
able ceaed053b4 Adding a melo example 2021-04-12 22:19:44 -05:00
Able e9aea03b29 Merge pull request #6 from erindesu/master
Added tokenization
2021-04-12 20:11:41 -05:00
Erin 2194e2726f 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 00cca9fc74 Merge branch 'master' of https://github.com/AbleTheAbove/able-lang
merge
2021-04-11 22:40:32 -05:00
Able b2e1db1d92 Merge pull request #2 from erindesu/patch-1 2021-04-11 18:03:34 -05:00
ondra05 b062b9a216 Add ANSWER const 2021-04-12 01:02:08 +02:00
26 changed files with 1836 additions and 2036 deletions

505
Cargo.lock generated
View file

@ -4,7 +4,7 @@ version = 3
[[package]]
name = "ablescript"
version = "0.5.4"
version = "0.3.0"
dependencies = [
"logos",
"rand",
@ -12,7 +12,7 @@ dependencies = [
[[package]]
name = "ablescript_cli"
version = "0.5.4"
version = "0.3.0"
dependencies = [
"ablescript",
"clap",
@ -20,58 +20,27 @@ dependencies = [
]
[[package]]
name = "anstream"
version = "0.6.4"
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "anstyle"
version = "1.0.4"
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
dependencies = [
"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",
]
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "beef"
version = "0.5.2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736"
[[package]]
name = "bitflags"
@ -80,10 +49,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
@ -93,61 +62,30 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.10"
version = "3.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"atty",
"bitflags",
"indexmap",
"os_str_bytes",
"strsim",
"termcolor",
"textwrap",
]
[[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"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db"
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"
@ -177,12 +115,23 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "errno"
version = "0.3.8"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
"windows-sys 0.52.0",
]
[[package]]
@ -197,13 +146,13 @@ dependencies = [
[[package]]
name = "fd-lock"
version = "3.0.13"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
checksum = "46e245f4c8ec30c6415c56cb132c07e69e74f1942f6b4a4061da748b49f486ca"
dependencies = [
"cfg-if",
"rustix",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@ -214,9 +163,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.11"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
dependencies = [
"cfg-if",
"libc",
@ -224,54 +173,71 @@ dependencies = [
]
[[package]]
name = "heck"
version = "0.4.1"
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "libc"
version = "0.2.150"
name = "hermit-abi"
version = "0.1.19"
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"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"bitflags 2.4.1",
"libc",
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.11"
name = "indexmap"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "io-lifetimes"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9448015e586b611e5d322f6703812bbca2f1e709d5773ecd38ddb4e3bb649504"
[[package]]
name = "libc"
version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259"
[[package]]
name = "linux-raw-sys"
version = "0.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7"
[[package]]
name = "log"
version = "0.4.20"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
dependencies = [
"cfg-if",
]
[[package]]
name = "logos"
version = "0.13.0"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1"
checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-codegen"
version = "0.13.0"
name = "logos-derive"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68"
checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d"
dependencies = [
"beef",
"fnv",
@ -279,22 +245,23 @@ dependencies = [
"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",
"utf8-ranges",
]
[[package]]
name = "memchr"
version = "2.6.4"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "nibble_vec"
@ -307,35 +274,46 @@ dependencies = [
[[package]]
name = "nix"
version = "0.26.4"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags 1.3.2",
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-ident",
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
@ -373,59 +351,60 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.6.4"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
dependencies = [
"bitflags 1.3.2",
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.4"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"libredox",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustix"
version = "0.38.25"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
checksum = "96619609a54d638872db136f56941d34e2a00bb0acf3fa783a90d6b96a093ba2"
dependencies = [
"bitflags 2.4.1",
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
"winapi",
]
[[package]]
name = "rustyline"
version = "11.0.0"
version = "9.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfc8644681285d1fb67a467fb3021bfea306b99b4146b166a1fe3ada965eece"
checksum = "db7826789c0e25614b03e5a54a0717a86f9ff6e6e5247f92b369472869320039"
dependencies = [
"bitflags 1.3.2",
"bitflags",
"cfg-if",
"clipboard-win",
"dirs-next",
@ -436,6 +415,7 @@ dependencies = [
"nix",
"radix_trie",
"scopeguard",
"smallvec",
"unicode-segmentation",
"unicode-width",
"utf8parse",
@ -444,21 +424,21 @@ dependencies = [
[[package]]
name = "scopeguard"
version = "1.2.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "smallvec"
version = "1.11.2"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "str-buf"
version = "1.0.6"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
[[package]]
name = "strsim"
@ -468,64 +448,85 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.39"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.50"
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
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"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-width"
version = "0.1.11"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "utf8-ranges"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba"
[[package]]
name = "utf8parse"
version = "0.2.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
@ -543,6 +544,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@ -551,132 +561,43 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
checksum = "030b7ff91626e57a05ca64a07c481973cbb2db774e4852c9c7ca342408c6a99a"
dependencies = [
"windows-targets 0.48.5",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[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"
version = "0.30.0"
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"
checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
version = "0.30.0"
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"
checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
version = "0.30.0"
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"
checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
version = "0.30.0"
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"
checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
version = "0.30.0"
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"
checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1"

View file

@ -1,14 +1,16 @@
[package]
name = "ablescript"
version = "0.5.4"
authors = ["AbleScript Developers"]
version = "0.3.0"
authors = ["able <abl3theabove@gmail.com>"]
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"
repository = "https://github.com/AbleCorp/able-script"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
logos = "0.13"
logos = "0.12"
rand = "0.8"

View file

@ -8,7 +8,7 @@
//! just plain subroutines and they do not return any value,
//! so their calls are statements.
use crate::{base_55::char2num, value::Value};
use crate::{base_55::char2num, variables::Value};
use std::{fmt::Debug, hash::Hash};
type Span = std::ops::Range<usize>;
@ -110,8 +110,8 @@ pub enum Stmt {
Loop {
body: Block,
},
Enough,
AndAgain,
Break,
HopBack,
Dim {
ident: Spanned<String>,
@ -136,13 +136,9 @@ pub enum Stmt {
expr: Spanned<Expr>,
args: Vec<Spanned<Expr>>,
},
Print {
expr: Spanned<Expr>,
newline: bool,
},
Print(Spanned<Expr>),
Read(Assignable),
Melo(Spanned<String>),
Finally(Block),
Rlyeh,
Rickroll,
}
@ -164,11 +160,10 @@ pub enum Expr {
index: Box<Spanned<Expr>>,
},
Len(Box<Spanned<Expr>>),
Keys(Box<Spanned<Expr>>),
Variable(String),
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum Literal {
Char(char),
Int(isize),
@ -185,7 +180,7 @@ impl From<Literal> for Value {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum BinOpKind {
Add,
Subtract,

View file

@ -5,7 +5,7 @@ pub const fn char2num(c: char) -> isize {
'/' => 53,
'\\' => 54,
'.' => 55,
'U' => -210, // Backwards compatibility
'U' => -210,
'A'..='Z' => -(c as isize) + 64,
'a'..='z' => (c as isize) - 96,
_ => 0,

View file

@ -221,7 +221,15 @@ impl<'a, I: BootlegRead> Interpreter<'a, I> {
};
Some((*counter, index))
})
.find_map(|(counter, index)| (counter == 0).then_some(index))
.find_map(
|(counter, index)| {
if counter == 0 {
Some(index)
} else {
None
}
},
)
}
fn get_matching_opening_bracket(&mut self, closing: usize) -> Option<usize> {
@ -237,7 +245,15 @@ impl<'a, I: BootlegRead> Interpreter<'a, I> {
};
Some((*counter, index))
})
.find_map(|(counter, index)| (counter == 0).then_some(index))
.find_map(
|(counter, index)| {
if counter == 0 {
Some(index)
} else {
None
}
},
)
}
}

View file

@ -1,6 +1,6 @@
//! Number constants.
use crate::value::{Value, Variable};
use crate::variables::{Value, Variable};
use std::collections::HashMap;
pub const ANSWER: isize = 42;
@ -30,12 +30,11 @@ pub fn ablescript_consts() -> HashMap<String, Variable> {
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)),
("always", Abool(crate::variables::Abool::Always)),
("sometimes", Abool(crate::variables::Abool::Sometimes)),
("never", Abool(crate::variables::Abool::Never)),
]
.into_iter()
.map(|(name, value)| (name.to_owned(), Variable::from_value(value)))

View file

@ -9,36 +9,15 @@ pub struct Error {
#[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
UnexpectedEof,
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
TopLevelBreak,
BfInterpretError(InterpretError),
MismatchedArgumentError,
MissingLhs,
/// Error when executing BF code
Brian(InterpretError),
/// IO Error
Io(io::Error),
IOError(io::Error),
}
impl Error {
@ -46,10 +25,10 @@ impl Error {
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)
/// Create an UnexpectedEof error, where the EOF occurs at the
/// given index in the file.
pub fn unexpected_eof(index: usize) -> Self {
Self::new(ErrorKind::UnexpectedEof, index..index)
}
}
@ -67,27 +46,26 @@ 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::UnexpectedEof => write!(f, "unexpected end of file"),
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),
ErrorKind::TopLevelBreak => write!(f, "can only `break` out of a loop"),
ErrorKind::BfInterpretError(err) => write!(f, "brainfuck error: {}", err),
// TODO: give concrete numbers here.
ErrorKind::MismatchedArgumentError => write!(f, "wrong number of function arguments"),
ErrorKind::MissingLhs => write!(f, "missing expression before binary operation"),
ErrorKind::Io(err) => write!(f, "I/O error: {}", err),
ErrorKind::IOError(err) => write!(f, "I/O error: {}", err),
}
}
}
impl From<io::Error> for ErrorKind {
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Self::Io(e)
Self {
kind: ErrorKind::IOError(e),
span: 0..0,
}
}
}

View file

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

View file

@ -9,22 +9,23 @@
#![deny(missing_docs)]
use crate::{
ast::{Assignable, AssignableKind, Block, Expr, Spanned, Stmt},
ast::{Assignable, AssignableKind, Expr, Spanned, Stmt},
consts::ablescript_consts,
error::{Error, ErrorKind},
host_interface::HostInterface,
value::{Functio, Value, ValueRef, Variable},
variables::{Functio, Value, ValueRef, Variable},
};
use rand::random;
use std::{
cmp::Ordering,
collections::{HashMap, VecDeque},
io::{stdin, stdout, Read, Write},
mem::take,
ops::Range,
process::exit,
};
/// An environment for executing AbleScript code.
pub struct ExecEnv<H> {
pub struct ExecEnv {
/// 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.
@ -36,12 +37,6 @@ pub struct ExecEnv<H> {
/// (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
@ -51,12 +46,12 @@ struct Scope {
variables: HashMap<String, Variable>,
}
impl<H> Default for ExecEnv<H>
where
H: Default + HostInterface,
{
impl Default for ExecEnv {
fn default() -> Self {
Self::with_host_interface(H::default())
Self {
stack: vec![Default::default()],
read_buf: Default::default(),
}
}
}
@ -73,80 +68,57 @@ enum HaltStatus {
/// We ran out of statements to execute.
Finished,
/// An `enough` statement occurred at the given span, and was not
/// A `break` statement occurred at the given span, and was not
/// caught by a `loop` statement up to this point.
Enough(Range<usize>),
Break(Range<usize>),
/// A `and again` statement occurred at the given span, and was not
/// A `hopback` statement occurred at the given span, and was not
/// caught by a `loop` statement up to this point.
AndAgain(Range<usize>),
Hopback(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> {
impl ExecEnv {
/// 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,
}
pub fn new() -> Self {
Self::default()
}
/// Create a new Scope with predefined variables
pub fn new_with_vars<I>(mut host_interface: H, vars: I) -> Self
pub fn new_with_vars<I>(vars: I) -> Self
where
I: IntoIterator<Item = (String, Variable)>,
{
Self {
stack: vec![
Scope {
let scope = Scope {
variables: ablescript_consts().into_iter().chain(vars).collect(),
},
Scope {
variables: host_interface.initial_vars(),
},
],
};
Self {
stack: vec![scope],
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.
/// `break` or `hopback` 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
HaltStatus::Break(span) | HaltStatus::Hopback(span) => Err(Error {
// It's an error to issue a `break` outside of a
// `loop` statement.
kind: ErrorKind::LoopOpOutsideLoop,
kind: ErrorKind::TopLevelBreak,
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"
/// The same as `eval_stmts`, but report "break" and "hopback"
/// exit codes as normal conditions in a HaltStatus enum, and
/// create a new stack frame if `stackframe` is true.
///
@ -223,30 +195,18 @@ impl<H: HostInterface> ExecEnv<H> {
.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()))?
}
Variable(name) => self.get_var(&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::Print(expr) => {
println!("{}", self.eval_expr(expr)?);
}
Stmt::Dim { ident, init } => {
let init = match init {
@ -294,59 +254,44 @@ impl<H: HostInterface> ExecEnv<H> {
}
Stmt::Call { expr, args } => {
let func = self.eval_expr(expr)?.into_functio();
return self.fn_call(func, args, &stmt.span);
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,
HaltStatus::Finished => {}
HaltStatus::Break(_) => break,
HaltStatus::Hopback(_) => continue,
}
},
Stmt::Assign { assignable, value } => {
self.assign(assignable, self.eval_expr(value)?)?;
}
Stmt::Enough => {
return Ok(HaltStatus::Enough(stmt.span.clone()));
Stmt::Break => {
return Ok(HaltStatus::Break(stmt.span.clone()));
}
Stmt::AndAgain => {
return Ok(HaltStatus::AndAgain(stmt.span.clone()));
Stmt::HopBack => {
return Ok(HaltStatus::Hopback(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::Melo(ident) => {
self.get_var_mut(ident)?.melo = true;
}
}
}
},
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(),
));
exit(random());
}
Stmt::Rickroll => {
self.host_interface
.print(include_str!("rickroll"), false)
.map_err(|e| Error::new(e.into(), stmt.span.clone()))?;
stdout()
.write_all(include_str!("rickroll").as_bytes())
.expect("Failed to write to stdout");
}
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;
value += self.get_bit()? as isize;
}
self.assign(assignable, Value::Int(value))?;
@ -360,10 +305,10 @@ impl<H: HostInterface> ExecEnv<H> {
fn assign(&mut self, dest: &Assignable, value: Value) -> Result<(), Error> {
match dest.kind {
AssignableKind::Variable => {
self.get_var_rc_mut(&dest.ident)?.replace(value);
self.get_var_mut(&dest.ident)?.value.replace(value);
}
AssignableKind::Index { ref indices } => {
let mut cell = self.get_var_rc_mut(&dest.ident)?.clone();
let mut cell = self.get_var_rc(&dest.ident)?;
for index in indices {
let index = self.eval_expr(index)?;
@ -412,15 +357,14 @@ impl<H: HostInterface> ExecEnv<H> {
func: Functio,
args: &[Spanned<Expr>],
span: &Range<usize>,
) -> Result<HaltStatus, Error> {
) -> Result<(), 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()
self.get_var_rc(&Spanned::new(name.to_owned(), arg.span.clone()))
} else {
self.eval_expr(arg).map(ValueRef::new)
}
@ -435,7 +379,7 @@ impl<H: HostInterface> ExecEnv<H> {
func: Functio,
args: &[ValueRef],
span: &Range<usize>,
) -> Result<HaltStatus, Error> {
) -> Result<(), Error> {
match func {
Functio::Bf {
instructions,
@ -455,21 +399,22 @@ impl<H: HostInterface> ExecEnv<H> {
)
.interpret_with_output(&mut output)
.map_err(|e| Error {
kind: ErrorKind::Brian(e),
kind: ErrorKind::BfInterpretError(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)
stdout()
.write_all(&output)
.expect("Failed to write to stdout");
}
Functio::Able { params, body } => {
if params.len() != args.len() {
return Err(Error {
kind: ErrorKind::MismatchedArgumentError,
span: span.to_owned(),
});
}
self.stack.push(Default::default());
for (param, arg) in params.iter().zip(args.iter()) {
@ -479,45 +424,40 @@ impl<H: HostInterface> ExecEnv<H> {
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)
res?;
}
Functio::Builtin(b) => b.call(args).map_err(|e| Error::new(e, span.clone()))?,
Functio::Chain { functios, kind } => {
use crate::value::functio::FunctioChainKind;
let (left_functio, right_functio) = *functios;
Ok(
match match kind {
FunctioChainKind::Equal => {
match kind {
crate::variables::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)?,
)
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()),
);
crate::variables::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)?,
)
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),
Functio::Eval(code) => {
if !args.is_empty() {
return Err(Error {
kind: ErrorKind::MismatchedArgumentError,
span: span.to_owned(),
});
}
let stmts = crate::parser::parse(&code)?;
self.eval_stmts(&stmts)?;
}
}
Ok(())
}
fn deinterlace(args: &[ValueRef], arities: (usize, usize)) -> (Vec<ValueRef>, Vec<ValueRef>) {
@ -533,23 +473,18 @@ impl<H: HostInterface> ExecEnv<H> {
.take(n_alternations)
.map(|chunk| ValueRef::clone(&chunk[0]))
.chain(
args.get(2 * n_alternations..)
args[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)
.map(|chunk| ValueRef::clone(&chunk[1]))
.chain(
args.get(2 * n_alternations..)
args[2 * n_alternations..]
.iter()
.copied()
.flatten()
.map(ValueRef::clone)
.take(extra_r),
)
@ -559,14 +494,15 @@ impl<H: HostInterface> ExecEnv<H> {
/// 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> {
fn get_bit(&mut self) -> Result<bool, Error> {
const BITS_PER_BYTE: u8 = 8;
if self.read_buf.is_empty() {
let byte = self.host_interface.read_byte()?;
let mut data = [0];
stdin().read_exact(&mut data)?;
for n in (0..BITS_PER_BYTE).rev() {
self.read_buf.push_back(((byte >> n) & 1) != 0);
self.read_buf.push_back(((data[0] >> n) & 1) != 0);
}
}
@ -578,7 +514,7 @@ impl<H: HostInterface> ExecEnv<H> {
/// 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> {
fn get_var(&self, name: &Spanned<String>) -> Result<Value, Error> {
// Search for the name in the stack from top to bottom.
match self
.stack
@ -586,16 +522,25 @@ impl<H: HostInterface> ExecEnv<H> {
.rev()
.find_map(|scope| scope.variables.get(&name.item))
{
Some(Variable::Ref(r)) => Ok(r.borrow().clone()),
Some(Variable::Melo) => Err(Error {
Some(var) => {
if !var.melo {
Ok(var.value.borrow().clone())
} else {
Err(Error {
kind: ErrorKind::MeloVariable(name.item.to_owned()),
span: name.span.clone(),
})
}
}
None => Err(Error {
kind: ErrorKind::UnknownVariable(name.item.to_owned()),
span: name.span.clone(),
}),
None => Ok(Value::Undefined),
}
}
/// Get a mutable reference to a variable.
/// Get a mutable reference to a variable. Throw an error if the
/// variable is inaccessible or banned.
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...
@ -605,7 +550,16 @@ impl<H: HostInterface> ExecEnv<H> {
.rev()
.find_map(|scope| scope.variables.get_mut(&name.item))
{
Some(var) => Ok(var),
Some(var) => {
if !var.melo {
Ok(var)
} else {
Err(Error {
kind: ErrorKind::MeloVariable(name.item.to_owned()),
span: name.span.clone(),
})
}
}
None => Err(Error {
kind: ErrorKind::UnknownVariable(name.item.to_owned()),
span: name.span.clone(),
@ -613,16 +567,10 @@ impl<H: HostInterface> ExecEnv<H> {
}
}
/// Get an reference to an Rc'd pointer to the value of a variable. Throw an error
/// Get 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(),
}),
}
fn get_var_rc(&mut self, name: &Spanned<String>) -> Result<ValueRef, Error> {
Ok(self.get_var_mut(name)?.value.clone())
}
/// Declare a new variable, with the given initial value.
@ -637,22 +585,19 @@ impl<H: HostInterface> ExecEnv<H> {
.last()
.expect("Declaring variable on empty stack")
.variables
.insert(name.to_owned(), Variable::Ref(value));
.insert(name.to_owned(), Variable { melo: false, value });
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
ast::{Expr, Literal},
host_interface::Standard,
};
use crate::ast::{Expr, Literal};
#[test]
fn basic_expression_test() {
// Check that 2 + 2 = 4.
let env = ExecEnv::<Standard>::default();
let env = ExecEnv::new();
assert_eq!(
env.eval_expr(&Spanned {
item: Expr::BinOp {
@ -678,7 +623,7 @@ mod tests {
// The sum of an integer and an aboolean causes an aboolean
// coercion.
let env = ExecEnv::<Standard>::default();
let env = ExecEnv::new();
assert_eq!(
env.eval_expr(&Spanned {
item: Expr::BinOp {
@ -703,7 +648,7 @@ mod tests {
fn overflow_should_not_panic() {
// Integer overflow should throw a recoverable error instead
// of panicking.
let env = ExecEnv::<Standard>::default();
let env = ExecEnv::new();
assert_eq!(
env.eval_expr(&Spanned {
item: Expr::BinOp {
@ -747,7 +692,7 @@ mod tests {
// 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> {
fn eval(env: &mut ExecEnv, 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();
@ -759,12 +704,12 @@ mod tests {
// Functions have no return values, so use some
// pass-by-reference hacks to detect the correct
// functionality.
let mut env = ExecEnv::<Standard>::default();
let mut env = ExecEnv::new();
// Declaring and reading from a variable.
eval(&mut env, "foo dim 32; bar dim foo + 1;").unwrap();
eval(&mut env, "dim foo 32; dim bar foo + 1;").unwrap();
assert_eq!(
env.get_var_value(&Spanned {
env.get_var(&Spanned {
item: "bar".to_owned(),
span: 1..1,
})
@ -775,7 +720,7 @@ mod tests {
// Assigning an existing variable.
eval(&mut env, "/*hi*/ =: foo;").unwrap();
assert_eq!(
env.get_var_value(&Spanned {
env.get_var(&Spanned {
item: "foo".to_owned(),
span: 1..1,
})
@ -793,15 +738,15 @@ mod tests {
// 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();
let mut env = ExecEnv::new();
eval(
&mut env,
"foo dim 1; 2 =: foo; unless (never) { foo dim 3; 4 =: foo; }",
"dim foo 1; 2 =: foo; unless (never) { dim foo 3; 4 =: foo; }",
)
.unwrap();
assert_eq!(
env.get_var_value(&Spanned {
env.get_var(&Spanned {
item: "foo".to_owned(),
span: 1..1,
})

View file

@ -1,60 +1,135 @@
use logos::{Lexer, Logos};
#[derive(Logos, Debug, PartialEq, Eq, Clone)]
#[logos(skip r"[ \t\n\f]+")]
#[logos(skip r"owo .*")]
#[rustfmt::skip]
#[derive(Logos, Debug, PartialEq, Clone)]
pub enum Token {
// Symbols
#[token("(")] LeftParen,
#[token(")")] RightParen,
#[token("[")] LeftBracket,
#[token("]")] RightBracket,
#[token("{")] LeftCurly,
#[token("}")] RightCurly,
#[token(";")] Semicolon,
#[token(",")] Comma,
#[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,
#[token("+")]
Plus,
#[token("-")]
Minus,
#[token("*")]
Star,
#[token("/")]
FwdSlash,
#[token("=:")]
Assign,
#[token("<=")]
Arrow,
// Logical operators
#[token("<")] LessThan,
#[token(">")] GreaterThan,
#[token("=")] Equals,
#[token("ain't")] Aint,
#[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,
#[token("functio")]
Functio,
/// Brain fuck FFI
#[token("bff")]
Bff,
/// Variable bro
#[token("dim")]
Dim,
/// Prints the preceding things
#[token("print")]
Print,
/// Read input into preceding variable
#[token("read")]
Read,
/// Ban the following variable from ever being used again
#[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("unless")]
Unless,
#[token("rickroll")] Rickroll,
#[token("loop")]
Loop,
#[token("break")]
Break,
/// HopBack hops on the back of loop - like `continue`
#[token("hopback")]
HopBack,
/// Crash with random error (see discussion #17)
#[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),
/// String
#[token("/*", get_string)]
String(String),
/// Integer
#[regex(r"-?[0-9]+", get_value)]
Integer(isize),
// A character (to be base-55 converted)
#[regex(r"\p{XID_Start}", get_value)]
Char(char),
/// An identifier
#[regex(r"\p{XID_Start}[\p{XID_Continue}]+", get_ident)]
#[token("and ", |_| "and".to_owned())]
Identifier(String),
#[regex(r"owo .*")]
Comment,
#[regex(r"[ \t\n\f]+", logos::skip)]
#[error]
Error,
}
fn get_value<T: std::str::FromStr>(lexer: &mut Lexer<Token>) -> Option<T> {
@ -63,31 +138,7 @@ fn get_value<T: std::str::FromStr>(lexer: &mut Lexer<Token>) -> Option<T> {
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);
let string = lexer.slice()[2..].to_owned();
lexer.bump(2);
Some(string)
@ -128,17 +179,8 @@ mod tests {
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();
let lexer = Token::lexer(code);
let result: Vec<Token> = lexer.collect();
assert_eq!(result, expected);
}
}

View file

@ -1,16 +1,11 @@
//! 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))]
#![doc = include_str!("../../README.md")]
#![forbid(unsafe_code, clippy::unwrap_used)]
pub mod ast;
pub mod error;
pub mod host_interface;
pub mod interpret;
pub mod parser;
pub mod value;
pub mod variables;
mod base_55;
mod brian;

View file

@ -31,12 +31,12 @@ impl<'source> Parser<'source> {
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)?),
// Ignore comments
Token::Comment => continue,
// Invalid token
Err(()) => return Err(Error::new(ErrorKind::InvalidToken, self.lexer.span())),
// T-Dark block (replace `lang` with `script`)
Token::TDark => ast.extend(self.tdark_flow()?),
token => ast.push(self.parse_stmt(token)?),
}
}
Ok(ast)
@ -46,10 +46,15 @@ impl<'source> Parser<'source> {
///
/// 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)),
loop {
match self
.lexer
.next()
.ok_or_else(|| Error::unexpected_eof(self.lexer.span().start))?
{
Token::Comment => (),
token => break Ok(token),
}
}
}
@ -61,16 +66,40 @@ impl<'source> Parser<'source> {
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::Unless => Ok(Spanned::new(
self.unless_flow()?,
start..self.lexer.span().end,
)),
Token::Functio => Ok(Spanned::new(
self.functio_flow()?,
start..self.lexer.span().end,
)),
Token::Bff => Ok(Spanned::new(self.bff_flow()?, start..self.lexer.span().end)),
Token::Dim => Ok(Spanned::new(self.dim_flow()?, start..self.lexer.span().end)),
Token::Melo => Ok(Spanned::new(
self.melo_flow()?,
start..self.lexer.span().end,
)),
Token::Loop => Ok(Spanned::new(
self.loop_flow()?,
start..self.lexer.span().end,
)),
Token::Break => Ok(Spanned::new(
self.semicolon_terminated(Stmt::Break)?,
start..self.lexer.span().end,
)),
Token::HopBack => Ok(Spanned::new(
self.semicolon_terminated(Stmt::HopBack)?,
start..self.lexer.span().end,
)),
Token::Rlyeh => Ok(Spanned::new(
self.semicolon_terminated(Stmt::Rlyeh)?,
start..self.lexer.span().end,
)),
Token::Rickroll => Ok(Spanned::new(
self.semicolon_terminated(Stmt::Rickroll)?,
start..self.lexer.span().end,
)),
Token::Identifier(_)
| Token::String(_)
@ -78,14 +107,16 @@ impl<'source> Parser<'source> {
| Token::Char(_)
| Token::Aint
| Token::LeftBracket
| Token::LeftParen => self.value_flow(token),
| Token::LeftParen => Ok(Spanned::new(
self.value_flow(token)?,
start..self.lexer.span().end,
)),
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
@ -107,9 +138,14 @@ impl<'source> Parser<'source> {
/// 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()))
}
Token::Identifier(ident) => Ok(Spanned::new(
if self.tdark {
ident.replace("lang", "script")
} else {
ident
},
self.lexer.span(),
)),
t => Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
}
}
@ -131,20 +167,50 @@ impl<'source> Parser<'source> {
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::Identifier(i) => Ok(Spanned::new(
Expr::Variable(if self.tdark {
i.replace("lang", "script")
} else {
i
}),
start..self.lexer.span().end,
)),
Token::Integer(i) => Ok(Spanned::new(
Expr::Literal(Literal::Int(i)),
start..self.lexer.span().end,
)),
Token::String(s) => Ok(Spanned::new(
Expr::Literal(Literal::Str(if self.tdark {
s.replace("lang", "script")
} else {
s
})),
start..self.lexer.span().end,
)),
Token::Char(c) => Ok(Spanned::new(
Expr::Literal(Literal::Char(c)),
start..self.lexer.span().end,
)),
Token::LeftBracket => match buf.take() {
Some(buf) => self.index_flow(buf),
None => self.cart_flow(),
Some(buf) => Ok(Spanned::new(
self.index_flow(buf)?,
start..self.lexer.span().end,
)),
None => Ok(Spanned::new(
self.cart_flow()?,
start..self.lexer.span().end,
)),
},
// Operations
Token::Aint if buf.is_none() => {
Token::Aint if buf.is_none() => Ok(Spanned::new(
{
let next = self.checked_next()?;
Ok(Expr::Aint(Box::new(self.parse_expr(next, buf)?)))
}
Expr::Aint(Box::new(self.parse_expr(next, buf)?))
},
start..self.lexer.span().end,
)),
Token::Plus
| Token::Minus
@ -153,15 +219,17 @@ impl<'source> Parser<'source> {
| Token::Equals
| Token::LessThan
| Token::GreaterThan
| Token::Aint => self.binop_flow(
| Token::Aint => Ok(Spanned::new(
self.binop_flow(
BinOpKind::from_token(token).map_err(|e| Error::new(e, self.lexer.span()))?,
buf,
),
)?,
start..self.lexer.span().end,
)),
Token::LeftParen => return self.expr_flow(Token::RightParen),
Token::LeftParen => 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
@ -190,7 +258,7 @@ impl<'source> Parser<'source> {
cart.push((
value,
buf.take().ok_or_else(|| {
Error::unexpected_eoi(self.lexer.span().start)
Error::unexpected_eof(self.lexer.span().start)
})?,
));
@ -200,7 +268,7 @@ impl<'source> Parser<'source> {
t => buf = Some(self.parse_expr(t, &mut buf)?),
}
}
.ok_or_else(|| Error::unexpected_eoi(self.lexer.span().start))?;
.ok_or_else(|| Error::unexpected_eof(self.lexer.span().start))?;
cart.push((value, key));
}
@ -226,10 +294,6 @@ impl<'source> Parser<'source> {
}
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)?),
}
})
@ -251,7 +315,10 @@ impl<'source> Parser<'source> {
.ok_or_else(|| Error::new(ErrorKind::MissingLhs, self.lexer.span()))?,
),
rhs: {
let next = self.checked_next()?;
let next = self
.lexer
.next()
.ok_or_else(|| Error::unexpected_eof(self.lexer.span().start))?;
Box::new(self.parse_expr(next, &mut None)?)
},
kind,
@ -301,28 +368,14 @@ impl<'source> Parser<'source> {
/// 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 {
let r = loop {
match self.checked_next()? {
// Print to stdout
Token::Print => {
break Stmt::Print {
expr: buf.take().ok_or_else(|| {
let stmt = Stmt::Print(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(),
));
}
},
};
})?);
break self.semicolon_terminated(stmt)?;
}
// Functio call
@ -335,32 +388,7 @@ impl<'source> Parser<'source> {
})?)?;
}
// 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
// Variable Assignment
Token::Assign => {
return match buf.take() {
Some(expr) => self.assignment_flow(expr),
@ -386,7 +414,9 @@ impl<'source> Parser<'source> {
t => buf = Some(self.parse_expr(t, &mut buf)?),
}
})
};
Ok(r)
}
/// Parse Unless flow
@ -462,10 +492,16 @@ impl<'source> Parser<'source> {
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)),
match self.checked_next()? {
Token::Plus
| Token::Minus
| Token::Comma
| Token::LeftBracket
| Token::RightBracket
| Token::LessThan
| Token::GreaterThan => code.push(self.lexer.slice().as_bytes()[0]),
Token::RightCurly => break,
_ => (),
}
}
@ -509,6 +545,20 @@ impl<'source> Parser<'source> {
Ok(Stmt::Call { expr, args })
}
/// Parse variable declaration
fn dim_flow(&mut self) -> Result<Stmt, Error> {
let ident = self.get_ident()?;
let mut init = None;
loop {
match self.checked_next()? {
Token::Semicolon => break,
t => init = Some(self.parse_expr(t, &mut init)?),
}
}
Ok(Stmt::Dim { ident, init })
}
/// Parse assignment to assignable
fn assignment_flow(&mut self, value: Spanned<Expr>) -> Result<Stmt, Error> {
let ident = self.get_ident()?;
@ -545,37 +595,13 @@ impl<'source> Parser<'source> {
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()
}
/// Parse loop flow
///
/// `loop` is an infinite loop, no condition, only body
fn loop_flow(&mut self) -> Result<Stmt, Error> {
Ok(Stmt::Loop {
body: self.get_block()?,
})
.collect::<String>()
+ match count_upper {
0 | 1 => "pt",
2 if rand::random() => "Pt",
2 => "pT",
_ => "PT",
}),
)
}
}
string
}
}
@ -592,8 +618,7 @@ mod tests {
fn simple_math() {
let code = "1 * (num + 3) / 666 print;";
let expected = &[Spanned {
item: Stmt::Print {
expr: Spanned {
item: Stmt::Print(Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::BinOp {
@ -626,9 +651,7 @@ mod tests {
kind: BinOpKind::Divide,
},
span: 0..17,
},
newline: true,
},
}),
span: 0..24,
}];
@ -638,16 +661,16 @@ mod tests {
#[test]
fn variable_declaration() {
let code = "var dim 42;";
let code = "dim var 42;";
let expected = &[Spanned {
item: Stmt::Dim {
ident: Spanned {
item: "var".to_owned(),
span: 0..3,
span: 4..5,
},
init: Some(Spanned {
item: Expr::Literal(Literal::Int(42)),
span: 4..6,
span: 8..10,
}),
},
span: 0..11,
@ -677,13 +700,10 @@ mod tests {
span: 8..21,
},
body: vec![Spanned {
item: Stmt::Print {
expr: Spanned {
item: Stmt::Print(Spanned {
item: Expr::Literal(Literal::Str("Buy Able products!".to_owned())),
span: 25..47,
},
newline: true,
},
}),
span: 25..54,
}],
},
@ -696,12 +716,12 @@ mod tests {
#[test]
fn tdark() {
let code = "T-Dark { lang dim /*lang*/ + lang; }";
let code = "T-Dark { dim lang /*lang*/ + lang; }";
let expected = &[Spanned {
item: Stmt::Dim {
ident: Spanned {
item: "script".to_owned(),
span: 9..15,
span: 13..17,
},
init: Some(Spanned {
item: Expr::BinOp {
@ -729,8 +749,7 @@ mod tests {
fn cart_construction() {
let code = "[/*able*/ <= 1, /*script*/ <= 3 - 1] print;";
let expected = &[Spanned {
item: Stmt::Print {
expr: Spanned {
item: Stmt::Print(Spanned {
item: Expr::Cart(vec![
(
Spanned {
@ -764,9 +783,7 @@ mod tests {
),
]),
span: 0..32,
},
newline: true,
},
}),
span: 0..39,
}];
@ -778,8 +795,7 @@ mod tests {
fn cart_index() {
let code = "[/*able*/ <= /*ablecorp*/][/*ablecorp*/] print;";
let expected = &[Spanned {
item: Stmt::Print {
expr: Spanned {
item: Stmt::Print(Spanned {
item: Expr::Index {
expr: Box::new(Spanned {
item: Expr::Cart(vec![(
@ -800,9 +816,7 @@ mod tests {
}),
},
span: 0..34,
},
newline: true,
},
}),
span: 0..41,
}];

View file

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

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

View file

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

View file

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

989
ablescript/src/variables.rs Normal file
View file

@ -0,0 +1,989 @@
use std::{
cell::{Ref, RefCell, RefMut},
collections::HashMap,
fmt::Display,
hash::Hash,
io::Write,
mem::discriminant,
ops,
rc::Rc,
vec,
};
use rand::Rng;
use crate::{ast::Block, brian::INSTRUCTION_MAPPINGS, consts};
#[derive(Debug, Clone, Copy, PartialEq, 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
}
}
}
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum Functio {
Bf {
instructions: Vec<u8>,
tape_len: usize,
},
Able {
params: Vec<String>,
body: Block,
},
Builtin(BuiltinFunctio),
Chain {
functios: Box<(Functio, Functio)>,
kind: FunctioChainKind,
},
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,
}
}
}
#[derive(Clone)]
pub struct BuiltinFunctio {
function: Rc<dyn Fn(&[ValueRef]) -> Result<(), crate::error::ErrorKind>>,
arity: usize,
}
impl BuiltinFunctio {
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);
}
}
#[derive(Debug, PartialEq, Copy, Clone, Hash)]
pub enum FunctioChainKind {
Equal,
ByArity,
}
pub type Cart = HashMap<Value, ValueRef>;
#[derive(Debug, Clone)]
pub enum Value {
Nul,
Str(String),
Int(isize),
Abool(Abool),
Functio(Functio),
Cart(Cart),
}
impl Default for Value {
fn default() -> Self {
Self::Nul
}
}
impl Hash for Value {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
discriminant(self).hash(state);
match self {
Value::Nul => (),
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");
}
/// Coerce a value to an integer.
pub fn into_isize(self) -> isize {
match self {
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::Int(i) => i,
Value::Nul => consts::ANSWER,
Value::Str(text) => text.parse().unwrap_or(consts::ANSWER),
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::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 => 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::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,
}
}
/// Get a length of a value
pub fn length(&self) -> isize {
match self {
Value::Nul => 0,
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 _,
}
}
}
impl ops::Add for Value {
type Output = Value;
fn add(self, rhs: Self) -> Self::Output {
match self {
Value::Nul => match rhs {
Value::Nul => Value::Nul,
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 ops::Sub for Value {
type Output = Value;
fn sub(self, rhs: Self) -> Self::Output {
match self {
Value::Nul => match rhs {
Value::Nul => Value::Nul,
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 ops::Mul for Value {
type Output = Value;
fn mul(self, rhs: Self) -> Self::Output {
match self {
Value::Nul => match rhs {
Value::Nul => Value::Nul,
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 ops::Div for Value {
type Output = Value;
fn div(self, rhs: Self) -> Self::Output {
match self {
Value::Nul => match rhs {
Value::Nul => Value::Nul,
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 = c.len();
let chunk_len = rhs.into_isize() as usize;
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 ops::Not for Value {
type Output = Value;
fn not(self) -> Self::Output {
match self {
Value::Nul => 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::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)
} else {
None
}
}
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())),
}
}
}
impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Nul => write!(f, "nul"),
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 {
FunctioChainKind::Equal => '+',
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 { "" },
value.borrow(),
key
)?;
}
write!(f, "]")
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
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)
}
}
#[derive(Debug)]
pub struct Variable {
pub melo: bool,
// Multiple Variables can reference the same underlying Value when
// pass-by-reference is used, therefore we use Rc here.
pub value: ValueRef,
}
impl Variable {
pub fn from_value(value: Value) -> Self {
Self {
melo: false,
value: ValueRef::new(value),
}
}
}

View file

@ -1,15 +1,18 @@
[package]
name = "ablescript_cli"
version = "0.5.4"
authors = ["AbleScript Developers"]
version = "0.3.0"
authors = ["able <abl3theabove@gmail.com>"]
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"
documentation = "https://ablecorp.us/able-script-the-book/"
repository = "https://github.com/AbleCorp/able-script"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ablescript = { version = "0.5.3", path = "../ablescript" }
clap = { version = "4.2", features = ["derive"] }
rustyline = "11.0"
ablescript = { version = "0.3.0", path = "../ablescript" }
clap = "3.1"
rustyline = "9.1"

View file

@ -1,31 +1,52 @@
#![forbid(unsafe_code)]
#![forbid(unsafe_code, clippy::unwrap_used)]
mod repl;
use ablescript::{interpret::ExecEnv, parser::parse};
use clap::Parser;
use std::{path::PathBuf, process::exit};
use ablescript::interpret::ExecEnv;
use ablescript::parser::parse;
use clap::{Arg, Command};
use std::process::exit;
fn main() {
// variables::test(); // NOTE(Able): Add this as a test case
let args = Args::parse();
match args.file {
let matches = Command::new("AbleScript")
.version(env!("CARGO_PKG_VERSION"))
.author("Able <abl3theabove@gmail.com>")
.about("AbleScript interpreter")
.arg(
Arg::new("file")
.short('f')
.long("file")
.value_name("FILE")
.help("Set the path to interpret from")
.takes_value(true),
)
.arg(
Arg::new("debug")
.long("debug")
.help("Enable debug AST printing"),
)
.get_matches();
let ast_print = matches.is_present("debug");
match matches.value_of("file") {
Some(file_path) => {
// Read file
let source = match std::fs::read_to_string(&file_path) {
let source = match std::fs::read_to_string(file_path) {
Ok(s) => s,
Err(e) => {
println!("Failed to read file \"{:?}\": {}", file_path, 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);
if ast_print {
println!("{:#?}", ast);
}
ExecEnv::<ablescript::host_interface::Standard>::default().eval_stmts(&ast)
ExecEnv::new().eval_stmts(&ast)
}) {
println!(
"Error `{:?}` occurred at span: {:?} = `{:?}`",
@ -37,19 +58,7 @@ fn main() {
}
None => {
println!("Hi [AbleScript {}]", env!("CARGO_PKG_VERSION"));
repl::repl(args.debug);
repl::repl(ast_print);
}
}
}
#[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

@ -1,16 +1,10 @@
use ablescript::{interpret::ExecEnv, parser::parse};
use rustyline::DefaultEditor;
use ablescript::interpret::ExecEnv;
use ablescript::parser::parse;
use rustyline::Editor;
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();
let mut rl = Editor::<()>::new();
let mut env = ExecEnv::new();
// If this is `Some`, the user has previously entered an
// incomplete statement and is now completing it; otherwise, the
@ -20,7 +14,7 @@ pub fn repl(ast_print: bool) {
match rl.readline(if partial.is_some() { ">> " } else { ":: " }) {
Ok(readline) => {
let readline = readline.trim_end();
let _ = rl.add_history_entry(readline);
rl.add_history_entry(readline);
let partial_data = match partial {
Some(line) => line + readline,
@ -29,7 +23,7 @@ pub fn repl(ast_print: bool) {
partial = match parse(&partial_data).and_then(|ast| {
if ast_print {
eprintln!("{:#?}", &ast);
println!("{:#?}", &ast);
}
env.eval_stmts(&ast)
}) {
@ -37,7 +31,7 @@ pub fn repl(ast_print: bool) {
Err(ablescript::error::Error {
// Treat "Unexpected EOF" errors as "we need
// more data".
kind: ablescript::error::ErrorKind::UnexpectedEoi,
kind: ablescript::error::ErrorKind::UnexpectedEof,
..
}) => Some(partial_data),
Err(e) => {

View file

@ -25,20 +25,20 @@ owo arity_1(/*foo*/);
owo arity_2(/*foo*/, /*bar*/);
owo arity_3(/*foo*/, /*bar*/, /*baz*/);
i1 dim arity_0 * arity_1;
dim i1 arity_0 * arity_1;
i1(/*second*/);
/*----*/ print;
i2 dim arity_1 * arity_0;
dim i2 arity_1 * arity_0;
i2(/*first*/);
/*----*/ print;
ifancy dim arity_3 * arity_3;
dim ifancy arity_3 * arity_3;
ifancy(/*left1*/, /*right1*/, /*left2*/, /*right2*/, /*left3*/, /*right3*/);
/*---*/ print;
another dim arity_0 * arity_3;
dim another arity_0 * arity_3;
another(/*right1*/, /*right2*/, /*right3*/);

View file

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

View file

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

View file

@ -1,4 +1,4 @@
data dim;
dim data;
loop {
data read;
data print;

View file

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

View file

@ -2,13 +2,13 @@ owo Pass-by-reference test
owo Swap two variables.
functio swap(left, right) {
tmp dim left;
dim tmp left;
right =: left;
tmp =: right;
}
foo dim /*hello*/;
bar dim /*world*/;
dim foo /*hello*/;
dim bar /*world*/;
swap(foo, bar);