145 Commits
0.3.0 ... 0.4.0

Author SHA1 Message Date
4a18fc489f tests; maps; remove test that now asserts in debug
for now, as there doesn't seem to be a way to test for that , it fails CI
2021-04-08 21:46:31 -07:00
d763c72e3d fix use of NULL_VAL, also invalid when nan tagging is disabled 2021-04-08 21:40:06 -07:00
34b01226a4 fix use of UNDEFINED_VAL which is invalid when nan tagging is disabled 2021-04-08 21:34:27 -07:00
a4ae905384 Introduce Attributes (#962)
* introduce Attributes for classes and methods
2021-04-08 21:30:09 -07:00
5244a9d001 Add an additional escape sequence \e. (#963) 2021-04-08 10:16:24 -07:00
9e86b0d26a clarify the list of escapes to show the whole list 2021-04-08 10:11:01 -07:00
e260b467c4 made wren version number accessable via function (#958) 2021-04-08 09:33:22 -07:00
1307bdfb64 value: Rename validateKeyTypeto wrenMapIsValidKey. (#965) 2021-04-08 09:10:55 -07:00
06b71897b0 Surface error messages for map key values in the API with asserts
closes https://github.com/wren-lang/wren/pull/921
2021-04-07 23:08:31 -07:00
059e407ed3 wren/compiler: Allow multiline empty parameter lists and calls. (#925) 2021-04-07 22:58:46 -07:00
fd1d095588 Add preamble for Timer module (#920)
Opening remarks also needed here.
2021-04-07 22:56:51 -07:00
7a131a67e6 Add preamble for Scheduler module (#919)
We need some opening remarks here to get rid of the TODO.
2021-04-07 22:56:29 -07:00
e4052a25d7 wren/vm: Add wrenIsFalsyValue. (#866) 2021-04-07 22:54:49 -07:00
041f1bab8d Fix returning from constructors (#845)
* Fix returning from constructors

 1. Do not allow returning with a value
 2. Return the instance, correctly, even when the user returned explicitly

* revise error message for consistency, revise implementation details a bit, fix extra args to finishBody

* clarify tests a bit

* document constructor return

Co-authored-by: ruby0x1 <ruby0x1@pm.me>
2021-04-07 22:53:05 -07:00
68f5c096d8 Disallow non-Num arguments in Num for min(_), max(_), clamp(_,_), pow(_) and atan(_) (#859)
* Disallow non-Num arguments in `Num.min(_)`, `Num.max(_)`, `Num.clamp(_,_)`

Previously this was an Undefined Behavior

* also validate args for pow, atan2, add tests, fix error messages

Co-authored-by: ruby0x1 <ruby0x1@pm.me>
2021-04-07 22:04:58 -07:00
4847b37789 minor cleanup 2021-04-07 21:56:19 -07:00
28da4b449c Add Num.[max/min]SafeInteger (#874) 2021-04-07 21:45:00 -07:00
61cc6cb745 more minor doc fixes 2021-04-07 21:37:12 -07:00
0ab930c9c2 reorder some docs a bit, fix missing constructor 2021-04-07 21:21:50 -07:00
d38c047a5a documentation revisions and missing pieces 2021-04-07 20:54:18 -07:00
0be504832e fix code documentation 2021-04-07 18:36:11 -07:00
4cd374e1a7 fix amalgamation documentation 2021-04-06 19:59:42 -07:00
5e60bbf0cf document string range indexing for substring 2021-04-06 19:56:28 -07:00
197c0ff4f9 fix loadModuleFn description in header 2021-04-06 19:49:40 -07:00
e3c76a3e76 Raw strings now ignore whitespace on both ends for consistency and clarity
added more tests, updated documentation
2021-04-04 22:28:57 -07:00
8304fd5ecc docs; strings; fix formatting for example 2021-04-04 14:02:07 -07:00
ea684194db try; update for testing latest 2021-04-04 13:54:58 -07:00
981ea4adf1 Add raw string literals
Also document + test multi line literals.
2021-04-04 13:52:32 -07:00
345f919e26 make WREN_API_DLLEXPORT required, fixes statically linked versions
-_-
2021-04-04 13:29:32 -07:00
853f5a3414 fix link in fiber page 2021-04-04 12:31:45 -07:00
11bea3ca01 Add documentation for Fiber.transfer methods (#949) 2021-04-04 12:20:15 -07:00
33ab8be7e3 fix possibility of having no reallocate function (#954)
Before, if a config was provided it was expected to have a reallocate function, now it can remain null and the default will be used.
2021-04-04 11:24:48 -07:00
a501fba4bb add WREN_API declaration for proper symbol export
Brought up by @Orcolom

Detected for MSVC or similar and can be explicitly activated by defining WREN_API_DLLEXPORT (or manually defining WREN_API to __declspec( dllexport ) before include).

Can be disabled by using a blank `#define WREN_API` before include.

Note: to use the import variant, `#define WREN_API __declspec(dllimport)` before including the wren header.
2021-04-04 11:18:40 -07:00
615a6aa208 try; fix random and meta properly 2021-04-03 22:09:19 -07:00
94e835cdd6 minor clean up of try readme 2021-04-03 22:02:13 -07:00
3c0fe12102 try; add try implementation + details, update to latest main branch 2021-04-03 21:58:31 -07:00
ecce1f6be9 List; add remove(value)
Having to encode this behaviour at every call site is tedious. It makes a lot of sense to just have the method available on list itself.
2021-04-03 19:55:42 -07:00
4d1d0d972e rename some test functions that clash with libc (#936)
* rename some test functions that clash with cosmopolitan libc
2021-03-07 16:38:29 -08:00
5b290cacc5 Tentative fix for Num class docs issue on the wren.io site. (#932) 2021-03-02 11:11:21 -08:00
dfa7d9c895 doc/site/functions Fn.new() clarification [minor] (#931)
In the description of Fn.new():
- Fix a typo
- Add the referent of "that" for clarity
2021-03-02 09:27:23 -08:00
2bc895c26d added documentation on * operator for strings (#930) 2021-02-18 19:16:09 -08:00
208bbc02a5 revert #826
3a06580b89
2021-02-13 10:17:59 -08:00
9dfbc021a0 wren/compiler: Store value in the correct token. (#923) 2021-02-06 07:53:39 -08:00
ae6fdb3da7 Add docs for newly merged (#905) Num.cbrt method (#918) 2021-01-31 10:22:19 -08:00
3a06580b89 Don't use strtoll() with explicit base to parse hex, instead use strtod() (#826)
`strtod()` automatically recognizes the `0x` and converts to hex

This has two advantages:
 1. Simplify code (no need of argument `isHex` to `makeNumber`, no `if`)
 2. Allow numbers larger than `long long` if they fit into `double`
2021-01-30 21:44:52 -08:00
0fa16a20ec Fix deprecated Python timing in benchmark & give more error info. (#844)
- Note: process_time gives CPU time used and perf_counter is absolute time used.
- Looks to have noise of about 1-2%.
2021-01-30 21:40:20 -08:00
af5227f03b Mention List.[Range] in the docs (#870) 2021-01-30 21:34:27 -08:00
1720a20979 In List.+(other), other should be an iterable, list is not required (#872)
We're iterating over it but do not check it's actually a list:

cb51d61a64/src/vm/wren_core.wren (L363-L369)
2021-01-30 21:33:54 -08:00
4ac41c36db Added hexadecimal number literals example (#876) 2021-01-30 21:32:42 -08:00
79000a320e Proposed additions to Map class docs. (#888)
The Map class documentation is missing a few details which I think should ideally be covered. The proposed changes should be self evident.
2021-01-30 21:22:54 -08:00
f1225ef7dd Add docs for remaining bitwise operators (#887)
I mentioned in #881 that the `^`, `<<` and `>>' bitwise operators were currently undocumented so this fixes that.

Have also added a note to Num.tau to clarify that this means `twice pi `and is not the golden ratio for which `tau` is sometimes used as an alternative to `phi`.
2021-01-30 21:21:59 -08:00
57bebd41ca Adds documentation for System.writeAll method (#886) 2021-01-30 21:21:33 -08:00
0f8b44e61b Fix undocumented methods in the Num class ( #879) (#884) 2021-01-30 21:21:18 -08:00
96eb68bae3 Fix undocumented methods in the List class ( #878) (#883)
Also noted that the ```add``` method returns the added item.
2021-01-30 21:21:07 -08:00
16ddbb66f8 Add docs for Scheduler class (#902)
I think it's time we had a go at this.
2021-01-30 21:15:56 -08:00
3ceb029df9 Add docs for Timer class (#903)
Another class that has remained undocumented for a long time.
2021-01-30 21:15:22 -08:00
ce599259bc wren/core: Add Num::cbrt. (#905) 2021-01-30 21:13:58 -08:00
97bc340737 Happy new year! (#904) 2020-12-31 18:52:09 -08:00
81aff84415 Fix incorrect heading (#871)
There was a space instead of `#`
2020-12-13 20:48:18 -08:00
cb51d61a64 fix unmarked compiler value causing memory issues
https://github.com/wren-lang/wren/issues/869
2020-12-07 12:34:31 -08:00
9da1e265df Remove unused field skipNewlines (#858) 2020-12-03 19:53:26 -08:00
9fbfe6a419 Add slashes before split comment line (#861) 2020-12-03 19:52:17 -08:00
3c07611b0a Syntax-highlight continue keyword (#864) 2020-12-03 19:51:48 -08:00
aac6296317 Add continue to the list of reserved words (#865) 2020-12-03 19:50:56 -08:00
a294da7974 Only the method name should be bold, not including the parameters list (#862) 2020-12-03 19:50:21 -08:00
c572345c3c &infinity; is not a valid HTML symbol - use &infin; (#863) 2020-12-03 19:48:49 -08:00
da3a3f0e0e Update draft 0.4.0 changelog 2020-12-03 14:32:53 -08:00
7d3f063e87 Merge branch 'next-token' into main 2020-12-03 13:43:40 -08:00
4687300ad6 fix forward declaration using c11 features (???) 2020-12-03 13:38:46 -08:00
89c5e22480 add Num.tau 2020-12-03 13:20:15 -08:00
8361217369 Num; add min, max and clamp 2020-12-03 13:18:13 -08:00
38f50fe091 List; add swap(index0, index1) 2020-12-03 13:17:53 -08:00
62009870a8 List; add indexOf(value) 2020-12-03 13:17:26 -08:00
3d5e68fc01 Added List.sort(comp) to List module (#802) 2020-12-03 11:59:07 -08:00
08d2fa3821 fix paste issues from https://github.com/wren-lang/wren/pull/798/ 2020-12-03 11:55:31 -08:00
d8bdc7359e Added myself to the authors file (#856) 2020-12-03 11:51:48 -08:00
76fb4f311b Test static operators (#798)
In #797 it was stated that static operators are valid in Wren, and proposed to check this behavior
2020-12-03 11:47:42 -08:00
84b29e6995 Add userData pointer to reallocateFn (#788)
* Add userData ptr to all reallocateFn calls
* Check that userData is correctly passed
* Update AUTHORS
2020-12-03 11:46:22 -08:00
a11d66cbd3 WIP wren/core: Add DEF_NUM_CONSTANT (with Num::infinity and Num::nan). (#781)
* wren/vm: Add "wren_math.h".
* wren/core: Add DEF_NUM_CONSTANT.
* wren/core: Add `Num::infinity` constant.
* wren/core: Add `Num::nan` constant.
2020-12-03 11:37:53 -08:00
59ee326523 Added a quick explanation of the continue keyword (#854)
* Added a quick explanation of the continue keyword
2020-12-03 11:29:52 -08:00
182ca90b8c add wrenHasVariable and wrenHasModule 2020-12-03 11:13:04 -08:00
bc7dd50a54 fix warning 2020-12-03 10:47:49 -08:00
97ebcc72c3 Add wrenSetListElement, correctly allow negative indices on wrenGetListElement 2020-12-03 10:30:47 -08:00
999acba06f bump version to 0.4.0, since that's the one we're working on 2020-12-03 09:41:27 -08:00
5264b46246 Fix outdated comment 2020-12-03 09:40:56 -08:00
3e0f71b742 [0.4.0] Import as (#775)
* Add import "..." for Variable as OtherName
2020-12-03 09:34:36 -08:00
6bd2f810e2 [0.4.0] Introduce WrenLoadModuleResult, fix unfreed strings from host. (#778) 2020-12-03 09:27:54 -08:00
e7071fffa5 Update AUTHORS (#855)
@ruby0x1 suggested I add my name to the AUTHORS file
2020-12-03 08:58:46 -08:00
55b926410d Add continue statement (#822)
Note that documentation is still required.
2020-12-03 08:30:36 -08:00
f5339993ce Add support for Fiber.try(_) (#835)
* Add support for Fiber.try(_)
* Add documentation for Fiber.try(_)
* Add another test for Fiber.try(_)
2020-12-03 08:21:37 -08:00
473392a56a docs: fix simple typo, similiar -> similar (#843)
There is a small typo in src/vm/wren_value.h.

Should read `similar` rather than `similiar`.
2020-12-03 08:19:19 -08:00
556eeac86e Fix broken link (#799)
The Variables guide linked to `/modules.html`, but the page talking about modules is `/modularity.html`
2020-11-25 22:49:52 -08:00
94e4888b6a Fix link in null.markdown (#848) 2020-11-25 21:09:37 -08:00
44d6d20586 Do not allow inheriting built-in classes Num, Bool and Null (#831)
* Do not allow inheriting `Num`, `Bool` and `Null`. fixes #830
2020-10-26 08:39:36 -07:00
ad4e039187 Fix svg link to travis build (#828) 2020-10-17 15:57:39 -07:00
3c475f01ee allow newline before dot for subscript as well, and add to tests 2020-09-19 22:03:16 -07:00
4c496c56a6 allow a newline before dot usage, for chained/fluent interfaces 2020-09-19 20:40:24 -07:00
1c5ac28831 compiler now tracks next token (in addition to current/previous) 2020-09-19 20:40:24 -07:00
45c67fae0c Fn call: move arity check into interpret loop, which avoid the expensive if after the call, since runtime errors originating inside the call itself will still be handled, we only have the one emitted from call itself.
This brings the benchmark back up to where it was.
2020-09-18 15:42:37 -07:00
beae242a41 vm; handle errors from fn.call (still investigating) 2020-09-18 13:11:12 -07:00
86463acb90 Fix stack corruption caused by Fn call primitives (#807)
Excerpt from @munificent on the nature of the bug:

In runInterpreter, for performance, the vm caches an IP pointing into some bytecode.

All primitives except for `.call`, do not touch Wren's own callstack. They run a little C code and return, so the array of CallFrames, their IPs, and the IP cached inside run() are not affected at all.

While runInterpreter() is running, the IP in the top CallFrame is not updated, so it gets out of sync. This is deliberate, since storing to a field is slow, but it means the value of that field is stale and doesn't represent where execution actually is at that point in time.

To get that field in sync, we use STORE_FRAME(), which stores the local IP value back into the IP field for the top CallFrame. The interpreter is careful to always call STORE_FRAME() before executing any code that pushes a new CallFrame onto the stack.

In particular, if you look around, you'll see that every place the interpreter calls wrenCallFunction() is preceded by a STORE_FRAME(). That is, except for the call to wrenCallFunction() in the call_fn() primitive. That's the bug.

The .call() method on Fn is special because it does modify the Wren call stack and the C code for that primitive directly calls wrenCallFunction(). When that happens, the correct IP for the current function, which lives only in runInterpreter()'s local variable gets discarded and you're left with a stale IP in the CallFrame.

Giving the function call primitives a different method type and having the case for that method type call STORE_FRAME() before invoking the primitive fixes the bug.
2020-09-18 12:32:43 -07:00
f769599bc6 docs; fix random module docs missing a closing tag (thanks @totallyRonja) 2020-08-29 12:03:46 -07:00
039150efeb docs; fix example formatting 2020-07-30 09:24:23 -07:00
81bfbfce23 fix issue with docs clearing the writeFn from the config. add complete embedding example to docs + repo 2020-07-30 09:09:15 -07:00
dead8df82e GC debug times are easier to reason about when printed in milliseconds, rather than seconds 2020-07-18 20:50:26 -07:00
b279e51fd1 Allow computed goto when using clang on Windows
Clang defines _MSC_VER for compatibility with MSVC, but that can often create problems for code that assumes MSVC only.
2020-07-18 20:41:16 -07:00
286162365a update wip 0.4.0 changelog 2020-07-18 20:26:23 -07:00
5b0f8740f2 Revert "Extended test for Random.sample to cover both branches (#715)"
This reverts commit f81cb5d23c.
2020-07-18 20:10:22 -07:00
f81cb5d23c Extended test for Random.sample to cover both branches (#715)
I've verified that this test fails without the fix in change
186a8c7c13.

See issue #713
2020-07-14 20:15:12 -07:00
54b4c233b9 test: Fix some tests so they fail on expected error, not on syntax error. (#779) 2020-07-14 20:14:08 -07:00
58611240e7 Remove magic values as exit codes in test application (#777) 2020-07-11 13:30:43 -07:00
a3f5b3d98f wren/vm: Allow wrenInterpret to call foreign function (complement 344d343 at fixing #730). (#764) 2020-07-11 13:05:22 -07:00
da091e250c set WREN_MAX_TEMP_ROOTS default to 8 instead of 5
that's 64 bytes, fits nicely in a cache line and isn't _as_ arbitrary.
2020-07-11 11:34:35 -07:00
2ce421eac5 use push root instead of a handle for module GC protection
related to d432b03d62
2020-07-11 11:30:52 -07:00
d432b03d62 fix many module imports causing GC to pull the rug on our module instance 2020-07-10 20:00:17 -07:00
433fbc4019 core; num; add exp & log2
I've had a couple use cases in time that the code is significantly clearer with these, and makes porting less error prone
2020-07-10 19:38:45 -07:00
0e8d56f874 add a note about switched goto for future reference 2020-07-10 19:15:21 -07:00
28ad8aa9e0 compiler; fix incorrect byte length for CODE_IMPORT_VARIABLE
this can lead to some REALLY fun debugging because various code bytes/instructions get skipped, leading to wrong inputs into wrong opcodes and all sorts 💯
2020-07-10 19:14:54 -07:00
b3d496ea36 compiler; rename getNumArguments to be clearer as to the intent 2020-07-10 19:13:44 -07:00
8be40ec14e runFile: Free file source before exiting the function (#774)
This prevents a memleak, noticeable when running `wren_test` under
`valgrind`. For example, the following command would leak

`./bin/wren_test_d any_example.wren`
2020-07-09 11:54:21 -07:00
1623654465 docs; fix embedding tutorial missing a write function so nothing shows up when learning. 2020-06-30 07:40:12 -07:00
e539279121 docs; fix // comments properly this time
...
2020-06-17 09:48:12 -07:00
7651459dfb add // comments to code highlighting, but not with errors in the code 2020-06-17 09:36:34 -07:00
d02903b7d0 add // comments to code highlighting 2020-06-17 09:20:15 -07:00
88043a7cb9 Change wren to wren_cli (#765) 2020-06-17 07:57:03 -07:00
b59c060ccd Small tweaks to error handling. (#762)
* wren/primitive: Remove duplicated declaration introduced in 9f64c05fa.
* wren/primitive: Allow RETURN_ERROR_FMT to have any number of arguments.
* wren/vm: Remove extra validateApiSlot in wrenGetVariable.
(The slot validation is guaranted by setSlot later in the function.)
* wren/primitive: Use RETURN_ERROR_FMT in validateFn.
2020-06-15 11:31:29 -07:00
30b2ebd3f7 fix util/generate_project.py
- fix premake args being incorrect
- remove platform assumptions, making it portable
- start with a best guess based on project layout
- use fallback if not specified or not found
- display errors/help if not found
2020-06-14 19:44:58 -07:00
de6a312868 Functions for operating on Maps from C (#725)
new API functions for maps:
wrenSetSlotNewMap
wrenGetMapCount
wrenGetMapContainsKey
wrenGetMapValue
wrenSetMapValue
wrenRemoveMapValue
2020-06-14 14:45:23 -07:00
344d3432b3 Fix slot array corrupted by wrenInterpret() (#730) 2020-06-13 21:42:06 -07:00
7983082b71 Support positive sign in scientific notation (#706)
* Support positive sign in scientific notation
* Add exponent with positive sign to docs
2020-06-13 21:37:30 -07:00
f3493d0499 Optimize Random.sample(_, _) for performance (#716)
* Optimize Random.sample(_, _) for performance
* Make tests treat random samples as unordered
* Test all sample sizes possible
* Tweak random sampling algorithm for performance
2020-06-13 21:31:23 -07:00
fea0dfafa0 Fix typos in wren_value.h (#749)
* Fix typo: "pointing too" -> "pointing to"
* Fix typo: It *is* heap-allocated
2020-06-13 21:25:55 -07:00
f894273f50 Refactor travis script (#754) 2020-06-13 21:25:18 -07:00
909d1c9471 Add script to regenerate projects using premake (#755) 2020-06-13 21:24:55 -07:00
b5894c6ff5 wren: Remove a magic number in the compiler. (#757) 2020-06-13 21:24:37 -07:00
7c357e1b02 Update gitignore (#759)
Add VSCode project folder and macOS specific stuff.
2020-06-13 21:24:16 -07:00
9fb6d02b5c main branch reference fixes 2020-06-12 10:11:49 -07:00
e45a9d0382 removed localhost (#758) 2020-06-09 17:03:36 -07:00
bef4099101 missed nitpick 2020-06-08 12:31:03 -07:00
9f64c05fa8 Make do blocks in macros consistent with the rest of the code 2020-06-08 12:28:15 -07:00
26d0194117 wren/vm: Uniformize macros to stick to 80 columns. (#756)
uniform macros
2020-06-08 12:23:15 -07:00
2c2f5936eb tests; warn against missing wren_test binary correctly 2020-06-06 10:38:43 -07:00
b694b2231c docs; fix blog template missing links and wrong paths 2020-06-06 10:32:44 -07:00
6cfe6dd6de tests; warn against missing wren_test binary instead of a loud error 2020-06-06 10:29:35 -07:00
8341f61cdb docs; nitpicks 2020-06-05 20:11:32 -07:00
186 changed files with 4464 additions and 888 deletions

6
.gitignore vendored
View File

@ -16,6 +16,9 @@ Release/
# The baseline file is machine-specific
/test/benchmark/baseline.txt
# VSCode project files.
.vscode
# XCode user-specific stuff
xcuserdata/
@ -33,3 +36,6 @@ ipch/
*.user
*.userosscache
*.sln.docstates
# macOS
.DS_Store

View File

@ -3,28 +3,23 @@ set -e
# This build script only builds mac or linux right now, for CI.
WREN_WD="projects/make"
if [ -n "$WREN_TARGET_MAC" ]
then
if [ -n "$WREN_TARGET_MAC" ]; then
WREN_WD="projects/make.mac"
fi
WREN_PY="python3"
if [ -n "$WREN_PY_BINARY" ]
then
WREN_PY="$WREN_PY_BINARY"
fi
WREN_PY=${WREN_PY_BINARY:-python3}
echo "using working directory '$WREN_WD' ..."
echo "using python binary '$WREN_PY' ..."
cd "$WREN_WD" && make config=debug_64bit-no-nan-tagging
cd ../../ && $WREN_PY ./util/test.py --suffix=_d
make -C $WREN_WD config=debug_64bit-no-nan-tagging
$WREN_PY ./util/test.py --suffix=_d
cd "$WREN_WD" && make config=debug_64bit
cd ../../ && $WREN_PY ./util/test.py --suffix=_d
make -C $WREN_WD config=debug_64bit
$WREN_PY ./util/test.py --suffix=_d
cd "$WREN_WD" && make config=release_64bit-no-nan-tagging
cd ../../ && $WREN_PY ./util/test.py
make -C $WREN_WD config=release_64bit-no-nan-tagging
$WREN_PY ./util/test.py
cd "$WREN_WD" && make config=release_64bit
cd ../../ && $WREN_PY ./util/test.py
make -C $WREN_WD config=release_64bit
$WREN_PY ./util/test.py

View File

@ -16,8 +16,8 @@ jobs:
- stage: deploy
script: ./util/deploy_docs_from_travis.sh
# Only deploy commits that land on master.
if: branch = master and type = push
# Only deploy commits that land on main.
if: branch = main and type = push
# Travis VMs are 64-bit but we compile both for 32 and 64 bit. To enable the
# 32-bit builds to work, we need gcc-multilib.

View File

@ -25,4 +25,8 @@ Michal Kozakiewicz <michalkozakiewicz3@gmail.com>
Charlotte Koch <cfkoch@edgebsd.org>
Michel Hermier <michel.hermier@gmail.com>
Taylor Hoff <primdevs@gmail.com>
ruby0x1 <ruby0x1@pm.me>
ruby0x1 <ruby0x1@pm.me>
Kolja Kube <code@koljaku.be>
Alexander Klingenbeck <alexander.klingenbeck@gmx.de>
Aviv Beeri <avbeeri@gmail.com>

View File

@ -1,3 +1,54 @@
## 0.4.0
### Language
- Add `continue` keyword
- Add `as`: `import "..." for Name as OtherName`
- Add Support positive sign in scientific notation
- Add Fiber.try(value) to complement Fiber.call(value)
- Allow `.` to be on a different line (for fluent/builder APIs)
### Modules
- Random: Random.sample optimizations
- List:
- add `list.sort()` and `list.sort {|a, b| ... }` (quicksort)
- add `list.swap(index0, index1)` for swapping elements within a list
- add `list.indexOf(value)` for finding values in a list
- Num:
- add `Num.tau`
- add `Num.nan`
- add `Num.infinity`
- add `min(other)`
- add `max(other)`
- add `clamp(min, max)`
- add `exp`
- add `log2`
### Fixes
- Fix stack corruption related to `Fn` calls
- Fix a byte offset bug in CODE_IMPORT_VARIABLE
- Fix some stack corruptions related to multiple wrenInterpret calls
- Fixed crash when GC collects module during import
- Fix `Bool`, `Num` and `Null` allowing subclassing, which is invalid
### API
- BREAKING: Add `userData` to `wrenReallocateFn`
- BREAKING: Add `WrenLoadModuleResult` which has a `onComplete` callback, allowing freeing module strings
- Add `wrenHasVariable` and `wrenHasModule` queries, for use with `wrenGetVariable`
- Add `wrenSetListElement` to complement `wrenGetListElement`, and allow negative index for both
- Add Map functions to API
- wrenSetSlotNewMap
- wrenGetMapCount
- wrenGetMapContainsKey
- wrenGetMapValue
- wrenSetMapValue
- wrenRemoveMapValue
### Other
- build; add util/generate_docs.py for regenerating project files
- vm; Allow computed goto when using clang on Windows
- vm; WREN_MAX_TEMP_ROOTS default is 8 (instead of 5)
- vm; GC debug times are printed in milliseconds, not seconds
## 0.3.0
0.3.0 is a fairly specific release, aimed at fixing build issues across platforms,
@ -13,7 +64,7 @@ across all platforms out of the box too (including the tests, benchmarks, metric
Like before, there was some things that didn't hold up on Windows or Mac. Fixed!
A lot of work has been done to also clarify the distinction between the CLI project and the VM,
as well as [move the CLI to it's own repo](https://github.com/wren-lang/wren-cli/)!
as well as [move the CLI to its own repo](https://github.com/wren-lang/wren-cli/)!
This removes a lot of code that wasn't being used, and also been clarified the project structure.
Docs have also had a clean up, and a new page to try Wren directly on the doc page was added.
@ -33,7 +84,7 @@ Docs have also had a clean up, and a new page to try Wren directly on the doc pa
## 0.2.0
0.2.0 spans a pretty wide time period with [around 290 commits](https://github.com/wren-lang/wren/compare/0.1.0...master).
0.2.0 spans a pretty wide time period with [around 290 commits](https://github.com/wren-lang/wren/compare/0.1.0...main).
This includes many bug fixes, improvements, clarity in the
code and documentation and so on. There's too many to explicitly list.
Below is the obvious user facing stuff that was easy to spot in the history.

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2013-2020 Robert Nystrom and Wren Contributors
Copyright (c) 2013-2021 Robert Nystrom and Wren Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -44,10 +44,10 @@ If you like the sound of this, [let's get started][started]. You can even try
it [in your browser][browser]! Excited? Well, come on and [get
involved][contribute]!
[![Build Status](https://travis-ci.org/wren-lang/wren.svg)](https://travis-ci.org/wren-lang/wren)
[![Build Status](https://travis-ci.org/wren-lang/wren.svg?branch=main)](https://travis-ci.org/wren-lang/wren)
[syntax]: http://wren.io/syntax.html
[src]: https://github.com/wren-lang/wren/tree/master/src
[src]: https://github.com/wren-lang/wren/tree/main/src
[nan]: https://github.com/wren-lang/wren/blob/93dac9132773c5bc0bbe92df5ccbff14da9d25a6/src/vm/wren_value.h#L486-L541
[perf]: http://wren.io/performance.html
[classes]: http://wren.io/classes.html

View File

@ -410,7 +410,7 @@ import strings will mean something different. We definitely *can* and will make
breaking changes in Wren, so that's OK, but I'd like to minimize the pain. Right
now, Wren is currently at version 0.1.0. I'll probably consider the commit right
before I start landing this to be the "official" 0.1.0 release and then the
import changes will land in "0.2.0". I'll work in a branch off master until
import changes will land in "0.2.0". I'll work in a branch off main until
everything looks solid and then merge it in.
If you have existing Wren code that you run on the CLI and that contains

View File

@ -25,7 +25,7 @@ Wren attracted me as a language because of what it _is_, not because it isn't {_
So, Wren is going to be changing in ways that align with it's existing design intentions. Staying small, simple, learnable and hackable is all vital to what makes Wren valuable, and will remain.
We're just as excited as you are to get to longer term changes and fun tweaks (we have lots of work done already in local projects like [the debugger](https://i.imgur.com/dazexnY.gifv)). There's plenty of ideas we've tried since we've been [using Wren full time](https://luxeengine.com) the last 2.5+ years, and can't wait to get started with bring those into master (and optional modules). There's a lot to do!
We're just as excited as you are to get to longer term changes and fun tweaks (we have lots of work done already in local projects like [the debugger](https://i.imgur.com/dazexnY.gifv)). There's plenty of ideas we've tried since we've been [using Wren full time](https://luxeengine.com) the last 2.5+ years, and can't wait to get started with bring those into the main branch (and optional modules). There's a lot to do!
In the next blog I want to try talk a bit more about the short to medium term goals and roadmap (rather than mixing it here with the meta/hello world post). Be sure to keep an eye out for that one, as it has more juicy details on what we're gonna get up to.

View File

@ -22,7 +22,7 @@ and mark what we managed to get done:
With 0.3.0 we've separated the CLI from the Wren repo,
and updated the docs to make the distinction clearer.
The [CLI now has it's own corner of the docs](../cli), so that the modules
The [CLI now has its own corner of the docs](../cli), so that the modules
and API docs aren't overlapped like before. This opens up space for the
CLI to get better, fuller documentation, and removes confusion about
built in modules vs ones that are in the CLI only.

View File

@ -34,8 +34,14 @@
<nav class="small">
<table>
<tr>
<td><a href="getting-started.html">Getting Started</a></td>
<td><a href="contributing.html">Contributing</a></td>
<td>
<ul>
<li><a href="../getting-started.html">Getting Started</a></li>
<li><a href="../contributing.html">Contributing</a></li>
<li><a href="../blog">Blog</a></li>
<li><a href="../try">Try it!</a></li>
</ul>
</td>
</tr>
</table>
</nav>
@ -51,7 +57,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -267,7 +267,7 @@ inside a method works like this:
So, in the above example, we hit case #2 and it prints "Francis". Distinguishing
self sends from outer variables based on the *case* of the first letter in the
name probably seems crazy but it works surprisingly well. Method names are
name probably seems weird but it works surprisingly well. Method names are
lowercase in Wren. Class names are capitalized.
Most of the time, when you're in a method and want to access a name from outside
@ -347,6 +347,20 @@ overloaded by [arity](#signature). A constructor *must* be a named method with
a (possibly empty) argument list. Operators, getters, and setters cannot be
constructors.
A constructor returns the instance of the class being created, even if you
don't explicitly use `return`. It is valid to use `return` inside of a
constructor, but it is an error to have an expression after the return.
That rule applies to `return this` as well, return handles that implicitly inside
a constructor, so just `return` is enough.
<pre class="snippet">
return //> valid, returns 'this'
return variable //> invalid
return null //> invalid
return this //> also invalid
</pre>
A constructor is actually a pair of methods. You get a method on the class:
<pre class="snippet">
@ -621,6 +635,131 @@ class Derived is Base {
}
</pre>
## Attributes
<small>**experimental stage**: subject to minor changes</small>
A class and methods within a class can be tagged with 'meta attributes'.
Like this:
<pre class="snippet">
#hidden = true
class Example {}
</pre>
These attributes are metadata, they give you a way to annotate and store
any additional information about a class, which you can optionally access at runtime.
This information can also be used by external tools, to provide additional
hints and information from code to the tool.
<small>
Since this feature has just been introduced, **take note**.
**Currently** there are no attributes with a built-in meaning.
Attributes are user-defined metadata. This may not remain
true as some may become well defined through convention or potentially
through use by Wren itself.
</small>
Attributes are placed before a class or method definition,
and use the `#` hash/pound symbol.
They can be
- a `#key` on it's own
- a `#key = value`
- a `#group(with, multiple = true, keys = "value")`
An attribute _key_ can only be a `Name`. This is the same type of name
as a method name, a class name or variable name, an identifier that matches
the Wren identifier rules. A name results in a String value at runtime.
An attribute _value_ can be any of these literal values: `Name, String, Bool, Num`.
Values cannot contain expressions, just a value, there is no compile time
evaluation.
Groups can span multiple lines, methods have their own attributes, and duplicate
keys are valid.
<pre class="snippet">
#key
#key = value
#group(
multiple,
lines = true,
lines = 0
)
class Example {
#test(skip = true, iterations = 32)
doStuff() {}
}
</pre>
### Accessing attributes at runtime
By default, attributes are compiled out and ignored.
For an attribute to be visible at runtime, mark it for runtime
access using an exclamation:
<pre class="snippet">
#doc = "not runtime data"
#!runtimeAccess = true
#!maxIterations = 16
</pre>
Attributes at runtime are stored on the class. You can access them via
`YourClass.attributes`. The `attributes` field on a class will
be null if a class has no attributes or if it's attributes aren't marked.
If the class contains class or method attributes, it will be an object with
two getters:
- `YourClass.attributes.self` for the class attributes
- `YourClass.attributes.methods` for the method attributes
Attributes are stored by group in a regular Wren Map.
Keys that are not grouped, use `null` as the group key.
Values are stored in a list, since duplicate keys are allowed, multiple
values need to be stored. They're stored in order of definition.
Method attributes are stored in a map by method signature, and each method
has it's own attributes that match the above structure. The method signature
is prefixed by `static` or `foreign static` as needed.
Let's see what that looks like:
<pre class="snippet">
// Example.attributes.self =
// {
// null: { "key":[null] },
// group: { "key":[value, 32, false] }
// }
#!key
#ignored //compiled out
#!group(key=value, key=32, key=false)
class Example {
#!getter
getter {}
// { regular(_,_): { regular:[null] } }
#!regular
regular(arg0, arg1) {}
// { static other(): { isStatic:[true] } }
#!isStatic = true
static other()
// { foreign static example(): { isForeignStatic:[32] } }
#!isForeignStatic=32
foreign static example()
}
</pre>
<br><hr>
<a class="right" href="concurrency.html">Concurrency &rarr;</a>
<a href="functions.html">&larr; Functions</a>

View File

@ -14,8 +14,8 @@ That includes the built in language features, the standard library and the VM it
In order to access files, networks and other IO, you'd need to make a tool _using_ the language VM.
That's what the CLI project is! It is not bundled as part of the wren project,
instead it is it's own project as a standalone tool you can run.
It exposes it's own standard library and modules that may be of interest
instead it is its own project as a standalone tool you can run.
It exposes its own standard library and modules that may be of interest
if looking for a general purpose single binary scriptable tool.
Wren CLI is a work in progress, and contributions are welcome to make it more useful over time.

View File

@ -1,6 +1,6 @@
^title CLI Modules
The Wren CLI executable extends the built in language modules with it's own,
The Wren CLI executable extends the built in language modules with its own,
which offer access to IO and other facilities for scripting.
The CLI modules are deeply tied to [libuv][], each other, and other internals

View File

@ -80,7 +80,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -72,7 +72,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -1,5 +1,7 @@
^title Module "scheduler"
**TODO**
This module provides a vehicle to allow other operations to be performed asynchronously whilst waiting for the main operation to be completed.
It contains a single class:
* [Scheduler](scheduler.html)

View File

@ -1,7 +1,27 @@
^title Scheduler Class
**TODO**
The Scheduler class maintains a list of fibers, to be started one after the other, when a signal to do so is received. The signal (a private method call) is typically transmitted by _long running_ methods in the File or Timer classes which suspend the current fiber so that Wren can carry out other tasks in the meantime.
## Methods
## Static Method
**TODO**
### Scheduler.**add**(callable)
Adds a new fiber to the scheduler's fibers list. This fiber calls `callable` and then transfers to the next fiber in the list, if there is one.
`callable` is a function or other object which has a call() method.
<pre class="snippet">
var a = 3
Scheduler.add {
a = a * a
}
Scheduler.add {
a = a + 1
}
System.print(a) // still 3
Timer.sleep(3000) // wait 3 seconds
System.print(a) // now 3 * 3 + 1 = 10
</pre>

View File

@ -70,7 +70,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -85,7 +85,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -1,5 +1,7 @@
^title Module "timer"
**TODO**
This module provides a mechanism to suspend the current fiber for a given period of time either as a simple delay or to allow other operations to be performed asynchronously in the meantime.
It contains a single class:
* [Timer](timer.html)

View File

@ -70,7 +70,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -1,7 +1,12 @@
^title Timer Class
**TODO**
## Static Method
## Methods
### Timer.**sleep**(milliseconds)
Suspends the current fiber for the given number of milliseconds. It is a runtime error if this is not a non-negative number.
This method is often used in conjunction with the Scheduler class which runs any scheduled tasks in separate fibers whilst the current fiber is sleeping.
Note that this method also suspends the System.clock method which will not give the correct running time for a program as a result.
**TODO**

View File

@ -79,7 +79,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -7,7 +7,7 @@ You can [download a build for your OS from the releases page](https://github.com
### Interactive mode
If you just run `wren` without any arguments, it starts the interpreter in
If you just run `wren_cli` without any arguments, it starts the interpreter in
interactive mode, where you can type in a line of code, and it immediately executes
it. You can exit the interpreter using good old Ctrl-C or Ctrl-D.
@ -26,7 +26,7 @@ for (i in 1..10) System.print("Counting up %(i)")
### Running scripts
The standalone interpreter can also load scripts from files and run them. Just
pass the name of the script to `wren`. Create a file named "my_script.wren" in
pass the name of the script to `wren_cli`. Create a file named "my_script.wren" in
your favorite text editor and paste this into it:
<pre class="snippet">
@ -52,4 +52,4 @@ for (yPixel in 0...24) {
Now run:
$ ./wren my_script.wren
$ ./wren_cli my_script.wren

View File

@ -88,8 +88,7 @@ The basic process is simple:
If there are no failures, you're good to go.
2. **[Fork the repo][fork] so you can change it locally.** Please make your
changes in separate [feature branches][] to make things a little easier on
me.
changes in separate [feature branches][] to make things a little easier.
3. **Change the code.** Please follow the style of the surrounding code. That
basically means `camelCase` names, `{` on the next line, keep within 80
@ -105,8 +104,7 @@ The basic process is simple:
6. **Add your name and email to the [AUTHORS][] file if you haven't already.**
7. **Send a [pull request][].** Pat yourself on the back for contributing to a
fun open source project! I'll take it from here and hopefully we'll get it
landed smoothly.
fun open source project!
## Getting help
@ -118,11 +116,11 @@ on the [discord community][discord] (or the [mailing list][list]). If you're a R
[mit]: http://opensource.org/licenses/MIT
[github]: https://github.com/wren-lang/
[fork]: https://help.github.com/articles/fork-a-repo/
[docs]: https://github.com/wren-lang/wren/tree/master/doc/site
[docs]: https://github.com/wren-lang/wren/tree/main/doc/site
[issue]: https://github.com/wren-lang/wren/issues
[proposal]: https://github.com/wren-lang/wren/labels/proposal
[feature branches]: https://www.atlassian.com/git/tutorials/comparing-workflows/centralized-workflow
[authors]: https://github.com/wren-lang/wren/tree/master/AUTHORS
[authors]: https://github.com/wren-lang/wren/tree/main/AUTHORS
[pull request]: https://github.com/wren-lang/wren/pulls
[list]: https://groups.google.com/forum/#!forum/wren-lang
[subreddit]: https://www.reddit.com/r/wren_lang/

View File

@ -65,7 +65,7 @@ if (ready) {
Unlike most other [operators][] in Wren which are just a special syntax for
[method calls][], the `&&` and `||` operators are special. This is because they
only conditionally evaluate right operand&mdash;they short-circuit.
only conditionally evaluate the right operand&mdash;they short-circuit.
[operators]: method-calls.html#operators
[method calls]: method-calls.html
@ -92,7 +92,7 @@ System.print(1 || 2) //> 1
Also known as the "ternary" operator since it takes three arguments, Wren has
the little "if statement in the form of an expression" you know and love from C
and its brethren.
and similar languages.
<pre class="snippet">
System.print(1 != 2 ? "math is sane" : "math is not sane!")
@ -177,6 +177,21 @@ for (i in [1, 2, 3, 4]) {
} //> 3
</pre>
## Continue statements
During the execution of a loop body, you might decide that you want to skip the
rest of this iteration and move on to the next one. You can use a `continue`
statement to do that. It's just the `continue` keyword all by itself. Execution
will immediately jump to the beginning of the next loop iteration (and check the
loop conditions).
<pre class="snippet">
for (i in [1, 2, 3, 4]) {
System.print(i) //> 1
if (i == 2) continue //> 3
} //> 4
</pre>
## Numeric ranges
Lists are one common use for `for` loops, but sometimes you want to walk over a

View File

@ -47,22 +47,53 @@ for a module.
The signature of this function is:
<pre class="snippet" data-lang="c">
char* loadModule(WrenVM* vm, const char* name)
WrenLoadModuleResult loadModule(WrenVM* vm, const char* name)
</pre>
When a module is imported, Wren calls this and passes in the module's name. The
host should return the source code for that module. Memory for the source should
be allocated using the same allocator that the VM uses for other allocation (see
below). Wren will take ownership of the returned string and free it later.
host should return the source code for that module in a `WrenLoadModuleResult` struct.
<pre class="snippet" data-lang="c">
WrenLoadModuleResult myLoadModule(WrenVM* vm, const char* name) {
WrenLoadModuleResult result = {0};
result.source = getSourceForModule(name);
return result;
}
</pre>
The module loader is only be called once for any given module name. Wren caches
the result internally so subsequent imports of the same module use the
previously loaded code.
If your host application isn't able to load a module with some name, it should
return `NULL` and Wren will report that as a runtime error.
make sure the `source` value is `NULL` when returned. Wren will then report that as a runtime error.
If you don't use any `import` statements, you can leave this `NULL`.
If you don't use any `import` statements, you can leave the `loadModuleFn` field in
the configuration set to `NULL` (the default).
Additionally, the `WrenLoadModuleResult` allows us to add a callback for when Wren is
done with the `source`, so we can free the memory if needed.
<pre class="snippet" data-lang="c">
static void loadModuleComplete(WrenVM* vm,
const char* module,
WrenLoadModuleResult result)
{
if(result.source) {
//for example, if we used malloc to allocate
//our source string, we use free to release it.
free((void*)result.source);
}
}
WrenLoadModuleResult myLoadModule(WrenVM* vm, const char* name) {
WrenLoadModuleResult result = {0};
result.onComplete = loadModuleComplete;
result.source = getSourceForModule(name);
return result;
}
</pre>
### **`bindForeignMethodFn`**
@ -151,7 +182,7 @@ These fields control how the VM allocates and manages memory.
This lets you provide a custom memory allocation function. Its signature is:
<pre class="snippet" data-lang="c">
void* reallocate(void* memory, size_t newSize)
void* reallocate(void* memory, size_t newSize, void* userData)
</pre>
Wren uses this one function to allocate, grow, shrink, and deallocate memory.

View File

@ -79,7 +79,7 @@ There are two (well, three) ways to get the Wren VM into your program:
In either case, you also want to add `src/include` to your include path so you
can find the [public header for Wren][wren.h]:
[wren.h]: https://github.com/wren-lang/wren/blob/master/src/include/wren.h
[wren.h]: https://github.com/wren-lang/wren/blob/main/src/include/wren.h
<pre class="snippet" data-lang="c">
#include "wren.h"
@ -102,13 +102,35 @@ this header handles the differences in calling conventions between C and C++:
## Creating a Wren VM
Once you've integrated the code into your executable, you need to create a
virtual machine. To do that, you create a
virtual machine. To do that, you create a `WrenConfiguration` object and
initialize it.
<pre class="snippet" data-lang="c">
WrenConfiguration config;
wrenInitConfiguration(&config);
</pre>
This gives you a basic configuration that has reasonable defaults for
everything. If you don't need to tweak stuff, you can leave it at that. We'll
[learn more][configuration] about what you can configure later.
everything. We'll [learn more][configuration] about what you can configure later,
but for now we'll just add the `writeFn`, so that we can print text.
First we need a function that will do something with the output
that Wren sends us from `System.print` (or `System.write`). *Note that it doesn't
include a newline in the output.*
<pre class="snippet" data-lang="c">
void writeFn(WrenVM* vm, const char* text) {
printf("%s", text);
}
</pre>
And then, we update the configuration to point to it.
<pre class="snippet" data-lang="c">
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
</pre>
[configuration]: configuring-the-vm.html
@ -178,6 +200,71 @@ objects when you call this. This makes sure you haven't lost track of any of
them (which leaks memory) and you don't try to use any of them after the VM has
been freed.
## A complete example
Below is a complete example of the above.
You can find this file in the [example](https://github.com/wren-lang/wren/blob/main/example/embedding/main.c) folder.
<pre class="snippet" data-lang="c">
//For more details, visit https://wren.io/embedding/
#include <stdio.h>
#include "wren.h"
static void writeFn(WrenVM* vm, const char* text)
{
printf("%s", text);
}
void errorFn(WrenVM* vm, WrenErrorType errorType,
const char* module, const int line,
const char* msg)
{
switch (errorType)
{
case WREN_ERROR_COMPILE:
{
printf("[%s line %d] [Error] %s\n", module, line, msg);
} break;
case WREN_ERROR_STACK_TRACE:
{
printf("[%s line %d] in %s\n", module, line, msg);
} break;
case WREN_ERROR_RUNTIME:
{
printf("[Runtime Error] %s\n", msg);
} break;
}
}
int main()
{
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
config.errorFn = &errorFn;
WrenVM* vm = wrenNewVM(&config);
const char* module = "main";
const char* script = "System.print(\"I am running in a VM!\")";
WrenInterpretResult result = wrenInterpret(vm, module, script);
switch (result) {
case WREN_RESULT_COMPILE_ERROR:
{ printf("Compile Error!\n"); } break;
case WREN_RESULT_RUNTIME_ERROR:
{ printf("Runtime Error!\n"); } break;
case WREN_RESULT_SUCCESS:
{ printf("Success!\n"); } break;
}
wrenFreeVM(vm);
}
</pre>
[handle]: slots-and-handles.html#handles
Next, we'll learn to make that VM do useful stuff...

View File

@ -12,7 +12,7 @@ written in C. It produces real Wren objects that you can pass around, do `is`
checks on, etc. But it also wraps a blob of raw memory that is opaque to Wren
but accessible from C.
[foreign methods]: http://localhost:8000/embedding/calling-c-from-wren.html
[foreign methods]: calling-c-from-wren.html
## Defining a Foreign Class

View File

@ -80,7 +80,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -1,129 +1,43 @@
^title Functions
No self-respecting language today can get by without functions&mdash;first
class little bundles of code. Since Wren is object-oriented, most of your code
will live in methods on classes, but free-floating functions are still
eminently handy.
Like many languages today, functions in Wren are little bundles of code
you can store in a variable, or pass as an argument to a method.
Notice there's a difference between _function_ and _method_.
Since Wren is object-oriented, most of your code will live in methods on
classes, but free-floating functions are still eminently handy.
Functions are objects like everything else in Wren, instances of the `Fn`
class.
## Block arguments
## Creating a function
Most of the time you create a function just to pass it to some method. For
example, if you want to filter a [list](lists.html) by some criteria, you'll
call its `where` method, passing in a function that defines the predicate
you're filtering on.
Since that's the most common usage pattern, Wren's syntax optimizes for that.
Taking a page from Ruby, a function is created by passing a *block argument* to
a method. At its simplest, it looks like this:
To create a function, we call `Fn.new`, which takes a block to execute.
To call the function, we use `.call()` on the function instance.
<pre class="snippet">
blondie.callMe {
System.print("This is the body!")
}
var sayHello = Fn.new { System.print("hello") }
sayHello.call() //> hello
</pre>
Here we're invoking the `callMe` method on `blondie`. We're passing one
argument, a function whose body is the
following [block](syntax.html#blocks)&mdash;everything between that pair of
curly braces.
Methods that take a block argument receive it as a normal parameter. `callMe`
could be defined like so:
<pre class="snippet">
class Blondie {
callMe(fn) {
// Call it...
}
}
var blondie = Blondie.new()
</pre>
A method can take other arguments in addition to the block. They appear before
the block just like a regular argument list. For example:
<pre class="snippet">
blondie.callMeAt(867, 5309) {
System.print("This is the body!")
}
</pre>
Of course, you don't *have* to use a block argument to pass a function to a
method. If you already have a function object, you can pass it like a regular
argument:
<pre class="snippet">
var someFn = // Get a function...
blondie.callMe(someFn)
</pre>
Block arguments are purely sugar for creating a function and passing it in one
little blob of syntax. There are some times when you want to create a function
but *don't* need to pass it to a method. For that, you can call the `Fn`
class's constructor:
<pre class="snippet">
var someFn = Fn.new {
System.print("Hi!")
}
</pre>
As you can see it takes a block argument too! All the constructor does it
return that, so this exists purely as a convenience method for you.
## Calling functions
Once you have a function, how do you invoke it? Like everything in Wren, you do
so by calling a method on it:
<pre class="snippet">
class Blondie {
callMe(fn) {
fn.call()
}
}
</pre>
Functions expose a `call()` method that executes the body of the function. This
method is dynamically-dispatched like any other, so you can define your own
"function-like" classes and pass them to methods that expect "real" functions.
<pre class="snippet">
class FakeFn {
call() {
System.print("I'm feeling functional!")
}
}
blondie.callMe(FakeFn.new())
</pre>
Note that we'll see a shorthand syntax for creating a function below.
## Function parameters
Of course, functions aren't very useful if you can't pass values to them. The
functions that we've seen so far take no arguments. To change that, you can
provide a parameter list surrounded by `|` immediately after the opening brace
of the body, like so:
function above takes no arguments. To change that, you can provide a parameter
list surrounded by `|` immediately after the opening brace of the body.
To pass arguments to the function, pass them to the `call` method:
<pre class="snippet">
blondie.callMe {|first, last|
System.print("Hi, " + first + " " + last + "!")
var sayMessage = Fn.new {|recipient, message|
System.print("message for %(recipient): %(message)")
}
</pre>
Here we're passing a function to `callMe` that takes two parameters, `first` and
`last`. They are passed to the function when it's called:
<pre class="snippet">
class Blondie {
callMe(fn) {
fn.call("Debbie", "Harry")
}
}
sayMessage.call("Bob", "Good day!")
</pre>
It's an error to call a function with fewer arguments than its parameter list
@ -148,6 +62,14 @@ Fn.new {
}
</pre>
The return value is handed back to you when using `call`:
<pre class="snippet">
var fn = Fn.new { "some value" }
var result = fn.call()
System.print(result) //> some value
</pre>
## Closures
As you expect, functions are closures&mdash;they can access variables defined
@ -175,6 +97,146 @@ System.print(counter.call()) //> 2
System.print(counter.call()) //> 3
</pre>
## Callable classes
Because `Fn` is a class, and responds to `call()`, any class can respond to
`call()` and be used in place of a function. This is particularly handy when
the function is passed to a method to be called, like a callback or event.
<pre class="snippet">
class Callable {
construct new() {}
call(name, version) {
System.print("called %(name) with version %(version)")
}
}
var fn = Callable.new()
fn.call("wren", "0.4.0")
</pre>
## Block arguments
Very frequently, functions are passed to methods to be called. There are
countless examples of this in Wren, like [list](lists.html) can be filtered
using a method `where` which accepts a function:
<pre class="snippet">
var list = [1, 2, 3, 4, 5]
var filtered = list.where(Fn.new {|value| value > 3 })
System.print(filtered.toList) //> [4, 5]
</pre>
This syntax is a bit less fun to read and write, so Wren implements the
_block argument_ concept. When a function is being passed to a method,
and is the last argument to the method, it can use a shorter syntax:
_just the block part_.
Let's use a block argument for `list.where`, it's the last (only) argument:
<pre class="snippet">
var list = [1, 2, 3, 4, 5]
var filtered = list.where {|value| value > 3 }
System.print(filtered.toList) //> [4, 5]
</pre>
We've seen this before in a previous page using `map` and `where`:
<pre class="snippet">
numbers.map {|n| n * 2 }.where {|n| n < 100 }
</pre>
## Block argument example
Let's look at a complete example, so we can see both ends.
Here's a fictional class for something that will call a function
when a click event is sent to it. It allows us to pass just a
function and assume the left mouse button, or to pass a button and a function.
<pre class="snippet">
class Clickable {
construct new() {
_fn = null
_button = 0
}
onClick(fn) {
_fn = fn
}
onClick(button, fn) {
_button = button
_fn = fn
}
fireEvent(button) {
if(_fn && button == _button) {
_fn.call(button)
}
}
}
</pre>
Now that we've got the clickable class, let's use it.
We'll start by using the method that accepts just a function
because we're fine with it just being the default left mouse button.
<pre class="snippet">
var link = Clickable.new()
link.onClick {|button|
System.print("I was clicked by button %(button)")
}
// send a left mouse click
// normally this would happen from elsewhere
link.fireEvent(0) //> I was clicked by button 0
</pre>
Now let's try with the extra button argument:
<pre class="snippet">
var contextMenu = Clickable.new()
contextMenu.onClick(1) {|button|
System.print("I was right-clicked")
}
link.fireEvent(0) //> (nothing happened)
link.fireEvent(1) //> I was right-clicked
</pre>
Notice that we still pass the other arguments normally,
it's only the last argument that is special.
**Just a regular function**
Block arguments are purely syntax sugar for creating a function and passing it
in one little blob of syntax. These two are equivalent:
<pre class="snippet">
onClick(Fn.new { System.print("clicked") })
onClick { System.print("clicked") }
</pre>
And this is just as valid:
<pre class="snippet">
var onEvent = Fn.new {|button|
System.print("clicked by button %(button)")
}
onClick(onEvent)
onClick(1, onEvent)
</pre>
**Fn.new**
As you may have noticed by now, `Fn` accepts a block argument for the `Fn.new`.
All the constructor does is return that argument right back to you!
<br><hr>
<a class="right" href="classes.html">Classes &rarr;</a>
<a href="variables.html">&larr; Variables</a>

View File

@ -59,7 +59,9 @@ Since it has no dependencies this is simple, all the code in `src/` comes along.
If you want an even simpler way, there's an 'amalgamated' build (often called `blob`, or `unity` builds.).
This is _all of the wren source code in one file_.
This file can be generated by running `python3 util/generate_amalgamation.py`, and the generated output will be in `build/wren.c`.
This file can be generated by running `python3 util/generate_amalgamation.py > build/wren.c`,
which saves the generated output in `build/wren.c`.
Include `build/wren.c` and `src/include/wren.h` in your project code and you're good to go.
<small>Ideally later we can automate generating this and include it in the repo.</small>

View File

@ -51,7 +51,7 @@ If you like the sound of this, [let's get started][started].
Excited? You're also welcome to [get involved][contribute]!
[syntax]: syntax.html
[src]: https://github.com/wren-lang/wren/tree/master/src
[src]: https://github.com/wren-lang/wren/tree/main/src
[nan]: https://github.com/wren-lang/wren/blob/46c1ba92492e9257aba6418403161072d640cb29/src/wren_value.h#L378-L433
[perf]: performance.html
[classes]: classes.html

View File

@ -20,23 +20,23 @@ element you want. Like most languages, indexes start at zero:
[subscript operator]: method-calls.html#subscripts
<pre class="snippet">
var hirsute = ["sideburns", "porkchops", "'stache", "goatee"]
System.print(hirsute[0]) //> sideburns
System.print(hirsute[1]) //> porkchops
var trees = ["cedar", "birch", "oak", "willow"]
System.print(trees[0]) //> cedar
System.print(trees[1]) //> birch
</pre>
Negative indices counts backwards from the end:
<pre class="snippet">
System.print(hirsute[-1]) //> goatee
System.print(hirsute[-2]) //> 'stache
System.print(trees[-1]) //> willow
System.print(trees[-2]) //> oak
</pre>
It's a runtime error to pass an index outside of the bounds of the list. If you
don't know what those bounds are, you can find out using count:
<pre class="snippet">
System.print(hirsute.count) //> 4
System.print(trees.count) //> 4
</pre>
## Slices and ranges
@ -45,7 +45,7 @@ Sometimes you want to copy a chunk of elements from a list. You can do that by
passing a [range](values.html#ranges) to the subscript operator, like so:
<pre class="snippet">
System.print(hirsute[1..2]) //> [porkchops, 'stache]
System.print(trees[1..2]) //> [birch, oak]
</pre>
This returns a new list containing the elements of the original list whose
@ -56,7 +56,7 @@ Negative bounds also work like they do when passing a single number, so to copy
a list, you can just do:
<pre class="snippet">
hirsute[0..-1]
trees[0..-1]
</pre>
## Adding elements
@ -65,22 +65,22 @@ Lists are *mutable*, meaning their contents can be changed. You can swap out an
existing element in the list using the subscript setter:
<pre class="snippet">
hirsute[1] = "muttonchops"
System.print(hirsute[1]) //> muttonchops
trees[1] = "spruce"
System.print(trees[1]) //> spruce
</pre>
It's an error to set an element that's out of bounds. To grow a list, you can
use `add` to append a single item to the end:
<pre class="snippet">
hirsute.add("goatee")
System.print(hirsute.count) //> 5
trees.add("maple")
System.print(trees.count) //> 5
</pre>
You can insert a new element at a specific position using `insert`:
<pre class="snippet">
hirsute.insert(2, "soul patch")
trees.insert(2, "hickory")
</pre>
The first argument is the index to insert at, and the second is the value to
@ -113,26 +113,38 @@ System.print(combined) //> [a, b, c, d, e, f]
## Removing elements
The opposite of `insert` is `removeAt`. It removes a single element from a
given position in the list. All following items are shifted up to fill in the
gap:
given position in the list.
To remove a specific _value_ instead, use `remove`. The first value that
matches using regular equality will be removed.
In both cases, all following items are shifted up to fill in the gap.
<pre class="snippet">
var letters = ["a", "b", "c", "d"]
letters.removeAt(1)
System.print(letters) //> [a, c, d]
letters.remove("a")
System.print(letters) //> [c, d]
</pre>
The `removeAt` method returns the removed item:
Both the `remove` and `removeAt` method return the removed item:
<pre class="snippet">
System.print(letters.removeAt(1)) //> c
</pre>
If `remove` couldn't find the value in the list, it returns null:
<pre class="snippet">
System.print(letters.remove("not found")) //> null
</pre>
If you want to remove everything from the list, you can clear it:
<pre class="snippet">
hirsute.clear()
System.print(hirsute) //> []
trees.clear()
System.print(trees) //> []
</pre>
<br><hr>

View File

@ -9,22 +9,26 @@ curly braces. Each entry is a key and a value separated by a colon:
<pre class="snippet">
{
"George": "Harrison",
"John": "Lennon",
"Paul": "McCartney",
"Ringo": "Starr"
"maple": "Sugar Maple (Acer Saccharum)",
"larch": "Alpine Larch (Larix Lyallii)",
"oak": "Red Oak (Quercus Rubra)",
"fir": "Fraser Fir (Abies Fraseri)"
}
</pre>
This creates a map that associates the first name of each Beatle with his last
name. Syntactically, in a map literal, keys can be any literal, a variable
name, or a parenthesized expression. Values can be any expression. Here, we're
using string literals for both keys and values.
This creates a map that associates a type of tree (key) to a specific
tree within that family (value). Syntactically, in a map literal, keys
can be any literal, a variable name, or a parenthesized expression.
Values can be any expression. Here, we're using string literals for both keys
and values.
*Semantically*, values can be any object, and multiple keys may map to the same
value. Keys have a few limitations. They must be one of the immutable built-in
value.
Keys have a few limitations. They must be one of the immutable built-in
[value types][] in Wren. That means a number, string, range, bool, or `null`.
You can also use a [class object][] as a key.
You can also use a [class object][] as a key (not an instance of that class,
the actual class itself).
[value types]: values.html
[class object]: classes.html
@ -67,12 +71,12 @@ doesn't necessarily mean the key wasn't found.
To tell definitively if a key exists, you can call `containsKey()`:
<pre class="snippet">
var belief = {"nihilism": null}
var capitals = {"Georgia": null}
System.print(belief["nihilism"]) //> null (though key exists)
System.print(belief["solipsism"]) //> null
System.print(belief.containsKey("nihilism")) //> true
System.print(belief.containsKey("solipsism")) //> false
System.print(capitals["Georgia"]) //> null (though key exists)
System.print(capitals["Idaho"]) //> null
System.print(capitals.containsKey("Georgia")) //> true
System.print(capitals.containsKey("Idaho")) //> false
</pre>
You can see how many entries a map contains using `count`:
@ -113,16 +117,38 @@ System.print(capitals.count) //> 0
The subscript operator works well for finding values when you know the key
you're looking for, but sometimes you want to see everything that's in the map.
For that, map exposes two methods: `keys` and `values`.
You can use a regular for loop to iterate the contents, and map exposes two
additional methods to access the contents: `keys` and `values`.
The first returns a [Sequence][] that [iterates][] over all of the keys in the
map, and the second returns one that iterates over the values.
The `keys` method on a map returns a [Sequence][] that [iterates][] over all of
the keys in the map, and the `values` method returns one that iterates over the values.
[sequence]: modules/core/sequence.html
[iterates]: control-flow.html#the-iterator-protocol
If you want to see all of the key-value pairs in a map, the easiest way is to
iterate over the keys and use each to look up its value:
Regardless of how you iterate, the *order* that things are iterated in
isn't defined. Wren makes no promises about what order keys and values are
iterated. All it promises is that every entry will appear exactly once.
**Iterating with for(entry in map)**
When you iterate a map with `for`, you'll be handed an _entry_, which contains
a `key` and a `value` field. That gives you the info for each element in the map.
<pre class="snippet">
var birds = {
"Arizona": "Cactus wren",
"Hawaii": "Nēnē",
"Ohio": "Northern Cardinal"
}
for (bird in birds) {
System.print("The state bird of %(bird.key) is %(bird.value)")
}
</pre>
**Iterating using the keys**
You can also iterate over the keys and use each to look up its value:
<pre class="snippet">
var birds = {
@ -132,15 +158,10 @@ var birds = {
}
for (state in birds.keys) {
System.print("The state bird of " + state + " is " + birds[state])
System.print("The state bird of %(state) is " + birds[state])
}
</pre>
This program prints the three states and their birds. However, the *order*
that they are printed isn't defined. Wren makes no promises about what order
keys and values are iterated in when you use these methods. All it promises is
that every entry will appear exactly once.
<br><hr>
<a class="right" href="method-calls.html">Method Calls &rarr;</a>
<a href="lists.html">&larr; Lists</a>

View File

@ -52,7 +52,9 @@ In a language like Python or JavaScript, these would both call a single `int()`
method, which has some kind of "optional" parameter. The body of the method
figures out how many arguments were passed and uses control flow to handle the
two different behaviors. That means first parameter represents "max unless
another parameter was passed, in which case it's min". Kind of gross.
another parameter was passed, in which case it's min".
This type of 'variadic' code isn't ideal, so Wren doesn't encourage it.
In Wren, these are calls to two entirely separate methods, `int(_,_)` and
`int(_)`. This makes it easier to define "overloads" like this since you don't
@ -163,7 +165,7 @@ like mocks or proxies where you want an object to masquerade as a certain class.
## Subscripts
Another familiar syntax from math class is *subscripting* using square brackets
Another familiar syntax from math is *subscripting* using square brackets
(`[]`). It's handy for working with collection-like objects. For example:
<pre class="snippet">

View File

@ -42,6 +42,17 @@ if (thirsty) {
}
</pre>
If you need to import a variable under a different name, you can use
`import "..." for Name as OtherName`. This looks up the top-level variable
`Name` in *that* module, but declares a variable called `OtherName` in *this* module
with its value.
<pre class="snippet">
import "liquids" for Water //Water is now taken
import "beverages" for Coffee, Water as H2O, Tea
// var water = H2O.new()
</pre>
If you want to load a module, but not bind any variables from it, you can omit
the `for` clause:
@ -81,17 +92,17 @@ WrenVM* vm = wrenNewVM(&config);
That function has this signature:
<pre class="snippet" data-lang="c">
char* WrenLoadModuleFn(WrenVM* vm, const char* name);
WrenLoadModuleResult WrenLoadModuleFn(WrenVM* vm, const char* name);
</pre>
Whenever a module is imported, the VM calls this and passes it the name of the
module. The embedder is expected to return the source code contents of the
module. When you embed Wren in your app, you can handle this however you want:
reach out to the file system, look inside resources bundled into your app,
whatever.
module in a `WrenLoadModuleResult`. When you embed Wren in your app, you can handle
this however you want: reach out to the file system, look inside resources bundled
into your app, whatever.
You can return `NULL` from this function to indicate that a module couldn't be
found. When you do this, Wren will report it as a runtime error.
You can return the source field as `NULL` from this function to indicate that a module
couldn't be found. When you do this, Wren will report it as a runtime error.
### The command-line loader

View File

@ -174,14 +174,93 @@ System.print("Caught error: " + error)
If the called fiber raises an error, it can no longer be used.
### **try**(value)
Tries to run the fiber. If a runtime error occurs
in the called fiber, the error is captured and is returned as a string.
If the fiber is being
started for the first time, and its function takes a parameter, `value` is
passed to it.
<pre class="snippet">
var fiber = Fiber.new {|value|
value.badMethod
}
var error = fiber.try("just a string")
System.print("Caught error: " + error)
</pre>
If the called fiber raises an error, it can no longer be used.
### **transfer**()
**TODO**
Pauses execution of the current running fiber, and transfers control to this fiber.
[Read more][transfers] about the difference between `call` and `transfer`.
Unlike `call`, `transfer` doesn't track the origin of the transfer.
[transfers]: ../../concurrency.html#transferring-control
<pre class="snippet">
// keep hold of the fiber we start in
var main = Fiber.current
// create a new fiber, note it doesn't execute yet!
var fiber = Fiber.new {
System.print("inside 'fiber'") //> #2: from #1
main.transfer() //> #3: go back to 'main'
}
fiber.transfer() //> #1: print "inside 'fiber'" via #2
//> this fiber is now paused by #1
System.print("main") //> #4: prints "main", unpaused by #3
</pre>
### **transfer**(value)
**TODO**
Pauses execution of the current running fiber, and transfers control to this fiber.
Similar to `transfer`, but a value can be passed between the fibers.
<pre class="snippet">
// keep hold of the fiber we start in
var main = Fiber.current
// create a new fiber, note it doesn't execute yet
// also note that we're accepting a 'value' parameter
var fiber = Fiber.new {|value|
System.print("in 'fiber' = %(value)") //> #2: in 'fiber' = 5
var result = main.transfer("hello?") //> #3: send to 'message'
System.print("end 'fiber' = %(result)") //> #6: end 'fiber' = 32
}
var message = fiber.transfer(5) //> #1: send to 'value'
System.print("... %(message)") //> #4: ... hello?
fiber.transfer(32) //> #5: send to 'result'
</pre>
### **transferError**(error)
**TODO**
Transfer to this fiber, but set this fiber into an error state.
The `fiber.error` value will be populated with the value in `error`.
<pre class="snippet">
var A = Fiber.new {
System.print("transferred to A") //> #4
B.transferError("error!") //> #5
}
var B = Fiber.new {
System.print("started B") //> #2
A.transfer() //> #3
System.print("should not get here")
}
B.try() //> #1
System.print(B.error) //> #6: prints "error!" from #5
// B fiber can no longer be used
B.call() //> #7: Cannot call an aborted fiber.
</pre>

View File

@ -12,7 +12,7 @@ An indexable contiguous collection of elements. More details [here][lists].
Creates a new list with `size` elements, all set to `element`.
It is a runtime error if `size` is not a nonnegative integer.
It is a runtime error if `size` is not a non-negative integer.
### List.**new**()
@ -22,7 +22,19 @@ Creates a new empty list. Equivalent to `[]`.
### **add**(item)
Appends `item` to the end of the list.
Appends `item` to the end of the list. Returns the added item.
### **addAll**(other)
Appends each element of `other` in the same order to the end of the list. `other` must be [an iterable](../../control-flow.html#the-iterator-protocol).
<pre class="snippet">
var list = [0, 1, 2, 3, 4]
list.addAll([5, 6])
System.print(list) //> [0, 1, 2, 3, 4, 5, 6]
</pre>
Returns the added items.
### **clear**()
@ -32,6 +44,16 @@ Removes all elements from the list.
The number of elements in the list.
### **indexOf**(value)
Returns the index of `value` in the list, if found. If not found, returns -1.
<pre class="snippet">
var list = [0, 1, 2, 3, 4]
System.print(list.indexOf(3)) //> 3
System.print(list.indexOf(20)) //> -1
</pre>
### **insert**(index, item)
Inserts the `item` at `index` in the list.
@ -74,6 +96,26 @@ list.
[iterator protocol]: ../../control-flow.html#the-iterator-protocol
### **remove**(value)
Removes the first value found in the list that matches the given `value`,
using regular equality to compare them. All trailing elements
are shifted up to fill in where the removed element was.
<pre class="snippet">
var list = ["a", "b", "c", "d"]
list.remove("b")
System.print(list) //> [a, c, d]
</pre>
Returns the removed value, if found.
If the value is not found in the list, returns null.
<pre class="snippet">
System.print(["a", "b", "c"].remove("b")) //> b
System.print(["a", "b", "c"].remove("not found")) //> null
</pre>
### **removeAt**(index)
Removes the element at `index`. If `index` is negative, it counts backwards
@ -94,6 +136,40 @@ System.print(["a", "b", "c"].removeAt(1)) //> b
It is a runtime error if the index is not an integer or is out of bounds.
### **sort**(), **sort**(comparer)
Sorts the elements of a list in-place; altering the list. The default sort is implemented using the quicksort algorithm.
<pre class="snippet">
var list = [4, 1, 3, 2].sort()
System.print(list) //> [1, 2, 3, 4]
</pre>
A comparison function `comparer` can be provided to customise the element sorting. The comparison function must return a boolean value specifying the order in which elements should appear in the list.
The comparison function accepts two arguments `a` and `b`, two values to compare, and must return a boolean indicating the inequality between the arguments. If the function returns true, the first argument `a` will appear before the second `b` in the sorted results.
A compare function like `{|a, b| true }` will always put `a` before `b`. The default compare function is `{|a, b| a < b }`.
<pre class="snippet">
var list = [9, 6, 8, 7]
list.sort {|a, b| a < b}
System.print(list) //> [6, 7, 8, 9]
</pre>
It is a runtime error if `comparer` is not a function.
### **swap**(index0, index1)
Swaps values inside the list around. Puts the value from `index0` in `index1`,
and the value from `index1` at `index0` in the list.
<pre class="snippet">
var list = [0, 1, 2, 3, 4]
list.swap(0, 3)
System.print(list) //> [3, 1, 2, 0, 4]
</pre>
### **[**index**]** operator
Gets the element at `index`. If `index` is negative, it counts backwards from
@ -104,7 +180,17 @@ var list = ["a", "b", "c"]
System.print(list[1]) //> b
</pre>
It is a runtime error if the index is not an integer or is out of bounds.
If `index` is a [Range](range.html), a new list is populated from the elements
in the range.
<pre class="snippet">
var list = ["a", "b", "c"]
System.print(list[0..1]) //> [a, b]
</pre>
You can use `list[0..-1]` to shallow-copy a list.
It is a runtime error if the index is not an integer or range, or is out of bounds.
### **[**index**]=**(item) operator
@ -119,13 +205,23 @@ System.print(list) //> [a, new, c]
It is a runtime error if the index is not an integer or is out of bounds.
## **+**(other) operator
### **+**(other) operator
Appends a list to the end of the list (concatenation). `other` must be a `List`.
Appends a list to the end of the list (concatenation). `other` must be [an iterable](../../control-flow.html#the-iterator-protocol).
<pre class="snippet">
var letters = ["a", "b", "c"]
var other = ["d", "e", "f"]
var combined = letters + other
System.print(combined) //> [a, b, c, d, e, f]
</pre>
</pre>
### **\***(count) operator
Creates a new list by repeating this one ```count``` times. It is a runtime error if ```count``` is not a non-negative integer.
<pre class="snippet">
var digits = [1, 2]
var tripleDigits = digits * 3
System.print(tripleDigits) //> [1, 2, 1, 2, 1, 2]
</pre>

View File

@ -1,7 +1,15 @@
^title Map Class
Extends [Sequence](sequence.html).
An associative collection that maps keys to values. More details [here](../../maps.html).
## Static Method
### Map.**new**()
Creates a new empty map. Equivalent to `{}`.
## Methods
### **clear**()
@ -56,3 +64,22 @@ replaces the previous association.
It is a runtime error if the key is not a [Bool](bool.html),
[Class](class.html), [Null](null.html), [Num](num.html), [Range](range.html),
or [String](string.html).
### **iterate**(iterator), **iteratorValue**(iterator)
Implements the [iterator protocol][] for iterating over the keys and values of a map at the same time.
[iterator protocol]: ../../control-flow.html#the-iterator-protocol
When a map (as opposed to its keys or values separately) is iterated over, each key/value pair is wrapped in a `MapEntry` object. `MapEntry` is a small helper class which has read-only `key` and `value` properties and a familiar `toString` representation.
<pre class="snippet">
var map = {"paul": "mccartney"}
for (entry in map) {
System.print(entry.type) // MapEntry
System.print(entry.key + " " + entry.value) // paul mccartney
System.print(entry) // paul:mccartney
}
</pre>
All map entries will be iterated over, but may be in any order, and may even change between invocations of Wren.

View File

@ -4,7 +4,7 @@
### **!** operator
Returns `true`, since `null` is considered [false](../control-flow.html#truth).
Returns `true`, since `null` is considered [false](../../control-flow.html#truth).
<pre class="snippet">
System.print(!null) //> true

View File

@ -9,10 +9,24 @@ Attempts to parse `value` as a decimal literal and return it as an instance of
It is a runtime error if `value` is not a string.
### Num.**infinity**
The value of &infin;.
### Num.**nan**
One value representing a NaN.
Provides a default NaN number suitable for the vm internal values.
### Num.**pi**
The value of &pi;.
### Num.**tau**
The value of &tau;. This is equivalent to ```2 * Num.pi```.
### Num.**largest**
The largest representable numeric value.
@ -21,6 +35,17 @@ The largest representable numeric value.
The smallest positive representable numeric value.
### Num.**maxSafeInteger**
The largest integer that Wren can safely represent. It's a constant value of `9007199254740991`.
This is relevant because Wren uses double precision [floating-point format](https://en.wikipedia.org/wiki/IEEE_floating_point)
for numbers, which can only safely represent integers between <code>-(2<sup>53</sup> - 1)</code> and <code>2<sup>53</sup> - 1</code>.
### Num.**minSafeInteger**
The smallest integer Wren can safely represent. It's a constant value of `-9007199254740991`.
## Methods
### **abs**
@ -48,6 +73,10 @@ The arc tangent of the number.
The arc tangent of the number when divided by `x`, using the signs of the two
numbers to determine the quadrant of the result.
### **cbrt**
The cube root of the number.
### **ceil**
Rounds the number up to the nearest integer.
@ -70,6 +99,17 @@ System.print(1.5.floor) //> 1
System.print((-3.2).floor) //> -4
</pre>
### **fraction**
The fractional part of a number i.e. the part after any decimal point.
The returned value has the same sign as `this`.
<pre class="snippet">
System.print(1.5.fraction) //> 0.5
System.print((-3.2).fraction) //> -0.2
</pre>
### **isInfinity**
Whether the number is positive or negative infinity or not.
@ -96,7 +136,29 @@ Whether the number is [not a number](http://en.wikipedia.org/wiki/NaN). This is
### **log**
The natural logarithm of the number.
The natural logarithm of the number. Returns `nan` if the base is negative.
### **log2**
The binary (base-2) logarithm of the number. Returns `nan` if the base is negative.
### **exp**
The exponential `e` (Eulers number) raised to the number. This: `eⁿ`.
### **min**(other)
Returns the minimum value when comparing this number and `other`.
### **max**(other)
Returns the maximum value when comparing this number and `other`.
### **clamp**(min, max)
Clamps a number into the range of `min` and `max`. If this number is less than min,
`min` is returned. If bigger than `max`, `max` is returned. Otherwise, the number
itself is returned.
### **pow**(power)
@ -128,6 +190,21 @@ The square root of the number. Returns `nan` if the number is negative.
The tangent of the number.
### **toString**
The string representation of the number.
### **truncate**
Rounds the number to the nearest integer towards zero.
It is therefore equivalent to `floor` if the number is non-negative or `ceil` if it is negative.
<pre class="snippet">
System.print(1.5.truncate) //> 1
System.print((-3.2).truncate) //> -3
</pre>
### **-** operator
Negates the number.
@ -179,6 +256,24 @@ unsigned values. The result is then a 32-bit unsigned number where each bit is
It is a runtime error if `other` is not a number.
### **^**(other) operator
Performs bitwise exclusive or on the number. Both numbers are first converted to 32-bit unsigned values. The result is then a 32-bit unsigned number where each bit is `true` only where the corresponding bits of one (but not both) inputs were `true`. Each bit is therefore `false` if the corresponding bits of both inputs were either both `true` or both `false`.
It is a runtime error if `other` is not a number.
### **&lt;&lt;**(other) operator
Performs a bitwise left shift on the number. Internally, both numbers are first converted to 32-bit unsigned values and C's left shift operator is then applied to them.
It is a runtime error if `other` is not a number.
### **&gt;&gt;**(other) operator
Performs a bitwise right shift on the number. Internally, both numbers are first converted to 32-bit unsigned values and C's right shift operator is then applied to them.
It is a runtime error if `other` is not a number.
### **..**(other) operator
Creates a [Range](range.html) representing a consecutive range of numbers
@ -201,4 +296,4 @@ var range = 1.2...3.4
System.print(range.min) //> 1.2
System.print(range.max) //> 3.4
System.print(range.isInclusive) //> false
</pre>
</pre>

View File

@ -40,6 +40,15 @@ var hPosition = metalBand.indexOf("h")
System.print(metalBand[hPosition]) //> h
</pre>
A string can also be indexed with a [Range](range.html), which will return a
new string as a substring of the original.
<pre class="snippet">
var example = "hello wren"
System.print(example[0...5]) //> hello
System.print(example[-4..-1]) //> wren
</pre>
If you want to work with a string as a sequence numeric code points, call the
`codePoints` getter. It returns a [Sequence](sequence.html) that decodes UTF-8
and iterates over the code points, returning each as a number.
@ -246,6 +255,12 @@ Returns a new string that concatenates this string and `other`.
It is a runtime error if `other` is not a string.
### *****(count) operator
Returns a new string that contains this string repeated `count` times.
It is a runtime error if `count` is not a positive integer.
### **==**(other) operator
Checks if the string is equal to `other`.

View File

@ -48,3 +48,8 @@ System.write(4 + 5) //> 9
In the above example, the result of `4 + 5` is printed, and then the prompt is
printed on the same line because no newline character was printed afterwards.
### System.**writeAll**(sequence)
Iterates over `sequence` and prints each element, but does not print a newline
character afterwards. Each element is converted to a string by calling `toString` on it.

View File

@ -92,7 +92,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -69,7 +69,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -15,6 +15,7 @@ It must be imported from the [random][] module:
<pre class="snippet">
import "random" for Random
</pre>
[random]: ../

View File

@ -69,7 +69,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -98,7 +98,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -92,7 +92,7 @@ languages benchmarked, including Wren, because Mike Pall is a robot from the
future.
The benchmark harness and programs are
[here](https://github.com/wren-lang/wren/tree/master/test/benchmark).
[here](https://github.com/wren-lang/wren/tree/main/test/benchmark).
## Why is Wren fast?

File diff suppressed because one or more lines are too long

View File

@ -607,8 +607,8 @@ var wasmMemory;
// In the wasm backend, we polyfill the WebAssembly object,
// so this creates a (non-native-wasm) table for us.
var wasmTable = new WebAssembly.Table({
'initial': 191,
'maximum': 191 + 0,
'initial': 203,
'maximum': 203 + 0,
'element': 'anyfunc'
});
@ -1229,11 +1229,11 @@ function updateGlobalBufferAndViews(buf) {
}
var STATIC_BASE = 1024,
STACK_BASE = 5271824,
STACK_BASE = 5271568,
STACKTOP = STACK_BASE,
STACK_MAX = 28944,
DYNAMIC_BASE = 5271824,
DYNAMICTOP_PTR = 28784;
STACK_MAX = 28688,
DYNAMIC_BASE = 5271568,
DYNAMICTOP_PTR = 28528;
assert(STACK_BASE % 16 === 0, 'stack must start aligned');
assert(DYNAMIC_BASE % 16 === 0, 'heap must start aligned');
@ -1817,7 +1817,7 @@ var ASM_CONSTS = {
// STATICTOP = STATIC_BASE + 27920;
// STATICTOP = STATIC_BASE + 27664;
/* global initializers */ __ATINIT__.push({ func: function() { ___wasm_call_ctors() } });
@ -1869,12 +1869,36 @@ var ASM_CONSTS = {
abort('stack overflow')
}
function ___setErrNo(value) {
if (Module['___errno_location']) HEAP32[((Module['___errno_location']())>>2)]=value;
else err('failed to set errno from JS');
return value;
function _clock() {
if (_clock.start === undefined) _clock.start = Date.now();
return ((Date.now() - _clock.start) * (1000000 / 1000))|0;
}
function _emscripten_get_sbrk_ptr() {
return 28528;
}
function _emscripten_memcpy_big(dest, src, num) {
HEAPU8.copyWithin(dest, src, src + num);
}
function _emscripten_get_heap_size() {
return HEAPU8.length;
}
function abortOnCannotGrowMemory(requestedSize) {
abort('Cannot enlarge memory arrays to size ' + requestedSize + ' bytes (OOM). Either (1) compile with -s INITIAL_MEMORY=X with X higher than the current value ' + HEAP8.length + ', (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ');
}function _emscripten_resize_heap(requestedSize) {
abortOnCannotGrowMemory(requestedSize);
}
function _exit(status) {
// void _exit(int status);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/exit.html
exit(status);
}
var PATH={splitPath:function(filename) {
@ -1963,62 +1987,11 @@ var ASM_CONSTS = {
if (low >= 0) assert(high === 0);
else assert(high === -1);
return low;
}};function ___sys_fcntl64(fd, cmd, varargs) {SYSCALLS.varargs = varargs;
return 0;
}
function ___sys_ioctl(fd, op, varargs) {SYSCALLS.varargs = varargs;
return 0;
}
function ___sys_open(path, flags, varargs) {SYSCALLS.varargs = varargs;
abort('it should not be possible to operate on streams when !SYSCALLS_REQUIRE_FILESYSTEM')}
function _clock() {
if (_clock.start === undefined) _clock.start = Date.now();
return ((Date.now() - _clock.start) * (1000000 / 1000))|0;
}
function _emscripten_get_sbrk_ptr() {
return 28784;
}
function _emscripten_memcpy_big(dest, src, num) {
HEAPU8.copyWithin(dest, src, src + num);
}
function _emscripten_get_heap_size() {
return HEAPU8.length;
}
function abortOnCannotGrowMemory(requestedSize) {
abort('Cannot enlarge memory arrays to size ' + requestedSize + ' bytes (OOM). Either (1) compile with -s INITIAL_MEMORY=X with X higher than the current value ' + HEAP8.length + ', (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ');
}function _emscripten_resize_heap(requestedSize) {
abortOnCannotGrowMemory(requestedSize);
}
function _exit(status) {
// void _exit(int status);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/exit.html
exit(status);
}
function _fd_close(fd) {
}};function _fd_close(fd) {
abort('it should not be possible to operate on streams when !SYSCALLS_REQUIRE_FILESYSTEM');
return 0;
}
function _fd_read(fd, iov, iovcnt, pnum) {
var stream = SYSCALLS.getStreamFromFD(fd);
var num = SYSCALLS.doReadv(stream, iov, iovcnt);
HEAP32[((pnum)>>2)]=num
return 0;
}
function _fd_seek(fd, offset_low, offset_high, whence, newOffset) {
abort('it should not be possible to operate on streams when !SYSCALLS_REQUIRE_FILESYSTEM')}
@ -2097,7 +2070,7 @@ function intArrayToString(array) {
// ASM_LIBRARY EXTERN PRIMITIVES: Math_floor,Math_ceil
var asmGlobalArg = {};
var asmLibraryArg = { "__handle_stack_overflow": ___handle_stack_overflow, "__sys_fcntl64": ___sys_fcntl64, "__sys_ioctl": ___sys_ioctl, "__sys_open": ___sys_open, "clock": _clock, "emscripten_get_sbrk_ptr": _emscripten_get_sbrk_ptr, "emscripten_memcpy_big": _emscripten_memcpy_big, "emscripten_resize_heap": _emscripten_resize_heap, "exit": _exit, "fd_close": _fd_close, "fd_read": _fd_read, "fd_seek": _fd_seek, "fd_write": _fd_write, "memory": wasmMemory, "round": _round, "setTempRet0": _setTempRet0, "table": wasmTable, "time": _time };
var asmLibraryArg = { "__handle_stack_overflow": ___handle_stack_overflow, "clock": _clock, "emscripten_get_sbrk_ptr": _emscripten_get_sbrk_ptr, "emscripten_memcpy_big": _emscripten_memcpy_big, "emscripten_resize_heap": _emscripten_resize_heap, "exit": _exit, "fd_close": _fd_close, "fd_seek": _fd_seek, "fd_write": _fd_write, "memory": wasmMemory, "round": _round, "setTempRet0": _setTempRet0, "table": wasmTable, "time": _time };
var asm = createWasm();
Module["asm"] = asm;
/** @type {function(...*):?} */
@ -2185,10 +2158,10 @@ var dynCall_vii = Module["dynCall_vii"] = function() {
};
/** @type {function(...*):?} */
var dynCall_iii = Module["dynCall_iii"] = function() {
var dynCall_viii = Module["dynCall_viii"] = function() {
assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');
assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)');
return Module["asm"]["dynCall_iii"].apply(null, arguments)
return Module["asm"]["dynCall_viii"].apply(null, arguments)
};
/** @type {function(...*):?} */
@ -2198,6 +2171,13 @@ var dynCall_iiii = Module["dynCall_iiii"] = function() {
return Module["asm"]["dynCall_iiii"].apply(null, arguments)
};
/** @type {function(...*):?} */
var dynCall_iii = Module["dynCall_iii"] = function() {
assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');
assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)');
return Module["asm"]["dynCall_iii"].apply(null, arguments)
};
/** @type {function(...*):?} */
var dynCall_vi = Module["dynCall_vi"] = function() {
assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');
@ -2205,13 +2185,6 @@ var dynCall_vi = Module["dynCall_vi"] = function() {
return Module["asm"]["dynCall_vi"].apply(null, arguments)
};
/** @type {function(...*):?} */
var dynCall_jiji = Module["dynCall_jiji"] = function() {
assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');
assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)');
return Module["asm"]["dynCall_jiji"].apply(null, arguments)
};
/** @type {function(...*):?} */
var dynCall_ii = Module["dynCall_ii"] = function() {
assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');
@ -2219,6 +2192,13 @@ var dynCall_ii = Module["dynCall_ii"] = function() {
return Module["asm"]["dynCall_ii"].apply(null, arguments)
};
/** @type {function(...*):?} */
var dynCall_jiji = Module["dynCall_jiji"] = function() {
assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');
assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)');
return Module["asm"]["dynCall_jiji"].apply(null, arguments)
};
/** @type {function(...*):?} */
var dynCall_iidiiii = Module["dynCall_iidiiii"] = function() {
assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');

Binary file not shown.

View File

@ -43,7 +43,7 @@ One way to get a quick feel for a language's style is to see what words it
reserves. Here's what Wren has:
<pre class="snippet">
break class construct else false for foreign if import
as break class construct continue else false for foreign if import
in is null return static super this true var while
</pre>
@ -149,7 +149,7 @@ put a newline in there:
</pre>
Using an initial newline after the `{` does feel a little weird or magical, but
newlines are already significant in Wren, so it's not totally crazy. The nice
newlines are already significant in Wren, so it's not totally unreasonable. The nice
thing about this syntax as opposed to something like `=>` is that the *end* of
the block has an explicit delimiter. That helps when chaining:

View File

@ -41,8 +41,8 @@
<li><a href="method-calls.html">Method Calls</a></li>
<li><a href="control-flow.html">Control Flow</a></li>
<li><a href="variables.html">Variables</a></li>
<li><a href="functions.html">Functions</a></li>
<li><a href="classes.html">Classes</a></li>
<li><a href="functions.html">Functions</a></li>
<li><a href="concurrency.html">Concurrency</a></li>
<li><a href="error-handling.html">Error Handling</a></li>
<li><a href="modularity.html">Modularity</a></li>
@ -90,8 +90,8 @@
<td>
<ul>
<li><a href="variables.html">Variables</a></li>
<li><a href="functions.html">Functions</a></li>
<li><a href="classes.html">Classes</a></li>
<li><a href="functions.html">Functions</a></li>
<li><a href="concurrency.html">Concurrency</a></li>
<li><a href="error-handling.html">Error Handling</a></li>
<li><a href="modularity.html">Modularity</a></li>
@ -120,7 +120,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -42,8 +42,8 @@
<li><a href="../method-calls.html">Method Calls</a></li>
<li><a href="../control-flow.html">Control Flow</a></li>
<li><a href="../variables.html">Variables</a></li>
<li><a href="../functions.html">Functions</a></li>
<li><a href="../classes.html">Classes</a></li>
<li><a href="../functions.html">Functions</a></li>
<li><a href="../concurrency.html">Concurrency</a></li>
<li><a href="../error-handling.html">Error Handling</a></li>
<li><a href="../modularity.html">Modularity</a></li>
@ -91,8 +91,8 @@
<td>
<ul>
<li><a href="../variables.html">Variables</a></li>
<li><a href="../functions.html">Functions</a></li>
<li><a href="../classes.html">Classes</a></li>
<li><a href="../functions.html">Functions</a></li>
<li><a href="../concurrency.html">Concurrency</a></li>
<li><a href="../error-handling.html">Error Handling</a></li>
<li><a href="../modularity.html">Modularity</a></li>
@ -121,7 +121,7 @@
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/master/AUTHORS">friends</a>.
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>

View File

@ -19,6 +19,7 @@ Like other scripting languages, Wren has a single numeric type:
double-precision floating point. Number literals look like you expect coming
from other languages:
<pre class="snippet">
0
1234
@ -26,6 +27,10 @@ from other languages:
3.14159
1.0
-12.34
0.0314159e02
0.0314159e+02
314.159e-02
0xcaffe2
</pre>
Numbers are instances of the [Num][] class.
@ -45,6 +50,16 @@ String literals are surrounded in double quotes:
"hi there"
</pre>
They can also span multiple lines:
<pre class="snippet">
"hi
there,
again"
</pre>
### Escaping
A handful of escape characters are supported:
<pre class="snippet">
@ -54,11 +69,23 @@ A handful of escape characters are supported:
"\%" // A percent sign.
"\a" // Alarm beep. (Who uses this?)
"\b" // Backspace.
"\e" // ESC character.
"\f" // Formfeed.
"\n" // Newline.
"\r" // Carriage return.
"\t" // Tab.
"\v" // Vertical tab.
"\x48" // Unencoded byte (2 hex digits)
"\u0041" // Unicode code point (4 hex digits)
"\U0001F64A" // Unicode code point (8 hex digits)
</pre>
A `\x` followed by two hex digits specifies a single unencoded byte:
<pre class="snippet">
System.print("\x48\x69\x2e") //> Hi.
</pre>
A `\u` followed by four hex digits can be used to specify a Unicode code point:
@ -74,12 +101,6 @@ of the basic multilingual plane, like all-important emoji:
System.print("\U0001F64A\U0001F680") //> 🙊🚀
</pre>
A `\x` followed by two hex digits specifies a single unencoded byte:
<pre class="snippet">
System.print("\x48\x69\x2e") //> Hi.
</pre>
Strings are instances of class [String][].
[string]: modules/core/string.html
@ -104,6 +125,63 @@ System.print("wow %((1..3).map {|n| n * n}.join())") //> wow 149
An interpolated expression can even contain a string literal which in turn has
its own nested interpolations, but doing that gets unreadable pretty quickly.
### Raw strings
A string literal can also be created using triple quotes `"""` which is
parsed as a raw string. A raw string is no different
from any other string, it's just parsed in a different way.
**Raw strings do not process escapes and do not apply any interpolation**.
<pre class="snippet">
"""hi there"""
</pre>
When a raw string spans multiple lines and a triple quote is on it's own line,
any whitespace on that line will be ignored. This means the opening and closing
lines are not counted as part of the string when the triple quotes are separate lines,
as long as they only contain whitespace (spaces + tabs).
<pre class="snippet">
"""
Hello world
"""
</pre>
The resulting value in the string above has no newlines or trailing whitespace.
Note the spaces in front of the Hello are preserved.
<pre class="snippet">
Hello world
</pre>
A raw string will be parsed exactly as is in the file, unmodified.
This means it can contain quotes, invalid syntax, other data formats
and so on without being modified by Wren.
<pre class="snippet">
"""
{
"hello": "wren",
"from" : "json"
}
"""
</pre>
One more example, embedding wren code inside a string safely.
<pre class="snippet">
"""
A markdown string with embedded wren code example.
class Example {
construct code() {
//
}
}
"""
</pre>
## Ranges
A range is a little object that represents a consecutive range of numbers. They
@ -127,12 +205,16 @@ This creates a range from four to six *not* including six itself. Ranges are
commonly used for [iterating](control-flow.html#for-statements) over a
sequences of numbers, but are useful in other places too. You can pass them to
a [list](lists.html)'s subscript operator to return a subset of the list, for
example:
example, or on a String, the substring in that range:
<pre class="snippet">
var list = ["a", "b", "c", "d", "e"]
var slice = list[1..3]
System.print(slice) //> [b, c, d]
var string = "hello wren"
var wren = string[-4..-1]
System.print(wren) //> wren
</pre>
Their class is [Range][].

View File

@ -31,7 +31,7 @@ System.print(a) //! "a" doesn't exist anymore.
</pre>
Variables defined at the top level of a script are *top-level* and are visible
to the [module](modules.html) system. All other variables are *local*.
to the [module](modularity.html) system. All other variables are *local*.
Declaring a variable in an inner scope with the same name as an outer one is
called *shadowing* and is not an error (although it's not something you likely
intend to do much).
@ -54,7 +54,7 @@ var a = "again" //! "a" is already declared.
## Assignment
After a variable has been declared, you can assign to it using `=`:
After a variable has been declared, you can assign to it using `=`
<pre class="snippet">
var a = 123

58
example/embedding/main.c Normal file
View File

@ -0,0 +1,58 @@
//For more details, visit https://wren.io/embedding/
#include <stdio.h>
#include "wren.h"
static void writeFn(WrenVM* vm, const char* text)
{
printf("%s", text);
}
void errorFn(WrenVM* vm, WrenErrorType errorType,
const char* module, const int line,
const char* msg)
{
switch (errorType)
{
case WREN_ERROR_COMPILE:
{
printf("[%s line %d] [Error] %s\n", module, line, msg);
} break;
case WREN_ERROR_STACK_TRACE:
{
printf("[%s line %d] in %s\n", module, line, msg);
} break;
case WREN_ERROR_RUNTIME:
{
printf("[Runtime Error] %s\n", msg);
} break;
}
}
int main()
{
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
config.errorFn = &errorFn;
WrenVM* vm = wrenNewVM(&config);
const char* module = "main";
const char* script = "System.print(\"I am running in a VM!\")";
WrenInterpretResult result = wrenInterpret(vm, module, script);
switch (result)
{
case WREN_RESULT_COMPILE_ERROR:
{ printf("Compile Error!\n"); } break;
case WREN_RESULT_RUNTIME_ERROR:
{ printf("Runtime Error!\n"); } break;
case WREN_RESULT_SUCCESS:
{ printf("Success!\n"); } break;
}
wrenFreeVM(vm);
}

View File

@ -121,6 +121,7 @@ OBJECTS += $(OBJDIR)/get_variable.o
OBJECTS += $(OBJDIR)/handle.o
OBJECTS += $(OBJDIR)/lists.o
OBJECTS += $(OBJDIR)/main.o
OBJECTS += $(OBJDIR)/maps.o
OBJECTS += $(OBJDIR)/new_vm.o
OBJECTS += $(OBJDIR)/reset_stack_after_call_abort.o
OBJECTS += $(OBJDIR)/reset_stack_after_foreign_construct.o
@ -219,6 +220,9 @@ $(OBJDIR)/handle.o: ../../test/api/handle.c
$(OBJDIR)/lists.o: ../../test/api/lists.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/maps.o: ../../test/api/maps.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/new_vm.o: ../../test/api/new_vm.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"

View File

@ -129,6 +129,7 @@ OBJECTS += $(OBJDIR)/get_variable.o
OBJECTS += $(OBJDIR)/handle.o
OBJECTS += $(OBJDIR)/lists.o
OBJECTS += $(OBJDIR)/main.o
OBJECTS += $(OBJDIR)/maps.o
OBJECTS += $(OBJDIR)/new_vm.o
OBJECTS += $(OBJDIR)/reset_stack_after_call_abort.o
OBJECTS += $(OBJDIR)/reset_stack_after_foreign_construct.o
@ -227,6 +228,9 @@ $(OBJDIR)/handle.o: ../../test/api/handle.c
$(OBJDIR)/lists.o: ../../test/api/lists.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/maps.o: ../../test/api/maps.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/new_vm.o: ../../test/api/new_vm.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"

View File

@ -121,6 +121,7 @@ OBJECTS += $(OBJDIR)/get_variable.o
OBJECTS += $(OBJDIR)/handle.o
OBJECTS += $(OBJDIR)/lists.o
OBJECTS += $(OBJDIR)/main.o
OBJECTS += $(OBJDIR)/maps.o
OBJECTS += $(OBJDIR)/new_vm.o
OBJECTS += $(OBJDIR)/reset_stack_after_call_abort.o
OBJECTS += $(OBJDIR)/reset_stack_after_foreign_construct.o
@ -219,6 +220,9 @@ $(OBJDIR)/handle.o: ../../test/api/handle.c
$(OBJDIR)/lists.o: ../../test/api/lists.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/maps.o: ../../test/api/maps.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/new_vm.o: ../../test/api/new_vm.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"

Binary file not shown.

View File

@ -265,6 +265,7 @@
<ClInclude Include="..\..\test\api\get_variable.h" />
<ClInclude Include="..\..\test\api\handle.h" />
<ClInclude Include="..\..\test\api\lists.h" />
<ClInclude Include="..\..\test\api\maps.h" />
<ClInclude Include="..\..\test\api\new_vm.h" />
<ClInclude Include="..\..\test\api\reset_stack_after_call_abort.h" />
<ClInclude Include="..\..\test\api\reset_stack_after_foreign_construct.h" />
@ -284,6 +285,7 @@
<ClCompile Include="..\..\test\api\get_variable.c" />
<ClCompile Include="..\..\test\api\handle.c" />
<ClCompile Include="..\..\test\api\lists.c" />
<ClCompile Include="..\..\test\api\maps.c" />
<ClCompile Include="..\..\test\api\new_vm.c" />
<ClCompile Include="..\..\test\api\reset_stack_after_call_abort.c" />
<ClCompile Include="..\..\test\api\reset_stack_after_foreign_construct.c" />

View File

@ -36,6 +36,9 @@
<ClInclude Include="..\..\test\api\lists.h">
<Filter>api</Filter>
</ClInclude>
<ClInclude Include="..\..\test\api\maps.h">
<Filter>api</Filter>
</ClInclude>
<ClInclude Include="..\..\test\api\new_vm.h">
<Filter>api</Filter>
</ClInclude>
@ -87,6 +90,9 @@
<ClCompile Include="..\..\test\api\lists.c">
<Filter>api</Filter>
</ClCompile>
<ClCompile Include="..\..\test\api\maps.c">
<Filter>api</Filter>
</ClCompile>
<ClCompile Include="..\..\test\api\new_vm.c">
<Filter>api</Filter>
</ClCompile>

View File

@ -265,6 +265,7 @@
<ClInclude Include="..\..\test\api\get_variable.h" />
<ClInclude Include="..\..\test\api\handle.h" />
<ClInclude Include="..\..\test\api\lists.h" />
<ClInclude Include="..\..\test\api\maps.h" />
<ClInclude Include="..\..\test\api\new_vm.h" />
<ClInclude Include="..\..\test\api\reset_stack_after_call_abort.h" />
<ClInclude Include="..\..\test\api\reset_stack_after_foreign_construct.h" />
@ -284,6 +285,7 @@
<ClCompile Include="..\..\test\api\get_variable.c" />
<ClCompile Include="..\..\test\api\handle.c" />
<ClCompile Include="..\..\test\api\lists.c" />
<ClCompile Include="..\..\test\api\maps.c" />
<ClCompile Include="..\..\test\api\new_vm.c" />
<ClCompile Include="..\..\test\api\reset_stack_after_call_abort.c" />
<ClCompile Include="..\..\test\api\reset_stack_after_foreign_construct.c" />

View File

@ -36,6 +36,9 @@
<ClInclude Include="..\..\test\api\lists.h">
<Filter>api</Filter>
</ClInclude>
<ClInclude Include="..\..\test\api\maps.h">
<Filter>api</Filter>
</ClInclude>
<ClInclude Include="..\..\test\api\new_vm.h">
<Filter>api</Filter>
</ClInclude>
@ -87,6 +90,9 @@
<ClCompile Include="..\..\test\api\lists.c">
<Filter>api</Filter>
</ClCompile>
<ClCompile Include="..\..\test\api\maps.c">
<Filter>api</Filter>
</ClCompile>
<ClCompile Include="..\..\test\api\new_vm.c">
<Filter>api</Filter>
</ClCompile>

View File

@ -14,6 +14,7 @@
31D07E222B367F941ED1DC62 /* test.c in Sources */ = {isa = PBXBuildFile; fileRef = 7B2F762A1E7AFF5C394BCC6A /* test.c */; };
47E53E839E72A8F576EBBCC3 /* call.c in Sources */ = {isa = PBXBuildFile; fileRef = 2F776B0B32E83D3DB454E14B /* call.c */; };
4D8AE463A8CC37D57C2682A3 /* handle.c in Sources */ = {isa = PBXBuildFile; fileRef = 069BFCEB1DAE981D0DCA932B /* handle.c */; };
4EA04F0DA52DB97F7DA6CD4D /* maps.c in Sources */ = {isa = PBXBuildFile; fileRef = 3AEABBF53E5B8E27BFC83235 /* maps.c */; };
59615B339DB000A5DFD73973 /* resolution.c in Sources */ = {isa = PBXBuildFile; fileRef = 535262BB751E0FEDB1D338FB /* resolution.c */; };
78667B8C71CC7CFE6567D9CC /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 6E0F7DF4115B07262C2BD434 /* main.c */; };
7CCD7163EDF01255A3B6BFA3 /* api_tests.c in Sources */ = {isa = PBXBuildFile; fileRef = 21B810EB49F2C99D318E572B /* api_tests.c */; };
@ -58,6 +59,7 @@
30E3DEE9D44B0C9BEB80C529 /* reset_stack_after_foreign_construct.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = reset_stack_after_foreign_construct.h; path = ../../test/api/reset_stack_after_foreign_construct.h; sourceTree = "<group>"; };
310165BFD4689371EB9E4BFF /* reset_stack_after_foreign_construct.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = reset_stack_after_foreign_construct.c; path = ../../test/api/reset_stack_after_foreign_construct.c; sourceTree = "<group>"; };
33526D1DD64093CF6566735D /* slots.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = slots.c; path = ../../test/api/slots.c; sourceTree = "<group>"; };
3AEABBF53E5B8E27BFC83235 /* maps.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = maps.c; path = ../../test/api/maps.c; sourceTree = "<group>"; };
3E23E7FBE1120EAD7037EE3B /* lists.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = lists.h; path = ../../test/api/lists.h; sourceTree = "<group>"; };
41058591E3F3AC4373198BD1 /* lists.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = lists.c; path = ../../test/api/lists.c; sourceTree = "<group>"; };
50338489786E3D3B6009CAC9 /* benchmark.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = benchmark.c; path = ../../test/api/benchmark.c; sourceTree = "<group>"; };
@ -73,6 +75,7 @@
9C375BF5B349F727A365F235 /* handle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = handle.h; path = ../../test/api/handle.h; sourceTree = "<group>"; };
A415E4D5A786B70728F35B15 /* call.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = call.h; path = ../../test/api/call.h; sourceTree = "<group>"; };
ABEF15744F3A9EA66A0B6BB4 /* test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = test.h; path = ../../test/test.h; sourceTree = "<group>"; };
AF8935BFB2FA07F13466ABFF /* maps.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = maps.h; path = ../../test/api/maps.h; sourceTree = "<group>"; };
B0175F83D8521835BFEDA5C3 /* user_data.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = user_data.c; path = ../../test/api/user_data.c; sourceTree = "<group>"; };
B0267C45D1F229770EA75285 /* resolution.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = resolution.h; path = ../../test/api/resolution.h; sourceTree = "<group>"; };
B1B12E89FA506CBB1D7C24C9 /* reset_stack_after_call_abort.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = reset_stack_after_call_abort.c; path = ../../test/api/reset_stack_after_call_abort.c; sourceTree = "<group>"; };
@ -128,6 +131,8 @@
9C375BF5B349F727A365F235 /* handle.h */,
41058591E3F3AC4373198BD1 /* lists.c */,
3E23E7FBE1120EAD7037EE3B /* lists.h */,
3AEABBF53E5B8E27BFC83235 /* maps.c */,
AF8935BFB2FA07F13466ABFF /* maps.h */,
C22EBF6BD9415A9DC95D55AB /* new_vm.c */,
57CA1E756EDCB9A75EF8B4B5 /* new_vm.h */,
B1B12E89FA506CBB1D7C24C9 /* reset_stack_after_call_abort.c */,
@ -259,6 +264,7 @@
E21864B54F40732750D362F5 /* get_variable.c in Sources */,
4D8AE463A8CC37D57C2682A3 /* handle.c in Sources */,
1C5491694BE6605B26F39FA9 /* lists.c in Sources */,
4EA04F0DA52DB97F7DA6CD4D /* maps.c in Sources */,
29C70EE3850862555862AD23 /* new_vm.c in Sources */,
BD0CC4E181C21B533630C321 /* reset_stack_after_call_abort.c in Sources */,
8F510F77A173C5697A74FDB7 /* reset_stack_after_foreign_construct.c in Sources */,

View File

@ -7,18 +7,26 @@
// The Wren semantic version number components.
#define WREN_VERSION_MAJOR 0
#define WREN_VERSION_MINOR 3
#define WREN_VERSION_MINOR 4
#define WREN_VERSION_PATCH 0
// A human-friendly string representation of the version.
#define WREN_VERSION_STRING "0.3.0"
#define WREN_VERSION_STRING "0.4.0"
// A monotonically increasing numeric representation of the version number. Use
// this if you want to do range checks over versions.
#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \
WREN_VERSION_MINOR * 1000 + \
#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \
WREN_VERSION_MINOR * 1000 + \
WREN_VERSION_PATCH)
#ifndef WREN_API
#if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT)
#define WREN_API __declspec( dllexport )
#else
#define WREN_API
#endif
#endif //WREN_API
// A single virtual machine for executing Wren code.
//
// Wren has no global state, so all state stored by a running interpreter lives
@ -47,7 +55,7 @@ typedef struct WrenHandle WrenHandle;
//
// - To free memory, [memory] will be the memory to free and [newSize] will be
// zero. It should return NULL.
typedef void* (*WrenReallocateFn)(void* memory, size_t newSize);
typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData);
// A function callable from Wren code, but implemented in C.
typedef void (*WrenForeignMethodFn)(WrenVM* vm);
@ -65,8 +73,25 @@ typedef void (*WrenFinalizerFn)(void* data);
typedef const char* (*WrenResolveModuleFn)(WrenVM* vm,
const char* importer, const char* name);
// Forward declare
struct WrenLoadModuleResult;
// Called after loadModuleFn is called for module [name]. The original returned result
// is handed back to you in this callback, so that you can free memory if appropriate.
typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result);
// The result of a loadModuleFn call.
// [source] is the source code for the module, or NULL if the module is not found.
// [onComplete] an optional callback that will be called once Wren is done with the result.
typedef struct WrenLoadModuleResult
{
const char* source;
WrenLoadModuleCompleteFn onComplete;
void* userData;
} WrenLoadModuleResult;
// Loads and returns the source code for the module [name].
typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
// Returns a pointer to a foreign method on [className] in [module] with
// [signature].
@ -163,9 +188,9 @@ typedef struct
// Since Wren does not talk directly to the file system, it relies on the
// embedder to physically locate and read the source code for a module. The
// first time an import appears, Wren will call this and pass in the name of
// the module being imported. The VM should return the soure code for that
// module. Memory for the source should be allocated using [reallocateFn] and
// Wren will take ownership over it.
// the module being imported. The method will return a result, which contains
// the source code for that module. Memory for the source is owned by the
// host application, and can be freed using the onComplete callback.
//
// This will only be called once for any given module name. Wren caches the
// result internally so subsequent imports of the same module will use the
@ -263,6 +288,7 @@ typedef enum
WREN_TYPE_NUM,
WREN_TYPE_FOREIGN,
WREN_TYPE_LIST,
WREN_TYPE_MAP,
WREN_TYPE_NULL,
WREN_TYPE_STRING,
@ -270,27 +296,32 @@ typedef enum
WREN_TYPE_UNKNOWN
} WrenType;
// Get the current wren version number.
//
// Can be used to range checks over versions.
WREN_API int wrenGetVersionNumber();
// Initializes [configuration] with all of its default values.
//
// Call this before setting the particular fields you care about.
void wrenInitConfiguration(WrenConfiguration* configuration);
WREN_API void wrenInitConfiguration(WrenConfiguration* configuration);
// Creates a new Wren virtual machine using the given [configuration]. Wren
// will copy the configuration data, so the argument passed to this can be
// freed after calling this. If [configuration] is `NULL`, uses a default
// configuration.
WrenVM* wrenNewVM(WrenConfiguration* configuration);
WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration);
// Disposes of all resources is use by [vm], which was previously created by a
// call to [wrenNewVM].
void wrenFreeVM(WrenVM* vm);
WREN_API void wrenFreeVM(WrenVM* vm);
// Immediately run the garbage collector to free unused memory.
void wrenCollectGarbage(WrenVM* vm);
WREN_API void wrenCollectGarbage(WrenVM* vm);
// Runs [source], a string of Wren source code in a new fiber in [vm] in the
// context of resolved [module].
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
const char* source);
// Creates a handle that can be used to invoke a method with [signature] on
@ -301,7 +332,7 @@ WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
//
// When you are done with this handle, it must be released using
// [wrenReleaseHandle].
WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature);
WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature);
// Calls [method], using the receiver and arguments previously set up on the
// stack.
@ -313,11 +344,11 @@ WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature);
// signature.
//
// After this returns, you can access the return value from slot 0 on the stack.
WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method);
WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method);
// Releases the reference stored in [handle]. After calling this, [handle] can
// no longer be used.
void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
// The following functions are intended to be called from foreign methods or
// finalizers. The interface Wren provides to a foreign method is like a
@ -357,7 +388,7 @@ void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
// return, you get a very fast FFI.
// Returns the number of slots available to the current foreign method.
int wrenGetSlotCount(WrenVM* vm);
WREN_API int wrenGetSlotCount(WrenVM* vm);
// Ensures that the foreign method stack has at least [numSlots] available for
// use, growing the stack if needed.
@ -365,15 +396,15 @@ int wrenGetSlotCount(WrenVM* vm);
// Does not shrink the stack if it has more than enough slots.
//
// It is an error to call this from a finalizer.
void wrenEnsureSlots(WrenVM* vm, int numSlots);
WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots);
// Gets the type of the object in [slot].
WrenType wrenGetSlotType(WrenVM* vm, int slot);
WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot);
// Reads a boolean value from [slot].
//
// It is an error to call this if the slot does not contain a boolean value.
bool wrenGetSlotBool(WrenVM* vm, int slot);
WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot);
// Reads a byte array from [slot].
//
@ -385,19 +416,19 @@ bool wrenGetSlotBool(WrenVM* vm, int slot);
// number of bytes in the array.
//
// It is an error to call this if the slot does not contain a string.
const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length);
WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length);
// Reads a number from [slot].
//
// It is an error to call this if the slot does not contain a number.
double wrenGetSlotDouble(WrenVM* vm, int slot);
WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot);
// Reads a foreign object from [slot] and returns a pointer to the foreign data
// stored with it.
//
// It is an error to call this if the slot does not contain an instance of a
// foreign class.
void* wrenGetSlotForeign(WrenVM* vm, int slot);
WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot);
// Reads a string from [slot].
//
@ -406,25 +437,25 @@ void* wrenGetSlotForeign(WrenVM* vm, int slot);
// function returns, since the garbage collector may reclaim it.
//
// It is an error to call this if the slot does not contain a string.
const char* wrenGetSlotString(WrenVM* vm, int slot);
WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot);
// Creates a handle for the value stored in [slot].
//
// This will prevent the object that is referred to from being garbage collected
// until the handle is released by calling [wrenReleaseHandle()].
WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot);
WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot);
// Stores the boolean [value] in [slot].
void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
// Stores the array [length] of [bytes] in [slot].
//
// The bytes are copied to a new string within Wren's heap, so you can free
// memory used by them after this is called.
void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length);
WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length);
// Stores the numeric [value] in [slot].
void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
// Creates a new instance of the foreign class stored in [classSlot] with [size]
// bytes of raw storage and places the resulting object in [slot].
@ -435,13 +466,16 @@ void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
// and then the constructor will be invoked when the allocator returns.
//
// Returns a pointer to the foreign object's data.
void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size);
WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size);
// Stores a new empty list in [slot].
void wrenSetSlotNewList(WrenVM* vm, int slot);
WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot);
// Stores a new empty map in [slot].
WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot);
// Stores null in [slot].
void wrenSetSlotNull(WrenVM* vm, int slot);
WREN_API void wrenSetSlotNull(WrenVM* vm, int slot);
// Stores the string [text] in [slot].
//
@ -449,40 +483,72 @@ void wrenSetSlotNull(WrenVM* vm, int slot);
// memory used by it after this is called. The length is calculated using
// [strlen()]. If the string may contain any null bytes in the middle, then you
// should use [wrenSetSlotBytes()] instead.
void wrenSetSlotString(WrenVM* vm, int slot, const char* text);
WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text);
// Stores the value captured in [handle] in [slot].
//
// This does not release the handle for the value.
void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle);
WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle);
// Returns the number of elements in the list stored in [slot].
int wrenGetListCount(WrenVM* vm, int slot);
WREN_API int wrenGetListCount(WrenVM* vm, int slot);
// Reads element [index] from the list in [listSlot] and stores it in
// [elementSlot].
void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
// Sets the value stored at [index] in the list at [listSlot],
// to the value from [elementSlot].
WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
// Takes the value stored at [elementSlot] and inserts it into the list stored
// at [listSlot] at [index].
//
// As in Wren, negative indexes can be used to insert from the end. To append
// an element, use `-1` for the index.
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot);
WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot);
// Returns the number of entries in the map stored in [slot].
WREN_API int wrenGetMapCount(WrenVM* vm, int slot);
// Returns true if the key in [keySlot] is found in the map placed in [mapSlot].
WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot);
// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and
// stores it in [valueSlot].
WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
// Takes the value stored at [valueSlot] and inserts it into the map stored
// at [mapSlot] with key [keySlot].
WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
// Removes a value from the map in [mapSlot], with the key from [keySlot],
// and place it in [removedValueSlot]. If not found, [removedValueSlot] is
// set to null, the same behaviour as the Wren Map API.
WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot,
int removedValueSlot);
// Looks up the top level variable with [name] in resolved [module] and stores
// it in [slot].
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
int slot);
// Looks up the top level variable with [name] in resolved [module],
// returns false if not found. The module must be imported at the time,
// use wrenHasModule to ensure that before calling.
WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name);
// Returns true if [module] has been imported/resolved before, false if not.
WREN_API bool wrenHasModule(WrenVM* vm, const char* module);
// Sets the current fiber to be aborted, and uses the value in [slot] as the
// runtime error object.
void wrenAbortFiber(WrenVM* vm, int slot);
WREN_API void wrenAbortFiber(WrenVM* vm, int slot);
// Returns the user data associated with the WrenVM.
void* wrenGetUserData(WrenVM* vm);
WREN_API void* wrenGetUserData(WrenVM* vm);
// Sets user data associated with the WrenVM.
void wrenSetUserData(WrenVM* vm, void* userData);
WREN_API void wrenSetUserData(WrenVM* vm, void* userData);
#endif

View File

@ -47,65 +47,38 @@ foreign class Random {
int(end) { (float() * end).floor }
int(start, end) { (float() * (end - start)).floor + start }
sample(list) { sample(list, 1)[0] }
sample(list) {
if (list.count == 0) Fiber.abort("Not enough elements to sample.")
return list[int(list.count)]
}
sample(list, count) {
if (count > list.count) Fiber.abort("Not enough elements to sample.")
// There are (at least) two simple algorithms for choosing a number of
// samples from a list without replacement -- where we don't pick the same
// element more than once.
//
// The first is faster when the number of samples is small relative to the
// size of the collection. In many cases, it avoids scanning the entire
// list. In the common case of just wanting one sample, it's a single
// random index lookup.
//
// However, its performance degrades badly as the sample size increases.
// Vitter's algorithm always scans the entire list, but it's also always
// O(n).
//
// The cutoff point between the two follows a quadratic curve on the same
// size. Based on some empirical testing, scaling that by 5 seems to fit
// pretty closely and chooses the fastest one for the given sample and
// collection size.
if (count * count * 5 < list.count) {
// Pick random elements and retry if you hit a previously chosen one.
var picked = {}
var result = []
for (i in 0...count) {
// Find an index that we haven't already selected.
var index
while (true) {
index = int(list.count)
if (!picked.containsKey(index)) break
}
var result = []
// The algorithm described in "Programming pearls: a sample of brilliance".
// Use a hash map for sample sizes less than 1/4 of the population size and
// an array of booleans for larger samples. This simple heuristic improves
// performance for large sample sizes as well as reduces memory usage.
if (count * 4 < list.count) {
var picked = {}
for (i in list.count - count...list.count) {
var index = int(i + 1)
if (picked.containsKey(index)) index = i
picked[index] = true
result.add(list[index])
}
return result
} else {
// Jeffrey Vitter's Algorithm R.
// Fill the reservoir with the first elements in the list.
var result = list[0...count]
// We want to ensure the results are always in random order, so shuffle
// them. In cases where the sample size is the entire collection, this
// devolves to running Fisher-Yates on a copy of the list.
shuffle(result)
// Now walk the rest of the list. For each element, randomly consider
// replacing one of the reservoir elements with it. The probability here
// works out such that it does this uniformly.
for (i in count...list.count) {
var slot = int(0, i + 1)
if (slot < count) result[slot] = list[i]
var picked = List.filled(list.count, false)
for (i in list.count - count...list.count) {
var index = int(i + 1)
if (picked[index]) index = i
picked[index] = true
result.add(list[index])
}
return result
}
return result
}
shuffle(list) {

View File

@ -49,65 +49,38 @@ static const char* randomModuleSource =
" int(end) { (float() * end).floor }\n"
" int(start, end) { (float() * (end - start)).floor + start }\n"
"\n"
" sample(list) { sample(list, 1)[0] }\n"
" sample(list) {\n"
" if (list.count == 0) Fiber.abort(\"Not enough elements to sample.\")\n"
" return list[int(list.count)]\n"
" }\n"
" sample(list, count) {\n"
" if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n"
"\n"
" // There are (at least) two simple algorithms for choosing a number of\n"
" // samples from a list without replacement -- where we don't pick the same\n"
" // element more than once.\n"
" //\n"
" // The first is faster when the number of samples is small relative to the\n"
" // size of the collection. In many cases, it avoids scanning the entire\n"
" // list. In the common case of just wanting one sample, it's a single\n"
" // random index lookup.\n"
" //\n"
" // However, its performance degrades badly as the sample size increases.\n"
" // Vitter's algorithm always scans the entire list, but it's also always\n"
" // O(n).\n"
" //\n"
" // The cutoff point between the two follows a quadratic curve on the same\n"
" // size. Based on some empirical testing, scaling that by 5 seems to fit\n"
" // pretty closely and chooses the fastest one for the given sample and\n"
" // collection size.\n"
" if (count * count * 5 < list.count) {\n"
" // Pick random elements and retry if you hit a previously chosen one.\n"
" var picked = {}\n"
" var result = []\n"
" for (i in 0...count) {\n"
" // Find an index that we haven't already selected.\n"
" var index\n"
" while (true) {\n"
" index = int(list.count)\n"
" if (!picked.containsKey(index)) break\n"
" }\n"
" var result = []\n"
"\n"
" // The algorithm described in \"Programming pearls: a sample of brilliance\".\n"
" // Use a hash map for sample sizes less than 1/4 of the population size and\n"
" // an array of booleans for larger samples. This simple heuristic improves\n"
" // performance for large sample sizes as well as reduces memory usage.\n"
" if (count * 4 < list.count) {\n"
" var picked = {}\n"
" for (i in list.count - count...list.count) {\n"
" var index = int(i + 1)\n"
" if (picked.containsKey(index)) index = i\n"
" picked[index] = true\n"
" result.add(list[index])\n"
" }\n"
"\n"
" return result\n"
" } else {\n"
" // Jeffrey Vitter's Algorithm R.\n"
"\n"
" // Fill the reservoir with the first elements in the list.\n"
" var result = list[0...count]\n"
"\n"
" // We want to ensure the results are always in random order, so shuffle\n"
" // them. In cases where the sample size is the entire collection, this\n"
" // devolves to running Fisher-Yates on a copy of the list.\n"
" shuffle(result)\n"
"\n"
" // Now walk the rest of the list. For each element, randomly consider\n"
" // replacing one of the reservoir elements with it. The probability here\n"
" // works out such that it does this uniformly.\n"
" for (i in count...list.count) {\n"
" var slot = int(0, i + 1)\n"
" if (slot < count) result[slot] = list[i]\n"
" var picked = List.filled(list.count, false)\n"
" for (i in list.count - count...list.count) {\n"
" var index = int(i + 1)\n"
" if (picked[index]) index = i\n"
" picked[index] = true\n"
" result.add(list[index])\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" shuffle(list) {\n"

View File

@ -33,10 +33,10 @@
// http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html
// Enabling this speeds up the main dispatch loop a bit, but requires compiler
// support.
//
// see https://bullno1.com/blog/switched-goto for alternative
// Defaults to true on supported compilers.
#ifndef WREN_COMPUTED_GOTO
#ifdef _MSC_VER
#if defined(_MSC_VER) && !defined(__clang__)
// No computed gotos in Visual Studio.
#define WREN_COMPUTED_GOTO 0
#else
@ -114,17 +114,17 @@
#define MAX_FIELDS 255
// Use the VM's allocator to allocate an object of [type].
#define ALLOCATE(vm, type) \
#define ALLOCATE(vm, type) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
// Use the VM's allocator to allocate an object of [mainType] containing a
// flexible array of [count] objects of [arrayType].
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
((mainType*)wrenReallocate(vm, NULL, 0, \
sizeof(mainType) + sizeof(arrayType) * (count)))
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
((mainType*)wrenReallocate(vm, NULL, 0, \
sizeof(mainType) + sizeof(arrayType) * (count)))
// Use the VM's allocator to allocate an array of [count] elements of [type].
#define ALLOCATE_ARRAY(vm, type, count) \
#define ALLOCATE_ARRAY(vm, type, count) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count)))
// Use the VM's allocator to free the previously allocated memory at [pointer].
@ -156,17 +156,16 @@
#include <stdio.h>
#define ASSERT(condition, message) \
do \
{ \
if (!(condition)) \
{ \
fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \
__FILE__, __LINE__, __func__, message); \
abort(); \
} \
} \
while(0)
#define ASSERT(condition, message) \
do \
{ \
if (!(condition)) \
{ \
fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \
__FILE__, __LINE__, __func__, message); \
abort(); \
} \
} while (false)
// Indicates that we know execution should never reach this point in the
// program. In debug mode, we assert this fact because it's a bug to get here.
@ -175,18 +174,17 @@
// compiler the code can't be reached. This avoids "missing return" warnings
// in some cases and also lets it perform some optimizations by assuming the
// code is never reached.
#define UNREACHABLE() \
do \
{ \
fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \
__FILE__, __LINE__, __func__); \
abort(); \
} \
while (0)
#define UNREACHABLE() \
do \
{ \
fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \
__FILE__, __LINE__, __func__); \
abort(); \
} while (false)
#else
#define ASSERT(condition, message) do {} while (0)
#define ASSERT(condition, message) do { } while (false)
// Tell the compiler that this part of the code will never be reached.
#if defined( _MSC_VER )

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
#include "wren_common.h"
#include "wren_core.h"
#include "wren_math.h"
#include "wren_primitive.h"
#include "wren_value.h"
@ -49,6 +50,11 @@ DEF_PRIMITIVE(class_toString)
RETURN_OBJ(AS_CLASS(args[0])->name);
}
DEF_PRIMITIVE(class_attributes)
{
RETURN_VAL(AS_CLASS(args[0])->attributes);
}
DEF_PRIMITIVE(fiber_new)
{
if (!validateFn(vm, args[1], "Argument")) return false;
@ -191,6 +197,15 @@ DEF_PRIMITIVE(fiber_try)
return false;
}
DEF_PRIMITIVE(fiber_try1)
{
runFiber(vm, AS_FIBER(args[0]), args, true, true, "try");
// If we're switching to a valid fiber to try, remember that we're trying it.
if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY;
return false;
}
DEF_PRIMITIVE(fiber_yield)
{
ObjFiber* current = vm->fiber;
@ -248,23 +263,16 @@ DEF_PRIMITIVE(fn_arity)
static void call_fn(WrenVM* vm, Value* args, int numArgs)
{
// We only care about missing arguments, not extras.
if (AS_CLOSURE(args[0])->fn->arity > numArgs)
{
vm->fiber->error = CONST_STRING(vm, "Function expects more arguments.");
return;
}
// +1 to include the function itself.
wrenCallFunction(vm, vm->fiber, AS_CLOSURE(args[0]), numArgs + 1);
}
#define DEF_FN_CALL(numArgs) \
DEF_PRIMITIVE(fn_call##numArgs) \
{ \
call_fn(vm, args, numArgs); \
return false; \
} \
#define DEF_FN_CALL(numArgs) \
DEF_PRIMITIVE(fn_call##numArgs) \
{ \
call_fn(vm, args, numArgs); \
return false; \
}
DEF_FN_CALL(0)
DEF_FN_CALL(1)
@ -391,6 +399,34 @@ DEF_PRIMITIVE(list_removeAt)
RETURN_VAL(wrenListRemoveAt(vm, list, index));
}
DEF_PRIMITIVE(list_removeValue) {
ObjList* list = AS_LIST(args[0]);
int index = wrenListIndexOf(vm, list, args[1]);
if(index == -1) RETURN_NULL;
RETURN_VAL(wrenListRemoveAt(vm, list, index));
}
DEF_PRIMITIVE(list_indexOf)
{
ObjList* list = AS_LIST(args[0]);
RETURN_NUM(wrenListIndexOf(vm, list, args[1]));
}
DEF_PRIMITIVE(list_swap)
{
ObjList* list = AS_LIST(args[0]);
uint32_t indexA = validateIndex(vm, args[1], list->elements.count, "Index 0");
if (indexA == UINT32_MAX) return false;
uint32_t indexB = validateIndex(vm, args[2], list->elements.count, "Index 1");
if (indexB == UINT32_MAX) return false;
Value a = list->elements.data[indexA];
list->elements.data[indexA] = list->elements.data[indexB];
list->elements.data[indexB] = a;
RETURN_NULL;
}
DEF_PRIMITIVE(list_subscript)
{
ObjList* list = AS_LIST(args[0]);
@ -594,17 +630,30 @@ DEF_PRIMITIVE(num_fromString)
RETURN_NUM(number);
}
DEF_PRIMITIVE(num_pi)
{
RETURN_NUM(3.14159265358979323846);
}
// Defines a primitive on Num that calls infix [op] and returns [type].
#define DEF_NUM_CONSTANT(name, value) \
DEF_PRIMITIVE(num_##name) \
{ \
RETURN_NUM(value); \
}
DEF_NUM_CONSTANT(infinity, INFINITY)
DEF_NUM_CONSTANT(nan, WREN_DOUBLE_NAN)
DEF_NUM_CONSTANT(pi, 3.14159265358979323846264338327950288)
DEF_NUM_CONSTANT(tau, 6.28318530717958647692528676655900577)
DEF_NUM_CONSTANT(largest, DBL_MAX)
DEF_NUM_CONSTANT(smallest, DBL_MIN)
DEF_NUM_CONSTANT(maxSafeInteger, 9007199254740991.0)
DEF_NUM_CONSTANT(minSafeInteger, -9007199254740991.0)
// Defines a primitive on Num that calls infix [op] and returns [type].
#define DEF_NUM_INFIX(name, op, type) \
DEF_PRIMITIVE(num_##name) \
{ \
if (!validateNum(vm, args[1], "Right operand")) return false; \
RETURN_##type(AS_NUM(args[0]) op AS_NUM(args[1])); \
#define DEF_NUM_INFIX(name, op, type) \
DEF_PRIMITIVE(num_##name) \
{ \
if (!validateNum(vm, args[1], "Right operand")) return false; \
RETURN_##type(AS_NUM(args[0]) op AS_NUM(args[1])); \
}
DEF_NUM_INFIX(minus, -, NUM)
@ -617,13 +666,13 @@ DEF_NUM_INFIX(lte, <=, BOOL)
DEF_NUM_INFIX(gte, >=, BOOL)
// Defines a primitive on Num that call infix bitwise [op].
#define DEF_NUM_BITWISE(name, op) \
DEF_PRIMITIVE(num_bitwise##name) \
{ \
if (!validateNum(vm, args[1], "Right operand")) return false; \
uint32_t left = (uint32_t)AS_NUM(args[0]); \
uint32_t right = (uint32_t)AS_NUM(args[1]); \
RETURN_NUM(left op right); \
#define DEF_NUM_BITWISE(name, op) \
DEF_PRIMITIVE(num_bitwise##name) \
{ \
if (!validateNum(vm, args[1], "Right operand")) return false; \
uint32_t left = (uint32_t)AS_NUM(args[0]); \
uint32_t right = (uint32_t)AS_NUM(args[1]); \
RETURN_NUM(left op right); \
}
DEF_NUM_BITWISE(And, &)
@ -633,16 +682,17 @@ DEF_NUM_BITWISE(LeftShift, <<)
DEF_NUM_BITWISE(RightShift, >>)
// Defines a primitive method on Num that returns the result of [fn].
#define DEF_NUM_FN(name, fn) \
DEF_PRIMITIVE(num_##name) \
{ \
RETURN_NUM(fn(AS_NUM(args[0]))); \
#define DEF_NUM_FN(name, fn) \
DEF_PRIMITIVE(num_##name) \
{ \
RETURN_NUM(fn(AS_NUM(args[0]))); \
}
DEF_NUM_FN(abs, fabs)
DEF_NUM_FN(acos, acos)
DEF_NUM_FN(asin, asin)
DEF_NUM_FN(atan, atan)
DEF_NUM_FN(cbrt, cbrt)
DEF_NUM_FN(ceil, ceil)
DEF_NUM_FN(cos, cos)
DEF_NUM_FN(floor, floor)
@ -652,6 +702,8 @@ DEF_NUM_FN(sin, sin)
DEF_NUM_FN(sqrt, sqrt)
DEF_NUM_FN(tan, tan)
DEF_NUM_FN(log, log)
DEF_NUM_FN(log2, log2)
DEF_NUM_FN(exp, exp)
DEF_PRIMITIVE(num_mod)
{
@ -697,18 +749,52 @@ DEF_PRIMITIVE(num_dotDotDot)
DEF_PRIMITIVE(num_atan2)
{
if (!validateNum(vm, args[1], "x value")) return false;
RETURN_NUM(atan2(AS_NUM(args[0]), AS_NUM(args[1])));
}
DEF_PRIMITIVE(num_min)
{
if (!validateNum(vm, args[1], "Other value")) return false;
double value = AS_NUM(args[0]);
double other = AS_NUM(args[1]);
RETURN_NUM(value <= other ? value : other);
}
DEF_PRIMITIVE(num_max)
{
if (!validateNum(vm, args[1], "Other value")) return false;
double value = AS_NUM(args[0]);
double other = AS_NUM(args[1]);
RETURN_NUM(value > other ? value : other);
}
DEF_PRIMITIVE(num_clamp)
{
if (!validateNum(vm, args[1], "Min value")) return false;
if (!validateNum(vm, args[2], "Max value")) return false;
double value = AS_NUM(args[0]);
double min = AS_NUM(args[1]);
double max = AS_NUM(args[2]);
double result = (value < min) ? min : ((value > max) ? max : value);
RETURN_NUM(result);
}
DEF_PRIMITIVE(num_pow)
{
if (!validateNum(vm, args[1], "Power value")) return false;
RETURN_NUM(pow(AS_NUM(args[0]), AS_NUM(args[1])));
}
DEF_PRIMITIVE(num_fraction)
{
double dummy;
RETURN_NUM(modf(AS_NUM(args[0]) , &dummy));
double unused;
RETURN_NUM(modf(AS_NUM(args[0]) , &unused));
}
DEF_PRIMITIVE(num_isInfinity)
@ -745,16 +831,6 @@ DEF_PRIMITIVE(num_sign)
}
}
DEF_PRIMITIVE(num_largest)
{
RETURN_NUM(DBL_MAX);
}
DEF_PRIMITIVE(num_smallest)
{
RETURN_NUM(DBL_MIN);
}
DEF_PRIMITIVE(num_toString)
{
RETURN_VAL(wrenNumToString(vm, AS_NUM(args[0])));
@ -1181,6 +1257,7 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->classClass, "name", class_name);
PRIMITIVE(vm->classClass, "supertype", class_supertype);
PRIMITIVE(vm->classClass, "toString", class_toString);
PRIMITIVE(vm->classClass, "attributes", class_attributes);
// Finally, we can define Object's metaclass which is a subclass of Class.
ObjClass* objectMetaclass = defineClass(vm, coreModule, "Object metaclass");
@ -1240,28 +1317,31 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1);
PRIMITIVE(vm->fiberClass, "transferError(_)", fiber_transferError);
PRIMITIVE(vm->fiberClass, "try()", fiber_try);
PRIMITIVE(vm->fiberClass, "try(_)", fiber_try1);
vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn"));
PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new);
PRIMITIVE(vm->fnClass, "arity", fn_arity);
PRIMITIVE(vm->fnClass, "call()", fn_call0);
PRIMITIVE(vm->fnClass, "call(_)", fn_call1);
PRIMITIVE(vm->fnClass, "call(_,_)", fn_call2);
PRIMITIVE(vm->fnClass, "call(_,_,_)", fn_call3);
PRIMITIVE(vm->fnClass, "call(_,_,_,_)", fn_call4);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_)", fn_call5);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call16);
FUNCTION_CALL(vm->fnClass, "call()", fn_call0);
FUNCTION_CALL(vm->fnClass, "call(_)", fn_call1);
FUNCTION_CALL(vm->fnClass, "call(_,_)", fn_call2);
FUNCTION_CALL(vm->fnClass, "call(_,_,_)", fn_call3);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_)", fn_call4);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_)", fn_call5);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call16);
PRIMITIVE(vm->fnClass, "toString", fn_toString);
vm->nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null"));
@ -1270,9 +1350,14 @@ void wrenInitializeCore(WrenVM* vm)
vm->numClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Num"));
PRIMITIVE(vm->numClass->obj.classObj, "fromString(_)", num_fromString);
PRIMITIVE(vm->numClass->obj.classObj, "infinity", num_infinity);
PRIMITIVE(vm->numClass->obj.classObj, "nan", num_nan);
PRIMITIVE(vm->numClass->obj.classObj, "pi", num_pi);
PRIMITIVE(vm->numClass->obj.classObj, "tau", num_tau);
PRIMITIVE(vm->numClass->obj.classObj, "largest", num_largest);
PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest);
PRIMITIVE(vm->numClass->obj.classObj, "maxSafeInteger", num_maxSafeInteger);
PRIMITIVE(vm->numClass->obj.classObj, "minSafeInteger", num_minSafeInteger);
PRIMITIVE(vm->numClass, "-(_)", num_minus);
PRIMITIVE(vm->numClass, "+(_)", num_plus);
PRIMITIVE(vm->numClass, "*(_)", num_multiply);
@ -1290,15 +1375,21 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->numClass, "acos", num_acos);
PRIMITIVE(vm->numClass, "asin", num_asin);
PRIMITIVE(vm->numClass, "atan", num_atan);
PRIMITIVE(vm->numClass, "cbrt", num_cbrt);
PRIMITIVE(vm->numClass, "ceil", num_ceil);
PRIMITIVE(vm->numClass, "cos", num_cos);
PRIMITIVE(vm->numClass, "floor", num_floor);
PRIMITIVE(vm->numClass, "-", num_negate);
PRIMITIVE(vm->numClass, "round", num_round);
PRIMITIVE(vm->numClass, "min(_)", num_min);
PRIMITIVE(vm->numClass, "max(_)", num_max);
PRIMITIVE(vm->numClass, "clamp(_,_)", num_clamp);
PRIMITIVE(vm->numClass, "sin", num_sin);
PRIMITIVE(vm->numClass, "sqrt", num_sqrt);
PRIMITIVE(vm->numClass, "tan", num_tan);
PRIMITIVE(vm->numClass, "log", num_log);
PRIMITIVE(vm->numClass, "log2", num_log2);
PRIMITIVE(vm->numClass, "exp", num_exp);
PRIMITIVE(vm->numClass, "%(_)", num_mod);
PRIMITIVE(vm->numClass, "~", num_bitwiseNot);
PRIMITIVE(vm->numClass, "..(_)", num_dotDot);
@ -1349,6 +1440,9 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->listClass, "iterate(_)", list_iterate);
PRIMITIVE(vm->listClass, "iteratorValue(_)", list_iteratorValue);
PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt);
PRIMITIVE(vm->listClass, "remove(_)", list_removeValue);
PRIMITIVE(vm->listClass, "indexOf(_)", list_indexOf);
PRIMITIVE(vm->listClass, "swap(_,_)", list_swap);
vm->mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map"));
PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new);

View File

@ -323,6 +323,41 @@ class List is Sequence {
return other
}
sort() { sort {|low, high| low < high } }
sort(comparer) {
if (!(comparer is Fn)) {
Fiber.abort("Comparer must be a function.")
}
quicksort_(0, count - 1, comparer)
return this
}
quicksort_(low, high, comparer) {
if (low < high) {
var p = partition_(low, high, comparer)
quicksort_(low, p - 1, comparer)
quicksort_(p + 1, high, comparer)
}
}
partition_(low, high, comparer) {
var p = this[high]
var i = low - 1
for (j in low..(high-1)) {
if (comparer.call(this[j], p)) {
i = i + 1
var t = this[i]
this[i] = this[j]
this[j] = t
}
}
var t = this[i+1]
this[i+1] = this[high]
this[high] = t
return i+1
}
toString { "[%(join(", "))]" }
+(other) {
@ -436,3 +471,13 @@ class System {
}
}
}
class ClassAttributes {
self { _attributes }
methods { _methods }
construct new(attributes, methods) {
_attributes = attributes
_methods = methods
}
toString { "attributes:%(_attributes) methods:%(_methods)" }
}

View File

@ -325,6 +325,41 @@ static const char* coreModuleSource =
" return other\n"
" }\n"
"\n"
" sort() { sort {|low, high| low < high } }\n"
"\n"
" sort(comparer) {\n"
" if (!(comparer is Fn)) {\n"
" Fiber.abort(\"Comparer must be a function.\")\n"
" }\n"
" quicksort_(0, count - 1, comparer)\n"
" return this\n"
" }\n"
"\n"
" quicksort_(low, high, comparer) {\n"
" if (low < high) {\n"
" var p = partition_(low, high, comparer)\n"
" quicksort_(low, p - 1, comparer)\n"
" quicksort_(p + 1, high, comparer)\n"
" }\n"
" }\n"
"\n"
" partition_(low, high, comparer) {\n"
" var p = this[high]\n"
" var i = low - 1\n"
" for (j in low..(high-1)) {\n"
" if (comparer.call(this[j], p)) { \n"
" i = i + 1\n"
" var t = this[i]\n"
" this[i] = this[j]\n"
" this[j] = t\n"
" }\n"
" }\n"
" var t = this[i+1]\n"
" this[i+1] = this[high]\n"
" this[high] = t\n"
" return i+1\n"
" }\n"
"\n"
" toString { \"[%(join(\", \"))]\" }\n"
"\n"
" +(other) {\n"
@ -437,4 +472,15 @@ static const char* coreModuleSource =
" writeString_(\"[invalid toString]\")\n"
" }\n"
" }\n"
"}\n"
"\n"
"class ClassAttributes {\n"
" self { _attributes }\n"
" methods { _methods }\n"
" construct new(attributes, methods) {\n"
" _attributes = attributes\n"
" _methods = methods\n"
" }\n"
" toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n"
"}\n";

View File

@ -121,9 +121,9 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
#define READ_BYTE() (bytecode[i++])
#define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1])
#define BYTE_INSTRUCTION(name) \
printf("%-16s %5d\n", name, READ_BYTE()); \
break; \
#define BYTE_INSTRUCTION(name) \
printf("%-16s %5d\n", name, READ_BYTE()); \
break
switch (code)
{
@ -296,6 +296,7 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
}
case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break;
case CODE_END_CLASS: printf("END_CLASS\n"); break;
case CODE_METHOD_INSTANCE:
{

34
src/vm/wren_math.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef wren_math_h
#define wren_math_h
#include <math.h>
#include <stdint.h>
// A union to let us reinterpret a double as raw bits and back.
typedef union
{
uint64_t bits64;
uint32_t bits32[2];
double num;
} WrenDoubleBits;
#define WREN_DOUBLE_QNAN_POS_MIN_BITS (UINT64_C(0x7FF8000000000000))
#define WREN_DOUBLE_QNAN_POS_MAX_BITS (UINT64_C(0x7FFFFFFFFFFFFFFF))
#define WREN_DOUBLE_NAN (wrenDoubleFromBits(WREN_DOUBLE_QNAN_POS_MIN_BITS))
static inline double wrenDoubleFromBits(uint64_t bits)
{
WrenDoubleBits data;
data.bits64 = bits;
return data.num;
}
static inline uint64_t wrenDoubleToBits(double num)
{
WrenDoubleBits data;
data.num = num;
return data.bits64;
}
#endif

View File

@ -169,6 +169,10 @@ OPCODE(FOREIGN_CONSTRUCT, 0)
// the name of the class. Byte [arg] is the number of fields in the class.
OPCODE(CLASS, -1)
// Ends a class.
// Atm the stack contains the class and the ClassAttributes (or null).
OPCODE(END_CLASS, -2)
// Creates a foreign class. Top of stack is the superclass. Below that is a
// string for the name of the class.
OPCODE(FOREIGN_CLASS, -1)

View File

@ -23,9 +23,7 @@ static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value,
bool validateFn(WrenVM* vm, Value arg, const char* argName)
{
if (IS_CLOSURE(arg)) return true;
vm->fiber->error = wrenStringFormat(vm, "$ must be a function.", argName);
return false;
RETURN_ERROR_FMT("$ must be a function.", argName);
}
bool validateNum(WrenVM* vm, Value arg, const char* argName)
@ -49,11 +47,7 @@ bool validateInt(WrenVM* vm, Value arg, const char* argName)
bool validateKey(WrenVM* vm, Value arg)
{
if (IS_BOOL(arg) || IS_CLASS(arg) || IS_NULL(arg) ||
IS_NUM(arg) || IS_RANGE(arg) || IS_STRING(arg))
{
return true;
}
if (wrenMapIsValidKey(arg)) return true;
RETURN_ERROR("Key must be a value type.");
}

View File

@ -5,23 +5,42 @@
// Binds a primitive method named [name] (in Wren) implemented using C function
// [fn] to `ObjClass` [cls].
#define PRIMITIVE(cls, name, function) \
{ \
int symbol = wrenSymbolTableEnsure(vm, \
&vm->methodNames, name, strlen(name)); \
Method method; \
method.type = METHOD_PRIMITIVE; \
method.as.primitive = prim_##function; \
wrenBindMethod(vm, cls, symbol, method); \
}
#define PRIMITIVE(cls, name, function) \
do \
{ \
int symbol = wrenSymbolTableEnsure(vm, \
&vm->methodNames, name, strlen(name)); \
Method method; \
method.type = METHOD_PRIMITIVE; \
method.as.primitive = prim_##function; \
wrenBindMethod(vm, cls, symbol, method); \
} while (false)
// Binds a primitive method named [name] (in Wren) implemented using C function
// [fn] to `ObjClass` [cls], but as a FN call.
#define FUNCTION_CALL(cls, name, function) \
do \
{ \
int symbol = wrenSymbolTableEnsure(vm, \
&vm->methodNames, name, strlen(name)); \
Method method; \
method.type = METHOD_FUNCTION_CALL; \
method.as.primitive = prim_##function; \
wrenBindMethod(vm, cls, symbol, method); \
} while (false)
// Defines a primitive method whose C function name is [name]. This abstracts
// the actual type signature of a primitive function and makes it clear which C
// functions are invoked as primitives.
#define DEF_PRIMITIVE(name) \
#define DEF_PRIMITIVE(name) \
static bool prim_##name(WrenVM* vm, Value* args)
#define RETURN_VAL(value) do { args[0] = value; return true; } while (0)
#define RETURN_VAL(value) \
do \
{ \
args[0] = value; \
return true; \
} while (false)
#define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj))
#define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value))
@ -30,17 +49,19 @@
#define RETURN_NUM(value) RETURN_VAL(NUM_VAL(value))
#define RETURN_TRUE RETURN_VAL(TRUE_VAL)
#define RETURN_ERROR(msg) \
do { \
vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \
return false; \
} while (0);
#define RETURN_ERROR(msg) \
do \
{ \
vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \
return false; \
} while (false)
#define RETURN_ERROR_FMT(msg, arg) \
do { \
vm->fiber->error = wrenStringFormat(vm, msg, arg); \
return false; \
} while (0);
#define RETURN_ERROR_FMT(...) \
do \
{ \
vm->fiber->error = wrenStringFormat(vm, __VA_ARGS__); \
return false; \
} while (false)
// Validates that the given [arg] is a function. Returns true if it is. If not,
// reports an error and returns false.
@ -60,7 +81,7 @@ bool validateInt(WrenVM* vm, Value arg, const char* argName);
// Validates that [arg] is a valid object for use as a map key. Returns true if
// it is. If not, reports an error and returns false.
bool validateKey(WrenVM* vm, Value arg);
bool validateKey(WrenVM * vm, Value arg);
// Validates that the argument at [argIndex] is an integer within `[0, count)`.
// Also allows negative indices which map backwards from the end. Returns the

View File

@ -194,3 +194,14 @@ int wrenPowerOf2Ceil(int n)
return n;
}
uint32_t wrenValidateIndex(uint32_t count, int64_t value)
{
// Negative indices count from the end.
if (value < 0) value = count + value;
// Check bounds.
if (value >= 0 && value < count) return (uint32_t)value;
return UINT32_MAX;
}

View File

@ -13,54 +13,54 @@ typedef struct sObjString ObjString;
// We need buffers of a few different types. To avoid lots of casting between
// void* and back, we'll use the preprocessor as a poor man's generics and let
// it generate a few type-specific ones.
#define DECLARE_BUFFER(name, type) \
typedef struct \
{ \
type* data; \
int count; \
int capacity; \
} name##Buffer; \
void wren##name##BufferInit(name##Buffer* buffer); \
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count); \
#define DECLARE_BUFFER(name, type) \
typedef struct \
{ \
type* data; \
int count; \
int capacity; \
} name##Buffer; \
void wren##name##BufferInit(name##Buffer* buffer); \
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count); \
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data)
// This should be used once for each type instantiation, somewhere in a .c file.
#define DEFINE_BUFFER(name, type) \
void wren##name##BufferInit(name##Buffer* buffer) \
{ \
buffer->data = NULL; \
buffer->capacity = 0; \
buffer->count = 0; \
} \
\
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \
{ \
wrenReallocate(vm, buffer->data, 0, 0); \
wren##name##BufferInit(buffer); \
} \
\
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count) \
{ \
if (buffer->capacity < buffer->count + count) \
{ \
int capacity = wrenPowerOf2Ceil(buffer->count + count); \
buffer->data = (type*)wrenReallocate(vm, buffer->data, \
buffer->capacity * sizeof(type), capacity * sizeof(type)); \
buffer->capacity = capacity; \
} \
\
for (int i = 0; i < count; i++) \
{ \
buffer->data[buffer->count++] = data; \
} \
} \
\
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \
{ \
wren##name##BufferFill(vm, buffer, data, 1); \
#define DEFINE_BUFFER(name, type) \
void wren##name##BufferInit(name##Buffer* buffer) \
{ \
buffer->data = NULL; \
buffer->capacity = 0; \
buffer->count = 0; \
} \
\
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \
{ \
wrenReallocate(vm, buffer->data, 0, 0); \
wren##name##BufferInit(buffer); \
} \
\
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count) \
{ \
if (buffer->capacity < buffer->count + count) \
{ \
int capacity = wrenPowerOf2Ceil(buffer->count + count); \
buffer->data = (type*)wrenReallocate(vm, buffer->data, \
buffer->capacity * sizeof(type), capacity * sizeof(type)); \
buffer->capacity = capacity; \
} \
\
for (int i = 0; i < count; i++) \
{ \
buffer->data[buffer->count++] = data; \
} \
} \
\
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \
{ \
wren##name##BufferFill(vm, buffer, data, 1); \
}
DECLARE_BUFFER(Byte, uint8_t);
@ -118,4 +118,9 @@ int wrenUtf8DecodeNumBytes(uint8_t byte);
// Returns the smallest power of two that is equal to or greater than [n].
int wrenPowerOf2Ceil(int n);
// Validates that [value] is within `[0, count)`. Also allows
// negative indices which map backwards from the end. Returns the valid positive
// index value. If invalid, returns `UINT32_MAX`.
uint32_t wrenValidateIndex(uint32_t count, int64_t value);
#endif

View File

@ -50,6 +50,7 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name)
classObj->superclass = NULL;
classObj->numFields = numFields;
classObj->name = name;
classObj->attributes = NULL_VAL;
wrenPushRoot(vm, (Obj*)classObj);
wrenMethodBufferInit(&classObj->methods);
@ -319,6 +320,19 @@ void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index)
list->elements.data[index] = value;
}
int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value)
{
int count = list->elements.count;
for (int i = 0; i < count; i++)
{
Value item = list->elements.data[i];
if(wrenValuesEqual(item, value)) {
return i;
}
}
return -1;
}
Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index)
{
Value removed = list->elements.data[index];
@ -374,9 +388,7 @@ static inline uint32_t hashBits(uint64_t hash)
static inline uint32_t hashNumber(double num)
{
// Hash the raw bits of the value.
DoubleBits bits;
bits.num = num;
return hashBits(bits.bits64);
return hashBits(wrenDoubleToBits(num));
}
// Generates a hash code for [object].
@ -977,7 +989,8 @@ void wrenGrayObj(WrenVM* vm, Obj* obj)
{
vm->grayCapacity = vm->grayCount * 2;
vm->gray = (Obj**)vm->config.reallocateFn(vm->gray,
vm->grayCapacity * sizeof(Obj*));
vm->grayCapacity * sizeof(Obj*),
vm->config.userData);
}
vm->gray[vm->grayCount++] = obj;
@ -1016,6 +1029,8 @@ static void blackenClass(WrenVM* vm, ObjClass* classObj)
wrenGrayObj(vm, (Obj*)classObj->name);
if(!IS_NULL(classObj->attributes)) wrenGrayObj(vm, AS_OBJ(classObj->attributes));
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjClass);
vm->bytesAllocated += classObj->methods.capacity * sizeof(Method);

View File

@ -5,6 +5,7 @@
#include <string.h>
#include "wren_common.h"
#include "wren_math.h"
#include "wren_utils.h"
// This defines the built-in types and their core representations in memory.
@ -76,6 +77,7 @@
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance
#define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList
#define IS_MAP(value) (wrenIsObjType(value, OBJ_MAP)) // ObjMap
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString
@ -182,7 +184,7 @@ typedef struct sObjUpvalue
// Pointer to the variable this upvalue is referencing.
Value* value;
// If the upvalue is closed (i.e. the local variable it was pointing too has
// If the upvalue is closed (i.e. the local variable it was pointing to has
// been popped off the stack) then the closed-over value will be hoisted out
// of the stack into here. [value] will then be changed to point to this.
Value closed;
@ -194,7 +196,7 @@ typedef struct sObjUpvalue
// The type of a primitive function.
//
// Primitives are similiar to foreign functions, but have more direct access to
// Primitives are similar to foreign functions, but have more direct access to
// VM internals. It is passed the arguments in [args]. If it returns a value,
// it places it in `args[0]` and returns `true`. If it causes a runtime error
// or modifies the running fiber, it returns `false`.
@ -316,8 +318,8 @@ typedef struct sObjFiber
Obj obj;
// The stack of value slots. This is used for holding local variables and
// temporaries while the fiber is executing. It heap-allocated and grown as
// needed.
// temporaries while the fiber is executing. It is heap-allocated and grown
// as needed.
Value* stack;
// A pointer to one past the top-most value on the stack.
@ -358,6 +360,9 @@ typedef enum
// this can directly manipulate the fiber's stack.
METHOD_PRIMITIVE,
// A primitive that handles .call on Fn.
METHOD_FUNCTION_CALL,
// A externally-defined C method.
METHOD_FOREIGN,
@ -405,6 +410,9 @@ struct sObjClass
// The name of the class.
ObjString* name;
// The ClassAttribute for the class, if any
Value attributes;
};
typedef struct
@ -609,14 +617,6 @@ typedef struct
#endif
// A union to let us reinterpret a double as raw bits and back.
typedef union
{
uint64_t bits64;
uint32_t bits32[2];
double num;
} DoubleBits;
// Creates a new "raw" class. It has no metaclass or superclass whatsoever.
// This is only used for bootstrapping the initial Object and Class classes,
// which are a little special.
@ -683,9 +683,17 @@ void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index);
// Removes and returns the item at [index] from [list].
Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index);
// Searches for [value] in [list], returns the index or -1 if not found.
int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value);
// Creates a new empty map.
ObjMap* wrenNewMap(WrenVM* vm);
// Validates that [arg] is a valid object for use as a map key. Returns true if
// it is and returns false otherwise. Use validateKey usually, for a runtime error.
// This separation exists to aid the API in surfacing errors to the developer as well.
static inline bool wrenMapIsValidKey(Value arg);
// Looks up [key] in [map]. If found, returns the value. Otherwise, returns
// `UNDEFINED_VAL`.
Value wrenMapGet(ObjMap* map, Value key);
@ -850,9 +858,7 @@ static inline Value wrenObjectToValue(Obj* obj)
static inline double wrenValueToNum(Value value)
{
#if WREN_NAN_TAGGING
DoubleBits data;
data.bits64 = value;
return data.num;
return wrenDoubleFromBits(value);
#else
return value.as.num;
#endif
@ -862,9 +868,7 @@ static inline double wrenValueToNum(Value value)
static inline Value wrenNumToValue(double num)
{
#if WREN_NAN_TAGGING
DoubleBits data;
data.num = num;
return data.bits64;
return wrenDoubleToBits(num);
#else
Value value;
value.type = VAL_NUM;
@ -873,4 +877,14 @@ static inline Value wrenNumToValue(double num)
#endif
}
static inline bool wrenMapIsValidKey(Value arg)
{
return IS_BOOL(arg)
|| IS_CLASS(arg)
|| IS_NULL(arg)
|| IS_NUM(arg)
|| IS_RANGE(arg)
|| IS_STRING(arg);
}
#endif

View File

@ -6,6 +6,7 @@
#include "wren_compiler.h"
#include "wren_core.h"
#include "wren_debug.h"
#include "wren_primitive.h"
#include "wren_vm.h"
#if WREN_OPT_META
@ -24,7 +25,7 @@
// may return a non-NULL pointer which must not be dereferenced but nevertheless
// should be freed. To prevent that, we avoid calling realloc() with a zero
// size.
static void* defaultReallocate(void* ptr, size_t newSize)
static void* defaultReallocate(void* ptr, size_t newSize, void* _)
{
if (newSize == 0)
{
@ -35,6 +36,11 @@ static void* defaultReallocate(void* ptr, size_t newSize)
return realloc(ptr, newSize);
}
int wrenGetVersionNumber()
{
return WREN_VERSION_NUMBER;
}
void wrenInitConfiguration(WrenConfiguration* config)
{
config->reallocateFn = defaultReallocate;
@ -53,15 +59,23 @@ void wrenInitConfiguration(WrenConfiguration* config)
WrenVM* wrenNewVM(WrenConfiguration* config)
{
WrenReallocateFn reallocate = defaultReallocate;
if (config != NULL) reallocate = config->reallocateFn;
void* userData = NULL;
if (config != NULL) {
userData = config->userData;
reallocate = config->reallocateFn ? config->reallocateFn : defaultReallocate;
}
WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm));
WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm), userData);
memset(vm, 0, sizeof(WrenVM));
// Copy the configuration if given one.
if (config != NULL)
{
memcpy(&vm->config, config, sizeof(WrenConfiguration));
// We choose to set this after copying,
// rather than modifying the user config pointer
vm->config.reallocateFn = reallocate;
}
else
{
@ -72,7 +86,7 @@ WrenVM* wrenNewVM(WrenConfiguration* config)
vm->grayCount = 0;
// TODO: Tune this.
vm->grayCapacity = 4;
vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*));
vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*), userData);
vm->nextGC = vm->config.initialHeapSize;
wrenSymbolTableInit(&vm->methodNames);
@ -96,7 +110,7 @@ void wrenFreeVM(WrenVM* vm)
}
// Free up the GC gray set.
vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, 0);
vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, 0, vm->config.userData);
// Tell the user if they didn't free any handles. We don't want to just free
// them here because the host app may still have pointers to them that they
@ -187,12 +201,12 @@ void wrenCollectGarbage(WrenVM* vm)
double elapsed = ((double)clock() / CLOCKS_PER_SEC) - startTime;
// Explicit cast because size_t has different sizes on 32-bit and 64-bit and
// we need a consistent type for the format string.
printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fs.\n",
printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fms.\n",
(unsigned long)before,
(unsigned long)vm->bytesAllocated,
(unsigned long)(before - vm->bytesAllocated),
(unsigned long)vm->nextGC,
elapsed);
elapsed*1000.0);
#endif
}
@ -219,7 +233,7 @@ void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize)
if (newSize > 0 && vm->bytesAllocated > vm->nextGC) wrenCollectGarbage(vm);
#endif
return vm->config.reallocateFn(memory, newSize);
return vm->config.reallocateFn(memory, newSize, vm->config.userData);
}
// Captures the local variable [local] into an [Upvalue]. If that local is
@ -445,10 +459,17 @@ static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source,
{
module = wrenNewModule(vm, AS_STRING(name));
// It's possible for the wrenMapSet below to resize the modules map,
// and trigger a GC while doing so. When this happens it will collect
// the module we've just created. Once in the map it is safe.
wrenPushRoot(vm, (Obj*)module);
// Store it in the VM's module registry so we don't load the same module
// multiple times.
wrenMapSet(vm, vm->modules, name, OBJ_VAL(module));
wrenPopRoot(vm);
// Implicitly import the core module.
ObjModule* coreModule = getModule(vm, NULL_VAL);
for (int i = 0; i < coreModule->variables.count; i++)
@ -504,7 +525,10 @@ static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue,
superclass == vm->listClass ||
superclass == vm->mapClass ||
superclass == vm->rangeClass ||
superclass == vm->stringClass)
superclass == vm->stringClass ||
superclass == vm->boolClass ||
superclass == vm->nullClass ||
superclass == vm->numClass)
{
return wrenStringFormat(vm,
"Class '@' cannot inherit from built-in class '@'.",
@ -583,6 +607,27 @@ static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
}
}
// Completes the process for creating a new class.
//
// The class attributes instance and the class itself should be on the
// top of the fiber's stack.
//
// This process handles moving the attribute data for a class from
// compile time to runtime, since it now has all the attributes associated
// with a class, including for methods.
static void endClass(WrenVM* vm)
{
// Pull the attributes and class off the stack
Value attributes = vm->fiber->stackTop[-2];
Value classValue = vm->fiber->stackTop[-1];
// Remove the stack items
vm->fiber->stackTop -= 2;
ObjClass* classObj = AS_CLASS(classValue);
classObj->attributes = attributes;
}
// Creates a new class.
//
// If [numFields] is -1, the class is a foreign class. The name and superclass
@ -693,44 +738,39 @@ static Value importModule(WrenVM* vm, Value name)
wrenPushRoot(vm, AS_OBJ(name));
WrenLoadModuleResult result = {0};
const char* source = NULL;
bool allocatedSource = true;
// Let the host try to provide the module.
if (vm->config.loadModuleFn != NULL)
{
source = vm->config.loadModuleFn(vm, AS_CSTRING(name));
result = vm->config.loadModuleFn(vm, AS_CSTRING(name));
}
// If the host didn't provide it, see if it's a built in optional module.
if (source == NULL)
if (result.source == NULL)
{
result.onComplete = NULL;
ObjString* nameString = AS_STRING(name);
#if WREN_OPT_META
if (strcmp(nameString->value, "meta") == 0) source = wrenMetaSource();
if (strcmp(nameString->value, "meta") == 0) result.source = wrenMetaSource();
#endif
#if WREN_OPT_RANDOM
if (strcmp(nameString->value, "random") == 0) source = wrenRandomSource();
if (strcmp(nameString->value, "random") == 0) result.source = wrenRandomSource();
#endif
// TODO: Should we give the host the ability to provide strings that don't
// need to be freed?
allocatedSource = false;
}
if (source == NULL)
if (result.source == NULL)
{
vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name);
wrenPopRoot(vm); // name.
return NULL_VAL;
}
ObjClosure* moduleClosure = compileInModule(vm, name, source, false, true);
ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true);
// Modules loaded by the host are expected to be dynamically allocated with
// ownership given to the VM, which will free it. The built in optional
// modules are constant strings which don't need to be freed.
if (allocatedSource) DEALLOCATE(vm, (char*)source);
// Now that we're done, give the result back in case there's cleanup to do.
if(result.onComplete) result.onComplete(vm, AS_CSTRING(name), result);
if (moduleClosure == NULL)
{
@ -766,6 +806,21 @@ static Value getModuleVariable(WrenVM* vm, ObjModule* module,
return NULL_VAL;
}
inline static bool checkArity(WrenVM* vm, Value value, int numArgs)
{
ASSERT(IS_CLOSURE(value), "Receiver must be a closure.");
ObjFn* fn = AS_CLOSURE(value)->fn;
// We only care about missing arguments, not extras. The "- 1" is because
// numArgs includes the receiver, the function itself, which we don't want to
// count.
if (numArgs - 1 >= fn->arity) return true;
vm->fiber->error = CONST_STRING(vm, "Function expects more arguments.");
return false;
}
// The main bytecode interpreter loop. This is where the magic happens. It is
// also, as you can imagine, highly performance critical.
static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
@ -797,36 +852,37 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
// Use this after a CallFrame has been pushed or popped to refresh the local
// variables.
#define LOAD_FRAME() \
frame = &fiber->frames[fiber->numFrames - 1]; \
stackStart = frame->stackStart; \
ip = frame->ip; \
fn = frame->closure->fn;
#define LOAD_FRAME() \
do \
{ \
frame = &fiber->frames[fiber->numFrames - 1]; \
stackStart = frame->stackStart; \
ip = frame->ip; \
fn = frame->closure->fn; \
} while (false)
// Terminates the current fiber with error string [error]. If another calling
// fiber is willing to catch the error, transfers control to it, otherwise
// exits the interpreter.
#define RUNTIME_ERROR() \
do \
{ \
STORE_FRAME(); \
runtimeError(vm); \
if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \
fiber = vm->fiber; \
LOAD_FRAME(); \
DISPATCH(); \
} \
while (false)
#define RUNTIME_ERROR() \
do \
{ \
STORE_FRAME(); \
runtimeError(vm); \
if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \
fiber = vm->fiber; \
LOAD_FRAME(); \
DISPATCH(); \
} while (false)
#if WREN_DEBUG_TRACE_INSTRUCTIONS
// Prints the stack and instruction before each instruction is executed.
#define DEBUG_TRACE_INSTRUCTIONS() \
do \
{ \
wrenDumpStack(fiber); \
wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \
} \
while (false)
#define DEBUG_TRACE_INSTRUCTIONS() \
do \
{ \
wrenDumpStack(fiber); \
wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \
} while (false)
#else
#define DEBUG_TRACE_INSTRUCTIONS() do { } while (false)
#endif
@ -842,19 +898,18 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
#define INTERPRET_LOOP DISPATCH();
#define CASE_CODE(name) code_##name
#define DISPATCH() \
do \
{ \
DEBUG_TRACE_INSTRUCTIONS(); \
goto *dispatchTable[instruction = (Code)READ_BYTE()]; \
} \
while (false)
#define DISPATCH() \
do \
{ \
DEBUG_TRACE_INSTRUCTIONS(); \
goto *dispatchTable[instruction = (Code)READ_BYTE()]; \
} while (false)
#else
#define INTERPRET_LOOP \
loop: \
DEBUG_TRACE_INSTRUCTIONS(); \
#define INTERPRET_LOOP \
loop: \
DEBUG_TRACE_INSTRUCTIONS(); \
switch (instruction = (Code)READ_BYTE())
#define CASE_CODE(name) case CODE_##name
@ -1007,6 +1062,17 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
}
break;
case METHOD_FUNCTION_CALL:
if (!checkArity(vm, args[0], numArgs)) {
RUNTIME_ERROR();
break;
}
STORE_FRAME();
method->as.primitive(vm, args);
LOAD_FRAME();
break;
case METHOD_FOREIGN:
callForeign(vm, fiber, method->as.foreign, numArgs);
if (wrenHasError(fiber)) RUNTIME_ERROR();
@ -1100,7 +1166,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
uint16_t offset = READ_SHORT();
Value condition = POP();
if (IS_FALSE(condition) || IS_NULL(condition)) ip += offset;
if (wrenIsFalsyValue(condition)) ip += offset;
DISPATCH();
}
@ -1109,7 +1175,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
uint16_t offset = READ_SHORT();
Value condition = PEEK();
if (IS_FALSE(condition) || IS_NULL(condition))
if (wrenIsFalsyValue(condition))
{
// Short-circuit the right hand side.
ip += offset;
@ -1127,7 +1193,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
uint16_t offset = READ_SHORT();
Value condition = PEEK();
if (IS_FALSE(condition) || IS_NULL(condition))
if (wrenIsFalsyValue(condition))
{
// Discard the condition and evaluate the right hand side.
DROP();
@ -1229,6 +1295,13 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
DISPATCH();
}
CASE_CODE(END_CLASS):
{
endClass(vm);
if (wrenHasError(fiber)) RUNTIME_ERROR();
DISPATCH();
}
CASE_CODE(CLASS):
{
createClass(vm, READ_BYTE(), NULL);
@ -1446,7 +1519,8 @@ WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
wrenPushRoot(vm, (Obj*)closure);
ObjFiber* fiber = wrenNewFiber(vm, closure);
wrenPopRoot(vm); // closure.
vm->apiStack = NULL;
return runInterpreter(vm, fiber);
}
@ -1592,6 +1666,7 @@ WrenType wrenGetSlotType(WrenVM* vm, int slot)
if (IS_NUM(vm->apiStack[slot])) return WREN_TYPE_NUM;
if (IS_FOREIGN(vm->apiStack[slot])) return WREN_TYPE_FOREIGN;
if (IS_LIST(vm->apiStack[slot])) return WREN_TYPE_LIST;
if (IS_MAP(vm->apiStack[slot])) return WREN_TYPE_MAP;
if (IS_NULL(vm->apiStack[slot])) return WREN_TYPE_NULL;
if (IS_STRING(vm->apiStack[slot])) return WREN_TYPE_STRING;
@ -1690,6 +1765,11 @@ void wrenSetSlotNewList(WrenVM* vm, int slot)
setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0)));
}
void wrenSetSlotNewMap(WrenVM* vm, int slot)
{
setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm)));
}
void wrenSetSlotNull(WrenVM* vm, int slot)
{
setSlot(vm, slot, NULL_VAL);
@ -1723,9 +1803,27 @@ void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot)
validateApiSlot(vm, listSlot);
validateApiSlot(vm, elementSlot);
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list.");
ValueBuffer elements = AS_LIST(vm->apiStack[listSlot])->elements;
vm->apiStack[elementSlot] = elements.data[index];
uint32_t usedIndex = wrenValidateIndex(elements.count, index);
ASSERT(usedIndex != UINT32_MAX, "Index out of bounds.");
vm->apiStack[elementSlot] = elements.data[usedIndex];
}
void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot)
{
validateApiSlot(vm, listSlot);
validateApiSlot(vm, elementSlot);
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list.");
ObjList* list = AS_LIST(vm->apiStack[listSlot]);
uint32_t usedIndex = wrenValidateIndex(list->elements.count, index);
ASSERT(usedIndex != UINT32_MAX, "Index out of bounds.");
list->elements.data[usedIndex] = vm->apiStack[elementSlot];
}
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot)
@ -1736,7 +1834,8 @@ void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot)
ObjList* list = AS_LIST(vm->apiStack[listSlot]);
// Negative indices count from the end.
// Negative indices count from the end.
// We don't use wrenValidateIndex here because insert allows 1 past the end.
if (index < 0) index = list->elements.count + 1 + index;
ASSERT(index <= list->elements.count, "Index out of bounds.");
@ -1744,13 +1843,90 @@ void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot)
wrenListInsert(vm, list, vm->apiStack[elementSlot], index);
}
int wrenGetMapCount(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
ASSERT(IS_MAP(vm->apiStack[slot]), "Slot must hold a map.");
ObjMap* map = AS_MAP(vm->apiStack[slot]);
return map->count;
}
bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot)
{
validateApiSlot(vm, mapSlot);
validateApiSlot(vm, keySlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
Value key = vm->apiStack[keySlot];
ASSERT(wrenMapIsValidKey(key), "Key must be a value type");
if (!validateKey(vm, key)) return false;
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
Value value = wrenMapGet(map, key);
return !IS_UNDEFINED(value);
}
void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot)
{
validateApiSlot(vm, mapSlot);
validateApiSlot(vm, keySlot);
validateApiSlot(vm, valueSlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
Value value = wrenMapGet(map, vm->apiStack[keySlot]);
if (IS_UNDEFINED(value)) {
value = NULL_VAL;
}
vm->apiStack[valueSlot] = value;
}
void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot)
{
validateApiSlot(vm, mapSlot);
validateApiSlot(vm, keySlot);
validateApiSlot(vm, valueSlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Must insert into a map.");
Value key = vm->apiStack[keySlot];
ASSERT(wrenMapIsValidKey(key), "Key must be a value type");
if (!validateKey(vm, key)) {
return;
}
Value value = vm->apiStack[valueSlot];
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
wrenMapSet(vm, map, key, value);
}
void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot,
int removedValueSlot)
{
validateApiSlot(vm, mapSlot);
validateApiSlot(vm, keySlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
Value key = vm->apiStack[keySlot];
if (!validateKey(vm, key)) {
return;
}
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
Value removed = wrenMapRemoveKey(vm, map, key);
setSlot(vm, removedValueSlot, removed);
}
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
int slot)
{
ASSERT(module != NULL, "Module cannot be NULL.");
ASSERT(name != NULL, "Variable name cannot be NULL.");
validateApiSlot(vm, slot);
Value moduleName = wrenStringFormat(vm, "$", module);
wrenPushRoot(vm, AS_OBJ(moduleName));
@ -1766,6 +1942,40 @@ void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
setSlot(vm, slot, moduleObj->variables.data[variableSlot]);
}
bool wrenHasVariable(WrenVM* vm, const char* module, const char* name)
{
ASSERT(module != NULL, "Module cannot be NULL.");
ASSERT(name != NULL, "Variable name cannot be NULL.");
Value moduleName = wrenStringFormat(vm, "$", module);
wrenPushRoot(vm, AS_OBJ(moduleName));
//We don't use wrenHasModule since we want to use the module object.
ObjModule* moduleObj = getModule(vm, moduleName);
ASSERT(moduleObj != NULL, "Could not find module.");
wrenPopRoot(vm); // moduleName.
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
name, strlen(name));
return variableSlot != -1;
}
bool wrenHasModule(WrenVM* vm, const char* module)
{
ASSERT(module != NULL, "Module cannot be NULL.");
Value moduleName = wrenStringFormat(vm, "$", module);
wrenPushRoot(vm, AS_OBJ(moduleName));
ObjModule* moduleObj = getModule(vm, moduleName);
wrenPopRoot(vm); // moduleName.
return moduleObj != NULL;
}
void wrenAbortFiber(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);

View File

@ -8,7 +8,7 @@
// The maximum number of temporary objects that can be made visible to the GC
// at one time.
#define WREN_MAX_TEMP_ROOTS 5
#define WREN_MAX_TEMP_ROOTS 8
typedef enum
{
@ -243,4 +243,9 @@ static inline bool wrenIsLocalName(const char* name)
return name[0] >= 'a' && name[0] <= 'z';
}
static inline bool wrenIsFalsyValue(Value value)
{
return IS_FALSE(value) || IS_NULL(value);
}
#endif

View File

@ -40,6 +40,9 @@ WrenForeignMethodFn APITest_bindForeignMethod(
method = listsBindMethod(fullName);
if (method != NULL) return method;
method = mapsBindMethod(fullName);
if (method != NULL) return method;
method = newVMBindMethod(fullName);
if (method != NULL) return method;

View File

@ -16,6 +16,7 @@
#include "foreign_class.h"
#include "handle.h"
#include "lists.h"
#include "maps.h"
#include "new_vm.h"
#include "reset_stack_after_call_abort.h"
#include "reset_stack_after_foreign_construct.h"

View File

@ -2,6 +2,7 @@
#include <string.h>
#include "wren.h"
#include "../test.h"
int callWrenCallRootRunTests(WrenVM* vm)
{
@ -17,7 +18,7 @@ int callWrenCallRootRunTests(WrenVM* vm)
WrenInterpretResult result = wrenCall(vm, run);
if (result == WREN_RESULT_RUNTIME_ERROR)
{
exitCode = 70;
exitCode = WREN_EX_SOFTWARE;
}
else
{

View File

@ -32,6 +32,25 @@ static void otherModule(WrenVM* vm)
wrenGetVariable(vm, "./test/api/get_variable_module", "Variable", 0);
}
static void hasVariable(WrenVM* vm)
{
const char* module = wrenGetSlotString(vm, 1);
const char* variable = wrenGetSlotString(vm, 2);
bool result = wrenHasVariable(vm, module, variable);
wrenEnsureSlots(vm, 1);
wrenSetSlotBool(vm, 0, result);
}
static void hasModule(WrenVM* vm)
{
const char* module = wrenGetSlotString(vm, 1);
bool result = wrenHasModule(vm, module);
wrenEnsureSlots(vm, 1);
wrenSetSlotBool(vm, 0, result);
}
WrenForeignMethodFn getVariableBindMethod(const char* signature)
{
if (strcmp(signature, "static GetVariable.beforeDefined()") == 0) return beforeDefined;
@ -39,6 +58,9 @@ WrenForeignMethodFn getVariableBindMethod(const char* signature)
if (strcmp(signature, "static GetVariable.afterAssigned()") == 0) return afterAssigned;
if (strcmp(signature, "static GetVariable.otherSlot()") == 0) return otherSlot;
if (strcmp(signature, "static GetVariable.otherModule()") == 0) return otherModule;
if (strcmp(signature, "static Has.variable(_,_)") == 0) return hasVariable;
if (strcmp(signature, "static Has.module(_)") == 0) return hasModule;
return NULL;
}

View File

@ -8,6 +8,11 @@ class GetVariable {
foreign static otherModule()
}
class Has {
foreign static variable(module, variable)
foreign static module(module)
}
System.print(GetVariable.beforeDefined()) // expect: null
var A = "a"
@ -22,3 +27,13 @@ var B = "b"
System.print(GetVariable.otherSlot()) // expect: b
System.print(GetVariable.otherModule()) // expect: value
System.print(Has.variable("./test/api/get_variable_module", "Variable")) // expect: true
System.print(Has.variable("./test/api/get_variable_module", "NotAVariable")) // expect: false
System.print(Has.variable("./test/api/get_variable", "Has")) // expect: true
System.print(Has.variable("./test/api/get_variable", "Fake")) // expect: false
System.print(Has.module("./test/api/get_variable_module")) // expect: true
System.print(Has.module("./test/api/get_variable")) // expect: true
System.print(Has.module("not a module")) // expect: false

View File

@ -15,6 +15,14 @@ static void insertNumber(WrenVM* vm, int index, double value)
wrenInsertInList(vm, 0, index, 1);
}
// Helper function to append a double in a slot then insert it into the list at
// slot zero.
static void appendNumber(WrenVM* vm, double value)
{
wrenSetSlotDouble(vm, 1, value);
wrenInsertInList(vm, 0, -1, 1);
}
static void insert(WrenVM* vm)
{
wrenSetSlotNewList(vm, 0);
@ -37,10 +45,40 @@ static void insert(WrenVM* vm)
insertNumber(vm, -3, 9.0);
}
static void get(WrenVM* vm)
{
int listSlot = 1;
int index = (int)wrenGetSlotDouble(vm, 2);
wrenGetListElement(vm, listSlot, index, 0);
}
static void set(WrenVM* vm)
{
wrenSetSlotNewList(vm, 0);
wrenEnsureSlots(vm, 2);
appendNumber(vm, 1.0);
appendNumber(vm, 2.0);
appendNumber(vm, 3.0);
appendNumber(vm, 4.0);
//list[2] = 33
wrenSetSlotDouble(vm, 1, 33);
wrenSetListElement(vm, 0, 2, 1);
//list[-1] = 44
wrenSetSlotDouble(vm, 1, 44);
wrenSetListElement(vm, 0, -1, 1);
}
WrenForeignMethodFn listsBindMethod(const char* signature)
{
if (strcmp(signature, "static Lists.newList()") == 0) return newList;
if (strcmp(signature, "static Lists.insert()") == 0) return insert;
if (strcmp(signature, "static Lists.set()") == 0) return set;
if (strcmp(signature, "static Lists.get(_,_)") == 0) return get;
return NULL;
}

View File

@ -1,6 +1,8 @@
class Lists {
foreign static newList()
foreign static insert()
foreign static set()
foreign static get(list, index)
}
var list = Lists.newList()
@ -8,3 +10,7 @@ System.print(list is List) // expect: true
System.print(list.count) // expect: 0
System.print(Lists.insert()) // expect: [4, 5, 6, 1, 2, 3, 9, 8, 7]
System.print(Lists.set()) // expect: [1, 2, 33, 44]
System.print(Lists.get([1,2,3,4], -2)) // expect: 3
System.print(Lists.get([1,2,3,4], 1)) // expect: 2

130
test/api/maps.c Normal file
View File

@ -0,0 +1,130 @@
#include <string.h>
#include "maps.h"
static void newMap(WrenVM* vm)
{
wrenSetSlotNewMap(vm, 0);
}
static void invalidInsert(WrenVM* vm)
{
wrenSetSlotNewMap(vm, 0);
wrenEnsureSlots(vm, 3);
// Foreign Class is in slot 1
wrenSetSlotString(vm, 2, "England");
wrenSetMapValue(vm, 0, 1, 2); // expect this to cause errors
}
static void insert(WrenVM* vm)
{
wrenSetSlotNewMap(vm, 0);
wrenEnsureSlots(vm, 3);
// Insert String
wrenSetSlotString(vm, 1, "England");
wrenSetSlotString(vm, 2, "London");
wrenSetMapValue(vm, 0, 1, 2);
// Insert Double
wrenSetSlotDouble(vm, 1, 1.0);
wrenSetSlotDouble(vm, 2, 42.0);
wrenSetMapValue(vm, 0, 1, 2);
// Insert Boolean
wrenSetSlotBool(vm, 1, false);
wrenSetSlotBool(vm, 2, true);
wrenSetMapValue(vm, 0, 1, 2);
// Insert Null
wrenSetSlotNull(vm, 1);
wrenSetSlotNull(vm, 2);
wrenSetMapValue(vm, 0, 1, 2);
// Insert List
wrenSetSlotString(vm, 1, "Empty");
wrenSetSlotNewList(vm, 2);
wrenSetMapValue(vm, 0, 1, 2);
}
static void removeKey(WrenVM* vm)
{
wrenEnsureSlots(vm, 3);
wrenSetSlotString(vm, 2, "key");
wrenRemoveMapValue(vm, 1, 2, 0);
}
static void countWren(WrenVM* vm)
{
int count = wrenGetMapCount(vm, 1);
wrenSetSlotDouble(vm, 0, count);
}
static void countAPI(WrenVM* vm)
{
insert(vm);
int count = wrenGetMapCount(vm, 0);
wrenSetSlotDouble(vm, 0, count);
}
static void containsWren(WrenVM* vm)
{
bool result = wrenGetMapContainsKey(vm, 1, 2);
wrenSetSlotBool(vm, 0, result);
}
static void containsAPI(WrenVM* vm)
{
insert(vm);
wrenEnsureSlots(vm, 1);
wrenSetSlotString(vm, 1, "England");
bool result = wrenGetMapContainsKey(vm, 0, 1);
wrenSetSlotBool(vm, 0, result);
}
static void containsAPIFalse(WrenVM* vm)
{
insert(vm);
wrenEnsureSlots(vm, 1);
wrenSetSlotString(vm, 1, "DefinitelyNotARealKey");
bool result = wrenGetMapContainsKey(vm, 0, 1);
wrenSetSlotBool(vm, 0, result);
}
WrenForeignMethodFn mapsBindMethod(const char* signature)
{
if (strcmp(signature, "static Maps.newMap()") == 0) return newMap;
if (strcmp(signature, "static Maps.insert()") == 0) return insert;
if (strcmp(signature, "static Maps.remove(_)") == 0) return removeKey;
if (strcmp(signature, "static Maps.count(_)") == 0) return countWren;
if (strcmp(signature, "static Maps.count()") == 0) return countAPI;
if (strcmp(signature, "static Maps.contains()") == 0) return containsAPI;
if (strcmp(signature, "static Maps.containsFalse()") == 0) return containsAPIFalse;
if (strcmp(signature, "static Maps.contains(_,_)") == 0) return containsWren;
if (strcmp(signature, "static Maps.invalidInsert(_)") == 0) return invalidInsert;
return NULL;
}
void foreignAllocate(WrenVM* vm) {
wrenSetSlotNewForeign(vm, 0, 0, 0);
}
void mapBindClass(
const char* className, WrenForeignClassMethods* methods)
{
if (strcmp(className, "ForeignClass") == 0)
{
methods->allocate = foreignAllocate;
return;
}
}

5
test/api/maps.h Normal file
View File

@ -0,0 +1,5 @@
#include "wren.h"
WrenForeignMethodFn mapsBindMethod(const char* signature);
void mapBindClass(
const char* className, WrenForeignClassMethods* methods);

64
test/api/maps.wren Normal file
View File

@ -0,0 +1,64 @@
class ForeignClass {
construct new() {}
}
class Maps {
foreign static newMap()
foreign static insert()
foreign static contains(map, key)
foreign static contains()
foreign static containsFalse()
foreign static count()
foreign static count(map)
foreign static remove(map)
foreign static invalidInsert(obj)
}
// map new + get/set API
var map = Maps.newMap()
System.print(map is Map) // expect: true
System.print(map.count) // expect: 0
var data = Maps.insert()
System.print(data["England"]) // expect: London
System.print(data["Empty"]) // expect: []
System.print(data[1.0]) // expect: 42
System.print(data[false]) // expect: true
System.print(data[null]) // expect: null
// remove API
var removed = Maps.remove({ "key":"value", "other":"data" })
System.print(removed) // expect: value
var removedNone = Maps.remove({})
System.print(removedNone) // expect: null
// count API
var countMap = { "key":"value", "other":"data", 4:"number key" }
System.print(Maps.count(countMap)) // expect: 3
Maps.remove(countMap) //remove using API
System.print(Maps.count(countMap)) // expect: 2
countMap.remove("other") //remove wren side
System.print(Maps.count(countMap)) // expect: 1
var countAPI = Maps.count()
System.print(countAPI) // expect: 5
//contains key API
var containsMap = { "key":"value", "other":"data", 4:"number key" }
System.print(Maps.contains(containsMap, "key")) // expect: true
System.print(Maps.contains(containsMap, "fake")) // expect: false
System.print(Maps.contains(containsMap, "other")) // expect: true
Maps.remove(containsMap) //remove using API
System.print(Maps.contains(containsMap, "key")) // expect: false
containsMap.remove("other") //remove wren side
System.print(Maps.contains(containsMap, "other")) // expect: false
System.print(Maps.contains()) // expect: true
System.print(Maps.containsFalse()) // expect: false

View File

@ -13,9 +13,47 @@ static void nullConfig(WrenVM* vm)
wrenFreeVM(otherVM);
}
static void multipleInterpretCalls(WrenVM* vm)
{
WrenVM* otherVM = wrenNewVM(NULL);
WrenInterpretResult result;
bool correct = true;
// Handles should be valid across calls into Wren code.
WrenHandle* absMethod = wrenMakeCallHandle(otherVM, "abs");
result = wrenInterpret(otherVM, "main", "import \"random\" for Random");
correct = correct && (result == WREN_RESULT_SUCCESS);
for (int i = 0; i < 5; i++) {
// Calling `wrenEnsureSlots()` before `wrenInterpret()` should not introduce
// problems later.
wrenEnsureSlots(otherVM, 2);
// Calling a foreign function should succeed.
result = wrenInterpret(otherVM, "main", "Random.new(12345)");
correct = correct && (result == WREN_RESULT_SUCCESS);
wrenEnsureSlots(otherVM, 2);
wrenSetSlotDouble(otherVM, 0, -i);
result = wrenCall(otherVM, absMethod);
correct = correct && (result == WREN_RESULT_SUCCESS);
double absValue = wrenGetSlotDouble(otherVM, 0);
correct = correct && (absValue == (double)i);
}
wrenSetSlotBool(vm, 0, correct);
wrenReleaseHandle(otherVM, absMethod);
wrenFreeVM(otherVM);
}
WrenForeignMethodFn newVMBindMethod(const char* signature)
{
if (strcmp(signature, "static VM.nullConfig()") == 0) return nullConfig;
if (strcmp(signature, "static VM.multipleInterpretCalls()") == 0) return multipleInterpretCalls;
return NULL;
}

View File

@ -1,6 +1,8 @@
class VM {
foreign static nullConfig()
foreign static multipleInterpretCalls()
}
// TODO: Other configuration settings.
System.print(VM.nullConfig()) // expect: true
System.print(VM.multipleInterpretCalls()) // expect: true

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