It was a bit silly that you've first had to declare a string builder
variable and then pass a reference to that into the init function, which
could not fail. Instead just return a ready to use string builder :)
A function can now have multiple subfunctions with their own parameter
list. These parameters are now no longer constrained to variables and
blanks only, but can also be consts and list destructurings (predicates
are also already compiled but don't get evaluated yet). The first
subfunction that matches the argument list gets evaluated.
When the GC calls the matchers deinit function, the milist (also an GC-able
object) might already have been deinitialized, so we can't use the count
values from there.
Maybe milist should actually be refcounted? It doesn't build cycles and
doesn't reference other GC objects. This would simplify it and remove the
now duplicated count data.
This allows the destructuring of lists into individual values.
We can have arbitrarily nested lists, can check for constant values and can
have up to one '~'-prefixed variable per list, that will capture the
remaining elements of the list.
It is implemented as a second set of bytecode instructions, which define a
matcher. These matchers should also enable us to implement the same pattern
matching capabiities for function parameters.
Not all matching features are implemented yet, predicate matching and
matching into a dictionary key is not implemented yet.
We failed at dumping empty strings, because the file writer reported 0
bytes written as an error. Let's just not write empty buffers in the first
place, this fixes the bug and also avoids some pointless function calls.
We can now define and call functions. Lexical closure scopes are also
working :).
It's limited to simple functions or complex functions with a single
argument list of only variable names for now.
The new roots callback mechanism makes it possible to traverse the gc
objects on the stack without needing the stack to be an gc object.
This also uncovered a nasty bug in apfl_stack_pop where we passed in a
wrong mem pointer into apfl_resizable_splice. We should probably find a way
to make the apfl_resizable_* functions a bit safer, the need for casting
stuff to void** easily hides errors.
This let's us get rid of that awkward hashmap in the GC that was used as a
set, makes determining the roots more flexible and now gc_init can't even
fail any more, as there are no allocations happening here any more :)
This way we can see the parse errors again in evaluation mode
Not fully fleshed out yet: We simply use apfl_debug_print_val to dump the
top of the stack (the formatted error) to stderr but don't nicely handle
if there is nothing on the stack (apfl_debug_print_val will print a rather
cryptic "stack index invalid" error). Also the whole dance we need to do to
put the formatted error onto the stack feels rather awkward (there should
probably a function for this) and we also should probably try to push an
error description on the stack in case this moving-string-to-stack business
fails.
Now "only" all other errors need to be put on the stack as a string :)
The callback and the opaque data are now grouped together in a struct
instead of being passed individually into the tokenizer.
This also exposes the string source reader struct and therefore removes
the need of heap allocating it. Neat!