The main apfl program can now take a script file name as an argument to
evaluate instead of stdin. In that case, the whole script is read at once
and there are no `>` / `...` prompts then. The tokenizer and parser are
still accessible but are now behind the `-T` / `-P` flags.
We're now first building a standalone bytecode compiler `apflc` that will
compile `globals.apfl` into bytecode and write it out as a C source file.
When initializing a new context, that embedded bytecode will then get
evaluated and the global scope will be populated from the dictionary
returned by that bytecode.
This will match all arguments and discard them. This makes the bytecode
for simple functions easier and will make it easier to construct simple
function programmatically.
I made it possible for variable names to be const strings, only to discover
this actually used *more* heap memory instead of less. Also only very low
performance gains. So let's not do that and keep things a bit simpler.
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 :)