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
34 changed files with 4457 additions and 278 deletions

570
Cargo.lock generated
View file

@ -3,19 +3,20 @@
version = 3
[[package]]
name = "able-script"
version = "0.1.0"
name = "ablescript"
version = "0.3.0"
dependencies = [
"clap",
"logos",
"rand",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
name = "ablescript_cli"
version = "0.3.0"
dependencies = [
"winapi",
"ablescript",
"clap",
"rustyline",
]
[[package]]
@ -30,67 +31,502 @@ dependencies = [
]
[[package]]
name = "bitflags"
version = "1.2.1"
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "beef"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.33.3"
version = "3.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"indexmap",
"os_str_bytes",
"strsim",
"termcolor",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "hermit-abi"
version = "0.1.18"
name = "clipboard-win"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",
]
[[package]]
name = "error-code"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "fd-lock"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46e245f4c8ec30c6415c56cb132c07e69e74f1942f6b4a4061da748b49f486ca"
dependencies = [
"cfg-if",
"rustix",
"windows-sys",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.93"
name = "indexmap"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
dependencies = [
"unicode-width",
"autocfg",
"hashbrown",
]
[[package]]
name = "unicode-width"
version = "0.1.8"
name = "io-lifetimes"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "9448015e586b611e5d322f6703812bbca2f1e709d5773ecd38ddb4e3bb649504"
[[package]]
name = "vec_map"
version = "0.8.2"
name = "libc"
version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
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.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
dependencies = [
"cfg-if",
]
[[package]]
name = "logos"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-derive"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d"
dependencies = [
"beef",
"fnv",
"proc-macro2",
"quote",
"regex-syntax",
"syn",
"utf8-ranges",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"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.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustix"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96619609a54d638872db136f56941d34e2a00bb0acf3fa783a90d6b96a093ba2"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"winapi",
]
[[package]]
name = "rustyline"
version = "9.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db7826789c0e25614b03e5a54a0717a86f9ff6e6e5247f92b369472869320039"
dependencies = [
"bitflags",
"cfg-if",
"clipboard-win",
"dirs-next",
"fd-lock",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"scopeguard",
"smallvec",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"winapi",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "str-buf"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
@ -108,8 +544,60 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "030b7ff91626e57a05ca64a07c481973cbb2db774e4852c9c7ca342408c6a99a"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca"
[[package]]
name = "windows_i686_gnu"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8"
[[package]]
name = "windows_i686_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6"
[[package]]
name = "windows_x86_64_gnu"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a"
[[package]]
name = "windows_x86_64_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1"

View file

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

26
LICENSE
View file

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

View file

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

View file

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

View file

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

16
ablescript/Cargo.toml Normal file
View file

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,71 @@
use crate::{brian::InterpretError, lexer::Token};
use std::{fmt::Display, io, ops::Range};
#[derive(Debug)]
pub struct Error {
pub kind: ErrorKind,
pub span: Range<usize>,
}
#[derive(Debug)]
pub enum ErrorKind {
UnexpectedEof,
UnexpectedToken(Token),
UnknownVariable(String),
MeloVariable(String),
TopLevelBreak,
BfInterpretError(InterpretError),
MismatchedArgumentError,
MissingLhs,
IOError(io::Error),
}
impl Error {
pub fn new(kind: ErrorKind, span: Range<usize>) -> Self {
Self { kind, span }
}
/// 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)
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Error at range {}-{}: {}",
self.span.start, self.span.end, self.kind
)
}
}
impl std::error::Error for Error {}
impl Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::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::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::IOError(err) => write!(f, "I/O error: {}", err),
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Self {
kind: ErrorKind::IOError(e),
span: 0..0,
}
}
}

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

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

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

@ -0,0 +1,186 @@
use logos::{Lexer, Logos};
#[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,
// Operators
#[token("+")]
Plus,
#[token("-")]
Minus,
#[token("*")]
Star,
#[token("/")]
FwdSlash,
#[token("=:")]
Assign,
#[token("<=")]
Arrow,
// Logical operators
#[token("<")]
LessThan,
#[token(">")]
GreaterThan,
#[token("=")]
Equals,
#[token("ain't")]
Aint,
// Keywords
#[token("functio")]
Functio,
/// 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("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
/// 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)]
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> {
lexer.slice().parse().ok()
}
fn get_string(lexer: &mut Lexer<Token>) -> Option<String> {
lexer.bump(lexer.remainder().find("*/")?);
let string = lexer.slice()[2..].to_owned();
lexer.bump(2);
Some(string)
}
fn get_ident(lexer: &mut Lexer<Token>) -> String {
lexer.slice().to_owned()
}
#[cfg(test)]
mod tests {
use super::Token;
use super::Token::*;
use logos::Logos;
#[test]
fn simple_fn() {
let code = "functio test() { dim var 3; unless (var ain't 3) { var print } }";
let expected = &[
Functio,
Identifier("test".to_owned()),
LeftParen,
RightParen,
LeftCurly,
Dim,
Identifier("var".to_owned()),
Integer(3),
Semicolon,
Unless,
LeftParen,
Identifier("var".to_owned()),
Aint,
Integer(3),
RightParen,
LeftCurly,
Identifier("var".to_owned()),
Print,
RightCurly,
RightCurly,
];
let lexer = Token::lexer(code);
let result: Vec<Token> = lexer.collect();
assert_eq!(result, expected);
}
}

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

@ -0,0 +1,13 @@
#![doc = include_str!("../../README.md")]
#![forbid(unsafe_code, clippy::unwrap_used)]
pub mod ast;
pub mod error;
pub mod interpret;
pub mod parser;
pub mod variables;
mod base_55;
mod brian;
mod consts;
mod lexer;

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

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

67
ablescript/src/rickroll Normal file
View file

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

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

18
ablescript_cli/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "ablescript_cli"
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://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.3.0", path = "../ablescript" }
clap = "3.1"
rustyline = "9.1"

View file

@ -0,0 +1,64 @@
#![forbid(unsafe_code, clippy::unwrap_used)]
mod repl;
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 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) {
Ok(s) => s,
Err(e) => {
println!("Failed to read file \"{}\": {}", file_path, e);
exit(1)
}
};
// Parse & evaluate
if let Err(e) = parse(&source).and_then(|ast| {
if ast_print {
println!("{:#?}", ast);
}
ExecEnv::new().eval_stmts(&ast)
}) {
println!(
"Error `{:?}` occurred at span: {:?} = `{:?}`",
e.kind,
e.span.clone(),
&source[e.span]
);
}
}
None => {
println!("Hi [AbleScript {}]", env!("CARGO_PKG_VERSION"));
repl::repl(ast_print);
}
}
}

View file

@ -0,0 +1,60 @@
use ablescript::interpret::ExecEnv;
use ablescript::parser::parse;
use rustyline::Editor;
pub fn repl(ast_print: bool) {
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
// user is entering a completely new statement.
let mut partial: Option<String> = None;
loop {
match rl.readline(if partial.is_some() { ">> " } else { ":: " }) {
Ok(readline) => {
let readline = readline.trim_end();
rl.add_history_entry(readline);
let partial_data = match partial {
Some(line) => line + readline,
None => readline.to_owned(),
};
partial = match parse(&partial_data).and_then(|ast| {
if ast_print {
println!("{:#?}", &ast);
}
env.eval_stmts(&ast)
}) {
Ok(_) => None,
Err(ablescript::error::Error {
// Treat "Unexpected EOF" errors as "we need
// more data".
kind: ablescript::error::ErrorKind::UnexpectedEof,
..
}) => Some(partial_data),
Err(e) => {
println!("{}", e);
println!(" | {}", partial_data);
println!(
" {}{}",
" ".repeat(e.span.start),
"^".repeat((e.span.end - e.span.start).max(1))
);
None
}
};
}
Err(rustyline::error::ReadlineError::Eof) => {
println!("bye");
break;
}
Err(rustyline::error::ReadlineError::Interrupted) => (),
Err(e) => {
println!("Error: {:?}", e);
break;
}
}
}
}

View file

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

8
examples/carts.able Normal file
View file

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

4
examples/functio.able Normal file
View file

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

View file

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

5
examples/iotest.able Normal file
View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

View file

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