Commit graph

52 commits

Author SHA1 Message Date
bd19f689b9 Add backtraces to errors 2023-01-28 21:44:56 +01:00
3cc2a83226 Add functions to get information about the call stack 2023-01-28 21:42:40 +01:00
1afec3c7a0 Add named functions
An `name = {}` assignment now sets the name of the function to `name`.
Also the globally defined functions are named too now.
2023-01-24 22:16:27 +01:00
dd580a1519 Improve a bunch of error messages 2023-01-24 21:30:05 +01:00
5e9ac36a39 Get rid of fatal errors and report allocation error directly 2023-01-13 22:54:06 +01:00
5b4ac67de9 Make APFL_ERR_INPUT_ERROR non fatal
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.
2022-12-08 22:10:18 +01:00
0febf48401 Remove duplicated code 2022-11-20 21:57:12 +01:00
874638748b Make iterative runner not panic on OOM
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.
2022-11-20 21:44:00 +01:00
e8a92a18b4 Implement functions for concatenating strings 2022-11-20 21:44:00 +01:00
3bbd0e79a1 Implement predicates
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.
2022-11-19 23:26:59 +01:00
3d7bef187c Matchers: Don't return twice when value to capture is missing
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.
2022-11-19 23:26:59 +01:00
20c2880f4c Implement assigning into dictionaries
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 :)
2022-11-19 23:26:52 +01:00
0267faede3 Rename matcher_stack -> matcher_state_stack
It's not a stack of matchers, a thing we'll soon need. So let's rename
this.
2022-11-08 22:03:05 +01:00
9f7c823103 Don't ignore result type in iterative runner
-.-
2022-10-30 21:21:40 +01:00
8ca24bcd49 Implement subfunctions and argument matching
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.
2022-08-13 00:50:26 +02:00
6d77715e0d eval: Set returning_from_matcher while cse ist still valid
matcher_init_matching appends an element of the call stack, so the cse
pointer into the stack is no longer valid.
2022-08-06 12:53:24 +02:00
d731fa828a eval: Guard against matcher being NULL when matching 2022-07-31 22:04:59 +02:00
1630314dc7 Implement matching assignments
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.
2022-07-28 20:46:32 +02:00
a4f7f0f2ff Implement functions and 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.
2022-07-11 21:41:05 +02:00
8d947244c9 Implement exceptions-like error handling
Most functzions will no longer return an enum apfl_result, but will raise
an error that bubbles up.
2022-06-26 16:06:14 +02:00
09f7df79bb apfl_debug_print_val: Use format writer abstraction 2022-06-05 22:06:33 +02:00
ad141a86fb Push some errors as const strings onto the stack 2022-04-24 16:13:39 +02:00
0de243995b Ditch that weird internal.h header 2022-04-22 23:17:28 +02:00
d1b2ba7a53 Use formatter for printing values 2022-04-22 23:13:01 +02:00
29d9c75c4e Add functions for creating nil/bool/number/string values 2022-04-22 21:39:03 +02:00
0f6f136873 Push formatted parser error on stack during evaluation
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 :)
2022-04-21 22:55:11 +02:00
9ce5c20736 No longer evaluate exprs directly 2022-04-21 21:17:17 +02:00
90a80152e1 Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).

The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.

The tri-color abstraction was chosen for two reasons:

- We don't have to maintain a list of objects that need to be marked, we
  can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
  we only do a stop-the-world collection).

This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.

As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 22:24:22 +02:00
4088fdd1c3 Fix typo 2022-02-25 21:54:27 +01:00
8df0991415 eval.c: Don't create copy of already refcounted string 2022-02-14 21:56:36 +01:00
5f8462fe34 Add apfl_stackidx typedef
This makes it clear that a value of that type is meant as an index into the
stack.
2022-02-10 22:39:39 +01:00
09c6459565 Fix typo 2022-02-10 22:04:48 +01:00
d86c9375a4 Shorter name for internal enum apfl_value_type 2022-02-10 21:56:48 +01:00
dd314c5abb Shorter name for internal enum get_item_result 2022-02-10 21:56:30 +01:00
78bd057c37 eval.c: Free the stack with FREE_LIST instead of FREE_OBJ
This way we pass the correct oldsize to the allocator.
2022-02-09 20:44:21 +01:00
ebf3fc89ff Introduce allocator abstraction
We now no longer call malloc/free/... directly, but use an allocator object
that is passed around.

This was mainly done as a preparation for a garbage collector: The
collector will need to know, how much memory we're using, introducing the
collector abstraction will allow the GC to hook into the memory allocation
and observe the memory usage.

This has other potential applications:

- We could now be embedded into applications that can't use the libc
  allocator.
- There could be an allocator that limits the total amount of used memory,
  e.g. for sandboxing purposes.
- In our tests we could use this to simulate out of memory conditions
  (implement an allocator that fails at the n-th allocation, increase n by
  one and restart the test until there are no more faked OOM conditions).

The function signature of the allocator is basically exactly the same as
the one Lua uses.
2022-02-08 22:53:13 +01:00
2966d6dc7c hashmap: Don't hide hashmap struct behind a pointer any more
Since the hashmap is meant for internal use only, there is no need to hide
the internal data. This also allows us to save some malloc() calls.
2022-01-22 15:55:56 +01:00
21efc85dba Convert to Lua-style stack API
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.
2022-01-20 22:45:09 +01:00
d81bef9184 parser/tokenizer: Save textual data as refcounted strings
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).
2022-01-18 21:18:27 +01:00
c9a935b161 Support assigning into dictionaries
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.
2022-01-15 23:09:24 +01:00
a14b490dfe Partially implement pattern matching assignments
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 :)
2022-01-14 23:25:57 +01:00
ae45aeebe2 parser+expr: Handle blank identifier (_) 2022-01-08 23:20:29 +01:00
50cd2c18d2 expr+parser: Restrict what an assignable can be
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.
2022-01-08 23:06:22 +01:00
ac3af0baf1 apfl_ctx_destroy: Do nothing on NULL input 2022-01-08 22:09:43 +01:00
e928f40da4 Implement simple variable support
We can now assign to variables and retrieve their values. Not all
assignables are implemented yet, but still pretty cool :).
2022-01-06 22:53:26 +01:00
721d2d2d21 Implement evaluating container member access 2022-01-05 00:25:41 +01:00
0384875ef3 values: Split lists into read-only lists and editable lists
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.
2022-01-04 23:11:38 +01:00
f3d0bbed17 Implement evaluating dictionary literals
We can now have dictionaries with keys and values of arbitrary types.
Very cool! :)
2022-01-04 21:51:44 +01:00
eaa6b723bc Fix refcounted strings
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.
2022-01-04 21:22:46 +01:00
4cd95d32c5 eval.c: Handle errors during evaluating string constants 2022-01-02 21:20:08 +01:00