apfl/src/scope.c
Laria Carolin Chabowski 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

181 lines
4.1 KiB
C

#include <assert.h>
#include "hashmap.h"
#include "scope.h"
typedef struct apfl_value *variable;
void
apfl_scope_deinit(struct apfl_allocator allocator, struct scope *scope)
{
(void)allocator;
apfl_hashmap_deinit(&scope->map);
}
static bool
scope_keys_eq(void *opaque, const void *_a, const void *_b)
{
(void)opaque;
const struct apfl_string * const *a = _a;
const struct apfl_string * const *b = _b;
return apfl_string_eq(**a, **b);
}
static apfl_hash
scope_calc_hash(void *opaque, const void *_key)
{
(void)opaque;
const struct apfl_string *const*key = _key;
struct apfl_string_view sv = apfl_string_view_from(**key);
return apfl_hash_fnv1a(sv.bytes, sv.len);
}
struct scope *
apfl_scope_new(struct gc *gc)
{
struct apfl_hashmap map;
if (!apfl_hashmap_init(
&map,
gc->allocator,
(struct apfl_hashmap_callbacks) {
.opaque = NULL,
.keys_eq = scope_keys_eq,
.calc_hash = scope_calc_hash,
},
sizeof(struct apfl_string *),
sizeof(variable)
)) {
return NULL;
}
struct scope *scope = apfl_gc_new_scope(gc);
if (scope == NULL) {
apfl_hashmap_deinit(&map);
return NULL;
}
*scope = (struct scope) {
.map = map,
};
return scope;
}
void
apfl_gc_var_traverse(struct apfl_value *var, gc_visitor cb, void *opaque)
{
apfl_value_visit_gc_object(*var, cb, opaque);
}
void
apfl_gc_scope_traverse(struct scope *scope, gc_visitor cb, void *opaque)
{
HASHMAP_EACH(&scope->map, cur) {
struct apfl_string **k = apfl_hashmap_cursor_peek_key(cur);
assert(k != NULL);
cb(opaque, GC_OBJECT_FROM(*k, GC_TYPE_STRING));
variable *v = apfl_hashmap_cursor_peek_value(cur);
assert(v != NULL);
cb(opaque, GC_OBJECT_FROM(*v, GC_TYPE_VAR));
}
}
bool
apfl_scope_get(struct scope *scope, struct apfl_string *name, struct apfl_value *out)
{
variable var;
if (!apfl_hashmap_get(&scope->map, &name, &var)) {
return false;
}
// The value is now in the variable and outside of it (presumably to be
// saved onto the stack). We need to set the COW flag so a mutation of one
// copy doesn't affect the other one.
*out = apfl_value_set_cow_flag(*var);
return true;
}
bool
apfl_scope_has(struct scope *scope, struct apfl_string *name)
{
return apfl_hashmap_isset(&scope->map, &name);
}
static variable
get_or_create_variable(struct gc *gc, struct scope *scope, struct apfl_string *name)
{
variable var;
if (apfl_hashmap_get(&scope->map, &name, &var)) {
return var;
}
struct apfl_hashmap_prepared_set prepared_set;
if (!apfl_hashmap_prepare_set(&scope->map, &prepared_set, &name)) {
return NULL;
}
var = apfl_gc_new_var(gc);
if (var == NULL) {
return NULL;
}
*var = (struct apfl_value) { .type = VALUE_NIL };
apfl_hashmap_set_prepared(prepared_set, &var);
return var;
}
bool
apfl_scope_set(struct gc *gc, struct scope *scope, struct apfl_string *name, struct apfl_value value)
{
variable var = get_or_create_variable(gc, scope, name);
if (var == NULL) {
return false;
}
*var = value;
return true;
}
bool
apfl_scope_update_existing(struct scope *scope, struct apfl_string *name, struct apfl_value value)
{
variable var;
if (!apfl_hashmap_get(&scope->map, &name, &var)) {
return false;
}
*var = value;
return true;
}
bool
apfl_scope_create_var(struct gc *gc, struct scope *scope, struct apfl_string *name)
{
return get_or_create_variable(gc, scope, name) != NULL;
}
bool
apfl_scope_merge_into(struct scope *self, struct scope *other)
{
if (self == NULL || other == NULL) {
return NULL;
}
HASHMAP_EACH(&other->map, cur) {
struct apfl_string **k = apfl_hashmap_cursor_peek_key(cur);
assert(k != NULL);
variable *v = apfl_hashmap_cursor_peek_value(cur);
assert(v != NULL);
if (!apfl_hashmap_set(&self->map, k, v)) {
return false;
}
}
return true;
}