Compare commits

...

134 Commits

Author SHA1 Message Date
Erin 67e1d26c15 Update Cargo lock 2023-11-30 16:55:40 +01:00
ondra05 dd261e5d6c
Release bump 2023-05-19 00:25:14 +02:00
ondra05 4d6b30bcd0
Fixed BF code parsing 2023-05-18 23:49:54 +02:00
ondra05 dbaa390113
Updated dependencies. 2023-05-18 20:33:58 +02:00
ondra05 f220e5d12d
shorten file 2023-01-04 15:30:03 +01:00
ondra05 1a9cf6c2a6 Fix clippy complaining when used instead of cargo check with RA 2022-11-04 00:58:23 +01:00
ondra05 8ef7118dc1 initial vars - &mut self 2022-09-21 00:28:57 +02:00
ondra05 8970dabcfd fmt??? 2022-09-19 20:58:50 +02:00
ondra05 2f5306795f Host Interface initial vars support 2022-09-19 20:58:32 +02:00
ondra05 daca854db4 stuff 2022-09-14 21:57:02 +02:00
ondra05 e2008be0e3 Bumped version 2022-09-14 21:52:17 +02:00
ondra05 b7c9a4b74d Fixed #13 2022-09-14 21:50:04 +02:00
ondra05 1bb33066ed fmt + clippy 2022-09-14 21:46:24 +02:00
ondra05 c4fda0c0ec Fixed issue #12 2022-09-14 21:46:17 +02:00
ondra05 a9cdc03341 thing 2022-08-18 14:58:46 +02:00
ondra05 70d6bf661f No need for the associated type :D 2022-07-02 12:55:10 +02:00
ondra05 96fdac10a4 Revert "Added emoji support for identifiers"
This reverts commit 21ab9dbe8f.
2022-07-02 12:47:16 +02:00
ondra05 628554df10 cycle detecion on cart print - no longer stack overflow 2022-07-02 01:17:31 +02:00
ondra05 21ab9dbe8f Added emoji support for identifiers 2022-07-02 00:47:09 +02:00
ondra05 0bcea85a1f Invalid Token error + fixed forgotten refactor 2022-07-02 00:30:51 +02:00
ondra05 e61ba2e533 Error comments + renaming 2022-07-02 00:27:38 +02:00
ondra05 18c4791201 EOF -> EOI 2022-07-02 00:22:40 +02:00
ondra05 17713e3197 Bumped AbleScript version
+ chore: update
+ clap minor update
+ change repo URLs
2022-07-02 00:19:44 +02:00
ondra05 43439b7e58 Implemented host interface 2022-07-02 00:17:29 +02:00
ondra05 32072e9dea Well, Able and Eval functios can now change loops flow! 2022-07-01 23:12:59 +02:00
ondra05 4a2674a035 Implemented finally 2022-07-01 22:56:22 +02:00
ondra05 7e31c857bf chore: cargo update 2022-06-23 19:00:11 +02:00
ondra05 bde8be865c Value Refactoring 2022-06-17 20:58:37 +02:00
ondra05 ba4fd074ab Dependencies update 2022-06-07 23:32:25 +02:00
ondra05 c93c55694c Parser cleanup 2022-06-06 23:30:08 +02:00
ondra05 8297c192ac Changed dim syntax 2022-06-03 00:10:19 +02:00
ondra05 a0c8e41296 ADDED VERY IMPORTANT CHANGE 2022-06-02 23:56:32 +02:00
ondra05 32f3bf471e Changed `hopback` to `and again` (credits: Evrey#6086) 2022-05-17 19:03:02 +02:00
ondra05 9a68cd984c chore: cargo update 2022-05-17 18:04:09 +02:00
Alex Bethel 6ee4c4679b Merge branch 'v0.4.0-fixes' 2022-05-09 17:58:36 -06:00
Alex Bethel 124b224300 Fix AbleScript lib doc comment 2022-05-09 17:56:28 -06:00
ondra05 ad2839d497 Replaced `break` with `enough` (credits: Evrey#6086) 2022-05-06 23:52:43 +02:00
ondra05 cda63c733c Bumped version to 0.4.0 2022-05-06 21:22:57 +02:00
ondra05 217fd20a0f changed undefined -> cart coercion 2022-05-06 21:12:55 +02:00
ondra05 7368a3afd1 Implemented all coercions for undefined 2022-05-06 21:08:16 +02:00
ondra05 97d6edcc57 Evaluating nonextant variable results into Undefined 2022-05-06 20:20:52 +02:00
ondra05 ba8560aa7c Added Undefined type 2022-05-06 20:19:35 +02:00
ondra05 3cc80b7a94 {} -> () 2022-05-06 17:30:41 +02:00
ondra05 a43b14741d Removed arity checking and arity error 2022-05-06 17:28:25 +02:00
ondra05 39e46f090d Renamed few errors 2022-05-06 17:25:50 +02:00
ondra05 f62c44ff01 Changed error variant name to go by conventions 2022-05-05 00:39:13 +02:00
ondra05 a4c8d845f5 get_bit returns error kind which makes the error spanned 2022-05-05 00:36:13 +02:00
ondra05 85eefeb953 Fixed zero division and zero assertion errors when [] / 0 2022-05-04 23:15:12 +02:00
ondra05 3e8cffe1a5 fmt + clippy 2022-05-03 23:19:10 +02:00
ondra05 cc7d6d6ba1 Added non-newline print. 2022-05-03 23:13:17 +02:00
ondra05 b1edffab98 Merge pull request 'T-Dark letter cases implemented' (#10) from feature/tdark-cases into master
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/10
2022-05-03 21:03:42 +00:00
ondra05 e10a6c8c9d T-Dark letter cases implemented 2022-05-02 23:40:42 +02:00
ondra05 77dc87901e extracted t-dark substitution to separate function 2022-05-02 23:12:32 +02:00
ondra05 b6f37b6ced Modified Str -> Int coercion 2022-04-26 00:18:11 +02:00
ondra05 4b7aa07d06 Implemented key extraction 2022-04-25 14:56:35 +02:00
able ebd61780f6 Merge pull request 'Added String character escaping by dozenal " delimited unicode values' (#9) from feature/string-unicode-escapes into master
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/9
2022-04-25 12:48:07 +00:00
ondra05 4ab63aea47 Added escapes test 2022-04-24 23:47:51 +02:00
ondra05 c7ea807a8e Added some comments 2022-04-24 23:33:11 +02:00
ondra05 b462fd9c8f Implemented string unicode escapes 2022-04-24 23:29:15 +02:00
ondra05 83e395ff7b sus 2022-04-24 22:01:31 +02:00
ondra05 45bb1ade84 Merge pull request 'Changed the way how Melo-ing variables works' (#8) from improvements/melo into master
Reviewed-on: https://git.ablecorp.us:443/AbleScript/able-script/pulls/8
2022-04-23 11:54:22 +00:00
ondra05 827a13f358 Double Melo causes variable deletion 2022-04-22 21:14:53 +02:00
ondra05 b035138998 Rc is got by reference by default 2022-04-22 21:09:03 +02:00
ondra05 b0e8acd438 Reimplemented Melo
- Variable made into enum of `ValueRef or Melo`
- Melo-ing variable causes dropping of the Rc, decreasing reference count
2022-04-22 21:06:34 +02:00
ondra05 53e2b0677a clearer comment 2022-04-18 22:09:56 +02:00
ondra05 5c11cd39bb 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
ondra05 dbe4835732 Renamed init function in parser 2022-04-18 22:01:35 +02:00
ondra05 6d195747e2 Added crate-level docs for ablescript 2022-04-18 21:59:57 +02:00
ondra05 4c218e3ab8 chore: fmt 2022-04-18 21:43:32 +02:00
ondra05 141164b46a fixed comments and naming 2022-04-18 21:43:19 +02:00
ondra05 097a5571ae replaced if with unless 2022-04-18 21:42:26 +02:00
ondra05 df32550844 Chars are no longer special case of identifiers 2022-04-18 21:04:19 +02:00
ondra05 e24bc5af35 Expr::Literal contains Literal type which contains only possible types instead of any Value 2022-04-18 20:56:11 +02:00
ondra05 0ef7177a03 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
ondra05 a4e3b98c6a Release binaries are now stripped 2022-04-16 22:11:13 +02:00
ondra05 e1f7533b2d No underscore! 2022-04-16 22:06:37 +02:00
ondra05 8234a9d9fe fmt 2022-04-12 22:15:53 +02:00
ondra05 95ecbdf4ce version bump 2022-04-12 14:17:20 +02:00
ondra05 2cdce8f77e dependency update + fmt 2022-04-12 14:16:34 +02:00
ondra05 87555a6184 removed `!=` in favour of `ain't` 2022-04-08 17:44:35 +02:00
ondra05 4c20ef179e Removed ! for ain't 2022-04-08 17:39:14 +02:00
Alex Bethel 96f2db4dc6 `cargo fmt` 2022-04-02 12:42:40 -06:00
ondra05 f842733757 removed \ from idents 2022-04-02 20:39:48 +02:00
ondra05 ddb15ba540 changed line order 2022-04-02 14:13:49 +02:00
ondra05 3bb1039e1d This isn't AbleScript, we can just negate expressions :D 2022-04-02 13:44:29 +02:00
ondra05 34cef2c613 Small change 2022-04-02 01:42:13 +02:00
ondra05 48618cced8 Merge branch 'refactoring/ast-spans' 2022-04-02 01:37:33 +02:00
ondra05 bd05ceb086 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
ondra05 1dc9979738 Removed ident type, fixed tests 2022-04-02 01:34:25 +02:00
ondra05 75df416679 Generalised Spanned items 2022-04-02 01:22:46 +02:00
ondra05 6cfd26dcc2 Simplified base 55 2022-04-02 01:06:47 +02:00
ondra05 8f6d2483f3 fixed tests 2022-03-30 21:03:17 +02:00
ondra05 bdbf8d0a29 Improved type coercions 2022-03-30 20:56:59 +02:00
ondra05 c867ad8406 Removed booleans 2022-03-30 20:55:05 +02:00
ondra05 7cc456ee40 Used checked next in require 2022-03-20 02:20:55 +01:00
ondra05 8b5be3d90f Changed way of String lexing 2022-03-13 13:18:51 +01:00
ondra05 6ea380f3a0 Added support for idents starting with _ 2022-03-12 23:26:39 +01:00
ondra05 d47bd65387 fixed spelling of ain't 2022-03-02 11:36:08 +01:00
ondra05 c3a51e3221 \ is valid part of identifiers 2022-03-01 23:07:23 +01:00
ondra05 e7a4a99edc Renamed dir 2022-03-01 22:17:01 +01:00
ondra05 9537879d8c There, we are now, we do not need those. 2022-03-01 22:15:27 +01:00
ondra05 0dee4ad9ab Introduced newtype for `Rc<RefCell<Value>>` 2022-03-01 22:13:49 +01:00
ondra05 acb9bed2aa Satisfied most of THE ALLMIGHTY CLIPPY's requests. 2022-03-01 21:53:58 +01:00
ondra05 816cb9ac82 Bool, Abool and Nil values are no longer tokens 2022-03-01 18:57:03 +01:00
ondra05 8c369d5cfc fixed strings 2022-02-24 21:53:43 +01:00
ondra05 260e9efa84 Rust 2021
- Consts map generated using iterators
2022-02-23 21:47:07 +01:00
able 67823468b1 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
ondra05 f703a2546d Fixed tests and examples 2022-02-22 22:49:56 +01:00
ondra05 ae7d3c6680 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
ondra05 9a93202979 Removed unused tokens from Lexer
Uh, they were lying there with no use for 9 months!
2022-02-22 22:31:05 +01:00
ondra05 41db71cb81 added simple history support 2022-02-14 00:17:55 +01:00
ondra05 e37931111f Exposed variables to public API 2022-02-14 00:12:03 +01:00
ondra05 3b81883b80 Add from_value variable constructor 2022-02-14 00:11:37 +01:00
ondra05 5f4c0da06a added new_with_vars 2022-02-14 00:08:48 +01:00
ondra05 23fcb800c4 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
ondra05 e8ec3e71e0 Merge branch 'master' into change/isize-as-int 2022-02-13 22:17:00 +00:00
ondra05 d478709057 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
ondra05 4d6dd0e58b Fixed all errors 2022-02-13 00:58:50 +01:00
ondra05 9e8456195c fixed base 55 2022-02-13 00:55:51 +01:00
ondra05 3dd91b0c97 Value::Int is isize 2022-02-13 00:55:19 +01:00
ondra05 a46b9ea910 Changed Built-in functio -> integer coercion 2022-02-13 00:37:17 +01:00
ondra05 84f519a2e5 Implemented builtin functio division 2022-02-13 00:30:40 +01:00
ondra05 2ecb492356 implemented subtraction for built-in functios 2022-02-13 00:25:56 +01:00
ondra05 785bce3095 Implemented not for built-in functios 2022-02-13 00:07:23 +01:00
ondra05 6f489d5435 Implemented Display for built-ins 2022-02-12 22:52:14 +01:00
ondra05 ebd8a28a83 Implemented coercions for built-in functios 2022-02-12 22:10:30 +01:00
ondra05 013c324a8e Implemented built-in functios
- Missing coercions and operations
2022-02-12 21:14:55 +01:00
ondra05 d67f50afe8 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
ondra05 4386e31ea5 Functio -> Int more cursed 2022-01-22 21:02:10 +01:00
ondra05 f4f7b102eb Cart -> Int more cursed 2022-01-22 20:52:09 +01:00
ondra05 ae15f6cfb3 Implemented [] as length operator 2022-01-22 20:48:21 +01:00
ondra05 622ce45435 Implemented Len in parser 2022-01-22 20:37:44 +01:00
Alex Bethel 9e6cc36135 Merge branch 'master' of ssh://git.ablecorp.us:20/AbleScript/able-script 2021-12-29 17:08:05 -06:00
Alex Bethel d59d75c6a2 Add martens owo 2021-12-29 17:06:46 -06:00
33 changed files with 2570 additions and 2150 deletions

View File

@ -1,22 +0,0 @@
name: Rust
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

500
Cargo.lock generated
View File

@ -4,7 +4,7 @@ version = 3
[[package]]
name = "ablescript"
version = "0.2.0"
version = "0.5.4"
dependencies = [
"logos",
"rand",
@ -12,7 +12,7 @@ dependencies = [
[[package]]
name = "ablescript_cli"
version = "0.2.0"
version = "0.5.4"
dependencies = [
"ablescript",
"clap",
@ -20,42 +20,70 @@ dependencies = [
]
[[package]]
name = "ansi_term"
version = "0.11.0"
name = "anstream"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
dependencies = [
"winapi",
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "atty"
version = "0.2.14"
name = "anstyle"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
dependencies = [
"hermit-abi",
"libc",
"winapi",
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
]
[[package]]
name = "beef"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.69"
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "cfg-if"
@ -65,30 +93,61 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.33.3"
version = "4.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
"clap_builder",
"clap_derive",
]
[[package]]
name = "clipboard-win"
version = "4.2.1"
name = "clap_builder"
version = "4.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4ea1881992efc993e4dc50a324cdbd03216e41bdc8385720ff47efc9bd2ca8"
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "clipboard-win"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "dirs-next"
version = "2.0.0"
@ -117,10 +176,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "error-code"
version = "2.3.0"
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "error-code"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [
"libc",
"str-buf",
@ -128,12 +197,13 @@ dependencies = [
[[package]]
name = "fd-lock"
version = "2.0.0"
version = "3.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0010f02effd88c702318c5dde0463206be67495d0b4d906ba7c0a8f166cc7f06"
checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
dependencies = [
"libc",
"winapi",
"cfg-if",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
@ -144,9 +214,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.3"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [
"cfg-if",
"libc",
@ -154,43 +224,54 @@ dependencies = [
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "libc"
version = "0.2.99"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "log"
version = "0.4.14"
name = "libredox"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
dependencies = [
"cfg-if",
"bitflags 2.4.1",
"libc",
"redox_syscall",
]
[[package]]
name = "logos"
version = "0.12.0"
name = "linux-raw-sys"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "logos"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-derive"
version = "0.12.0"
name = "logos-codegen"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d"
checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68"
dependencies = [
"beef",
"fnv",
@ -198,14 +279,22 @@ dependencies = [
"quote",
"regex-syntax",
"syn",
"utf8-ranges",
]
[[package]]
name = "logos-derive"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e"
dependencies = [
"logos-codegen",
]
[[package]]
name = "memchr"
version = "2.4.0"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "nibble_vec"
@ -218,36 +307,35 @@ dependencies = [
[[package]]
name = "nix"
version = "0.20.0"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags",
"cc",
"bitflags 1.3.2",
"cfg-if",
"libc",
]
[[package]]
name = "ppv-lite86"
version = "0.2.10"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.28"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-xid",
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.9"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
@ -264,14 +352,13 @@ dependencies = [
[[package]]
name = "rand"
version = "0.8.4"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
@ -286,54 +373,59 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.6.3"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.0"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
dependencies = [
"getrandom",
"redox_syscall",
"libredox",
"thiserror",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "rustix"
version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "rustyline"
version = "8.2.0"
version = "11.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbd4eaf7a7738f76c98e4f0395253ae853be3eb018f7b0bb57fe1b6c17e31874"
checksum = "5dfc8644681285d1fb67a467fb3021bfea306b99b4146b166a1fe3ada965eece"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cfg-if",
"clipboard-win",
"dirs-next",
@ -344,7 +436,6 @@ dependencies = [
"nix",
"radix_trie",
"scopeguard",
"smallvec",
"unicode-segmentation",
"unicode-width",
"utf8parse",
@ -353,89 +444,88 @@ dependencies = [
[[package]]
name = "scopeguard"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "smallvec"
version = "1.6.1"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "str-buf"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "strsim"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.74"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
"unicode-ident",
]
[[package]]
name = "textwrap"
version = "0.11.0"
name = "thiserror"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
dependencies = [
"unicode-width",
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.8"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[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.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "utf8parse"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
@ -458,3 +548,135 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"

View File

@ -2,4 +2,5 @@
members = ["ablescript", "ablescript_cli"]
[profile.release]
lto = "fat"
lto = true
strip = true

View File

@ -1,44 +0,0 @@
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");
var i1 = arity_0 * arity_1;
i1("second");
"----" print;
var i2 = arity_1 * arity_0;
i2("first");
"----" print;
var ifancy = arity_3 * arity_3;
ifancy("left1", "right1", "left2", "right2", "left3", "right3");
"----" print;
var another = arity_0 * arity_3;
another("right1", "right2", "right3");

View File

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

View File

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

View File

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

View File

@ -1,16 +1,14 @@
[package]
name = "ablescript"
version = "0.2.0"
authors = ["able <abl3theabove@gmail.com>"]
edition = "2018"
name = "ablescript"
version = "0.5.4"
authors = ["AbleScript Developers"]
edition = "2021"
description = "The best programming language"
license = "MIT"
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
repository = "https://git.ablecorp.us/AbleScript/able-script"
[dependencies]
logos = "0.12"
rand = "0.8"
logos = "0.13"
rand = "0.8"

View File

@ -8,198 +8,184 @@
//! just plain subroutines and they do not return any value,
//! so their calls are statements.
use std::hash::Hash;
use crate::variables::Value;
use crate::{base_55::char2num, value::Value};
use std::{fmt::Debug, hash::Hash};
type Span = std::ops::Range<usize>;
#[derive(Debug, Clone)]
pub struct Ident {
pub ident: String,
#[derive(Clone)]
pub struct Spanned<T> {
pub item: T,
pub span: Span,
}
impl Ident {
pub fn new(ident: String, span: Span) -> Self {
Self { ident, span }
impl<T> Spanned<T> {
pub fn new(item: T, span: Span) -> Self {
Self { item, span }
}
}
impl PartialEq for Ident {
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.ident == other.ident
self.item == other.item
}
}
impl Hash for Ident {
impl<T: Hash> Hash for Spanned<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.ident.hash(state)
self.item.hash(state);
}
}
#[derive(Debug, PartialEq, Clone, Hash)]
pub struct Assignable {
pub ident: Ident,
pub ident: Spanned<String>,
pub kind: AssignableKind,
}
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum AssignableKind {
Variable,
Index { indices: Vec<Expr> },
Index { indices: Vec<Spanned<Expr>> },
}
pub struct InvalidAssignable;
impl Assignable {
pub fn from_expr(expr: Expr) -> Result<Assignable, ()> {
match expr.kind {
ExprKind::Variable(ident) => Ok(Assignable {
ident: Ident::new(ident, expr.span),
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,
}),
ExprKind::Index { expr, index } => Self::from_index(*expr, *index),
_ => Err(()),
Expr::Index { expr, index } => Self::from_index(*expr, *index),
_ => Err(InvalidAssignable),
}
}
fn from_index(mut buf: Expr, index: Expr) -> Result<Assignable, ()> {
fn from_index(
mut buf: Spanned<Expr>,
index: Spanned<Expr>,
) -> Result<Assignable, InvalidAssignable> {
let mut indices = vec![index];
let ident = loop {
match buf.kind {
ExprKind::Variable(ident) => break ident,
ExprKind::Index { expr, index } => {
match buf.item {
Expr::Variable(ident) => break ident,
Expr::Index { expr, index } => {
indices.push(*index);
buf = *expr;
}
_ => return Err(()),
_ => return Err(InvalidAssignable),
}
};
indices.reverse();
Ok(Assignable {
ident: Ident::new(ident, buf.span),
ident: Spanned::new(ident, buf.span),
kind: AssignableKind::Index { indices },
})
}
}
#[derive(Debug, PartialEq, Clone, Hash)]
pub struct Block {
pub block: Vec<Stmt>,
}
pub type Block = Vec<Spanned<Stmt>>;
/// A syntactic unit expressing an effect.
#[derive(Debug, Clone)]
pub struct Stmt {
pub kind: StmtKind,
pub span: Span,
}
impl PartialEq for Stmt {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
}
}
impl Hash for Stmt {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.kind.hash(state)
}
}
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum StmtKind {
pub enum Stmt {
// Control flow
If {
cond: Expr,
Unless {
cond: Spanned<Expr>,
body: Block,
},
Loop {
body: Block,
},
Break,
HopBack,
Enough,
AndAgain,
Var {
ident: Ident,
init: Option<Expr>,
Dim {
ident: Spanned<String>,
init: Option<Spanned<Expr>>,
},
Assign {
assignable: Assignable,
value: Expr,
value: Spanned<Expr>,
},
Functio {
ident: Ident,
params: Vec<Ident>,
ident: Spanned<String>,
params: Vec<Spanned<String>>,
body: Block,
},
BfFunctio {
ident: Ident,
tape_len: Option<Expr>,
ident: Spanned<String>,
tape_len: Option<Spanned<Expr>>,
code: Vec<u8>,
},
Call {
expr: Expr,
args: Vec<Expr>,
expr: Spanned<Expr>,
args: Vec<Spanned<Expr>>,
},
Print {
expr: Spanned<Expr>,
newline: bool,
},
Print(Expr),
Read(Assignable),
Melo(Ident),
Melo(Spanned<String>),
Finally(Block),
Rlyeh,
Rickroll,
}
impl Stmt {
pub fn new(kind: StmtKind, span: Span) -> Self {
Self { kind, span }
}
}
/// Expression is parse unit which do not cause any effect,
/// like math and logical operations or values.
#[derive(Debug, Clone)]
pub struct Expr {
pub kind: ExprKind,
pub span: Span,
}
impl PartialEq for Expr {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
}
}
impl Hash for Expr {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.kind.hash(state)
}
}
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum ExprKind {
pub enum Expr {
BinOp {
lhs: Box<Expr>,
rhs: Box<Expr>,
lhs: Box<Spanned<Expr>>,
rhs: Box<Spanned<Expr>>,
kind: BinOpKind,
},
Not(Box<Expr>),
Literal(Value),
Cart(Vec<(Expr, Expr)>),
Aint(Box<Spanned<Expr>>),
Literal(Literal),
Cart(Vec<(Spanned<Expr>, Spanned<Expr>)>),
Index {
expr: Box<Expr>,
index: Box<Expr>,
expr: Box<Spanned<Expr>>,
index: Box<Spanned<Expr>>,
},
Len(Box<Spanned<Expr>>),
Keys(Box<Spanned<Expr>>),
Variable(String),
}
impl Expr {
pub fn new(kind: ExprKind, span: Span) -> Self {
Self { kind, span }
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum Literal {
Char(char),
Int(isize),
Str(String),
}
impl From<Literal> for Value {
fn from(lit: Literal) -> Self {
match lit {
Literal::Char(c) => Self::Int(char2num(c)),
Literal::Int(i) => Self::Int(i),
Literal::Str(s) => Self::Str(s),
}
}
}
#[derive(Debug, PartialEq, Clone, Hash)]
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum BinOpKind {
Add,
Subtract,
@ -222,8 +208,8 @@ impl BinOpKind {
Token::FwdSlash => Ok(Self::Divide),
Token::GreaterThan => Ok(Self::Greater),
Token::LessThan => Ok(Self::Less),
Token::EqualEqual => Ok(Self::Equal),
Token::NotEqual => Ok(Self::NotEqual),
Token::Equals => Ok(Self::Equal),
Token::Aint => Ok(Self::NotEqual),
t => Err(crate::error::ErrorKind::UnexpectedToken(t)),
}
}

View File

@ -1,62 +1,13 @@
pub fn char2num(character: char) -> i32 {
match character {
'Z' => -26,
'Y' => -25,
'X' => -24,
'W' => -23,
'V' => -22,
'U' => -210,
'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,
pub const fn char2num(c: char) -> isize {
match c {
' ' => 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,
'U' => -210, // Backwards compatibility
'A'..='Z' => -(c as isize) + 64,
'a'..='z' => (c as isize) - 96,
_ => 0,
}
}
@ -66,7 +17,7 @@ mod tests {
use super::*;
#[test]
fn str_to_base55() {
let chrs: Vec<i32> = "AbleScript".chars().map(char2num).collect();
let chrs: Vec<isize> = "AbleScript".chars().map(char2num).collect();
assert_eq!(chrs, &[-1, 2, 12, 5, -19, 3, 18, 9, 16, 20]);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -7,29 +7,24 @@
//! evaluate or execute any number of expressions or statements.
#![deny(missing_docs)]
use std::{
cell::RefCell,
cmp::Ordering,
collections::{HashMap, VecDeque},
io::{stdin, stdout, Read, Write},
mem::take,
ops::Range,
process::exit,
rc::Rc,
};
use rand::random;
use crate::{
ast::{Assignable, AssignableKind, Expr, ExprKind, Ident, Stmt, StmtKind},
base_55,
ast::{Assignable, AssignableKind, Block, Expr, Spanned, Stmt},
consts::ablescript_consts,
error::{Error, ErrorKind},
variables::{Functio, Value, Variable},
host_interface::HostInterface,
value::{Functio, Value, ValueRef, Variable},
};
use rand::random;
use std::{
cmp::Ordering,
collections::{HashMap, VecDeque},
mem::take,
ops::Range,
};
/// An environment for executing AbleScript code.
pub struct ExecEnv {
pub struct ExecEnv<H> {
/// The stack, ordered such that `stack[stack.len() - 1]` is the
/// top-most (newest) stack frame, and `stack[0]` is the
/// bottom-most (oldest) stack frame.
@ -41,6 +36,12 @@ pub struct ExecEnv {
/// (via the `read` statement). We store each of those bits as
/// booleans to facilitate easy manipulation.
read_buf: VecDeque<bool>,
/// Interface to interact with the host interface
host_interface: H,
/// Vector of blocks to be executed at the end of the program
finalisers: Vec<Block>,
}
/// A set of visible variable and function definitions in a single
@ -50,6 +51,15 @@ struct Scope {
variables: HashMap<String, Variable>,
}
impl<H> Default for ExecEnv<H>
where
H: Default + HostInterface,
{
fn default() -> Self {
Self::with_host_interface(H::default())
}
}
impl Default for Scope {
fn default() -> Self {
Self {
@ -63,52 +73,90 @@ enum HaltStatus {
/// We ran out of statements to execute.
Finished,
/// A `break` statement occurred at the given span, and was not
/// An `enough` statement occurred at the given span, and was not
/// caught by a `loop` statement up to this point.
Break(Range<usize>),
Enough(Range<usize>),
/// A `hopback` statement occurred at the given span, and was not
/// A `and again` statement occurred at the given span, and was not
/// caught by a `loop` statement up to this point.
Hopback(Range<usize>),
AndAgain(Range<usize>),
}
/// The number of bits the `read` statement reads at once from
/// standard input.
pub const READ_BITS: u8 = 3;
impl ExecEnv {
impl<H: HostInterface> ExecEnv<H> {
/// Create a new Scope with no predefined variable definitions or
/// other information.
pub fn new() -> Self {
pub fn with_host_interface(mut host_interface: H) -> Self {
Self {
// We always need at least one stackframe.
stack: vec![Default::default()],
stack: vec![
Default::default(),
Scope {
variables: host_interface.initial_vars(),
},
],
read_buf: Default::default(),
finalisers: vec![],
host_interface,
}
}
/// Create a new Scope with predefined variables
pub fn new_with_vars<I>(mut host_interface: H, vars: I) -> Self
where
I: IntoIterator<Item = (String, Variable)>,
{
Self {
stack: vec![
Scope {
variables: ablescript_consts().into_iter().chain(vars).collect(),
},
Scope {
variables: host_interface.initial_vars(),
},
],
read_buf: Default::default(),
finalisers: vec![],
host_interface,
}
}
/// Execute a set of Statements in the root stack frame. Return an
/// error if one or more of the Stmts failed to evaluate, or if a
/// `break` or `hopback` statement occurred at the top level.
pub fn eval_stmts(&mut self, stmts: &[Stmt]) -> Result<(), Error> {
/// `enough` or `and again` statement occurred at the top level.
pub fn eval_stmts(&mut self, stmts: &[Spanned<Stmt>]) -> Result<(), Error> {
match self.eval_stmts_hs(stmts, false)? {
HaltStatus::Finished => Ok(()),
HaltStatus::Break(span) | HaltStatus::Hopback(span) => Err(Error {
// It's an error to issue a `break` outside of a
HaltStatus::Enough(span) | HaltStatus::AndAgain(span) => Err(Error {
// It's an error to issue a `enough` outside of a
// `loop` statement.
kind: ErrorKind::TopLevelBreak,
kind: ErrorKind::LoopOpOutsideLoop,
span,
}),
}?;
while !self.finalisers.is_empty() {
for block in std::mem::take(&mut self.finalisers) {
self.eval_stmts_hs(&block, true)?;
}
}
Ok(())
}
/// The same as `eval_stmts`, but report "break" and "hopback"
/// The same as `eval_stmts`, but report "enough" and "and again"
/// exit codes as normal conditions in a HaltStatus enum, and
/// create a new stack frame if `stackframe` is true.
///
/// `interpret`-internal code should typically prefer this
/// function over `eval_stmts`.
fn eval_stmts_hs(&mut self, stmts: &[Stmt], stackframe: bool) -> Result<HaltStatus, Error> {
fn eval_stmts_hs(
&mut self,
stmts: &[Spanned<Stmt>],
stackframe: bool,
) -> Result<HaltStatus, Error> {
let init_depth = self.stack.len();
if stackframe {
@ -133,11 +181,11 @@ impl ExecEnv {
}
/// Evaluate an Expr, returning its value or an error.
fn eval_expr(&self, expr: &Expr) -> Result<Value, Error> {
fn eval_expr(&self, expr: &Spanned<Expr>) -> Result<Value, Error> {
use crate::ast::BinOpKind::*;
use crate::ast::ExprKind::*;
use crate::ast::Expr::*;
Ok(match &expr.kind {
Ok(match &expr.item {
BinOp { lhs, rhs, kind } => {
let lhs = self.eval_expr(lhs)?;
let rhs = self.eval_expr(rhs)?;
@ -146,21 +194,20 @@ impl ExecEnv {
Subtract => lhs - rhs,
Multiply => lhs * rhs,
Divide => lhs / rhs,
Greater => Value::Bool(lhs > rhs),
Less => Value::Bool(lhs < rhs),
Equal => Value::Bool(lhs == rhs),
NotEqual => Value::Bool(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()),
}
}
Not(expr) => !self.eval_expr(expr)?,
Literal(value) => value.clone(),
ExprKind::Cart(members) => Value::Cart(
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, Rc::new(RefCell::new(value))))
self.eval_expr(key).map(|key| (key, ValueRef::new(value)))
})
})
.collect::<Result<HashMap<_, _>, _>>()?,
@ -175,104 +222,131 @@ impl ExecEnv {
.map(|x| x.borrow().clone())
.unwrap_or(Value::Nul)
}
Len(expr) => Value::Int(self.eval_expr(expr)?.length()),
Keys(expr) => Value::Cart(
self.eval_expr(expr)?
.into_cart()
.into_keys()
.enumerate()
.map(|(i, k)| (Value::Int(i as isize + 1), ValueRef::new(k)))
.collect(),
),
// TODO: not too happy with constructing an artificial
// Ident here.
Variable(name) => self.get_var(&Ident {
ident: name.to_owned(),
span: expr.span.clone(),
})?,
Variable(name) => {
self.get_var_value(&Spanned::new(name.to_owned(), expr.span.clone()))?
}
})
}
/// Perform the action indicated by a statement.
fn eval_stmt(&mut self, stmt: &Stmt) -> Result<HaltStatus, Error> {
match &stmt.kind {
StmtKind::Print(expr) => {
println!("{}", self.eval_expr(expr)?);
fn eval_stmt(&mut self, stmt: &Spanned<Stmt>) -> Result<HaltStatus, Error> {
match &stmt.item {
Stmt::Print { expr, newline } => {
let value = self.eval_expr(expr)?;
self.host_interface
.print(&value.to_string(), *newline)
.map_err(|e| Error::new(e.into(), stmt.span.clone()))?;
}
StmtKind::Var { ident, init } => {
Stmt::Dim { ident, init } => {
let init = match init {
Some(e) => self.eval_expr(e)?,
None => Value::Nul,
};
self.decl_var(&ident.ident, init);
self.decl_var(&ident.item, init);
}
StmtKind::Functio {
Stmt::Functio {
ident,
params,
body,
} => {
self.decl_var(
&ident.ident,
&ident.item,
Value::Functio(Functio::Able {
params: params.iter().map(|ident| ident.ident.to_owned()).collect(),
body: body.block.to_owned(),
params: params.iter().map(|ident| ident.item.to_owned()).collect(),
body: body.to_owned(),
}),
);
}
StmtKind::BfFunctio {
Stmt::BfFunctio {
ident,
tape_len,
code,
} => {
self.decl_var(
&ident.ident,
&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_i32() as usize))
.map(|tape_len| {
self.eval_expr(tape_len).map(|v| v.into_isize() as usize)
})
.unwrap_or(Ok(crate::brian::DEFAULT_TAPE_SIZE_LIMIT))?,
}),
);
}
StmtKind::If { cond, body } => {
if self.eval_expr(cond)?.into_bool() {
return self.eval_stmts_hs(&body.block, true);
Stmt::Unless { cond, body } => {
if !self.eval_expr(cond)?.into_abool().to_bool() {
return self.eval_stmts_hs(body, true);
}
}
StmtKind::Call { expr, args } => {
Stmt::Call { expr, args } => {
let func = self.eval_expr(expr)?.into_functio();
self.fn_call(func, args, &stmt.span)?;
return self.fn_call(func, args, &stmt.span);
}
StmtKind::Loop { body } => loop {
let res = self.eval_stmts_hs(&body.block, true)?;
Stmt::Loop { body } => loop {
let res = self.eval_stmts_hs(body, true)?;
match res {
HaltStatus::Finished => {}
HaltStatus::Break(_) => break,
HaltStatus::Hopback(_) => continue,
HaltStatus::Finished => (),
HaltStatus::Enough(_) => break,
HaltStatus::AndAgain(_) => continue,
}
},
StmtKind::Assign { assignable, value } => {
Stmt::Assign { assignable, value } => {
self.assign(assignable, self.eval_expr(value)?)?;
}
StmtKind::Break => {
return Ok(HaltStatus::Break(stmt.span.clone()));
Stmt::Enough => {
return Ok(HaltStatus::Enough(stmt.span.clone()));
}
StmtKind::HopBack => {
return Ok(HaltStatus::Hopback(stmt.span.clone()));
Stmt::AndAgain => {
return Ok(HaltStatus::AndAgain(stmt.span.clone()));
}
StmtKind::Melo(ident) => {
self.get_var_mut(ident)?.melo = true;
}
StmtKind::Rlyeh => {
Stmt::Melo(ident) => match self.get_var_mut(ident)? {
var @ Variable::Ref(_) => *var = Variable::Melo,
Variable::Melo => {
for s in &mut self.stack {
if s.variables.remove(&ident.item).is_some() {
break;
}
}
}
},
Stmt::Finally(block) => self.finalisers.push(block.clone()),
Stmt::Rlyeh => {
// Maybe print a creepy error message or something
// here at some point. ~~Alex
exit(random());
let code = random();
self.host_interface.exit(code);
return Err(Error::new(
ErrorKind::NonExitingRlyeh(code),
stmt.span.clone(),
));
}
StmtKind::Rickroll => {
stdout()
.write_all(include_str!("rickroll").as_bytes())
.expect("Failed to write to stdout");
Stmt::Rickroll => {
self.host_interface
.print(include_str!("rickroll"), false)
.map_err(|e| Error::new(e.into(), stmt.span.clone()))?;
}
StmtKind::Read(assignable) => {
Stmt::Read(assignable) => {
let mut value = 0;
for _ in 0..READ_BITS {
value <<= 1;
value += self.get_bit()? as i32;
value += self
.get_bit()
.map_err(|e| Error::new(e, stmt.span.clone()))?
as isize;
}
self.assign(assignable, Value::Int(value))?;
@ -286,10 +360,10 @@ impl ExecEnv {
fn assign(&mut self, dest: &Assignable, value: Value) -> Result<(), Error> {
match dest.kind {
AssignableKind::Variable => {
self.get_var_mut(&dest.ident)?.value.replace(value);
self.get_var_rc_mut(&dest.ident)?.replace(value);
}
AssignableKind::Index { ref indices } => {
let mut cell = self.get_var_rc(&dest.ident)?;
let mut cell = self.get_var_rc_mut(&dest.ident)?.clone();
for index in indices {
let index = self.eval_expr(index)?;
@ -300,14 +374,13 @@ impl ExecEnv {
if let Some(x) = c.get(&index) {
// cell[index] exists, get a shared
// reference to it.
Rc::clone(x)
ValueRef::clone(x)
} else {
// cell[index] does not exist, so we
// insert an empty cart by default
// instead.
let next_cell =
Rc::new(RefCell::new(Value::Cart(Default::default())));
c.insert(index, Rc::clone(&next_cell));
let next_cell = ValueRef::new(Value::Cart(Default::default()));
c.insert(index, ValueRef::clone(&next_cell));
next_cell
}
}
@ -316,8 +389,8 @@ impl ExecEnv {
// it into a cart, and write the result
// back into it.
let mut cart = take(x).into_cart();
let next_cell = Rc::new(RefCell::new(Value::Cart(Default::default())));
cart.insert(index, Rc::clone(&next_cell));
let next_cell = ValueRef::new(Value::Cart(Default::default()));
cart.insert(index, ValueRef::clone(&next_cell));
*x = Value::Cart(cart);
next_cell
}
@ -334,19 +407,22 @@ impl ExecEnv {
/// 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: &[Expr], span: &Range<usize>) -> Result<(), Error> {
fn fn_call(
&mut self,
func: Functio,
args: &[Spanned<Expr>],
span: &Range<usize>,
) -> Result<HaltStatus, Error> {
// Arguments that are ExprKind::Variable are pass by
// reference; all other expressions are pass by value.
let args = args
.iter()
.map(|arg| {
if let ExprKind::Variable(name) = &arg.kind {
self.get_var_rc(&Ident {
ident: name.to_owned(),
span: arg.span.clone(),
})
if let Expr::Variable(name) = &arg.item {
self.get_var_rc_mut(&Spanned::new(name.to_owned(), arg.span.clone()))
.cloned()
} else {
self.eval_expr(arg).map(|v| Rc::new(RefCell::new(v)))
self.eval_expr(arg).map(ValueRef::new)
}
})
.collect::<Result<Vec<_>, Error>>()?;
@ -357,9 +433,9 @@ impl ExecEnv {
fn fn_call_with_values(
&mut self,
func: Functio,
args: &[Rc<RefCell<Value>>],
args: &[ValueRef],
span: &Range<usize>,
) -> Result<(), Error> {
) -> Result<HaltStatus, Error> {
match func {
Functio::Bf {
instructions,
@ -379,22 +455,21 @@ impl ExecEnv {
)
.interpret_with_output(&mut output)
.map_err(|e| Error {
kind: ErrorKind::BfInterpretError(e),
kind: ErrorKind::Brian(e),
span: span.to_owned(),
})?;
stdout()
.write_all(&output)
.expect("Failed to write to stdout");
match String::from_utf8(output) {
Ok(string) => self.host_interface.print(&string, false),
Err(e) => self
.host_interface
.print(&format!("{:?}", e.as_bytes()), true),
}
.map_err(|e| Error::new(e.into(), span.clone()))?;
Ok(HaltStatus::Finished)
}
Functio::Able { params, body } => {
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()) {
@ -404,45 +479,48 @@ impl ExecEnv {
let res = self.eval_stmts_hs(&body, false);
self.stack.pop();
res?;
res
}
Functio::Builtin(b) => {
b.call(args).map_err(|e| Error::new(e, span.clone()))?;
Ok(HaltStatus::Finished)
}
Functio::Chain { functios, kind } => {
use crate::value::functio::FunctioChainKind;
let (left_functio, right_functio) = *functios;
match kind {
crate::variables::FunctioChainKind::Equal => {
let (l, r) = args.split_at(args.len() / 2);
Ok(
match match kind {
FunctioChainKind::Equal => {
let (l, r) = args.split_at(args.len() / 2);
self.fn_call_with_values(left_functio, l, span)?;
self.fn_call_with_values(right_functio, r, span)?;
}
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)?,
)
}
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::Parser::new(&code).init()?;
self.eval_stmts(&stmts)?;
(
self.fn_call_with_values(left_functio, &l, span)?,
self.fn_call_with_values(right_functio, &r, span)?,
)
}
} {
(s, HaltStatus::Finished) => s,
(HaltStatus::Finished, s) => s,
(_, r) => r,
},
)
}
Functio::Eval(code) => self.eval_stmts_hs(&crate::parser::parse(&code)?, false),
}
Ok(())
}
fn deinterlace(
args: &[Rc<RefCell<Value>>],
arities: (usize, usize),
) -> (Vec<Rc<RefCell<Value>>>, Vec<Rc<RefCell<Value>>>) {
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),
@ -453,21 +531,26 @@ impl ExecEnv {
(
args.chunks(2)
.take(n_alternations)
.map(|chunk| Rc::clone(&chunk[0]))
.map(|chunk| ValueRef::clone(&chunk[0]))
.chain(
args[2 * n_alternations..]
args.get(2 * n_alternations..)
.iter()
.map(|r| Rc::clone(r))
.copied()
.flatten()
.map(ValueRef::clone)
.take(extra_l),
)
.collect(),
args.chunks(2)
.take(n_alternations)
.map(|chunk| Rc::clone(&chunk[1]))
.flat_map(|chunk| chunk.get(1))
.map(ValueRef::clone)
.chain(
args[2 * n_alternations..]
args.get(2 * n_alternations..)
.iter()
.map(|r| Rc::clone(r))
.copied()
.flatten()
.map(ValueRef::clone)
.take(extra_r),
)
.collect(),
@ -476,15 +559,14 @@ impl ExecEnv {
/// 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> {
fn get_bit(&mut self) -> Result<bool, ErrorKind> {
const BITS_PER_BYTE: u8 = 8;
if self.read_buf.is_empty() {
let mut data = [0];
stdin().read_exact(&mut data)?;
let byte = self.host_interface.read_byte()?;
for n in (0..BITS_PER_BYTE).rev() {
self.read_buf.push_back(((data[0] >> n) & 1) != 0);
self.read_buf.push_back(((byte >> n) & 1) != 0);
}
}
@ -496,107 +578,90 @@ impl ExecEnv {
/// Get the value of a variable. Throw an error if the variable is
/// inaccessible or banned.
fn get_var(&self, name: &Ident) -> Result<Value, Error> {
// One-letter names are reserved as base55 numbers.
let mut chars = name.ident.chars();
if let (Some(first), None) = (chars.next(), chars.next()) {
return Ok(Value::Int(base_55::char2num(first)));
}
// Otherwise, search for the name in the stack from top to
// bottom.
fn get_var_value(&self, name: &Spanned<String>) -> Result<Value, Error> {
// Search for the name in the stack from top to bottom.
match self
.stack
.iter()
.rev()
.find_map(|scope| scope.variables.get(&name.ident))
.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.ident.to_owned()),
span: name.span.clone(),
})
}
}
None => Err(Error {
kind: ErrorKind::UnknownVariable(name.ident.to_owned()),
Some(Variable::Ref(r)) => Ok(r.borrow().clone()),
Some(Variable::Melo) => Err(Error {
kind: ErrorKind::MeloVariable(name.item.to_owned()),
span: name.span.clone(),
}),
None => Ok(Value::Undefined),
}
}
/// Get a mutable reference to a variable. Throw an error if the
/// variable is inaccessible or banned.
fn get_var_mut(&mut self, name: &Ident) -> Result<&mut Variable, Error> {
/// Get a mutable reference to a variable.
fn get_var_mut(&mut self, name: &Spanned<String>) -> Result<&mut Variable, Error> {
// This function has a lot of duplicated code with `get_var`,
// which I feel like is a bad sign...
match self
.stack
.iter_mut()
.rev()
.find_map(|scope| scope.variables.get_mut(&name.ident))
.find_map(|scope| scope.variables.get_mut(&name.item))
{
Some(var) => {
if !var.melo {
Ok(var)
} else {
Err(Error {
kind: ErrorKind::MeloVariable(name.ident.to_owned()),
span: name.span.clone(),
})
}
}
Some(var) => Ok(var),
None => Err(Error {
kind: ErrorKind::UnknownVariable(name.ident.to_owned()),
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
/// Get an reference to an Rc'd pointer to the value of a variable. Throw an error
/// if the variable is inaccessible or banned.
fn get_var_rc(&mut self, name: &Ident) -> Result<Rc<RefCell<Value>>, Error> {
Ok(self.get_var_mut(name)?.value.clone())
fn get_var_rc_mut(&mut self, name: &Spanned<String>) -> Result<&mut ValueRef, Error> {
match self.get_var_mut(name)? {
Variable::Ref(r) => Ok(r),
Variable::Melo => Err(Error {
kind: ErrorKind::MeloVariable(name.item.to_owned()),
span: name.span.clone(),
}),
}
}
/// Declare a new variable, with the given initial value.
fn decl_var(&mut self, name: &str, value: Value) {
self.decl_var_shared(name, Rc::new(RefCell::new(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: Rc<RefCell<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 });
.insert(name.to_owned(), Variable::Ref(value));
}
}
#[cfg(test)]
mod tests {
use crate::ast::ExprKind;
use super::*;
use crate::{
ast::{Expr, Literal},
host_interface::Standard,
};
#[test]
fn basic_expression_test() {
// Check that 2 + 2 = 4.
let env = ExecEnv::new();
let env = ExecEnv::<Standard>::default();
assert_eq!(
env.eval_expr(&Expr {
kind: ExprKind::BinOp {
lhs: Box::new(Expr {
kind: ExprKind::Literal(Value::Int(2)),
env.eval_expr(&Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(2)),
span: 1..1,
}),
rhs: Box::new(Expr {
kind: ExprKind::Literal(Value::Int(2)),
rhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(2)),
span: 1..1,
}),
kind: crate::ast::BinOpKind::Add,
@ -610,18 +675,19 @@ mod tests {
#[test]
fn type_coercions() {
// The sum of an integer and a boolean causes a boolean
// The sum of an integer and an aboolean causes an aboolean
// coercion.
let env = ExecEnv::new();
let env = ExecEnv::<Standard>::default();
assert_eq!(
env.eval_expr(&Expr {
kind: ExprKind::BinOp {
lhs: Box::new(Expr {
kind: ExprKind::Literal(Value::Int(2)),
env.eval_expr(&Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(2)),
span: 1..1,
}),
rhs: Box::new(Expr {
kind: ExprKind::Literal(Value::Bool(true)),
rhs: Box::new(Spanned {
item: Expr::Variable("always".to_owned()),
span: 1..1,
}),
kind: crate::ast::BinOpKind::Add,
@ -637,16 +703,16 @@ mod tests {
fn overflow_should_not_panic() {
// Integer overflow should throw a recoverable error instead
// of panicking.
let env = ExecEnv::new();
let env = ExecEnv::<Standard>::default();
assert_eq!(
env.eval_expr(&Expr {
kind: ExprKind::BinOp {
lhs: Box::new(Expr {
kind: ExprKind::Literal(Value::Int(i32::MAX)),
env.eval_expr(&Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(isize::MAX)),
span: 1..1,
}),
rhs: Box::new(Expr {
kind: ExprKind::Literal(Value::Int(1)),
rhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(1)),
span: 1..1,
}),
kind: crate::ast::BinOpKind::Add,
@ -654,19 +720,19 @@ mod tests {
span: 1..1
})
.unwrap(),
Value::Int(-2147483648)
Value::Int(-9223372036854775808)
);
// And the same for divide by zero.
assert_eq!(
env.eval_expr(&Expr {
kind: ExprKind::BinOp {
lhs: Box::new(Expr {
kind: ExprKind::Literal(Value::Int(84)),
env.eval_expr(&Spanned {
item: Expr::BinOp {
lhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(84)),
span: 1..1,
}),
rhs: Box::new(Expr {
kind: ExprKind::Literal(Value::Int(0)),
rhs: Box::new(Spanned {
item: Expr::Literal(Literal::Int(0)),
span: 1..1,
}),
kind: crate::ast::BinOpKind::Divide,
@ -681,12 +747,10 @@ mod tests {
// From here on out, I'll use this function to parse and run
// expressions, because writing out abstract syntax trees by hand
// takes forever and is error-prone.
fn eval(env: &mut ExecEnv, src: &str) -> Result<Value, Error> {
let mut parser = crate::parser::Parser::new(src);
fn eval(env: &mut ExecEnv<Standard>, src: &str) -> Result<Value, Error> {
// We can assume there won't be any syntax errors in the
// interpreter tests.
let ast = parser.init().unwrap();
let ast = crate::parser::parse(src).unwrap();
env.eval_stmts(&ast).map(|()| Value::Nul)
}
@ -695,13 +759,13 @@ mod tests {
// Functions have no return values, so use some
// pass-by-reference hacks to detect the correct
// functionality.
let mut env = ExecEnv::new();
let mut env = ExecEnv::<Standard>::default();
// Declaring and reading from a variable.
eval(&mut env, "var foo = 32; var bar = foo + 1;").unwrap();
eval(&mut env, "foo dim 32; bar dim foo + 1;").unwrap();
assert_eq!(
env.get_var(&Ident {
ident: "bar".to_owned(),
env.get_var_value(&Spanned {
item: "bar".to_owned(),
span: 1..1,
})
.unwrap(),
@ -709,10 +773,10 @@ mod tests {
);
// Assigning an existing variable.
eval(&mut env, "foo = \"hi\";").unwrap();
eval(&mut env, "/*hi*/ =: foo;").unwrap();
assert_eq!(
env.get_var(&Ident {
ident: "foo".to_owned(),
env.get_var_value(&Spanned {
item: "foo".to_owned(),
span: 1..1,
})
.unwrap(),
@ -721,7 +785,7 @@ mod tests {
// But variable assignment should be illegal when the variable
// hasn't been declared in advance.
eval(&mut env, "invalid = bar + 1;").unwrap_err();
eval(&mut env, "bar + 1 =: invalid;").unwrap_err();
}
#[test]
@ -729,16 +793,16 @@ mod tests {
// Declaration and assignment of variables declared in an `if`
// statement should have no effect on those declared outside
// of it.
let mut env = ExecEnv::new();
let mut env = ExecEnv::<Standard>::default();
eval(
&mut env,
"var foo = 1; foo = 2; if (true) { var foo = 3; foo = 4; }",
"foo dim 1; 2 =: foo; unless (never) { foo dim 3; 4 =: foo; }",
)
.unwrap();
assert_eq!(
env.get_var(&Ident {
ident: "foo".to_owned(),
env.get_var_value(&Spanned {
item: "foo".to_owned(),
span: 1..1,
})
.unwrap(),

View File

@ -1,171 +1,96 @@
use logos::{Lexer, Logos};
use crate::variables::Abool;
#[derive(Logos, Debug, PartialEq, Clone)]
#[derive(Logos, Debug, PartialEq, Eq, Clone)]
#[logos(skip r"[ \t\n\f]+")]
#[logos(skip r"owo .*")]
#[rustfmt::skip]
pub enum Token {
// Symbols
#[token("(")]
LeftParen,
#[token(")")]
RightParen,
#[token("[")]
LeftBracket,
#[token("]")]
RightBracket,
#[token("{")]
LeftCurly,
#[token("}")]
RightCurly,
#[token(";")]
Semicolon,
#[token(".")]
Dot,
#[token(",")]
Comma,
#[token("(")] LeftParen,
#[token(")")] RightParen,
#[token("[")] LeftBracket,
#[token("]")] RightBracket,
#[token("{")] LeftCurly,
#[token("}")] RightCurly,
#[token(";")] Semicolon,
#[token(",")] Comma,
// Operators
#[token("+")]
Plus,
#[token("-")]
Minus,
#[token("*")]
Star,
#[token("/")]
FwdSlash,
#[token("=")]
Equal,
#[token("<=")]
Arrow,
#[token("+")] Plus,
#[token("-")] Minus,
#[token("*")] Star,
#[token("/")] FwdSlash,
#[token("=:")] Assign,
#[token("<=")] Arrow,
// Logical operators
#[token("<")]
LessThan,
#[token(">")]
GreaterThan,
#[token("==")]
EqualEqual,
#[token("!=")]
NotEqual,
#[regex("!|aint")] // also add aint as a not keyword
Not,
#[token("<")] LessThan,
#[token(">")] GreaterThan,
#[token("=")] Equals,
#[token("ain't")] Aint,
// Keywords
#[token("functio")]
Functio,
/// Brain fuck FFI
#[token("bff")]
Bff,
/// Variable bro
#[token("var")]
Var,
/// 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,
#[token("functio")] Functio,
#[token("bff")] Bff,
#[token("dim")] Dim,
#[token("print")] Print,
#[token("read")] Read,
#[token("melo")] Melo,
#[token("T-Dark")] TDark,
// Control flow keywords
#[token("if")]
If,
#[token("unless")] Unless,
#[token("loop")] Loop,
#[token("enough")] Enough,
#[token("and again")] AndAgain,
#[token("finally")] Finally,
#[token("rlyeh")] Rlyeh,
#[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,
#[token("rickroll")] Rickroll,
// Literals
/// True, False
#[regex("true|false", get_value)]
Bool(bool),
/// Always, Sometimes, Never
#[regex("always|sometimes|never", get_abool)]
Abool(Abool),
/// Base52 based character ('a')
#[token("'.*'")]
Char,
/// String
#[regex("\"(\\.|[^\"])*\"", get_string)]
String(String),
/// Integer
#[regex(r"-?[0-9]+", get_value)]
Integer(i32),
/// A C-complaint identifier
#[regex(r"[a-zA-Z_][a-zA-Z_0-9]*", get_ident)]
#[token("/*", get_string)] String(String),
#[regex(r"-?[0-9]+", get_value)] Integer(isize),
#[regex(r"\p{XID_Start}", get_value)] Char(char),
#[regex(r"\p{XID_Start}[\p{XID_Continue}]+", get_ident)]
#[token("and ", |_| "and".to_owned())]
Identifier(String),
#[regex(r"owo .*")]
Comment,
#[regex("nul")]
Nul,
#[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>) -> String {
lexer.slice().trim_matches('"').to_owned()
}
fn get_string(lexer: &mut Lexer<Token>) -> Option<String> {
lexer.bump(lexer.remainder().find("*/")?);
fn get_abool(lexer: &mut Lexer<Token>) -> Option<Abool> {
match lexer.slice() {
"always" => Some(Abool::Always),
"sometimes" => Some(Abool::Sometimes),
"never" => Some(Abool::Never),
_ => None,
let mut string = String::new();
let mut slice = &lexer.slice()[2..];
while let Some(escape_start) = slice.find('"') {
// Push predeceasing string
string.push_str(slice.get(..escape_start)?);
// Move slice behind escape start delimiter
slice = slice.get(escape_start + 1..)?;
// Get escape end delimiter position and parse string before it to
// a character from it's unicode value (base-12) and push it to string
let escape_end = slice.find('"')?;
string.push(
u32::from_str_radix(slice.get(..escape_end)?, 12)
.ok()
.and_then(char::from_u32)?,
);
// Move slice behind escape end delimiter
slice = slice.get(escape_end + 1..)?;
}
// Push remaining string
string.push_str(slice);
lexer.bump(2);
Some(string)
}
fn get_ident(lexer: &mut Lexer<Token>) -> String {
@ -180,30 +105,40 @@ mod tests {
#[test]
fn simple_fn() {
let code = "functio test() { var a = 3; if a == 3 { a print } }";
let code = "functio test() { dim var 3; unless (var ain't 3) { var print } }";
let expected = &[
Functio,
Identifier("test".to_owned()),
LeftParen,
RightParen,
LeftCurly,
Var,
Identifier("a".to_owned()),
Equal,
Dim,
Identifier("var".to_owned()),
Integer(3),
Semicolon,
If,
Identifier("a".to_owned()),
EqualEqual,
Unless,
LeftParen,
Identifier("var".to_owned()),
Aint,
Integer(3),
RightParen,
LeftCurly,
Identifier("a".to_owned()),
Identifier("var".to_owned()),
Print,
RightCurly,
RightCurly,
];
let lexer = Token::lexer(code);
let result: Vec<Token> = lexer.collect();
let result: Vec<_> = Token::lexer(code).collect::<Result<_, _>>().unwrap();
assert_eq!(result, expected);
}
#[test]
fn escapes() {
let code = r#"/*»"720B""722B""7195"«*/"#;
let expected = &[Token::String("»にゃぁ«".to_owned())];
let result: Vec<_> = Token::lexer(code).collect::<Result<_, _>>().unwrap();
assert_eq!(result, expected);
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

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

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

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

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

View File

@ -1,855 +0,0 @@
use std::{
cell::RefCell, collections::HashMap, fmt::Display, hash::Hash, io::Write, mem::discriminant,
ops, rc::Rc, vec,
};
use rand::Rng;
use crate::{ast::Stmt, brian::INSTRUCTION_MAPPINGS, consts};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum Abool {
Never = -1,
Sometimes = 0,
Always = 1,
}
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<Abool> for bool {
fn from(val: Abool) -> Self {
match val {
Abool::Never => false,
Abool::Always => true,
Abool::Sometimes => rand::thread_rng().gen(), // NOTE(Able): This is amazing and should be applied anywhere abooleans exist
}
}
}
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum Functio {
Bf {
instructions: Vec<u8>,
tape_len: usize,
},
Able {
params: Vec<String>,
body: Vec<Stmt>,
},
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::Chain { functios, kind: _ } => functios.0.arity() + functios.1.arity(),
Functio::Eval(_) => 0,
}
}
}
#[derive(Debug, PartialEq, Copy, Clone, Hash)]
pub enum FunctioChainKind {
Equal,
ByArity,
}
pub type Cart = HashMap<Value, Rc<RefCell<Value>>>;
#[derive(Debug, Clone)]
pub enum Value {
Nul,
Str(String),
Int(i32),
Bool(bool),
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::Bool(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_i32() as u8])
.expect("Failed to write to Brainfuck input");
}
/// Coerce a value to an integer.
pub fn into_i32(self) -> i32 {
match self {
Value::Abool(a) => a as _,
Value::Bool(b) => b as _,
Value::Functio(func) => match func {
// 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::Chain { functios, kind } => {
let (lhs, rhs) = *functios;
match kind {
FunctioChainKind::Equal => {
Value::Int(Value::Functio(lhs).into_i32())
+ Value::Int(Value::Functio(rhs).into_i32())
}
FunctioChainKind::ByArity => {
Value::Int(Value::Functio(lhs).into_i32())
* Value::Int(Value::Functio(rhs).into_i32())
}
}
.into_i32()
}
Functio::Eval(s) => s.len() as _,
},
Value::Int(i) => i,
Value::Nul => consts::ANSWER,
Value::Str(text) => text.parse().unwrap_or(consts::ANSWER),
Value::Cart(c) => c.len() as _,
}
}
/// Coerce a value to a boolean.
pub fn into_bool(self) -> bool {
match self {
Value::Abool(b) => b.into(),
Value::Bool(b) => b,
Value::Functio(_) => true,
Value::Int(x) => x != 0,
Value::Nul => false,
Value::Str(s) => match s.to_lowercase().as_str() {
"false" | "no" | "🇳🇴" => false,
"true" | "yes" => true,
s => !s.is_empty(),
},
Value::Cart(c) => !c.is_empty(),
}
}
/// 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" => Abool::Never,
"sometimes" => Abool::Sometimes,
"always" => Abool::Always,
s => {
if s.is_empty() {
Abool::Never
} else {
Abool::Always
}
}
},
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::Bool(b) => {
if b {
Abool::Always
} else {
Abool::Never
}
}
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_i32 =
|x: String| -> i32 { x.as_bytes().into_iter().map(|x| *x as i32).sum() };
let params: i32 = params.into_iter().map(str_to_i32).sum();
let body: i32 = body
.into_iter()
.map(|x| format!("{:?}", x))
.map(str_to_i32)
.sum();
Value::Int((params + body) % 3 - 1).into_abool()
}
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::Bool(b) => Functio::Eval(
if b {
r#"loop{"Buy Able products!"print;}"#
} else {
""
}
.to_owned(),
),
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_i32())
{
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 i32 + 1),
Rc::new(RefCell::new(Value::Str(x.to_string()))),
)
})
.collect(),
Value::Int(i) => Value::Str(i.to_string()).into_cart(),
Value::Bool(b) => Value::Str(b.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 i32 + 1),
Rc::new(RefCell::new(Value::Str(x))),
)
})
.collect();
let body: Cart = body
.into_iter()
.enumerate()
.map(|(i, x)| {
(
Value::Int(i as i32 + 1),
Rc::new(RefCell::new(Value::Str(format!("{:?}", x)))),
)
})
.collect();
let mut cart = HashMap::new();
cart.insert(
Value::Str("params".to_owned()),
Rc::new(RefCell::new(Value::Cart(params))),
);
cart.insert(
Value::Str("body".to_owned()),
Rc::new(RefCell::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 i32 + 1),
Rc::new(RefCell::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()),
Rc::new(RefCell::new(Value::Int(tape_len 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,
}
}
}
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_i32()) + rhs,
Value::Bool(_) => Value::Bool(self.into_bool()) + 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.to_string())),
Value::Int(i) => Value::Int(i.wrapping_add(rhs.into_i32())),
Value::Bool(b) => Value::Bool(b || rhs.into_bool()),
Value::Abool(_) => {
Value::Abool(Value::Int(self.into_i32().max(rhs.into_i32())).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_i32()) - rhs,
Value::Bool(_) => Value::Bool(self.into_bool()) - 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_i32())),
Value::Bool(b) => Value::Bool(b ^ rhs.into_bool()),
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_i32()
- Value::Functio(rhs).into_i32(),
)
.into_functio(),
},
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_i32()) * rhs,
Value::Bool(_) => Value::Bool(self.into_bool()) * 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_i32() as usize)),
Value::Int(i) => Value::Int(i.wrapping_mul(rhs.into_i32())),
Value::Bool(b) => Value::Bool(b && rhs.into_bool()),
Value::Abool(_) => {
Value::Abool(Value::Int(self.into_i32().min(rhs.into_i32())).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_i32()) / rhs,
Value::Bool(_) => Value::Bool(self.into_bool()) / 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 i32 + 1),
Rc::new(RefCell::new(Value::Str(x.to_owned()))),
)
})
.collect(),
),
Value::Int(i) => Value::Int(i.wrapping_div(match rhs.into_i32() {
0 => consts::ANSWER,
x => x,
})),
Value::Bool(b) => Value::Bool(!b || rhs.into_bool()),
Value::Abool(_) => !self + rhs,
Value::Functio(f) => Value::Functio(match f {
Functio::Bf {
instructions,
tape_len,
} => {
let fraction = 1.0 / rhs.into_i32() 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_i32() as f64;
let len = body.len();
Functio::Able {
params,
body: body
.into_iter()
.take((len as f64 * fraction) as usize)
.collect(),
}
}
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_i32() 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_i32() 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 i32 + 1),
Rc::new(RefCell::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::Bool(b) => Value::Bool(!b),
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::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(), Rc::new(RefCell::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_i32(),
Value::Bool(b) => *b == other.into_bool(),
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_i32())),
Value::Bool(b) => Some(b.cmp(&other.into_bool())),
Value::Abool(a) => a.partial_cmp(&other.into_abool()),
Value::Functio(_) => self.clone().into_i32().partial_cmp(&other.into_i32()),
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::Bool(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::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)]
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: Rc<RefCell<Value>>,
}

View File

@ -1,18 +1,15 @@
[package]
name = "ablescript_cli"
version = "0.2.0"
authors = ["able <abl3theabove@gmail.com>"]
edition = "2018"
version = "0.5.4"
authors = ["AbleScript Developers"]
edition = "2021"
description = "The best programming language"
license = "MIT"
documentation = "https://ablecorp.us/able-script-the-book/"
repository = "https://github.com/AbleCorp/able-script"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
description = "The best programming language"
license = "MIT"
documentation = "https://gblecorp.github.io/able-script-the-book"
repository = "https://git.ablecorp.us/AbleScript/able-script"
[dependencies]
ablescript = { version = "0.2.0", path = "../ablescript" }
clap = "2.33"
rustyline = "8.0.0"
ablescript = { version = "0.5.3", path = "../ablescript" }
clap = { version = "4.2", features = ["derive"] }
rustyline = "11.0"

View File

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

View File

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

View File

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

8
examples/carts.able Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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