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 :)
This commit is contained in:
Laria 2022-11-19 22:06:23 +01:00
parent f0811ba8fe
commit 20c2880f4c
17 changed files with 1034 additions and 613 deletions

View file

@ -13,7 +13,6 @@ add_library(apfl
gc.c
hashmap.c
globals.c
matcher.c
messages.c
parser.c
position.c
@ -67,6 +66,7 @@ functionaltest("if")
functionaltest("while")
functionaltest("eq")
functionaltest("chained-assignments")
functionaltest("dictionary-assignments")
install(TARGETS apfl DESTINATION lib)
install(TARGETS apfl-bin DESTINATION bin)

View file

@ -211,7 +211,6 @@ enum apfl_error_type {
APFL_ERR_UNEXPECTED_CONSTANT_IN_MEMBER_ACCESS,
APFL_ERR_UNEXPECTED_EXPR_IN_MEMBER_ACCESS,
APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS,
APFL_ERR_NOT_IMPLEMENTED,
};
const char *apfl_error_type_name(enum apfl_error_type);
@ -682,6 +681,8 @@ void apfl_dict_create(apfl_ctx);
void apfl_dict_set(apfl_ctx, apfl_stackidx dict, apfl_stackidx k, apfl_stackidx v);
// Get a value from a container (list or dict) and push it on the stack. container and k will be dropped.
void apfl_get_member(apfl_ctx, apfl_stackidx container, apfl_stackidx k);
// Get a value from a container (list or dict) and push it on the stack, if it exists. container and k will be dropped.
bool apfl_get_member_if_exists(apfl_ctx, apfl_stackidx container, apfl_stackidx k);
// Get a value from a list and push it on the stack. The list will stay on the stack.
void apfl_get_list_member_by_index(apfl_ctx, apfl_stackidx list, size_t index_in_list);
// Get the length of a string/list/dict. The value stays on the stack,

View file

@ -54,9 +54,9 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi
case INSN_GET_MEMBER:
case INSN_NEXT_LINE:
case INSN_DROP:
case INSN_DUP:
case INSN_CALL:
case INSN_MATCHER_MUST_MATCH:
case INSN_MATCHER_DROP:
break;
case INSN_NUMBER:
case INSN_LIST:
@ -80,14 +80,41 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi
GET_ARGUMENT(ilist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.body, GC_TYPE_INSTRUCTIONS));
break;
case INSN_MATCHER_LOAD:
case INSN_MATCHER_PUSH:
GET_ARGUMENT(ilist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.matcher, GC_TYPE_MATCHER_INSTRUCTIONS));
break;
case INSN_VAR_SET_FROM_MATCHER:
case INSN_VAR_SET_LOCAL_FROM_MATCHER:
GET_ARGUMENT(ilist, i, arg);
}
}
}
void
apfl_gc_matcher_instructions_traverse(struct matcher_instruction_list *milist, gc_visitor cb, void *opaque)
{
union matcher_instruction_or_arg arg;
for (size_t i = 0; i < milist->len; i++) {
switch (milist->instructions[i].instruction) {
case MATCHER_IGNORE:
case MATCHER_ENTER_LIST:
case MATCHER_LEAVE_LIST:
case MATCHER_CONTINUE_FROM_END:
case MATCHER_REMAINDING:
break;
case MATCHER_CHECK_CONST: // with index as values index
case MATCHER_CHECK_PRED: // with index as values index
i++;
break;
case MATCHER_CAPTURE_TO_VAR: // with name
case MATCHER_CAPTURE_TO_VAR_LOCAL: // with name
GET_ARGUMENT(milist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING));
break;
case MATCHER_CAPTURE_TO_VAR_WITH_PATH: // with name, index and len
case MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH: // with name, index and len
GET_ARGUMENT(milist, i, arg);
cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING));
i++;
i++;
break;
}
@ -140,24 +167,20 @@ apfl_instruction_to_string(enum instruction insn)
return "INSN_GET_BY_INDEX_KEEP";
case INSN_DROP:
return "INSN_DROP";
case INSN_DUP:
return "INSN_DUP";
case INSN_CALL:
return "INSN_CALL";
case INSN_FUNC:
return "INSN_FUNC";
case INSN_FUNC_ADD_SUBFUNC:
return "INSN_FUNC_ADD_SUBFUNC";
case INSN_MATCHER_LOAD:
return "INSN_MATCHER_LOAD";
case INSN_MATCHER_PUSH:
return "INSN_MATCHER_PUSH";
case INSN_MATCHER_SET_VAL:
return "INSN_MATCHER_SET_VAL";
case INSN_MATCHER_MUST_MATCH:
return "INSN_MATCHER_MUST_MATCH";
case INSN_MATCHER_DROP:
return "INSN_MATCHER_DROP";
case INSN_VAR_SET_FROM_MATCHER:
return "INSN_VAR_SET_FROM_MATCHER";
case INSN_VAR_SET_LOCAL_FROM_MATCHER:
return "INSN_VAR_SET_LOCAL_FROM_MATCHER";
}
return "??";
@ -169,8 +192,14 @@ apfl_matcher_instruction_to_string(enum matcher_instruction insn)
switch (insn) {
case MATCHER_IGNORE:
return "MATCHER_IGNORE";
case MATCHER_CAPTURE:
return "MATCHER_CAPTURE";
case MATCHER_CAPTURE_TO_VAR:
return "MATCHER_CAPTURE_TO_VAR";
case MATCHER_CAPTURE_TO_VAR_LOCAL:
return "MATCHER_CAPTURE_TO_VAR_LOCAL";
case MATCHER_CAPTURE_TO_VAR_WITH_PATH:
return "MATCHER_CAPTURE_TO_VAR_WITH_PATH";
case MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH:
return "MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH";
case MATCHER_CHECK_CONST:
return "MATCHER_CHECK_CONST";
case MATCHER_CHECK_PRED:
@ -211,10 +240,21 @@ apfl_matcher_instructions_deinit(struct apfl_allocator allocator, struct matcher
FREE_LIST(allocator, milist->instructions, milist->cap);
}
#define GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg) \
do { \
if (i >= ilist->len) { \
FMT_TRY(apfl_format_put_string(w, "Bytecode corrupted")); \
return false; \
} \
arg = ilist->instructions[++i]; \
} while (0)
bool
apfl_bytecode_dump_matcher(unsigned indent, struct apfl_format_writer w, struct matcher_instruction_list *milist)
{
for (size_t i = 0; i < milist->len; i++) {
union matcher_instruction_or_arg arg;
FMT_TRY(apfl_format_put_indent(w, indent));
FMT_TRY(apfl_format_put_string(w, apfl_matcher_instruction_to_string(milist->instructions[i].instruction)));
@ -225,15 +265,29 @@ apfl_bytecode_dump_matcher(unsigned indent, struct apfl_format_writer w, struct
case MATCHER_CONTINUE_FROM_END:
case MATCHER_REMAINDING:
break;
case MATCHER_CAPTURE:
case MATCHER_CAPTURE_TO_VAR:
case MATCHER_CAPTURE_TO_VAR_LOCAL:
GET_ARGUMENT_FOR_DUMP(w, milist, i, arg);
FMT_TRY(apfl_format_put_string(w, " "));
FMT_TRY(apfl_format_put_string(w, *arg.string));
break;
case MATCHER_CAPTURE_TO_VAR_WITH_PATH: // with string, index and len
case MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH: // with string, index and len
GET_ARGUMENT_FOR_DUMP(w, milist, i, arg);
FMT_TRY(apfl_format_put_string(w, " "));
FMT_TRY(apfl_format_put_string(w, *arg.string));
GET_ARGUMENT_FOR_DUMP(w, milist, i, arg);
FMT_TRY(apfl_format_put_string(w, ", "));
FMT_TRY(apfl_format_put_int(w, (int)arg.index));
GET_ARGUMENT_FOR_DUMP(w, milist, i, arg);
FMT_TRY(apfl_format_put_string(w, ", "));
FMT_TRY(apfl_format_put_int(w, (int)arg.len));
break;
case MATCHER_CHECK_CONST:
case MATCHER_CHECK_PRED:
if (i >= milist->len) {
FMT_TRY(apfl_format_put_string(w, "Bytecode corrupted"));
return false;
}
GET_ARGUMENT_FOR_DUMP(w, milist, i, arg);
FMT_TRY(apfl_format_put_string(w, " "));
FMT_TRY(apfl_format_put_int(w, (int)milist->instructions[++i].index));
FMT_TRY(apfl_format_put_int(w, (int)arg.index));
break;
}
@ -243,15 +297,6 @@ apfl_bytecode_dump_matcher(unsigned indent, struct apfl_format_writer w, struct
return true;
}
#define GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg) \
do { \
if (i >= ilist->len) { \
FMT_TRY(apfl_format_put_string(w, "Bytecode corrupted")); \
return false; \
} \
arg = ilist->instructions[++i]; \
} while (0)
bool
apfl_bytecode_dump(unsigned indent, struct apfl_format_writer w, struct instruction_list *ilist)
{
@ -272,9 +317,9 @@ apfl_bytecode_dump(unsigned indent, struct apfl_format_writer w, struct instruct
case INSN_GET_MEMBER:
case INSN_NEXT_LINE:
case INSN_DROP:
case INSN_DUP:
case INSN_CALL:
case INSN_MATCHER_MUST_MATCH:
case INSN_MATCHER_DROP:
break;
case INSN_NUMBER:
GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg);
@ -312,22 +357,13 @@ apfl_bytecode_dump(unsigned indent, struct apfl_format_writer w, struct instruct
FMT_TRY(apfl_format_put_indent(w, indent));
FMT_TRY(apfl_format_put_string(w, "}"));
break;
case INSN_MATCHER_LOAD:
case INSN_MATCHER_PUSH:
GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg);
FMT_TRY(apfl_format_put_string(w, " milist{\n"));
FMT_TRY(apfl_bytecode_dump_matcher(indent+1, w, arg.matcher));
FMT_TRY(apfl_format_put_indent(w, indent));
FMT_TRY(apfl_format_put_string(w, "}"));
break;
case INSN_VAR_SET_FROM_MATCHER:
case INSN_VAR_SET_LOCAL_FROM_MATCHER:
GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg);
FMT_TRY(apfl_format_put_string(w, " "));
FMT_TRY(apfl_format_put_string(w, *arg.string));
FMT_TRY(apfl_format_put_string(w, ", "));
GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg);
FMT_TRY(apfl_format_put_int(w, (int)arg.index));
break;
}
FMT_TRY(apfl_format_put_char(w, '\n'));

View file

@ -11,7 +11,10 @@ extern "C" {
enum matcher_instruction {
MATCHER_IGNORE,
MATCHER_CAPTURE, // with index as capture index
MATCHER_CAPTURE_TO_VAR, // with name
MATCHER_CAPTURE_TO_VAR_LOCAL, // with name
MATCHER_CAPTURE_TO_VAR_WITH_PATH, // with name, index and len
MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH, // with name, index and len
MATCHER_CHECK_CONST, // with index as values index
MATCHER_CHECK_PRED, // with index as values index
MATCHER_ENTER_LIST,
@ -23,14 +26,16 @@ enum matcher_instruction {
union matcher_instruction_or_arg {
enum matcher_instruction instruction;
size_t index;
size_t len;
struct apfl_string *string;
};
struct matcher_instruction_list {
union matcher_instruction_or_arg *instructions;
size_t len;
size_t cap;
size_t capture_count;
size_t value_count;
size_t capture_count;
};
enum instruction {
@ -55,15 +60,13 @@ enum instruction {
INSN_NEXT_LINE, // ( -- )
INSN_SET_LINE, // ( -- ), arg: count (new line number)
INSN_DROP, // ( value -- )
INSN_DUP, // ( value -- value value)
INSN_CALL, // ( func list -- value )
INSN_FUNC, // ( -- func ), arg: count
INSN_FUNC_ADD_SUBFUNC, // ( func -- func' ), arg: body
INSN_MATCHER_LOAD, // ( -- ), arg: matcher
INSN_FUNC_ADD_SUBFUNC, // ( func -- func' ), arg: body; pops a matcher from the matcher stack
INSN_MATCHER_PUSH, // ( -- ), arg: matcher; pushes a matcher onto the matcher stack
INSN_MATCHER_SET_VAL, // ( val -- ), arg: index
INSN_MATCHER_MUST_MATCH, // ( val -- )
INSN_MATCHER_DROP, // ( -- )
INSN_VAR_SET_FROM_MATCHER, // ( -- ), args: string, index
INSN_VAR_SET_LOCAL_FROM_MATCHER, // ( -- ), args: string, index
INSN_MATCHER_MUST_MATCH, // ( val -- ); pops a matcher from the matcher stack
};
union instruction_or_arg {
@ -91,6 +94,7 @@ struct instruction_list *apfl_instructions_new(struct gc *, int line);
void apfl_instructions_deinit(struct apfl_allocator, struct instruction_list *);
void apfl_gc_instructions_traverse(struct instruction_list *, gc_visitor, void *);
void apfl_gc_matcher_instructions_traverse(struct matcher_instruction_list *, gc_visitor, void *);
struct matcher_instruction_list *apfl_matcher_instructions_new(struct gc *);
void apfl_matcher_instructions_deinit(struct apfl_allocator, struct matcher_instruction_list *);

View file

@ -112,28 +112,37 @@ milist_ensure_cap(struct compiler *compiler, struct matcher_instruction_list *mi
));
}
#define MILIST_PUT(milist, member, value) \
assert(milist->cap > 0); \
assert(milist->len < milist->cap); \
\
milist->instructions[milist->len] = (union matcher_instruction_or_arg) { \
.member = value, \
}; \
milist->len++;
static void
milist_put_insn(struct matcher_instruction_list *milist, enum matcher_instruction instruction)
{
assert(milist->cap > 0);
assert(milist->len < milist->cap);
milist->instructions[milist->len] = (union matcher_instruction_or_arg) {
.instruction = instruction,
};
milist->len++;
MILIST_PUT(milist, instruction, instruction)
}
static void
milist_put_index(struct matcher_instruction_list *milist, size_t index)
{
assert(milist->cap > 0);
assert(milist->len < milist->cap);
MILIST_PUT(milist, index, index)
}
milist->instructions[milist->len] = (union matcher_instruction_or_arg) {
.index = index,
};
milist->len++;
static void
milist_put_len(struct matcher_instruction_list *milist, size_t len)
{
MILIST_PUT(milist, len, len)
}
static void
milist_put_string(struct matcher_instruction_list *milist, struct apfl_string *string)
{
MILIST_PUT(milist, string, string)
}
static bool
@ -320,36 +329,88 @@ tmp_milist(struct compiler *compiler)
return milist;
}
struct compile_matchable_ilists {
struct instruction_list *prelude;
struct instruction_list *newvars;
struct instruction_list *setvars;
struct matcher_instruction_list *matcher;
struct compile_matchable_args {
struct instruction_list *ilist;
struct matcher_instruction_list *milist;
bool local;
};
static bool
compile_assignable(
struct compiler *compiler,
bool local,
struct apfl_position position,
struct apfl_expr_assignable *assignable,
struct compile_matchable_ilists ilists
struct compile_matchable_args args
);
static enum matcher_instruction
matcher_capture_to_var(bool local)
{
return local ? MATCHER_CAPTURE_TO_VAR_LOCAL : MATCHER_CAPTURE_TO_VAR;
}
static enum matcher_instruction
matcher_capture_to_var_with_path(bool local)
{
return local ? MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH : MATCHER_CAPTURE_TO_VAR_WITH_PATH;
}
static enum instruction
insn_var_new(bool local)
{
return local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW;
}
static bool
compile_assignable_var_path(
struct compiler *compiler,
struct apfl_expr_assignable_var_or_member *var_or_member,
struct compile_matchable_args args,
size_t *path_len,
struct apfl_string **varname
) {
size_t index;
struct apfl_string *dot_rhs = NULL;
switch (var_or_member->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR:
TRY(ilist_ensure_cap(compiler, args.ilist, 2));
TRY(string_move_into_new(compiler, varname, &var_or_member->var));
ilist_put_insn(args.ilist, insn_var_new(args.local));
ilist_put_string(args.ilist, *varname);
return true;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT:
TRY(compile_assignable_var_path(compiler, var_or_member->dot.lhs, args, path_len, varname));
index = args.milist->value_count++;
++*path_len;
TRY(ilist_ensure_cap(compiler, args.ilist, 4));
TRY(string_move_into_new(compiler, &dot_rhs, &var_or_member->dot.rhs));
ilist_put_insn(args.ilist, INSN_STRING);
ilist_put_string(args.ilist, dot_rhs);
ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL);
ilist_put_insn(args.ilist, index);
return true;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT:
TRY(compile_assignable_var_path(compiler, var_or_member->dot.lhs, args, path_len, varname));
index = args.milist->value_count++;
++*path_len;
TRY(compile_expr(compiler, var_or_member->at.rhs, args.ilist));
TRY(ilist_ensure_cap(compiler, args.ilist, 2));
ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL);
ilist_put_index(args.ilist, index);
return true;
}
assert(false);
return false;
}
static bool
compile_assignable_var_or_member(
struct compiler *compiler,
bool local,
struct apfl_expr_assignable_var_or_member *var_or_member,
struct compile_matchable_ilists ilists
struct compile_matchable_args args
) {
size_t index = ilists.matcher->capture_count++;
if (var_or_member->type != APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR) {
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); // TODO: Implement me
return false;
}
/* TODO: What should happen, if we try to match the same variable twice?
*
* Right now, the second capture will overwrite the first one, but it would
@ -362,22 +423,27 @@ compile_assignable_var_or_member(
* }
*/
TRY(milist_ensure_cap(compiler, ilists.matcher, 2));
milist_put_insn(ilists.matcher, MATCHER_CAPTURE);
milist_put_index(ilists.matcher, index);
TRY(ilist_ensure_cap(compiler, ilists.newvars, 2));
args.milist->capture_count++;
size_t path_start = args.milist->value_count;
size_t path_len = 0;
struct apfl_string *varname = NULL;
TRY(string_move_into_new(compiler, &varname, &var_or_member->var));
ilist_put_insn(ilists.newvars, local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW);
ilist_put_string(ilists.newvars, varname);
TRY(compile_assignable_var_path(compiler, var_or_member, args, &path_len, &varname));
TRY(ilist_ensure_cap(compiler, ilists.setvars, 3));
ilist_put_insn(ilists.setvars, local ? INSN_VAR_SET_LOCAL_FROM_MATCHER : INSN_VAR_SET_FROM_MATCHER);
ilist_put_string(ilists.setvars, varname);
ilist_put_index(ilists.setvars, index);
assert(varname != NULL /* if compile_assignable_var_path succeeded, it should have set varname */);
if (path_len == 0) {
TRY(milist_ensure_cap(compiler, args.milist, 2));
milist_put_insn(args.milist, matcher_capture_to_var(args.local));
milist_put_string(args.milist, varname);
} else {
TRY(milist_ensure_cap(compiler, args.milist, 4));
milist_put_insn(args.milist, matcher_capture_to_var_with_path(args.local));
milist_put_string(args.milist, varname);
milist_put_index(args.milist, path_start);
milist_put_len(args.milist, path_len);
}
return true;
}
@ -386,45 +452,44 @@ static bool
compile_matchable_constant(
struct compiler *compiler,
struct apfl_expr_const *constant,
struct compile_matchable_ilists ilists
struct compile_matchable_args args
) {
size_t index = ilists.matcher->value_count++;
size_t index = args.milist->value_count++;
TRY(compile_constant(compiler, constant, ilists.prelude));
TRY(ilist_ensure_cap(compiler, ilists.prelude, 2));
ilist_put_insn(ilists.prelude, INSN_MATCHER_SET_VAL);
ilist_put_index(ilists.prelude, index);
TRY(compile_constant(compiler, constant, args.ilist));
TRY(ilist_ensure_cap(compiler, args.ilist, 2));
ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL);
ilist_put_index(args.ilist, index);
TRY(milist_ensure_cap(compiler, ilists.matcher, 3));
milist_put_insn(ilists.matcher, MATCHER_CHECK_CONST);
milist_put_index(ilists.matcher, index);
milist_put_insn(ilists.matcher, MATCHER_IGNORE);
TRY(milist_ensure_cap(compiler, args.milist, 3));
milist_put_insn(args.milist, MATCHER_CHECK_CONST);
milist_put_index(args.milist, index);
milist_put_insn(args.milist, MATCHER_IGNORE);
return true;
}
#define DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(name, compile_matchable, predtype) \
static bool \
name( \
struct compiler *compiler, \
bool local, \
struct apfl_position position, \
predtype *predicate, \
struct compile_matchable_ilists ilists \
) { \
size_t index = ilists.matcher->value_count++; \
\
TRY(compile_expr(compiler, predicate->rhs, ilists.prelude)); \
TRY(ilist_ensure_cap(compiler, ilists.prelude, 2)); \
ilist_put_insn(ilists.prelude, INSN_MATCHER_SET_VAL); \
ilist_put_index(ilists.prelude, index); \
\
TRY(milist_ensure_cap(compiler, ilists.matcher, 2)); \
milist_put_insn(ilists.matcher, MATCHER_CHECK_PRED); \
milist_put_index(ilists.matcher, index); \
\
return compile_matchable(compiler, local, position, predicate->lhs, ilists); \
} \
#define DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(name, compile_matchable, predtype) \
static bool \
name( \
struct compiler *compiler, \
struct apfl_position position, \
predtype *predicate, \
struct compile_matchable_args args \
) { \
size_t index = args.milist->value_count++; \
\
TRY(compile_expr(compiler, predicate->rhs, args.ilist)); \
TRY(ilist_ensure_cap(compiler, args.ilist, 2)); \
ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL); \
ilist_put_index(args.ilist, index); \
\
TRY(milist_ensure_cap(compiler, args.milist, 2)); \
milist_put_insn(args.milist, MATCHER_CHECK_PRED); \
milist_put_index(args.milist, index); \
\
return compile_matchable(compiler, position, predicate->lhs, args); \
} \
DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(
compile_assignable_predicate,
@ -436,18 +501,17 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(
static bool \
name##_expand( \
struct compiler *compiler, \
bool local, \
struct apfl_position position, \
listtype *list, \
struct compile_matchable_ilists ilists, \
struct compile_matchable_args args, \
size_t expand_at \
) { \
assert(expand_at < list->len); \
listitemtype *expand_item = &list->listmemb[expand_at]; \
assert(expand_item->expand); \
\
TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \
milist_put_insn(ilists.matcher, MATCHER_CONTINUE_FROM_END); \
TRY(milist_ensure_cap(compiler, args.milist, 1)); \
milist_put_insn(args.milist, MATCHER_CONTINUE_FROM_END); \
\
for (size_t i = list->len; i-- > expand_at+1; ) { \
listitemtype *item = &list->listmemb[i]; \
@ -460,13 +524,13 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(
return false; \
} \
\
TRY(compile_matchable(compiler, local, position, &item->listitemmemb, ilists)); \
TRY(compile_matchable(compiler, position, &item->listitemmemb, args)); \
} \
\
TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \
milist_put_insn(ilists.matcher, MATCHER_REMAINDING); \
TRY(milist_ensure_cap(compiler, args.milist, 1)); \
milist_put_insn(args.milist, MATCHER_REMAINDING); \
\
TRY(compile_matchable(compiler, local, position, &expand_item->listitemmemb, ilists)); \
TRY(compile_matchable(compiler, position, &expand_item->listitemmemb, args)); \
\
return true; \
} \
@ -474,13 +538,12 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(
static bool \
name( \
struct compiler *compiler, \
bool local, \
struct apfl_position position, \
listtype *list, \
struct compile_matchable_ilists ilists \
struct compile_matchable_args args \
) { \
TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \
milist_put_insn(ilists.matcher, MATCHER_ENTER_LIST); \
TRY(milist_ensure_cap(compiler, args.milist, 1)); \
milist_put_insn(args.milist, MATCHER_ENTER_LIST); \
\
for (size_t i = 0; i < list->len; i++) { \
listitemtype *item = &list->listmemb[i]; \
@ -488,19 +551,18 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(
if (item->expand) { \
return name##_expand( \
compiler, \
local, \
position, \
list, \
ilists, \
args, \
i \
); \
} \
\
TRY(compile_matchable(compiler, local, position, &item->listitemmemb, ilists)); \
TRY(compile_matchable(compiler, position, &item->listitemmemb, args)); \
} \
\
TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \
milist_put_insn(ilists.matcher, MATCHER_LEAVE_LIST); \
TRY(milist_ensure_cap(compiler, args.milist, 1)); \
milist_put_insn(args.milist, MATCHER_LEAVE_LIST); \
return true; \
} \
@ -514,32 +576,31 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_LIST(
)
static bool
compile_matchable_blank(struct compiler *compiler, struct compile_matchable_ilists ilists)
compile_matchable_blank(struct compiler *compiler, struct matcher_instruction_list *milist)
{
TRY(milist_ensure_cap(compiler, ilists.matcher, 1));
milist_put_insn(ilists.matcher, MATCHER_IGNORE);
TRY(milist_ensure_cap(compiler, milist, 1));
milist_put_insn(milist, MATCHER_IGNORE);
return true;
}
static bool
compile_assignable(
struct compiler *compiler,
bool local,
struct apfl_position position,
struct apfl_expr_assignable *assignable,
struct compile_matchable_ilists ilists
struct compile_matchable_args args
) {
switch (assignable->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER:
return compile_assignable_var_or_member(compiler, local, &assignable->var_or_member, ilists);
return compile_assignable_var_or_member(compiler, &assignable->var_or_member, args);
case APFL_EXPR_ASSIGNABLE_CONSTANT:
return compile_matchable_constant(compiler, &assignable->constant, ilists);
return compile_matchable_constant(compiler, &assignable->constant, args);
case APFL_EXPR_ASSIGNABLE_PREDICATE:
return compile_assignable_predicate(compiler, local, position, &assignable->predicate, ilists);
return compile_assignable_predicate(compiler, position, &assignable->predicate, args);
case APFL_EXPR_ASSIGNABLE_LIST:
return compile_assignable_list(compiler, local, position, &assignable->list, ilists);
return compile_assignable_list(compiler, position, &assignable->list, args);
case APFL_EXPR_ASSIGNABLE_BLANK:
return compile_matchable_blank(compiler, ilists);
return compile_matchable_blank(compiler, args.milist);
}
assert(false);
@ -547,16 +608,28 @@ compile_assignable(
}
static bool
concat_ilists(struct compiler *compiler, struct instruction_list *out, const struct instruction_list *in)
{
TRY(ilist_ensure_cap(compiler, out, in->len));
memcpy(
out->instructions + out->len,
in->instructions,
sizeof(union instruction_or_arg) * in->len
);
out->len += in->len;
return true;
compile_assignment_lhs(
struct compiler *compiler,
struct apfl_expr_assignable *assignable,
bool local,
struct apfl_position position,
struct instruction_list *ilist
) {
// By ensuring the capacity early, we can avoid tmprooting the milist, since
// we can immediately append it to the already GC-rooted ilist.
TRY(ilist_ensure_cap(compiler, ilist, 2));
struct matcher_instruction_list *milist = apfl_matcher_instructions_new(compiler->gc);
MALLOC_FAIL_IF_NULL(compiler, milist);
ilist_put_insn(ilist, INSN_MATCHER_PUSH);
ilist_put_matcher(ilist, milist);
return compile_assignable(compiler, position, assignable, (struct compile_matchable_args) {
.ilist = ilist,
.milist = milist,
.local = local,
});
}
static bool
@ -566,43 +639,19 @@ compile_complex_assignment(
struct apfl_position position,
struct instruction_list *ilist
) {
// TODO: This is not entirely correct: By evaluating the RHS first, it might
// affect variables used in the matcher. This is only really important when
// predicates are used, which are not yet implemented:
//
// x = {true}
// foo?x = ({x={false}})
//
// This should succeed: The predicate x should be loaded before the
// evaluation of the rhs occurs. With our current implementation it will
// fail, though.
TRY(compile_assignment_lhs(
compiler,
&assignment->lhs,
assignment->local,
position,
ilist
));
TRY(compile_expr(compiler, assignment->rhs, ilist));
TRY(ilist_ensure_cap(compiler, ilist, 2));
struct compile_matchable_ilists ilists = {
.prelude = ilist,
};
MALLOC_FAIL_IF_NULL(compiler, (ilists.matcher = tmp_milist(compiler)));
MALLOC_FAIL_IF_NULL(compiler, (ilists.newvars = tmp_ilist(compiler, position.line)));
MALLOC_FAIL_IF_NULL(compiler, (ilists.setvars = tmp_ilist(compiler, position.line)));
ilist_put_insn(ilist, INSN_MATCHER_LOAD);
ilist_put_matcher(ilist, ilists.matcher);
TRY(compile_assignable(compiler, assignment->local, position, &assignment->lhs, ilists));
TRY(concat_ilists(compiler, ilist, ilists.newvars));
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_DUP);
ilist_put_insn(ilist, INSN_MATCHER_MUST_MATCH);
TRY(concat_ilists(compiler, ilist, ilists.setvars));
TRY(ilist_ensure_cap(compiler, ilist, 1));
ilist_put_insn(ilist, INSN_MATCHER_DROP);
return true;
}
@ -647,6 +696,8 @@ compile_call(struct compiler *compiler, struct apfl_expr_call *call, struct inst
static bool
compile_body(struct compiler *compiler, struct apfl_expr_body *body, struct instruction_list *ilist)
{
// TODO: This leaves the results of all expressions on the value stack.
// The runtime will clean this up, but we van be less wasteful here.
for (size_t i = 0; i < body->len; i++) {
TRY(compile_expr(compiler, &body->items[i], ilist));
}
@ -666,16 +717,12 @@ compile_simple_func_inner(struct compiler *compiler, struct apfl_expr_body *func
TRY(milist_ensure_cap(compiler, milist, 1));
milist_put_insn(milist, MATCHER_IGNORE); // Ignore all arguments
// Drop the matcher, we ignore arguments in simple functions
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
ilist_put_insn(body_ilist, INSN_MATCHER_DROP);
TRY(compile_body(compiler, func, body_ilist));
TRY(ilist_ensure_cap(compiler, ilist, 6));
ilist_put_insn(ilist, INSN_FUNC);
ilist_put_count(ilist, 1);
ilist_put_insn(ilist, INSN_MATCHER_LOAD);
ilist_put_insn(ilist, INSN_MATCHER_PUSH);
ilist_put_matcher(ilist, milist);
ilist_put_insn(ilist, INSN_FUNC_ADD_SUBFUNC);
ilist_put_body(ilist, body_ilist);
@ -686,10 +733,9 @@ compile_simple_func_inner(struct compiler *compiler, struct apfl_expr_body *func
static bool
compile_param(
struct compiler *compiler,
bool local,
struct apfl_position position,
struct apfl_expr_param *param,
struct compile_matchable_ilists ilists
struct compile_matchable_args args
);
DEF_COMPILE_ABSTRACT_MATCHABLE_LIST(
@ -711,9 +757,9 @@ static bool
compile_param_var(
struct compiler *compiler,
struct apfl_string *var,
struct compile_matchable_ilists ilists
struct compile_matchable_args args
) {
size_t index = ilists.matcher->capture_count++;
args.milist->capture_count++;
/* TODO: What should happen, if we try to match the same variable twice?
*
@ -727,18 +773,13 @@ compile_param_var(
* }
*/
TRY(milist_ensure_cap(compiler, ilists.matcher, 2));
milist_put_insn(ilists.matcher, MATCHER_CAPTURE);
milist_put_index(ilists.matcher, index);
TRY(ilist_ensure_cap(compiler, ilists.setvars, 3));
TRY(milist_ensure_cap(compiler, args.milist, 2));
struct apfl_string *varname = NULL;
TRY(string_move_into_new(compiler, &varname, var));
ilist_put_insn(ilists.setvars, INSN_VAR_SET_LOCAL_FROM_MATCHER);
ilist_put_string(ilists.setvars, varname);
ilist_put_index(ilists.setvars, index);
milist_put_insn(args.milist, MATCHER_CAPTURE_TO_VAR_LOCAL);
milist_put_string(args.milist, varname);
return true;
}
@ -746,24 +787,21 @@ compile_param_var(
static bool
compile_param(
struct compiler *compiler,
bool local,
struct apfl_position position,
struct apfl_expr_param *param,
struct compile_matchable_ilists ilists
struct compile_matchable_args args
) {
(void)local;
switch (param->type) {
case APFL_EXPR_PARAM_VAR:
return compile_param_var(compiler, &param->var, ilists);
return compile_param_var(compiler, &param->var, args);
case APFL_EXPR_PARAM_CONSTANT:
return compile_matchable_constant(compiler, &param->constant, ilists);
return compile_matchable_constant(compiler, &param->constant, args);
case APFL_EXPR_PARAM_LIST:
return compile_param_list(compiler, true, position, &param->list, ilists);
return compile_param_list(compiler, position, &param->list, args);
case APFL_EXPR_PARAM_PREDICATE:
return compile_param_predicate(compiler, true, position, &param->predicate, ilists);
return compile_param_predicate(compiler, position, &param->predicate, args);
case APFL_EXPR_PARAM_BLANK:
return compile_matchable_blank(compiler, ilists);
return compile_matchable_blank(compiler, args.milist);
}
assert(false);
@ -785,23 +823,19 @@ compile_subfunc(struct compiler *compiler, struct apfl_expr_subfunc *subfunc, st
struct instruction_list *body_ilist = NULL;
MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, position.line)));
struct matcher_instruction_list *matcher = NULL;
MALLOC_FAIL_IF_NULL(compiler, (matcher = tmp_milist(compiler)));
struct matcher_instruction_list *milist = NULL;
MALLOC_FAIL_IF_NULL(compiler, (milist = tmp_milist(compiler)));
TRY(ilist_ensure_cap(compiler, ilist, 2));
ilist_put_insn(ilist, INSN_MATCHER_LOAD);
ilist_put_matcher(ilist, matcher);
ilist_put_insn(ilist, INSN_MATCHER_PUSH);
ilist_put_matcher(ilist, milist);
TRY(compile_param_list(compiler, true, position, &subfunc->params, (struct compile_matchable_ilists) {
.prelude = ilist,
.newvars = NULL, // We don't use newvars when compiling parameters
.setvars = body_ilist,
.matcher = matcher,
TRY(compile_param_list(compiler, position, &subfunc->params, (struct compile_matchable_args) {
.ilist = ilist,
.milist = milist,
.local = true,
}));
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
ilist_put_insn(body_ilist, INSN_MATCHER_DROP);
TRY(compile_body(compiler, &subfunc->body, body_ilist));
TRY(ilist_ensure_cap(compiler, ilist, 2));

View file

@ -445,10 +445,7 @@ static void
stack_traverse(struct stack stack, gc_visitor visitor, void *opaque)
{
for (size_t i = 0; i < stack.len; i++) {
struct gc_object *object = apfl_value_get_gc_object(stack.items[i]);
if (object != NULL) {
visitor(opaque, object);
}
apfl_value_visit_gc_object(stack.items[i], visitor, opaque);
}
}
@ -460,6 +457,72 @@ visit_nullable_scope(struct scope *scope, gc_visitor visitor, void *opaque)
}
}
static void
visit_scopes(struct scopes scopes, gc_visitor visitor, void *opaque)
{
visit_nullable_scope(scopes.local, visitor, opaque);
visit_nullable_scope(scopes.closure, visitor, opaque);
}
static void
visit_matcher_stack(struct matcher_stack matcher_stack, gc_visitor visitor, void *opaque)
{
for (size_t i = 0; i < matcher_stack.len; i++) {
visitor(
opaque,
GC_OBJECT_FROM(matcher_stack.items[i], GC_TYPE_MATCHER)
);
}
}
static void
visit_func_cse(struct func_call_stack_entry cse, gc_visitor visitor, void *opaque)
{
visitor(
opaque,
GC_OBJECT_FROM(cse.instructions, GC_TYPE_INSTRUCTIONS)
);
visit_scopes(cse.scopes, visitor, opaque);
visit_matcher_stack(cse.matcher_stack, visitor, opaque);
}
static void
visit_cfunc_cse(struct cfunc_call_stack_entry cse, gc_visitor visitor, void *opaque)
{
visitor(
opaque,
GC_OBJECT_FROM(cse.func, GC_TYPE_CFUNC)
);
}
static void
visit_matcher_cse(struct matcher_call_stack_entry cse, gc_visitor visitor, void* opaque)
{
visitor(
opaque,
GC_OBJECT_FROM(cse.matcher, GC_TYPE_MATCHER)
);
visit_scopes(cse.scopes, visitor, opaque);
for (size_t i = 0; i < cse.capture_count; i++) {
apfl_value_visit_gc_object(cse.captures[i], visitor, opaque);
visitor(
opaque,
GC_OBJECT_FROM(cse.transfers[i].var, GC_TYPE_STRING)
);
}
}
static void
visit_func_dispatch_cse(struct func_dispatch_call_stack_entry cse, gc_visitor visitor, void *opaque)
{
visitor(
opaque,
GC_OBJECT_FROM(cse.function, GC_TYPE_FUNC)
);
visit_scopes(cse.scopes, visitor, opaque);
}
static void
gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, void *opaque)
{
@ -467,41 +530,18 @@ gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, vo
switch (cse.type) {
case CSE_FUNCTION:
visitor(
opaque,
GC_OBJECT_FROM(cse.func.instructions, GC_TYPE_INSTRUCTIONS)
);
visit_nullable_scope(cse.func.scope, visitor, opaque);
visit_nullable_scope(cse.func.closure_scope, visitor, opaque);
if (cse.func.matcher != NULL) {
visitor(
opaque,
GC_OBJECT_FROM(cse.func.matcher, GC_TYPE_MATCHER)
);
}
visit_func_cse(cse.func, visitor, opaque);
break;
case CSE_CFUNCTION:
visitor(
opaque,
GC_OBJECT_FROM(cse.cfunc.func, GC_TYPE_CFUNC)
);
visit_cfunc_cse(cse.cfunc, visitor, opaque);
break;
case CSE_MATCHER:
visitor(
opaque,
GC_OBJECT_FROM(cse.matcher.matcher, GC_TYPE_MATCHER)
);
visit_matcher_cse(cse.matcher, visitor, opaque);
break;
case CSE_FUNCTION_DISPATCH:
visitor(
opaque,
GC_OBJECT_FROM(cse.func_dispatch.function, GC_TYPE_FUNC)
);
visit_func_dispatch_cse(cse.func_dispatch, visitor, opaque);
break;
}
}
static void
@ -541,6 +581,23 @@ deinit_stack(struct apfl_allocator allocator, struct stack *stack)
*stack = apfl_stack_new();
}
static void
func_call_stack_entry_deinit(struct apfl_allocator allocator, struct func_call_stack_entry *cse)
{
FREE_LIST(allocator, cse->matcher_stack.items, cse->matcher_stack.cap);
}
void
apfl_matcher_call_stack_entry_deinit(struct apfl_allocator allocator, struct matcher_call_stack_entry *cse)
{
FREE_LIST(allocator, cse->captures, cse->capture_count);
cse->captures = NULL;
FREE_LIST(allocator, cse->transfers, cse->capture_count);
cse->transfers = NULL;
FREE_LIST(allocator, cse->matcher_state_stack, cse->matcher_state_stack_cap);
cse->matcher_state_stack = NULL;
}
void
apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry)
{
@ -548,11 +605,13 @@ apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_
switch (entry->type) {
case CSE_FUNCTION:
func_call_stack_entry_deinit(allocator, &entry->func);
break;
case CSE_CFUNCTION:
case CSE_FUNCTION_DISPATCH:
break;
case CSE_MATCHER:
FREE_LIST(allocator, entry->matcher.matcher_state_stack, entry->matcher.matcher_state_stack_cap);
apfl_matcher_call_stack_entry_deinit(allocator, &entry->matcher);
break;
}
}
@ -988,8 +1047,8 @@ apfl_dict_set(
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
}
void
apfl_get_member(
bool
apfl_get_member_if_exists(
apfl_ctx ctx,
apfl_stackidx container_index,
apfl_stackidx k_index
@ -1011,17 +1070,16 @@ apfl_get_member(
enum get_item_result result = apfl_value_get_item(container, k, value);
if (result != GET_ITEM_OK) {
assert(apfl_stack_drop(ctx, -1));
assert(apfl_stack_drop(ctx, -1)); // Drop the placeholder
}
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
switch (result) {
case GET_ITEM_OK:
break;
return true;
case GET_ITEM_KEY_DOESNT_EXIST:
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist);
break;
return false;
case GET_ITEM_NOT_A_CONTAINER:
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.value_is_not_a_container);
break;
@ -1029,6 +1087,20 @@ apfl_get_member(
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_key_type);
break;
}
assert(false);
return false;
}
void
apfl_get_member(
apfl_ctx ctx,
apfl_stackidx container_index,
apfl_stackidx k_index
) {
if (!apfl_get_member_if_exists(ctx, container_index, k_index)) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist);
}
}
void
@ -1180,7 +1252,7 @@ apfl_eq(apfl_ctx ctx, apfl_stackidx _a, apfl_stackidx _b)
}
static struct scope *
closure_scope_for_func_inner(apfl_ctx ctx)
closure_scope_for_func_inner(apfl_ctx ctx, struct scopes scopes)
{
struct scope *out = apfl_scope_new(&ctx->gc);
if (out == NULL) {
@ -1191,22 +1263,18 @@ closure_scope_for_func_inner(apfl_ctx ctx)
return NULL;
}
struct call_stack_entry *entry = apfl_call_stack_cur_entry(ctx);
if (entry == NULL || entry->type != CSE_FUNCTION) {
return out;
}
// The order is important here: by merging the local scope last, we make
// sure a variable from the current scope shadows a variable from the
// closure scope
// The order is important here: by merging entry->scope last, we make sure a
// variable from the current scope shadows a variable from the closure scope
if (entry->func.closure_scope != NULL) {
if (!apfl_scope_merge_into(out, entry->func.closure_scope)) {
if (scopes.closure != NULL) {
if (!apfl_scope_merge_into(out, scopes.closure)) {
return NULL;
}
}
if (entry->func.scope != NULL) {
if (!apfl_scope_merge_into(out, entry->func.scope)) {
if (scopes.local != NULL) {
if (!apfl_scope_merge_into(out, scopes.local)) {
return NULL;
}
}
@ -1215,10 +1283,10 @@ closure_scope_for_func_inner(apfl_ctx ctx)
}
struct scope *
apfl_closure_scope_for_func(apfl_ctx ctx)
apfl_closure_scope_for_func(apfl_ctx ctx, struct scopes scopes)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
struct scope *out = closure_scope_for_func_inner(ctx);
struct scope *out = closure_scope_for_func_inner(ctx, scopes);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
return out;
}
@ -1350,3 +1418,64 @@ struct apfl_format_writer apfl_get_output_writer(apfl_ctx ctx)
{
return ctx->output_writer;
}
static bool
init_values_list(struct apfl_allocator allocator, struct apfl_value **list, size_t len)
{
if (len == 0) {
return true;
}
if ((*list = ALLOC_LIST(allocator, struct apfl_value, len)) == NULL) {
return false;
}
for (size_t i = 0; i < len; i++) {
(*list)[i] = (struct apfl_value) { .type = VALUE_NIL };
}
return true;
}
struct matcher *
apfl_matcher_new(struct gc *gc, struct matcher_instruction_list *milist)
{
struct matcher matcher = {
.instructions = milist,
.value_count = 0,
.values = NULL,
};
if (!init_values_list(gc->allocator, &matcher.values, milist->value_count)) {
goto error;
}
matcher.value_count = milist->value_count;
struct matcher *gc_matcher = apfl_gc_new_matcher(gc);
if (gc_matcher == NULL) {
goto error;
}
*gc_matcher = matcher;
return gc_matcher;
error:
apfl_matcher_deinit(gc->allocator, &matcher);
return NULL;
}
void
apfl_matcher_deinit(struct apfl_allocator allocator, struct matcher *matcher)
{
FREE_LIST(allocator, matcher->values, matcher->value_count);
}
void
apfl_gc_matcher_traverse(struct matcher *matcher, gc_visitor visitor, void *opaque)
{
visitor(opaque, GC_OBJECT_FROM(matcher->instructions, GC_TYPE_MATCHER_INSTRUCTIONS));
for (size_t i = 0; i < matcher->instructions->value_count; i++) {
apfl_value_visit_gc_object(matcher->values[i], visitor, opaque);
}
}

View file

@ -11,18 +11,42 @@ extern "C" {
#include "bytecode.h"
#include "hashmap.h"
#include "gc.h"
#include "matcher.h"
#include "value.h"
#include "scope.h"
#define RESULT_OFF_FOR_LONGJMP 1
struct scopes {
// Both scope and closure_scope can be null
struct scope *local;
struct scope *closure;
};
struct matcher_capture_transfer {
struct apfl_string *var;
size_t path_start;
size_t path_len;
bool local;
};
struct matcher {
struct matcher_instruction_list *instructions;
size_t value_count;
struct apfl_value *values;
};
struct stack {
struct apfl_value *items;
size_t len;
size_t cap;
};
struct matcher_stack {
struct matcher **items;
size_t len;
size_t cap;
};
enum call_stack_entry_type {
CSE_FUNCTION,
CSE_CFUNCTION,
@ -34,14 +58,14 @@ struct func_call_stack_entry {
size_t pc;
struct instruction_list *instructions;
// Both scope and closure_scope can be null (scope will be created lazily)
struct scope *scope;
struct scope *closure_scope;
// scopes.scope will be created lazily
struct scopes scopes;
int execution_line;
struct matcher *matcher;
struct matcher_stack matcher_stack;
bool returning_from_matcher;
bool matcher_result;
};
struct cfunc_call_stack_entry {
@ -67,6 +91,12 @@ struct matcher_state {
struct matcher_call_stack_entry {
size_t pc;
struct matcher *matcher;
struct scopes scopes;
size_t capture_index;
size_t capture_count;
struct apfl_value *captures;
struct matcher_capture_transfer *transfers;
struct matcher_state *matcher_state_stack;
size_t matcher_state_stack_len;
@ -75,7 +105,9 @@ struct matcher_call_stack_entry {
struct func_dispatch_call_stack_entry {
size_t subfunc;
struct scopes scopes;
bool returning_from_matcher;
bool matcher_result;
struct function *function;
};
@ -127,6 +159,7 @@ struct apfl_ctx_data {
struct apfl_format_writer output_writer;
};
void apfl_matcher_call_stack_entry_deinit(struct apfl_allocator, struct matcher_call_stack_entry *);
void apfl_call_stack_entry_deinit(struct apfl_allocator, struct call_stack_entry *);
struct stack apfl_stack_new(void);
@ -147,13 +180,17 @@ bool apfl_move_string_onto_stack(apfl_ctx, struct apfl_string);
struct call_stack_entry *apfl_call_stack_cur_entry(apfl_ctx);
struct scope *apfl_closure_scope_for_func(apfl_ctx);
struct scope *apfl_closure_scope_for_func(apfl_ctx, struct scopes);
struct apfl_format_writer apfl_get_output_writer(apfl_ctx);
bool apfl_ctx_register_iterative_runner(apfl_ctx, apfl_iterative_runner);
void apfl_ctx_unregister_iterative_runner(apfl_ctx, apfl_iterative_runner);
struct matcher *apfl_matcher_new(struct gc *, struct matcher_instruction_list *);
void apfl_matcher_deinit(struct apfl_allocator, struct matcher *);
void apfl_gc_matcher_traverse(struct matcher *, gc_visitor, void *);
void apfl_iterative_runner_visit_gc_objects(apfl_iterative_runner, gc_visitor, void *);
#ifdef __cplusplus

View file

@ -58,8 +58,6 @@ apfl_error_type_name(enum apfl_error_type type)
return "APFL_ERR_UNEXPECTED_EXPR_IN_MEMBER_ACCESS";
case APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS:
return "APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS";
case APFL_ERR_NOT_IMPLEMENTED:
return "APFL_ERR_NOT_IMPLEMENTED";
}
return "<unknown error>";
@ -75,8 +73,6 @@ apfl_error_as_const_string(struct apfl_error error)
return apfl_messages.input_error_while_parsing;
case APFL_ERR_UNEXPECTED_EOF:
return apfl_messages.unexpected_end_of_file;
case APFL_ERR_NOT_IMPLEMENTED:
return apfl_messages.feature_not_implemented;
default:
return NULL;
}
@ -186,9 +182,6 @@ format_error(struct apfl_format_writer w, struct apfl_error error)
TRY(apfl_format_put_string(w, "Unexpected blank (\"_\") in member access near "));
TRY(apfl_format_put_pos(w, error.position));
return true;
case APFL_ERR_NOT_IMPLEMENTED:
TRY(apfl_format_put_string(w, apfl_messages.feature_not_implemented));
return true;
}
TRY(apfl_format_put_string(w, "Unknown error "));

View file

@ -8,7 +8,6 @@
#include "context.h"
#include "format.h"
#include "hashmap.h"
#include "matcher.h"
#include "resizable.h"
#include "strings.h"
#include "value.h"
@ -16,7 +15,7 @@
static void evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse);
static void evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse);
static void dispatch(apfl_ctx ctx, struct call_stack_entry *cse);
static void matcher_init_matching(apfl_ctx ctx, struct matcher *matcher);
static void matcher_init_matching(apfl_ctx ctx, struct matcher *matcher, struct scopes scopes);
static void
stack_must_drop(apfl_ctx ctx, apfl_stackidx index)
@ -61,19 +60,6 @@ must_get_matcher_argument(apfl_ctx ctx, size_t *i, struct matcher_instruction_li
ABSTRACT_MUST_GET_ARG(get_matcher_argument, ctx, i, milist, arg)
}
static struct func_call_stack_entry *
get_current_func_cse(apfl_ctx ctx)
{
struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx);
if (cse == NULL) {
return NULL;
}
return cse->type == CSE_FUNCTION
? &cse->func
: NULL;
}
enum scope_type {
SCOPE_LOCAL,
SCOPE_CLOSUE,
@ -81,21 +67,13 @@ enum scope_type {
};
static struct scope *
get_scope(apfl_ctx ctx, enum scope_type type)
get_scope(apfl_ctx ctx, struct scopes scopes, enum scope_type type)
{
struct func_call_stack_entry *func_cse;
switch (type) {
case SCOPE_LOCAL:
if ((func_cse = get_current_func_cse(ctx)) != NULL) {
return func_cse->scope;
}
return NULL;
return scopes.local;
case SCOPE_CLOSUE:
if ((func_cse = get_current_func_cse(ctx)) != NULL) {
return func_cse->closure_scope;
}
return NULL;
return scopes.closure;
case SCOPE_GLOBAL:
return ctx->globals;
}
@ -105,29 +83,30 @@ get_scope(apfl_ctx ctx, enum scope_type type)
}
static struct scope *
get_or_create_local_scope(apfl_ctx ctx)
get_or_create_local_scope(apfl_ctx ctx, struct scopes *scopes)
{
struct func_call_stack_entry *func_cse = get_current_func_cse(ctx);
assert(func_cse != NULL);
if (func_cse->scope != NULL) {
return func_cse->scope;
if (scopes->local != NULL) {
return scopes->local;
}
if ((func_cse->scope = apfl_scope_new(&ctx->gc)) == NULL) {
if ((scopes->local = apfl_scope_new(&ctx->gc)) == NULL) {
apfl_raise_alloc_error(ctx);
}
return func_cse->scope;
return scopes->local;
}
static bool
try_variable_get_for_scope_type(apfl_ctx ctx, struct apfl_string *name, enum scope_type type)
{
try_variable_get_for_scope_type(
apfl_ctx ctx,
struct scopes scopes,
struct apfl_string *name,
enum scope_type type
) {
struct apfl_value value;
struct scope *scope;
if ((scope = get_scope(ctx, type)) != NULL) {
if ((scope = get_scope(ctx, scopes, type)) != NULL) {
if (apfl_scope_get(scope, name, &value)) {
apfl_stack_must_push(ctx, value);
return true;
@ -137,15 +116,15 @@ try_variable_get_for_scope_type(apfl_ctx ctx, struct apfl_string *name, enum sco
}
static void
variable_get(apfl_ctx ctx, struct apfl_string *name)
variable_get(apfl_ctx ctx, struct scopes scopes, struct apfl_string *name, bool global)
{
if (try_variable_get_for_scope_type(ctx, name, SCOPE_LOCAL)) {
if (try_variable_get_for_scope_type(ctx, scopes, name, SCOPE_LOCAL)) {
return;
}
if (try_variable_get_for_scope_type(ctx, name, SCOPE_CLOSUE)) {
if (try_variable_get_for_scope_type(ctx, scopes, name, SCOPE_CLOSUE)) {
return;
}
if (try_variable_get_for_scope_type(ctx, name, SCOPE_GLOBAL)) {
if (global && try_variable_get_for_scope_type(ctx, scopes, name, SCOPE_GLOBAL)) {
return;
}
@ -154,33 +133,28 @@ variable_get(apfl_ctx ctx, struct apfl_string *name)
static bool
try_variable_update_existing_for_scope_type(
apfl_ctx ctx,
struct apfl_string *name,
struct apfl_value value,
enum scope_type type
struct scope *scope
) {
struct scope *scope;
if ((scope = get_scope(ctx, type)) != NULL) {
if (apfl_scope_update_existing(scope, name, value)) {
return true;
}
if (scope == NULL) {
return false;
}
return false;
return apfl_scope_update_existing(scope, name, value);
}
static void
variable_set_value(apfl_ctx ctx, struct apfl_string *name, bool local, struct apfl_value value)
variable_set_value(apfl_ctx ctx, struct scopes *scopes, struct apfl_string *name, bool local, struct apfl_value value)
{
bool was_set = false;
if (!local) {
was_set = try_variable_update_existing_for_scope_type(ctx, name, value, SCOPE_LOCAL)
|| try_variable_update_existing_for_scope_type(ctx, name, value, SCOPE_CLOSUE);
was_set = try_variable_update_existing_for_scope_type(name, value, scopes->local)
|| try_variable_update_existing_for_scope_type(name, value, scopes->closure);
}
if (!was_set) {
struct scope *scope = get_or_create_local_scope(ctx);
struct scope *scope = get_or_create_local_scope(ctx, scopes);
assert(scope != NULL /*get_or_create_local_scope should never return NULL*/);
if (!apfl_scope_set(&ctx->gc, scope, name, value)) {
@ -190,11 +164,11 @@ variable_set_value(apfl_ctx ctx, struct apfl_string *name, bool local, struct ap
}
static void
variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool local)
variable_set(apfl_ctx ctx, struct scopes *scopes, struct apfl_string *name, bool keep_on_stack, bool local)
{
struct apfl_value value = apfl_stack_must_get(ctx, -1);
variable_set_value(ctx, name, local, value);
variable_set_value(ctx, scopes, name, local, value);
if (keep_on_stack) {
// If the value should be kept on the stack, the value is now in two
@ -206,42 +180,88 @@ variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool lo
}
}
static bool
variable_exists_by_scope_type(apfl_ctx ctx, struct apfl_string *name, enum scope_type type)
{
struct scope *scope;
if ((scope = get_scope(ctx, type)) != NULL) {
if (apfl_scope_has(scope, name)) {
return true;
}
}
return false;
}
static void
variable_new(apfl_ctx ctx, struct apfl_string *name, bool local)
variable_new(apfl_ctx ctx, struct scopes *scopes, struct apfl_string *name, bool local)
{
if (!local) {
if (variable_exists_by_scope_type(ctx, name, SCOPE_LOCAL)) {
if (scopes->local != NULL && apfl_scope_has(scopes->local, name)) {
return;
}
if (variable_exists_by_scope_type(ctx, name, SCOPE_CLOSUE)) {
if (scopes->closure != NULL && apfl_scope_has(scopes->closure, name)) {
return;
}
}
struct scope *scope = get_or_create_local_scope(ctx);
struct scope *scope = get_or_create_local_scope(ctx, scopes);
if (!apfl_scope_create_var(&ctx->gc, scope, name)) {
apfl_raise_alloc_error(ctx);
}
}
static void
func_inner(apfl_ctx ctx, size_t count)
matcher_push(apfl_ctx ctx, struct func_call_stack_entry *cse, struct matcher_instruction_list *milist)
{
struct scope *scope = apfl_closure_scope_for_func(ctx);
struct matcher_stack *matcher_stack = &cse->matcher_stack;
if (!apfl_resizable_ensure_cap_for_more_elements(
ctx->gc.allocator,
sizeof(struct matcher *),
(void **)&matcher_stack->items,
matcher_stack->len,
&matcher_stack->cap,
1
)) {
apfl_raise_alloc_error(ctx);
}
if ((matcher_stack->items[matcher_stack->len] = apfl_matcher_new(&ctx->gc, milist)) == NULL) {
apfl_raise_alloc_error(ctx);
}
matcher_stack->len++;
}
static struct matcher *
matcher_stack_top(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
struct matcher_stack *matcher_stack = &cse->matcher_stack;
if (matcher_stack->len == 0) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
struct matcher *matcher = matcher_stack->items[matcher_stack->len-1];
if (matcher == NULL) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
return matcher;
}
static void
matcher_stack_drop(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
struct matcher_stack *matcher_stack = &cse->matcher_stack;
if (matcher_stack->len == 0) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
assert(
// We're shrinking, should not fail
apfl_resizable_resize(
ctx->gc.allocator,
sizeof(struct matcher *),
(void **)&matcher_stack->items,
&matcher_stack->len,
&matcher_stack->cap,
matcher_stack->len-1
)
);
}
static void
func_inner(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t count)
{
struct scope *scope = apfl_closure_scope_for_func(ctx, cse->scopes);
if (scope == NULL) {
apfl_raise_alloc_error(ctx);
}
@ -264,10 +284,10 @@ func_inner(apfl_ctx ctx, size_t count)
}
static void
func(apfl_ctx ctx, size_t count)
func(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t count)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
func_inner(ctx, count);
func_inner(ctx, cse, count);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
@ -281,21 +301,17 @@ func_add_subfunc(apfl_ctx ctx, struct func_call_stack_entry *cse, struct instruc
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
if (cse->matcher == NULL) {
if (!apfl_func_add_subfunc(value.func, body, matcher_stack_top(ctx, cse))) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
if (!apfl_func_add_subfunc(value.func, body, cse->matcher)) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
cse->matcher = NULL;
matcher_stack_drop(ctx, cse);
}
static void
call_stack_push(apfl_ctx ctx, struct call_stack_entry cse)
static bool
try_call_stack_push(apfl_ctx ctx, struct call_stack_entry cse)
{
if (!apfl_resizable_append(
return apfl_resizable_append(
ctx->gc.allocator,
sizeof(struct call_stack_entry),
(void**)&ctx->call_stack.items,
@ -303,7 +319,13 @@ call_stack_push(apfl_ctx ctx, struct call_stack_entry cse)
&ctx->call_stack.cap,
&cse,
1
)) {
);
}
static void
call_stack_push(apfl_ctx ctx, struct call_stack_entry cse)
{
if (!try_call_stack_push(ctx, cse)) {
apfl_call_stack_entry_deinit(ctx->gc.allocator, &cse);
apfl_raise_alloc_error(ctx);
}
@ -425,14 +447,28 @@ call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackid
}
switch (func.type) {
case VALUE_FUNC:
case VALUE_FUNC: {
struct scope *local_scope = apfl_scope_new(&ctx->gc);
if (local_scope == NULL) {
apfl_raise_alloc_error(ctx);
}
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(local_scope, GC_TYPE_SCOPE))) {
apfl_raise_alloc_error(ctx);
}
prepare_call(ctx, tmproots, args, (struct call_stack_entry) {
.type = CSE_FUNCTION_DISPATCH,
.stack = apfl_stack_new(),
.func_dispatch = {
.subfunc = 0,
.scopes = {
.local = local_scope,
.closure = func.func->scope,
},
.function = func.func,
.returning_from_matcher = false,
.matcher_result = false,
},
});
@ -444,6 +480,7 @@ call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackid
evaluate_until_call_stack_return(ctx);
}
break;
}
case VALUE_CFUNC:
prepare_call(ctx, tmproots, args, (struct call_stack_entry) {
.type = CSE_CFUNCTION,
@ -475,23 +512,10 @@ apfl_call(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx args_index)
call(ctx, func_index, args_index, false);
}
static void
matcher_load(apfl_ctx ctx, struct func_call_stack_entry *cse, struct matcher_instruction_list *milist)
matcher_set_val(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t index)
{
assert(cse != NULL);
if ((cse->matcher = apfl_matcher_new(&ctx->gc, milist)) == NULL) {
apfl_raise_alloc_error(ctx);
}
}
static void
matcher_set_val(apfl_ctx ctx, struct matcher *matcher, size_t index)
{
if (matcher == NULL) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
struct matcher *matcher = matcher_stack_top(ctx, cse);
if (index >= matcher->instructions->value_count) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
@ -501,37 +525,17 @@ matcher_set_val(apfl_ctx ctx, struct matcher *matcher, size_t index)
}
static void
variable_set_from_matcher_inner(
apfl_ctx ctx,
struct matcher *matcher,
struct apfl_string *varname,
size_t index,
bool local
) {
if (matcher == NULL) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
if (index >= matcher->instructions->capture_count) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
struct apfl_value value = apfl_value_move(&matcher->captures[index]);
must_tmproot_add_value(ctx, value);
variable_set_value(ctx, varname, local, value);
}
static void
variable_set_from_matcher(
apfl_ctx ctx,
struct matcher *matcher,
struct apfl_string *varname,
size_t index,
bool local
) {
matcher_must_match(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
variable_set_from_matcher_inner(ctx, matcher, varname, index, local);
struct matcher *matcher = matcher_stack_top(ctx, cse);
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(matcher, GC_TYPE_MATCHER))) {
apfl_raise_alloc_error(ctx);
}
matcher_stack_drop(ctx, cse);
matcher_init_matching(ctx, matcher, cse->scopes);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
@ -539,16 +543,13 @@ static void
evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
if (cse->returning_from_matcher) {
assert(cse->matcher != NULL);
if (!cse->matcher->result) {
cse->matcher = NULL;
if (!cse->matcher_result) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.value_doesnt_match);
}
cse->returning_from_matcher = false;
}
union instruction_or_arg arg;
union instruction_or_arg arg2;
size_t *pc = &cse->pc;
struct instruction_list *ilist = cse->instructions;
@ -596,27 +597,27 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse)
goto continue_loop;
case INSN_VAR_NEW:
must_get_argument(ctx, pc, ilist, &arg);
variable_new(ctx, arg.string, false);
variable_new(ctx, &cse->scopes, arg.string, false);
goto continue_loop;
case INSN_VAR_NEW_LOCAL:
must_get_argument(ctx, pc, ilist, &arg);
variable_new(ctx, arg.string, true);
variable_new(ctx, &cse->scopes, arg.string, true);
goto continue_loop;
case INSN_VAR_GET:
must_get_argument(ctx, pc, ilist, &arg);
variable_get(ctx, arg.string);
variable_get(ctx, cse->scopes, arg.string, true);
goto continue_loop;
case INSN_VAR_SET:
must_get_argument(ctx, pc, ilist, &arg);
variable_set(ctx, arg.string, true, false);
variable_set(ctx, &cse->scopes, arg.string, true, false);
goto continue_loop;
case INSN_VAR_SET_LOCAL:
must_get_argument(ctx, pc, ilist, &arg);
variable_set(ctx, arg.string, true, true);
variable_set(ctx, &cse->scopes, arg.string, true, true);
goto continue_loop;
case INSN_MOVE_TO_LOCAL_VAR:
must_get_argument(ctx, pc, ilist, &arg);
variable_set(ctx, arg.string, false, true);
variable_set(ctx, &cse->scopes, arg.string, false, true);
goto continue_loop;
case INSN_NEXT_LINE:
cse->execution_line++;
@ -634,6 +635,9 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse)
apfl_raise_invalid_stackidx(ctx);
}
goto continue_loop;
case INSN_DUP:
apfl_copy(ctx, -1);
goto continue_loop;
case INSN_CALL:
call(ctx, -2, -1, true);
@ -643,42 +647,28 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse)
return;
case INSN_FUNC:
must_get_argument(ctx, pc, ilist, &arg);
func(ctx, arg.count);
func(ctx, cse, arg.count);
goto continue_loop;
case INSN_FUNC_ADD_SUBFUNC:
must_get_argument(ctx, pc, ilist, &arg);
func_add_subfunc(ctx, cse, arg.body);
goto continue_loop;
case INSN_MATCHER_LOAD:
case INSN_MATCHER_PUSH:
must_get_argument(ctx, pc, ilist, &arg);
matcher_load(ctx, cse, arg.matcher);
matcher_push(ctx, cse, arg.matcher);
goto continue_loop;
case INSN_MATCHER_SET_VAL:
must_get_argument(ctx, pc, ilist, &arg);
matcher_set_val(ctx, cse->matcher, arg.index);
matcher_set_val(ctx, cse, arg.index);
goto continue_loop;
case INSN_MATCHER_MUST_MATCH:
// matcher_init_matching pushes a new call stack entry for the matcher onto the stack. We rturn from this
// matcher_must_match pushes a new call stack entry for the matcher onto the stack. We return from this
// So this new CSE gets executed. By setting returning_from_matcher, we know that we came from the matcher,
// once it returns.
cse->returning_from_matcher = true;
matcher_init_matching(ctx, cse->matcher);
matcher_must_match(ctx, cse);
return;
case INSN_MATCHER_DROP:
cse->matcher = NULL;
goto continue_loop;
case INSN_VAR_SET_FROM_MATCHER:
must_get_argument(ctx, pc, ilist, &arg);
must_get_argument(ctx, pc, ilist, &arg2);
variable_set_from_matcher(ctx, cse->matcher, arg.string, arg2.index, false);
goto continue_loop;
case INSN_VAR_SET_LOCAL_FROM_MATCHER:
must_get_argument(ctx, pc, ilist, &arg);
must_get_argument(ctx, pc, ilist, &arg2);
variable_set_from_matcher(ctx, cse->matcher, arg.string, arg2.index, true);
goto continue_loop;
}
assert(false);
@ -732,41 +722,99 @@ matcher_state_drop(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
}
static void
matcher_init_matching_inner(apfl_ctx ctx, struct matcher *matcher)
matcher_init_matching_inner(apfl_ctx ctx, struct matcher *matcher, struct scopes scopes)
{
struct apfl_value value = apfl_stack_must_get(ctx, -1);
struct apfl_value value = apfl_stack_must_pop(ctx, -1);
must_tmproot_add_value(ctx, value);
if (matcher == NULL) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
}
size_t capture_count = matcher->instructions->capture_count;
struct matcher_call_stack_entry matcher_cse = {
.pc = 0,
.matcher = matcher,
.scopes = scopes,
.capture_index = 0,
.capture_count = capture_count,
.captures = NULL,
.transfers = NULL,
.matcher_state_stack = NULL,
.matcher_state_stack_len = 0,
.matcher_state_stack_cap = 0,
};
matcher_state_push(ctx, &matcher_cse, (struct matcher_state) {
.mode = MATCHER_MODE_VALUE,
});
if (capture_count > 0) {
if ((matcher_cse.captures = ALLOC_LIST(ctx->gc.allocator, struct apfl_value, capture_count)) == NULL) {
goto error;
}
call_stack_push(ctx, (struct call_stack_entry) {
for (size_t i = 0; i < capture_count; i++) {
matcher_cse.captures[i] = (struct apfl_value) { .type = VALUE_NIL };
}
if ((matcher_cse.transfers = ALLOC_LIST(
ctx->gc.allocator,
struct matcher_capture_transfer,
capture_count
)) == NULL) {
goto error;
}
for (size_t i = 0; i < capture_count; i++) {
matcher_cse.transfers[i] = (struct matcher_capture_transfer) {
.var = NULL,
.path_start = 0,
.path_len = 0,
.local = false,
};
}
}
if ((matcher_cse.matcher_state_stack = ALLOC_LIST(
ctx->gc.allocator,
struct matcher_state,
1
)) == NULL) {
goto error;
}
matcher_cse.matcher_state_stack[0] = (struct matcher_state) {
.mode = MATCHER_MODE_VALUE,
};
matcher_cse.matcher_state_stack_len = 1;
matcher_cse.matcher_state_stack_cap = 1;
if (!try_call_stack_push(ctx, (struct call_stack_entry) {
.type = CSE_MATCHER,
.stack = apfl_stack_new(),
.matcher = matcher_cse,
});
})) {
goto error;
}
// No need for `goto error` on failure here, all dynamically allocated
// elements are on the call stack now, so the GC can clean them up in case
// of an error.
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(value));
return;
error:
apfl_matcher_call_stack_entry_deinit(ctx->gc.allocator, &matcher_cse);
apfl_raise_alloc_error(ctx);
}
/*
* Initialise matching. Pushes a new call stack and pops a value of the current+
* value stack.
*/
static void
matcher_init_matching(apfl_ctx ctx, struct matcher *matcher)
matcher_init_matching(apfl_ctx ctx, struct matcher *matcher, struct scopes scopes)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
matcher_init_matching_inner(ctx, matcher);
matcher_init_matching_inner(ctx, matcher, scopes);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
@ -978,6 +1026,63 @@ matcher_leave_list(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
return matcher_next(ctx, cse);
}
static void
matcher_transfer(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
for (size_t i = 0; i < cse->capture_count; i++) {
struct matcher_capture_transfer transfer = cse->transfers[i];
if (transfer.path_len == 0) {
variable_set_value(ctx, &cse->scopes, transfer.var, transfer.local, cse->captures[i]);
} else {
// Set the value at a key path in a (nested) dictionary.
variable_get(ctx, cse->scopes, transfer.var, false);
if (apfl_get_type(ctx, -1) != APFL_VALUE_DICT) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_dict);
}
// Get or create intermediary dictionaries along the key path and leave a copy of the previous one on the
// stack, so we can set the result in reverse order there later.
for (size_t i = 0; i < transfer.path_len - 1; i++) {
apfl_copy(ctx, -1);
size_t value_index = transfer.path_start + i;
matcher_check_index(ctx, cse->matcher->value_count, value_index);
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cse->matcher->values[value_index]));
if (apfl_get_member_if_exists(ctx, -2, -1)) {
if (apfl_get_type(ctx, -1) != APFL_VALUE_DICT) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_dict);
}
} else {
apfl_dict_create(ctx);
}
}
// Set the value to the rightmost dictionary key
size_t value_index = transfer.path_start + transfer.path_len - 1;
matcher_check_index(ctx, cse->matcher->value_count, value_index);
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cse->matcher->values[value_index]));
apfl_stack_must_push(ctx, cse->captures[i]);
apfl_dict_set(ctx, -3, -2, -1);
// Go through the key path (minus the rightmost key) in reverse order and set the value in the intermediary
// dictionaries. Note that i has a offset of one here so we can use the i > 0 check in the loop (>= 0 would
// not work as size_t is unsigned).
for (size_t i = transfer.path_len - 1; i > 0; i--) {
size_t value_index = transfer.path_start + i - 1;
matcher_check_index(ctx, cse->matcher->value_count, value_index);
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cse->matcher->values[value_index]));
apfl_dict_set(ctx, -3, -1, -2);
}
// Finally set the copied and modified dictionary to the variable again.
variable_set(ctx, &cse->scopes, transfer.var, false, false);
}
}
}
static void
return_from_matcher(apfl_ctx ctx, bool result)
{
@ -985,12 +1090,27 @@ return_from_matcher(apfl_ctx ctx, bool result)
assert(cse != NULL);
assert(cse->type == CSE_MATCHER);
cse->matcher.matcher->result = result;
if (result) {
matcher_transfer(ctx, &cse->matcher);
}
call_stack_drop(ctx);
cse = apfl_call_stack_cur_entry(ctx);
assert(cse != NULL);
assert(cse->type == CSE_FUNCTION || cse->type == CSE_FUNCTION_DISPATCH);
switch (cse->type) {
case CSE_FUNCTION:
cse->func.returning_from_matcher = true;
cse->func.matcher_result = result;
break;
case CSE_FUNCTION_DISPATCH:
cse->func_dispatch.returning_from_matcher = true;
cse->func_dispatch.matcher_result = result;
break;
default:
assert(false /* Invalid stack entry below matcher stack */);
}
}
#define RETURN_WITHOUT_MATCH(ctx) \
@ -1006,6 +1126,53 @@ return_from_matcher(apfl_ctx ctx, bool result)
} \
} while (0)
static bool
matcher_evaluate_capturing_instruction(
apfl_ctx ctx,
struct matcher_call_stack_entry *cse,
bool local,
bool with_path
) {
size_t *pc = &cse->pc;
struct matcher *matcher = cse->matcher;
struct matcher_instruction_list *milist = matcher->instructions;
struct matcher_capture_transfer transfer = {
.var = NULL,
.path_start = 0,
.path_len = 0,
.local = local,
};
union matcher_instruction_or_arg arg;
must_get_matcher_argument(ctx, pc, milist, &arg);
transfer.var = arg.string;
if (with_path) {
must_get_matcher_argument(ctx, pc, milist, &arg);
transfer.path_start = arg.index;
must_get_matcher_argument(ctx, pc, milist, &arg);
transfer.path_len = arg.index;
}
struct apfl_value cur;
if (!matcher_current_val(ctx, cse, &cur)) {
return_from_matcher(ctx, false);
return false;
}
size_t capture = cse->capture_index++;
matcher_check_index(ctx, milist->capture_count, capture);
cse->captures[capture] = apfl_value_set_cow_flag(cur);
cse->transfers[capture] = transfer;
return matcher_next(ctx, cse);
}
static void
evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
@ -1019,14 +1186,29 @@ evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
struct apfl_value cur;
switch (milist->instructions[(*pc)++].instruction) {
case MATCHER_CAPTURE:
if (!matcher_current_val(ctx, cse, &cur)) {
RETURN_WITHOUT_MATCH(ctx);
}
must_get_matcher_argument(ctx, pc, milist, &arg);
matcher_check_index(ctx, milist->capture_count, arg.index);
matcher->captures[arg.index] = apfl_value_set_cow_flag(cur);
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_next(ctx, cse));
case MATCHER_CAPTURE_TO_VAR:
RETURN_WITHOUT_MATCH_ON_FALSE(
ctx,
matcher_evaluate_capturing_instruction(ctx, cse, false, false)
);
goto continue_loop;
case MATCHER_CAPTURE_TO_VAR_LOCAL:
RETURN_WITHOUT_MATCH_ON_FALSE(
ctx,
matcher_evaluate_capturing_instruction(ctx, cse, true, false)
);
goto continue_loop;
case MATCHER_CAPTURE_TO_VAR_WITH_PATH:
RETURN_WITHOUT_MATCH_ON_FALSE(
ctx,
matcher_evaluate_capturing_instruction(ctx, cse, false, true)
);
goto continue_loop;
case MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH:
RETURN_WITHOUT_MATCH_ON_FALSE(
ctx,
matcher_evaluate_capturing_instruction(ctx, cse, true, true)
);
goto continue_loop;
case MATCHER_IGNORE:
if (!matcher_current_val(ctx, cse, &cur)) {
@ -1075,6 +1257,16 @@ evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
);
}
struct matcher_stack
matcher_stack_new(void)
{
return (struct matcher_stack) {
.items = NULL,
.len = 0,
.cap = 0,
};
}
static void
dispatch(apfl_ctx ctx, struct call_stack_entry *cse)
{
@ -1084,19 +1276,20 @@ dispatch(apfl_ctx ctx, struct call_stack_entry *cse)
struct function *function = fd_cse->function;
if (fd_cse->returning_from_matcher) {
struct subfunction *subfunction = &function->subfunctions[fd_cse->subfunc];
if (subfunction->matcher->result) {
if (fd_cse->matcher_result) {
struct subfunction *subfunction = &function->subfunctions[fd_cse->subfunc];
// Replace the current CSE with a function CSE
cse->type = CSE_FUNCTION;
cse->stack.len = 0;
cse->func = (struct func_call_stack_entry) {
.pc = 0,
.instructions = subfunction->body,
.scope = NULL,
.closure_scope = function->scope,
.scopes = fd_cse->scopes,
.execution_line = subfunction->body->line,
.matcher = subfunction->matcher,
.matcher_stack = matcher_stack_new(),
.returning_from_matcher = false,
.matcher_result = false,
};
return;
@ -1110,8 +1303,14 @@ dispatch(apfl_ctx ctx, struct call_stack_entry *cse)
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.no_matching_subfunction);
}
fd_cse->returning_from_matcher = true;
matcher_init_matching(ctx, function->subfunctions[fd_cse->subfunc].matcher);
// matcher_init_matching consumes the value on the top of the stack, we need
// to copy the value for further subfunctions.
apfl_copy(ctx, -1);
matcher_init_matching(
ctx,
function->subfunctions[fd_cse->subfunc].matcher,
fd_cse->scopes
);
}
struct apfl_iterative_runner_data {
@ -1149,11 +1348,13 @@ iterative_runner_eval_expr_inner(apfl_iterative_runner runner, struct apfl_expr
.func = {
.pc = 0,
.instructions = ilist,
.scope = runner->scope,
.closure_scope = NULL,
.scopes = {
.local = runner->scope,
.closure = NULL,
},
.execution_line = ilist->line,
.matcher_stack = matcher_stack_new(),
.returning_from_matcher = false,
.matcher = NULL,
},
});
evaluate_until_call_stack_return(ctx);

View file

@ -0,0 +1,78 @@
===== script =====
foo = [->]
foo.a = 1
print foo.a
print ""
# Like every value in apfl, dictionaries are copied when passed around.
# Let's check if this semantic works by copying the dictionary and then modify
# the copy.
bar = foo
bar.a = 2
print foo.a
print bar.a
print ""
# Same thing with passing as argument
{ d ->
d.a = 3
print d.a
} foo
print foo.a
print ""
# And the other way around
printcopy = { d ->
{print d.a}
} foo
foo.a = 4
print foo.a
(printcopy)
print ""
# What about nested element access?
foo.x@(+ 1 2).y = "bar"
print foo.x
print ""
# And as part of List destructuring
k := 10
l = [a foo.zzz@k] = [10 ({ k = 20 })]
print k
print l
print a
print foo.zzz
===== output =====
1
1
2
3
1
4
1
[
3 -> [
"y" -> "bar"
]
]
20
[
10
20
]
10
[
10 -> 20
]

View file

@ -13,6 +13,21 @@ print "Outside:"
print foo
print bar
print ""
# Do the same thing but now use complex assignables. They are compiled into different bytecode, so let's test this too
({
[~_ foo] = [3]
[~_ bar] := [3]
print "Inside:"
print foo
print bar
})
print "Outside:"
print foo
print bar
===== output =====
Inside:
2
@ -20,3 +35,10 @@ Inside:
Outside:
2
1
Inside:
3
3
Outside:
3
1

View file

@ -5,8 +5,8 @@
#include "alloc.h"
#include "bytecode.h"
#include "context.h"
#include "format.h"
#include "gc.h"
#include "matcher.h"
#include "resizable.h"
#include "scope.h"
#include "value.h"
@ -287,7 +287,7 @@ visit_children(struct gc_object *object, gc_visitor cb, void *opaque)
apfl_gc_cfunc_traverse(&object->cfunction, cb, opaque);
return;
case GC_TYPE_MATCHER_INSTRUCTIONS:
// Intentionally left blank. Object doesn't reference other objects.
apfl_gc_matcher_instructions_traverse(&object->matcher_instructions, cb, opaque);
return;
case GC_TYPE_MATCHER:
apfl_gc_matcher_traverse(&object->matcher, cb, opaque);

View file

@ -1,83 +0,0 @@
#include "alloc.h"
#include "gc.h"
#include "matcher.h"
static bool
init_values_list(struct apfl_allocator allocator, struct apfl_value **list, size_t len)
{
if (len == 0) {
return true;
}
if ((*list = ALLOC_LIST(allocator, struct apfl_value, len)) == NULL) {
return false;
}
for (size_t i = 0; i < len; i++) {
(*list)[i] = (struct apfl_value) { .type = VALUE_NIL };
}
return true;
}
struct matcher *
apfl_matcher_new(struct gc *gc, struct matcher_instruction_list *milist)
{
struct matcher matcher = {
.instructions = milist,
.value_count = 0,
.capture_count = 0,
.values = NULL,
.captures = NULL,
.result = false,
};
if (!init_values_list(gc->allocator, &matcher.values, milist->value_count)) {
goto error;
}
matcher.value_count = milist->value_count;
if (!init_values_list(gc->allocator, &matcher.captures, milist->capture_count)) {
goto error;
}
matcher.capture_count = milist->capture_count;
struct matcher *gc_matcher = apfl_gc_new_matcher(gc);
if (gc_matcher == NULL) {
goto error;
}
*gc_matcher = matcher;
return gc_matcher;
error:
apfl_matcher_deinit(gc->allocator, &matcher);
return NULL;
}
void
apfl_matcher_deinit(struct apfl_allocator allocator, struct matcher *matcher)
{
FREE_LIST(allocator, matcher->values, matcher->value_count);
FREE_LIST(allocator, matcher->captures, matcher->capture_count);
}
void
apfl_gc_matcher_traverse(struct matcher *matcher, gc_visitor visitor, void *opaque)
{
visitor(opaque, GC_OBJECT_FROM(matcher->instructions, GC_TYPE_MATCHER_INSTRUCTIONS));
for (size_t i = 0; i < matcher->instructions->value_count; i++) {
struct gc_object *object = apfl_value_get_gc_object(matcher->values[i]);
if (object != NULL) {
visitor(opaque, object);
}
}
for (size_t i = 0; i < matcher->instructions->capture_count; i++) {
struct gc_object *object = apfl_value_get_gc_object(matcher->captures[i]);
if (object != NULL) {
visitor(opaque, object);
}
}
}

View file

@ -1,29 +0,0 @@
#ifndef APFL_MATCHER_H
#define APFL_MATCHER_H
#ifdef __cplusplus
extern "C" {
#endif
#include "bytecode.h"
#include "gc.h"
#include "value.h"
struct matcher {
struct matcher_instruction_list *instructions;
size_t value_count;
size_t capture_count;
struct apfl_value *values;
struct apfl_value *captures;
bool result;
};
struct matcher *apfl_matcher_new(struct gc *, struct matcher_instruction_list *);
void apfl_matcher_deinit(struct apfl_allocator, struct matcher *);
void apfl_gc_matcher_traverse(struct matcher *, gc_visitor, void *);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -65,10 +65,7 @@ apfl_scope_new(struct gc *gc)
void
apfl_gc_var_traverse(struct apfl_value *var, gc_visitor cb, void *opaque)
{
struct gc_object *child = apfl_value_get_gc_object(*var);
if (child != NULL) {
cb(opaque, child);
}
apfl_value_visit_gc_object(*var, cb, opaque);
}
void

View file

@ -661,8 +661,8 @@ apfl_value_get_gc_object(struct apfl_value value)
return NULL;
}
static void
call_visitor_for_value(gc_visitor cb, void *opaque, struct apfl_value value)
void
apfl_value_visit_gc_object(struct apfl_value value, gc_visitor cb, void *opaque)
{
struct gc_object *child = apfl_value_get_gc_object(value);
if (child != NULL) {
@ -674,7 +674,7 @@ void
apfl_gc_list_traverse(struct list_header *list, gc_visitor cb, void *opaque)
{
for (size_t i = 0; i < list->len; i++) {
call_visitor_for_value(cb, opaque, list->items[i]);
apfl_value_visit_gc_object(list->items[i], cb, opaque);
}
}
@ -687,8 +687,8 @@ apfl_gc_dict_traverse(struct dict_header *dict, gc_visitor cb, void *opaque)
struct apfl_value *v = apfl_hashmap_cursor_peek_value(cur);
assert(v != NULL);
call_visitor_for_value(cb, opaque, *k);
call_visitor_for_value(cb, opaque, *v);
apfl_value_visit_gc_object(*k, cb, opaque);
apfl_value_visit_gc_object(*v, cb, opaque);
}
}

View file

@ -133,6 +133,7 @@ void apfl_cfunction_deinit(struct apfl_allocator, struct cfunction *);
// Functions for garbage collection
struct gc_object *apfl_value_get_gc_object(struct apfl_value);
void apfl_value_visit_gc_object(struct apfl_value value, gc_visitor cb, void *opaque);
void apfl_gc_list_traverse(struct list_header *, gc_visitor, void *);
void apfl_gc_dict_traverse(struct dict_header *, gc_visitor, void *);
void apfl_gc_func_traverse(struct function*, gc_visitor, void *);