Instead, Fn.call(...) is a special *method* type that has the same
special sauce. The goal is eventually to get rid of the primitive
result type entirely.
- Add Fiber.transferError(_).
- Primitives place runtime errors directly in the fiber instead of on
the stack.
- Primitives that change fibers set it directly in the VM.
- Allow a fiber's error to be any object (except null).
- Add an explicit va_list version. That lets variadic functions
forward to it.
- Fix a GC bug in wrenCall() with return values.
- Make the call API test not re-enter the VM.
- Test that a foreign method can return strings.
- Test that a foreign method can return a string with null bytes.
- Test wrenCall().
- Allow passing NULL for "v" to wrenCall().
- Allow "a" for passing an explicit length byte array to wrenCall().
Get rid of the separate opt-in IO class and replace it with a core
System class.
- Remove wren_io.c, wren_io.h, and io.wren.
- Remove the flags that disable it.
- Remove the overloads for print() with different arity. (It was an
experiment, but I don't think it's that useful.)
- Remove IO.read(). That will reappear using libuv in the CLI at some
point.
- Remove IO.time. Doesn't seem to have been used.
- Update all of the tests, docs, etc.
I'm sorry for all the breakage this causes, but I think "System" is a
better name for this class (it makes it natural to add things like
"System.gc()") and frees up "IO" for referring to the CLI's IO module.
The .count getter on string returns the number of code points. That's
O(n), but it's consistent with the rest of the main string API.
If you want the number of bytes, it's "string".bytes.count.
Updated the docs.
Fixes 68. Woo!
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.
Returns the number of elements in the sequence that pass the
`predicate`.
It could also have been implemented as:
count(f) { reduce(0) {|a, b| f.call(b) ? a + 1 : a } }
But I considered the simple version more readable.
Also documented Sequence.count.
The previous order, insert(element, index), was counter-intuitive.
I'm not aware of any list API that uses this order. I've checked:
* Ruby Array.insert(index, obj...)
* JavaScript array.splice(start, deleteCount[, item1[, item2[, ...]]])
* C++ / QList::insert(int i, const T & value)
* C++ / std::vector::insert
* Lua table.insert (list, [pos,] value)
* C# List<T>.Insert(int index, T item)
* Java Interface List<E>.add(int index, E element)
* Python list.insert(i, x)
So it seemed to me more like an oversight in Wren.
- Allow yielding the main fiber. This exits the interpreter. The
host can resume it by calling a method.
- Allow getting a reference to the current fiber.
Right now, it uses a weird "import_" method on String which
should either be replaced or at least hidden behind some syntax.
But it does roughly the right thing. Still lots of corner cases to
clean up and stuff to fix. In particular:
- Need to handle compilation errors in imported modules.
- Need to implicitly import all core and IO types into imported module.
- Need to handle circular imports.
(Just need to give entry module the right name for this to work.)
Still lots of methods missing and clean up and tests to do.
Also still no literal syntax.
But the core hash table code is there and working. The supported
key types are all, uh, supported.
If a capitalized name cannot be resolved, a new top-level
variable with its name is implicitly declared. If a real
definition is not found later, a compile time error is raised.
Mutual recursion at the top level works now!
Fix#101. Fix#106.
Inside a method, all local variable lookup stops at the method boundary. In other
words, methods, do not close over outer local variables.
If a name is not found inside a method and is lowercase, it's a method on this.
If it's capitalized, it's a global variable.
Fixes#93.
Previously, "1 -1" was lexed as two number tokens: a positive literal and
a negative literal. This caused problems when it came to parsing.
Now the '-' and the second number are separate tokens.
Note this is a breaking change, since `-16.sqrt` is now parsed as `-(16.sqrt)`,
as opposed to `(-16).sqrt`.
There is a small bit of overhead to doing it this way, but it might be possible
to optimize that out in the compiler at some point in the future.