Now that I'm starting to write a real async scheduler on top of Wren's
basic fiber API, I have a better feel for what it needs. It turns out
run() is not it.
- Remove run() methods.
- Add transfer() which leaves the caller of the invoked fiber alone.
- Add suspend() to return control to the host application.
- Add Timer.schedule() to start a new independently scheduled fiber.
- Change Timer.sleep() so that it only transfers control to explicitly
scheduled fibers, not any one.
- Create separate libs for each architecture. OS X doesn't need this
(we just build a universal binary), but it will help Linux.
- Move the libuv build stuff into wren.mk where the actual dependency
on the lib is.
- Download libuv to deps/ instead of build/. That way "make clean"
doesn't blow it away.
- Don't redownload libuv unless needed.
Most of the pieces are there:
- You can declare a foreign class.
- It will call your C function to provide an allocator function.
- Whenever a foreign object is created, it calls the allocator.
- Foreign methods can access the foreign bytes of an object.
- Most of the runtime checking is in place for things like subclassing
foreign classes.
There is still some loose ends to tie up:
- Finalizers are not called.
- Some of the error-handling could be better.
- The GC doesn't track how much memory a marked foreign object uses.
This adds a "timer" module to the CLI that provides a Timer class with
a static sleep() method. Not the most exciting functionality in the
world, but it requires the full hunk of libuv integration:
- The CLI sets up libuv and runs the event loop.
- Added a new directory src/module for CLI modules.
- Updated all the make scripts to handle it.
- Reorganized some other CLI code.
* Eliminate "new" reserved word.
* Allow "this" before a method definition to define a constructor.
* Only create a default constructor for classes that don't define one.
If a test expected an error and found at least one, it would not fail
on any other expected errors that didn't occur.
Also, some tests were expecting a compile time error message even though
the test script doesn't validate those (yet).
The test function was getting monolithic, so I went ahead and split it
into a separate little class.
Previously, fibers had a hard-coded limit to how big their stack size
is. This limit exists in two forms: the number of distinct call frames
(basically the maximum call depth), and the number of unique stack
slots.
This fixes the first half of this by dynamically allocating the call
frame array and growing it as needed. This makes new fibers smallers
since they can start with a very small array. Checking and growing as
needed doesn't noticeably regress the perf on the other benchmarks, and
it makes a new fiber benchmark about 45% faster.
The stack array is still hardcoded, but that will be in another commit.
Had to add a new metaclass for Object since it now has its own static method so we
can't just use Class as its metaclass. (We don't want *every* class to have a same(_,_)
static method.)
- Made it use primitives instead of foreign functions.
- This fixed an issue where the interpreter loop was running re-entrantly.
- Which in turn fixed a GC bug.
- Report a runtime error if the argument isn't a string.
- Report a runtime error if the source doesn't compile.
The methods Sequence.map and Sequence.where are now implemented using
deferred execution. They return an instance of a new Sequence-derived
class that performs the operation while iterating. This has three main
advantages:
* It can be computationally cheaper when not the whole sequence is
iterated.
* It consumes less memory since it does not store the result in a newly
allocated list.
* They can work on infinite sequences.
Some disadvantages are:
* Iterating the returned iterator will be slightly slower due to
the added indirection.
* You should be aware that modifications made to the original sequence
will affect the returned sequence.
* If you need the result in a list, you now need to call Sequence.list
on the result.
This is a bit of a style preference since of course you can always write
the same thing with a for loop. However, I think sometimes the code
looks better when using this method.
It also provides an alternative to Sequence.map for cases where you
don't need the resulting list, and one that becomes especially necessary
when Sequence.map is changed to return a new sequence. The example in
the README.md file was using Sequence.map in a way that required this
alternative in that case.
When possible, they return the actual value from the predicate
instead of always just "true" and "false". This matches && and ||
which evaluate to the RHS or LHS when appropriate.
- "\x" escape sequence to put byte values in strings: "\x34"
- String.byteAt(index) gets value of byte in string.
- String.bytes returns a raw sequence of bytes for a string.
- String.codePointAt(index) gets the code point at an offset as a raw number.