For two reasons:
- We'll later want apfl code to load other apfl code. If an IO error
happens in that case, we don't want that to be a fatal error, only a
regular error that can be catched in apfl (once we have something like
`try`).
- I want to get rid of fatal errors as a generic category completely.
Instead the error reporting functions themselves should tell you the type
of error directly, if it was something fatal.
Previously we were getting a pointer into the value stack for the parts
list. However, that pointer would not necessarily be a valid pointer later
any more, as we're also manipulating the value stack.
This introduces the higher level API apfl_set_list_member_by_index to
modify a list and also adds an option for our test suite to run the tests
with valgrind --tool=memcheck. This should catch such errors in the future.
The iterative runner used functions that could throw errors on OOM before
protecting itself using apfl_call_protected. An error at that point would
have resulted in calling the panic handler as a last resort.
This now introduces the apfl_do_protected function which allows running C
code protected without needing to wrap it in a cfunc value, thus removing
the need for the functions that could throw in the iterative runner.
This implements the last remaining feature of the language syntax! :)
The implementation of this was delightfully simple, since all the heavy
lifting is done by already implemented functions.
We called return_from_matcher once inside
matcher_evaluate_capturing_instruction and outside of it. That way we
returned not to the parent call stack entry but to the grandparent.
It's now possible to assign to a key of a dictionary and even to a nested
key path.
This patch changes the way matchers work a bit:
First, a function call stack frame now has a stack of matchers that are
manipulateable instead of a single matcher.
Second, the matcher is now in charge of setting the matched values to the
variables (previously the caller of the matcher needed to extract the
matched values and assign them itself). This change simplifies code
generation, especially for chained assignments and dictionary key paths.
This removes the last usage of APFL_ERR_NOT_IMPLEMENTED :)
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.