Instead of passing a flag through the parser and tokenizer for telling
the input source if we need further input or not, we steal a trick from
Lua: In the REPL, we just continue to read lines and append them to the
input, until the input was loaded with no "unexpected EOF" error. After
all, when we didn't expect an EOF is exactly the scenario, when we need
more input.
Doing things this way simplifies a bunch of places and lets us remove
the ugly source_reader and iterative_runner concepts.
To allow the REPL to see the error that happened during loading required
some smaller refactorings, but those were honestly for the better
anyway.
I also decided to get rid of the token_source concept, the parser now
gets the tokenizer directly. This also made things a bit simpler, also
I want to soon-ish implement string interpolation, and for that the
parser needs to do more with the tokenizer than just reading the next
token.
One last thing: This also cleans up the web playground and makes the
playground and REPL share a bunch of code. Nice!
Turns out past me allowed a subfunction to not have a matcher at all,
in which case everything is matched. This is exactly what we want to
happen for a apfl_load()ed function, so we don't needt to construct a
matcher here at all and can keep things a bit simpler. Nice :)
It's very likely that if we find a free slot, another free slot will be
nearby. So let's remember the position in the block and make sure the block
where we found a free slot will be checked first next time.
This improves performance in mandelbrot.apfl by about 40% :)
We'll soon need the full apfl_ctx, where we previously only needed the
gc. apflc was the only place manually constructing a struct gc without
an apfl_ctx, so lets change that.
Very much inspired by lua, as so often: The module system maintains a list
of searchers that will be called with the requested module name. They can
then return a loader function, which will then be called again with the
module name. That loader then returns the actual module, which is then
cached. We also add a goofy "foo" module in main.c to test this.
Pairs are 2-tuples of values that are constructed and matched with the `::`
operator. They can also be matched with a `:` operator, the LHS is an
expression then, the pair will then only match, if the LHS matches the
result of that expression.
Pairs should be useful to do something similar what sum types / tagged
unions do in statically typed languages, e.g. you could write something
like:
some := (symbol) # Somthing that creates a unique value
filter-map := {
_ [] -> []
f [x ~xs] ->
{
some:y -> [y ~(filter-map f xs)]
nil -> filter-map f xs
} (f x)
}
filter-map {
x?even -> some :: (* x 10)
_ -> nil
} some-list
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.