Only for evaluating expressions for now and right now the only exposed
operation is to debug print a value on the stack, this obviously needs to
be expanded.
I've done this for two reasons:
1. A Lua-style stack API is much nicer to work with than to manually manage
refcounts.
2. We'll soon need a more sophisticated garbage collector (if you even want
to count the refcounting as garbage collection). For this, the GC will
need root objects to start tracing for live objects, the stack will be
one of these roots.
This avoids creating refcounted strings during evaluation and makes it
easier to use the same parsed string in multiple places (should be
useful once we implement functions).
You can now set keys in dictionaries to a value. If a key in a key path is
missing, we automatically create an empty dictionary. Otherwise setting
deeply nested keys becomes annoying.
We're still missing predicates (need to be able to call functions for that
one) and assignments into dictionaries. But we now can deconstruct a list
and check against constants.
So things like this work now:
[1 foo ~bar [a b]] = [1 "Hello" 2 3 4 [5 6]]
# foo is: "Hello"
# bar is: [2 3 4]
# a is: 5
# b is: 6
Pretty cool :)
If you assign into a member access (`foo.bar = baz` or `foo@bar = baz`), it
is no longer permitted that the LHS of the at/dot is an arbitrary
assignable. It now must be a variable, at or dot. This disallows some silly
constructs (e.g. `[foo]@bar = baz`), increases the similarity to function
parameters and should make writing the evaluation code for these more easy.
The previous representation didn't properly model the fact that an
assignable / parameter can only be expanded, if it's a list element. This
now better models this. Other than being more correct, this should also
make evaluating these a bit easier.
While I was at it, I also improved the error message for multiple
expansions on the same level and added tests for these.
Since we're using refcounts, we don't really copy anything and no error can
occur. So let's make these callbacks return void to simplify things. This
also makes the return value false of the value getters unambiguous: It now
always means that the key was not present.
This is analogous to dictionaries and ensures that no circular references
can be created when using the exported API in apfl.h.
This also changes apfl_value_copy into apfl_value_incref to better reflect
what it does and to reflect that it is no longer an operation that can
fail.
Increasing the refcount (confusingly called copy before, fixed that too)
didn't work properly, as the object was copied and the refcount was only
updated in one of the copies.
This avoids copying the string every time we pass it around. Not too
important right now, but will become important onve we're able to evaluate
more complex expressions.
This replaces the grow_cap function with the ensure_cap family of
functions, as they actually do what you want: You'll likely not want to
blindly increase the capacity of a growable, but you want to make sure that
the capacity is large enough to hold the elements you're about to insert.
It's really easy to accidentally pass an uninitialized string as dst into
the copy function, which will result in an free() call to an arbitrary
pointer. Maybe it's a better idea to not deinit the dst string before
copying? The documentation at least makes it more clear and the new
apfl_string_blank() function makes it easy to create an empty string.
With the varargs approach that was used before, it was very easy to add a
list item of the wrong type, which would (hopefully) result in an assertion
violation, because va_arg() then read some senseless data.