summaryrefslogtreecommitdiffhomepage
path: root/compiler.c
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2021-02-17 18:28:01 +0100
committerGitHub <noreply@github.com>2021-02-17 18:28:01 +0100
commit679270fd3afa93cca84ab31b5041922037fec0c5 (patch)
treee55752bae52bf7eed38b91c42e990a8b116b6621 /compiler.c
parent77580a893283f2bde7ab46496bd3a3d7b2fc6784 (diff)
parent14e46b8e225dc329f4e14777960b10abb8a09699 (diff)
Merge pull request #2 from jow-/rewrite
treewide: rewrite ucode interpreter
Diffstat (limited to 'compiler.c')
-rw-r--r--compiler.c2622
1 files changed, 2622 insertions, 0 deletions
diff --git a/compiler.c b/compiler.c
new file mode 100644
index 0000000..70e65d8
--- /dev/null
+++ b/compiler.c
@@ -0,0 +1,2622 @@
+/*
+ * Copyright (C) 2020-2021 Jo-Philipp Wich <jo@mein.io>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include "compiler.h"
+#include "chunk.h"
+#include "vm.h" /* I_* */
+#include "source.h"
+#include "lib.h" /* format_error_context() */
+
+static void uc_compiler_compile_unary(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_binary(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_paren(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_call(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_post_inc(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_constant(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_comma(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_labelexpr(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_function(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_and(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_or(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_dot(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_subscript(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_ternary(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_array(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_object(uc_compiler *compiler, bool assignable);
+
+static void uc_compiler_compile_declaration(uc_compiler *compiler);
+static void uc_compiler_compile_statement(uc_compiler *compiler);
+static void uc_compiler_compile_expstmt(uc_compiler *compiler);
+
+static uc_parse_rule
+uc_compiler_parse_rules[TK_ERROR + 1] = {
+ [TK_LPAREN] = { uc_compiler_compile_paren, uc_compiler_compile_call, P_CALL },
+ [TK_SUB] = { uc_compiler_compile_unary, uc_compiler_compile_binary, P_ADD },
+ [TK_ADD] = { uc_compiler_compile_unary, uc_compiler_compile_binary, P_ADD },
+ [TK_COMPL] = { uc_compiler_compile_unary, NULL, P_UNARY },
+ [TK_NOT] = { uc_compiler_compile_unary, NULL, P_UNARY },
+ [TK_INC] = { uc_compiler_compile_unary, uc_compiler_compile_post_inc, P_INC },
+ [TK_DEC] = { uc_compiler_compile_unary, uc_compiler_compile_post_inc, P_INC },
+ [TK_DIV] = { NULL, uc_compiler_compile_binary, P_MUL },
+ [TK_MUL] = { NULL, uc_compiler_compile_binary, P_MUL },
+ [TK_MOD] = { NULL, uc_compiler_compile_binary, P_MUL },
+ [TK_NUMBER] = { uc_compiler_compile_constant, NULL, P_NONE },
+ [TK_DOUBLE] = { uc_compiler_compile_constant, NULL, P_NONE },
+ [TK_STRING] = { uc_compiler_compile_constant, NULL, P_NONE },
+ [TK_BOOL] = { uc_compiler_compile_constant, NULL, P_NONE },
+ [TK_NULL] = { uc_compiler_compile_constant, NULL, P_NONE },
+ [TK_THIS] = { uc_compiler_compile_constant, NULL, P_NONE },
+ [TK_REGEXP] = { uc_compiler_compile_constant, NULL, P_NONE },
+ [TK_COMMA] = { NULL, uc_compiler_compile_comma, P_COMMA },
+ [TK_LABEL] = { uc_compiler_compile_labelexpr, NULL, P_NONE },
+ [TK_FUNC] = { uc_compiler_compile_function, NULL, P_NONE },
+ [TK_AND] = { NULL, uc_compiler_compile_and, P_AND },
+ [TK_OR] = { NULL, uc_compiler_compile_or, P_OR },
+ [TK_BOR] = { NULL, uc_compiler_compile_binary, P_BOR },
+ [TK_BXOR] = { NULL, uc_compiler_compile_binary, P_BXOR },
+ [TK_BAND] = { NULL, uc_compiler_compile_binary, P_BAND },
+ [TK_EQ] = { NULL, uc_compiler_compile_binary, P_EQUAL },
+ [TK_EQS] = { NULL, uc_compiler_compile_binary, P_EQUAL },
+ [TK_NE] = { NULL, uc_compiler_compile_binary, P_EQUAL },
+ [TK_NES] = { NULL, uc_compiler_compile_binary, P_EQUAL },
+ [TK_LT] = { NULL, uc_compiler_compile_binary, P_COMPARE },
+ [TK_LE] = { NULL, uc_compiler_compile_binary, P_COMPARE },
+ [TK_GT] = { NULL, uc_compiler_compile_binary, P_COMPARE },
+ [TK_GE] = { NULL, uc_compiler_compile_binary, P_COMPARE },
+ [TK_IN] = { NULL, uc_compiler_compile_binary, P_COMPARE },
+ [TK_LSHIFT] = { NULL, uc_compiler_compile_binary, P_SHIFT },
+ [TK_RSHIFT] = { NULL, uc_compiler_compile_binary, P_SHIFT },
+ [TK_DOT] = { NULL, uc_compiler_compile_dot, P_CALL },
+ [TK_LBRACK] = { uc_compiler_compile_array, uc_compiler_compile_subscript, P_CALL },
+ [TK_QMARK] = { NULL, uc_compiler_compile_ternary, P_TERNARY },
+ [TK_LBRACE] = { uc_compiler_compile_object, NULL, P_NONE },
+};
+
+static ssize_t
+uc_compiler_declare_local(uc_compiler *compiler, json_object *name);
+
+static ssize_t
+uc_compiler_initialize_local(uc_compiler *compiler);
+
+static void
+uc_compiler_init(uc_compiler *compiler, const char *name, size_t srcpos, uc_source *source)
+{
+ json_object *varname = xjs_new_string("(callee)");
+
+ compiler->scope_depth = 0;
+
+ compiler->function = uc_function_new(name, srcpos, source);
+
+ compiler->locals.count = 0;
+ compiler->locals.entries = NULL;
+
+ compiler->upvals.count = 0;
+ compiler->upvals.entries = NULL;
+
+ compiler->patchlist = NULL;
+
+ compiler->parent = NULL;
+
+ compiler->current_srcpos = srcpos;
+
+ /* reserve stack slot 0 */
+ uc_compiler_declare_local(compiler, varname);
+ uc_compiler_initialize_local(compiler);
+ uc_value_put(varname);
+}
+
+static uc_chunk *
+uc_compiler_current_chunk(uc_compiler *compiler)
+{
+ return &compiler->function->chunk;
+}
+
+__attribute__((format(printf, 3, 0))) static void
+uc_compiler_syntax_error(uc_compiler *compiler, size_t off, const char *fmt, ...)
+{
+ size_t line = 0, byte = 0, len = 0;
+ char *context = NULL;
+ char *s, *tmp;
+ va_list ap;
+
+ if (compiler->parser->synchronizing)
+ return;
+
+ compiler->parser->synchronizing = true;
+
+ if (!off)
+ off = uc_function_get_srcpos(compiler->function,
+ uc_compiler_current_chunk(compiler)->count);
+
+ if (off) {
+ byte = off;
+ line = uc_source_get_line(compiler->function->source, &byte);
+
+ format_error_context(&context, &len, compiler->function->source, NULL, off);
+ }
+
+ va_start(ap, fmt);
+ xvasprintf(&s, fmt, ap);
+ va_end(ap);
+
+ xasprintf(&tmp, "Syntax error: %s\n", s);
+ free(s);
+ s = tmp;
+
+ if (line) {
+ xasprintf(&tmp, "%sIn line %zu, byte %zu:\n", s, line, byte);
+ free(s);
+ s = tmp;
+ }
+
+ if (context) {
+ xasprintf(&tmp, "%s%s\n\n", s, context);
+ free(context);
+ free(s);
+ s = tmp;
+ }
+
+ if (compiler->parser->error) {
+ xasprintf(&tmp, "%s%s", compiler->parser->error, s);
+ free(compiler->parser->error);
+ free(s);
+ compiler->parser->error = tmp;
+ }
+ else {
+ compiler->parser->error = s;
+ }
+}
+
+static size_t
+uc_compiler_set_srcpos(uc_compiler *compiler, size_t srcpos)
+{
+ size_t delta;
+
+ /* ensure that lines counts are strictly increasing */
+ assert(srcpos == 0 || srcpos >= compiler->current_srcpos);
+
+ delta = srcpos ? srcpos - compiler->current_srcpos : 0;
+ compiler->current_srcpos += delta;
+
+ return delta;
+}
+
+static void
+uc_compiler_parse_advance(uc_compiler *compiler)
+{
+ uc_value_put(compiler->parser->prev.val);
+ compiler->parser->prev = compiler->parser->curr;
+
+ while (true) {
+ compiler->parser->curr = *uc_lexer_next_token(&compiler->parser->lex);
+
+ if (compiler->parser->curr.type != TK_ERROR)
+ break;
+
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "%s",
+ json_object_get_string(compiler->parser->curr.val));
+
+ uc_value_put(compiler->parser->curr.val);
+ compiler->parser->curr.val = NULL;
+ }
+}
+
+static void
+uc_compiler_parse_consume(uc_compiler *compiler, uc_tokentype_t type)
+{
+ if (compiler->parser->curr.type == type) {
+ uc_compiler_parse_advance(compiler);
+
+ return;
+ }
+
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos,
+ "Unexpected token\nExpecting %s", uc_get_tokenname(type));
+}
+
+static bool
+uc_compiler_parse_check(uc_compiler *compiler, uc_tokentype_t type)
+{
+ return (compiler->parser->curr.type == type);
+}
+
+static bool
+uc_compiler_parse_match(uc_compiler *compiler, uc_tokentype_t type)
+{
+ if (!uc_compiler_parse_check(compiler, type))
+ return false;
+
+ uc_compiler_parse_advance(compiler);
+
+ return true;
+}
+
+static void
+uc_compiler_parse_synchronize(uc_compiler *compiler)
+{
+ compiler->parser->synchronizing = false;
+
+ while (compiler->parser->curr.type != TK_EOF) {
+ if (compiler->parser->prev.type == TK_SCOL)
+ return;
+
+ switch (compiler->parser->curr.type) {
+ case TK_IF:
+ case TK_FOR:
+ case TK_WHILE:
+ case TK_SWITCH:
+ case TK_FUNC:
+ case TK_TRY:
+ case TK_RETURN:
+ case TK_BREAK:
+ case TK_CONTINUE:
+ case TK_LOCAL:
+ return;
+
+ default:
+ break;
+ }
+
+ uc_compiler_parse_advance(compiler);
+ }
+}
+
+static uc_parse_rule *
+uc_compiler_parse_rule(uc_tokentype_t type)
+{
+ return &uc_compiler_parse_rules[type];
+}
+
+static bool
+uc_compiler_parse_at_assignment_op(uc_compiler *compiler)
+{
+ switch (compiler->parser->curr.type) {
+ case TK_ASBAND:
+ case TK_ASBXOR:
+ case TK_ASBOR:
+ case TK_ASLEFT:
+ case TK_ASRIGHT:
+ case TK_ASMUL:
+ case TK_ASDIV:
+ case TK_ASMOD:
+ case TK_ASADD:
+ case TK_ASSUB:
+ case TK_ASSIGN:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static void
+uc_compiler_parse_precedence(uc_compiler *compiler, uc_precedence_t precedence)
+{
+ uc_parse_rule *rule;
+ bool assignable;
+
+ uc_compiler_parse_advance(compiler);
+
+ rule = uc_compiler_parse_rule(compiler->parser->prev.type);
+
+ if (!rule->prefix) {
+ uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Expecting expression");
+
+ return;
+ }
+
+ assignable = (precedence <= P_ASSIGN);
+ rule->prefix(compiler, assignable);
+
+ while (precedence <= uc_compiler_parse_rule(compiler->parser->curr.type)->precedence) {
+ uc_compiler_parse_advance(compiler);
+ uc_compiler_parse_rule(compiler->parser->prev.type)->infix(compiler, assignable);
+ }
+
+ if (assignable && uc_compiler_parse_at_assignment_op(compiler))
+ uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Invalid left-hand side expression for assignment");
+}
+
+static size_t
+uc_compiler_reladdr(uc_compiler *compiler, size_t from, size_t to)
+{
+ ssize_t delta = to - from;
+
+ if (delta < -0x7fffffff || delta > 0x7fffffff) {
+ uc_compiler_syntax_error(compiler, 0, "Jump address too far");
+
+ return 0;
+ }
+
+ return (size_t)(delta + 0x7fffffff);
+}
+
+static size_t
+uc_compiler_emit_insn(uc_compiler *compiler, size_t srcpos, enum insn_type insn)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t lineoff = uc_compiler_set_srcpos(compiler, srcpos);
+
+ compiler->last_insn = uc_chunk_add(chunk, (uint8_t)insn, lineoff);
+
+ return compiler->last_insn;
+}
+
+static size_t
+uc_compiler_emit_u8(uc_compiler *compiler, size_t srcpos, uint8_t n)
+{
+ return uc_chunk_add(
+ uc_compiler_current_chunk(compiler),
+ n,
+ uc_compiler_set_srcpos(compiler, srcpos));
+}
+
+static size_t
+uc_compiler_emit_s8(uc_compiler *compiler, size_t srcpos, int8_t n)
+{
+ return uc_chunk_add(
+ uc_compiler_current_chunk(compiler),
+ n + 0x7f,
+ uc_compiler_set_srcpos(compiler, srcpos));
+}
+
+static size_t
+uc_compiler_emit_u16(uc_compiler *compiler, size_t srcpos, uint16_t n)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t lineoff = uc_compiler_set_srcpos(compiler, srcpos);
+
+ uc_chunk_add(chunk, n / 0x100, lineoff);
+ uc_chunk_add(chunk, n % 0x100, 0);
+
+ return chunk->count - 2;
+}
+
+static size_t
+uc_compiler_emit_s16(uc_compiler *compiler, size_t srcpos, int16_t n)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t lineoff = uc_compiler_set_srcpos(compiler, srcpos);
+ uint16_t v = n + 0x7fff;
+
+ uc_chunk_add(chunk, v / 0x100, lineoff);
+ uc_chunk_add(chunk, v % 0x100, 0);
+
+ return chunk->count - 2;
+}
+
+static size_t
+uc_compiler_emit_u32(uc_compiler *compiler, size_t srcpos, uint32_t n)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t lineoff = uc_compiler_set_srcpos(compiler, srcpos);
+
+ uc_chunk_add(chunk, n / 0x1000000, lineoff);
+ uc_chunk_add(chunk, (n / 0x10000) % 0x100, 0);
+ uc_chunk_add(chunk, (n / 0x100) % 0x100, 0);
+ uc_chunk_add(chunk, n % 0x100, 0);
+
+ return chunk->count - 4;
+}
+
+static size_t
+uc_compiler_emit_s32(uc_compiler *compiler, size_t srcpos, int32_t n)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t lineoff = uc_compiler_set_srcpos(compiler, srcpos);
+ uint32_t v = n + 0x7fffffff;
+
+ uc_chunk_add(chunk, v / 0x1000000, lineoff);
+ uc_chunk_add(chunk, (v / 0x10000) % 0x100, 0);
+ uc_chunk_add(chunk, (v / 0x100) % 0x100, 0);
+ uc_chunk_add(chunk, v % 0x100, 0);
+
+ return chunk->count - 4;
+}
+
+static uint32_t
+uc_compiler_get_u32(uc_compiler *compiler, size_t off)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+
+ return chunk->entries[off + 0] * 0x1000000 +
+ chunk->entries[off + 1] * 0x10000 +
+ chunk->entries[off + 2] * 0x100 +
+ chunk->entries[off + 3];
+}
+
+static void
+uc_compiler_set_u32(uc_compiler *compiler, size_t off, uint32_t n)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+
+ chunk->entries[off + 0] = n / 0x1000000;
+ chunk->entries[off + 1] = (n / 0x10000) % 0x100;
+ chunk->entries[off + 2] = (n / 0x100) % 0x100;
+ chunk->entries[off + 3] = n % 0x100;
+}
+
+static size_t
+uc_compiler_emit_constant(uc_compiler *compiler, size_t srcpos, json_object *val)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t cidx = uc_chunk_add_constant(chunk, val);
+
+ uc_compiler_emit_insn(compiler, srcpos, I_LOAD);
+ uc_compiler_emit_u32(compiler, 0, cidx);
+
+ return cidx;
+}
+
+static size_t
+uc_compiler_emit_regexp(uc_compiler *compiler, size_t srcpos, json_object *val)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t cidx = uc_chunk_add_constant(chunk, val);
+
+ uc_compiler_emit_insn(compiler, srcpos, I_LREXP);
+ uc_compiler_emit_u32(compiler, 0, cidx);
+
+ return cidx;
+}
+
+static size_t
+uc_compiler_emit_jmp(uc_compiler *compiler, size_t srcpos, uint32_t dest)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+
+ uc_compiler_emit_insn(compiler, srcpos, I_JMP);
+ uc_compiler_emit_u32(compiler, 0, dest ? uc_compiler_reladdr(compiler, chunk->count - 1, dest) : 0);
+
+ return chunk->count - 5;
+}
+
+static size_t
+uc_compiler_emit_jmpz(uc_compiler *compiler, size_t srcpos, uint32_t dest)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+
+ uc_compiler_emit_insn(compiler, srcpos, I_JMPZ);
+ uc_compiler_emit_u32(compiler, 0, dest ? uc_compiler_reladdr(compiler, chunk->count - 1, dest) : 0);
+
+ return chunk->count - 5;
+}
+
+static ssize_t
+uc_compiler_get_jmpaddr(uc_compiler *compiler, size_t off)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+
+ assert(chunk->entries[off] == I_JMP || chunk->entries[off] == I_JMPZ);
+ assert(off + 4 < chunk->count);
+
+ return (
+ chunk->entries[off + 1] * 0x1000000 +
+ chunk->entries[off + 2] * 0x10000 +
+ chunk->entries[off + 3] * 0x100 +
+ chunk->entries[off + 4]
+ ) - 0x7fffffff;
+}
+
+static void
+uc_compiler_set_jmpaddr(uc_compiler *compiler, size_t off, uint32_t dest)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t addr = uc_compiler_reladdr(compiler, off, dest);
+
+ assert(chunk->entries[off] == I_JMP || chunk->entries[off] == I_JMPZ);
+ assert(off + 4 < chunk->count);
+
+ chunk->entries[off + 1] = addr / 0x1000000;
+ chunk->entries[off + 2] = (addr / 0x10000) % 0x100;
+ chunk->entries[off + 3] = (addr / 0x100) % 0x100;
+ chunk->entries[off + 4] = addr % 0x100;
+}
+
+static uc_function *
+uc_compiler_finish(uc_compiler *compiler)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ uc_locals *locals = &compiler->locals;
+ uc_upvals *upvals = &compiler->upvals;
+ size_t i;
+
+ uc_compiler_emit_insn(compiler, 0, I_LNULL);
+ uc_compiler_emit_insn(compiler, 0, I_RETURN);
+
+ for (i = 0; i < locals->count; i++) {
+ uc_chunk_debug_add_variable(chunk,
+ locals->entries[i].from,
+ chunk->count,
+ i,
+ false,
+ locals->entries[i].name);
+
+ uc_value_put(locals->entries[i].name);
+ }
+
+ for (i = 0; i < upvals->count; i++) {
+ uc_chunk_debug_add_variable(chunk,
+ 0,
+ chunk->count,
+ i,
+ true,
+ upvals->entries[i].name);
+
+ uc_value_put(upvals->entries[i].name);
+ }
+
+ uc_vector_clear(locals);
+ uc_vector_clear(upvals);
+
+ if (compiler->parser->error) {
+ uc_value_put(compiler->function->header.jso);
+
+ return NULL;
+ }
+
+ return compiler->function;
+}
+
+static void
+uc_compiler_enter_scope(uc_compiler *compiler)
+{
+ compiler->scope_depth++;
+}
+
+static void
+uc_compiler_leave_scope(uc_compiler *compiler)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ uc_locals *locals = &compiler->locals;
+
+ compiler->scope_depth--;
+
+ while (locals->count > 0 && locals->entries[locals->count - 1].depth > compiler->scope_depth) {
+ locals->count--;
+
+ uc_chunk_debug_add_variable(chunk,
+ locals->entries[locals->count].from,
+ chunk->count,
+ locals->count,
+ false,
+ locals->entries[locals->count].name);
+
+ uc_value_put(locals->entries[locals->count].name);
+ locals->entries[locals->count].name = NULL;
+
+ uc_compiler_emit_insn(compiler, 0,
+ locals->entries[locals->count].captured ? I_CUPV : I_POP);
+ }
+}
+
+static ssize_t
+uc_compiler_declare_local(uc_compiler *compiler, json_object *name)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ uc_locals *locals = &compiler->locals;
+ const char *str1, *str2;
+ size_t i, len1, len2;
+
+ //if (compiler->scope_depth == 0)
+ // return;
+
+ if (locals->count >= 0x00FFFFFF) {
+ uc_compiler_syntax_error(compiler, 0, "Too many local variables");
+
+ return -1;
+ }
+
+ str1 = json_object_get_string(name);
+ len1 = json_object_get_string_len(name);
+
+ for (i = locals->count; i > 0; i--) {
+ if (locals->entries[i - 1].depth != -1 && locals->entries[i - 1].depth < compiler->scope_depth)
+ break;
+
+ str2 = json_object_get_string(locals->entries[i - 1].name);
+ len2 = json_object_get_string_len(locals->entries[i - 1].name);
+
+ if (len1 == len2 && !strcmp(str1, str2)) {
+ if (compiler->parser->config &&
+ compiler->parser->config->strict_declarations) {
+ uc_compiler_syntax_error(compiler, 0, "Variable '%s' redeclared", str2);
+
+ return -1;
+ }
+
+ return i - 1;
+ }
+ }
+
+ uc_vector_grow(locals);
+
+ locals->entries[locals->count].name = uc_value_get(name);
+ locals->entries[locals->count].depth = -1;
+ locals->entries[locals->count].captured = false;
+ locals->entries[locals->count].from = chunk->count;
+ locals->count++;
+
+ return -1;
+}
+
+static ssize_t
+uc_compiler_initialize_local(uc_compiler *compiler)
+{
+ uc_locals *locals = &compiler->locals;
+
+ locals->entries[locals->count - 1].depth = compiler->scope_depth;
+
+ return locals->count - 1;
+}
+
+static ssize_t
+uc_compiler_resolve_local(uc_compiler *compiler, json_object *name)
+{
+ uc_locals *locals = &compiler->locals;
+ const char *str1, *str2;
+ size_t i, len1, len2;
+
+ str1 = json_object_get_string(name);
+ len1 = json_object_get_string_len(name);
+
+ for (i = locals->count; i > 0; i--) {
+ str2 = json_object_get_string(locals->entries[i - 1].name);
+ len2 = json_object_get_string_len(locals->entries[i - 1].name);
+
+ if (len1 != len2 || strcmp(str1, str2))
+ continue;
+
+ if (locals->entries[i - 1].depth == -1) {
+ uc_compiler_syntax_error(compiler, 0,
+ "Can't access lexical declaration '%s' before initialization", str2);
+
+ return -1;
+ }
+
+ return i - 1;
+ }
+
+ return -1;
+}
+
+static ssize_t
+uc_compiler_add_upval(uc_compiler *compiler, ssize_t idx, bool local, json_object *name)
+{
+ uc_function *function = compiler->function;
+ uc_upvals *upvals = &compiler->upvals;
+ uc_upval *uv;
+ size_t i;
+
+ for (i = 0, uv = upvals->entries; i < upvals->count; i++, uv = upvals->entries + i)
+ if (uv->index == idx && uv->local == local)
+ return i;
+
+ /* XXX: encoding... */
+ if (upvals->count >= (2 << 14)) {
+ uc_compiler_syntax_error(compiler, 0, "Too many upvalues");
+
+ return -1;
+ }
+
+ uc_vector_grow(upvals);
+
+ upvals->entries[upvals->count].local = local;
+ upvals->entries[upvals->count].index = idx;
+ upvals->entries[upvals->count].name = uc_value_get(name);
+
+ function->nupvals++;
+
+ return upvals->count++;
+}
+
+static ssize_t
+uc_compiler_resolve_upval(uc_compiler *compiler, json_object *name)
+{
+ ssize_t idx;
+
+ if (!compiler->parent)
+ return -1;
+
+ idx = uc_compiler_resolve_local(compiler->parent, name);
+
+ if (idx > -1) {
+ compiler->parent->locals.entries[idx].captured = true;
+
+ return uc_compiler_add_upval(compiler, idx, true, name);
+ }
+
+ idx = uc_compiler_resolve_upval(compiler->parent, name);
+
+ if (idx > -1)
+ return uc_compiler_add_upval(compiler, idx, false, name);
+
+ return -1;
+}
+
+static void
+uc_compiler_backpatch(uc_compiler *compiler, size_t break_addr, size_t next_addr)
+{
+ uc_patchlist *pl = compiler->patchlist;
+ uc_patchlist *pp = pl->parent;
+ volatile ssize_t jmpaddr;
+ size_t i;
+
+ for (i = 0; i < pl->count; i++) {
+ jmpaddr = uc_compiler_get_jmpaddr(compiler, pl->entries[i]);
+
+ switch (jmpaddr) {
+ case TK_BREAK:
+ /* if we have a break addr, patch instruction */
+ if (break_addr) {
+ uc_compiler_set_jmpaddr(compiler, pl->entries[i], break_addr);
+ continue;
+ }
+
+ break;
+
+ case TK_CONTINUE:
+ /* if we have a continue addr, patch instruction */
+ if (next_addr) {
+ uc_compiler_set_jmpaddr(compiler, pl->entries[i], next_addr);
+ continue;
+ }
+
+ break;
+ }
+
+ /* propagate unhandled patch instructions to parent patch list */
+ if (pp) {
+ uc_vector_grow(pp);
+ pp->entries[pp->count++] = pl->entries[i];
+ }
+ }
+
+ free(pl->entries);
+
+ compiler->patchlist = pl->parent;
+}
+
+static void
+uc_compiler_emit_inc_dec(uc_compiler *compiler, uc_tokentype_t toktype, bool is_postfix)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ enum insn_type type;
+ uint32_t cidx = 0;
+
+ /* determine kind of emitted load instruction and operand value (if any) */
+ type = chunk->entries[compiler->last_insn];
+
+ if (type == I_LVAR || type == I_LLOC || type == I_LUPV) {
+ cidx = uc_compiler_get_u32(compiler, compiler->last_insn + 1);
+
+ uc_chunk_pop(chunk);
+ uc_chunk_pop(chunk);
+ uc_chunk_pop(chunk);
+ uc_chunk_pop(chunk);
+ uc_chunk_pop(chunk);
+ }
+
+ /* if we're mutating an object or array field, pop the last lval instruction
+ * to leave object + last field name value on stack */
+ else if (type == I_LVAL) {
+ uc_chunk_pop(chunk);
+ }
+ else {
+ uc_compiler_syntax_error(compiler, 0, "Invalid increment/decrement operand");
+
+ return;
+ }
+
+ /* add / substract 1 */
+ uc_compiler_emit_insn(compiler, 0, I_LOAD8);
+ uc_compiler_emit_s8(compiler, 0, (toktype == TK_INC) ? 1 : -1);
+
+ /* depending on variable type, emit corresponding increment instruction */
+ switch (type) {
+ case I_LVAR:
+ uc_compiler_emit_insn(compiler, 0, I_UVAR);
+ uc_compiler_emit_u32(compiler, 0, (I_PLUS << 24) | cidx);
+ break;
+
+ case I_LLOC:
+ uc_compiler_emit_insn(compiler, 0, I_ULOC);
+ uc_compiler_emit_u32(compiler, 0, (I_PLUS << 24) | cidx);
+ break;
+
+ case I_LUPV:
+ uc_compiler_emit_insn(compiler, 0, I_UUPV);
+ uc_compiler_emit_u32(compiler, 0, (I_PLUS << 24) | cidx);
+ break;
+
+ case I_LVAL:
+ uc_compiler_emit_insn(compiler, 0, I_UVAL);
+ uc_compiler_emit_u8(compiler, 0, I_PLUS);
+ break;
+
+ default:
+ break;
+ }
+
+ /* for post increment or decrement, add/substract 1 to yield final value */
+ if (is_postfix) {
+ uc_compiler_emit_insn(compiler, 0, I_LOAD8);
+ uc_compiler_emit_s8(compiler, 0, 1);
+
+ uc_compiler_emit_insn(compiler, 0, (toktype == TK_INC) ? I_SUB : I_ADD);
+ }
+}
+
+
+static void
+uc_compiler_compile_unary(uc_compiler *compiler, bool assignable)
+{
+ uc_tokentype_t type = compiler->parser->prev.type;
+
+ uc_compiler_parse_precedence(compiler, P_UNARY);
+
+ switch (type) {
+ case TK_SUB:
+ uc_compiler_emit_insn(compiler, 0, I_MINUS);
+ break;
+
+ case TK_ADD:
+ uc_compiler_emit_insn(compiler, 0, I_PLUS);
+ break;
+
+ case TK_NOT:
+ uc_compiler_emit_insn(compiler, 0, I_NOT);
+ break;
+
+ case TK_COMPL:
+ uc_compiler_emit_insn(compiler, 0, I_COMPL);
+ break;
+
+ case TK_INC:
+ case TK_DEC:
+ uc_compiler_emit_inc_dec(compiler, type, false);
+ break;
+
+ default:
+ return;
+ }
+}
+
+static void
+uc_compiler_compile_binary(uc_compiler *compiler, bool assignable)
+{
+ uc_tokentype_t type = compiler->parser->prev.type;
+
+ uc_compiler_parse_precedence(compiler, uc_compiler_parse_rule(type)->precedence + 1);
+
+ switch (type) {
+ case TK_ADD: uc_compiler_emit_insn(compiler, 0, I_ADD); break;
+ case TK_SUB: uc_compiler_emit_insn(compiler, 0, I_SUB); break;
+ case TK_MUL: uc_compiler_emit_insn(compiler, 0, I_MUL); break;
+ case TK_DIV: uc_compiler_emit_insn(compiler, 0, I_DIV); break;
+ case TK_MOD: uc_compiler_emit_insn(compiler, 0, I_MOD); break;
+ case TK_LSHIFT: uc_compiler_emit_insn(compiler, 0, I_LSHIFT); break;
+ case TK_RSHIFT: uc_compiler_emit_insn(compiler, 0, I_RSHIFT); break;
+ case TK_BAND: uc_compiler_emit_insn(compiler, 0, I_BAND); break;
+ case TK_BXOR: uc_compiler_emit_insn(compiler, 0, I_BXOR); break;
+ case TK_BOR: uc_compiler_emit_insn(compiler, 0, I_BOR); break;
+ case TK_LT: uc_compiler_emit_insn(compiler, 0, I_LT); break;
+ case TK_LE:
+ uc_compiler_emit_insn(compiler, 0, I_GT);
+ uc_compiler_emit_insn(compiler, 0, I_NOT);
+ break;
+ case TK_GT: uc_compiler_emit_insn(compiler, 0, I_GT); break;
+ case TK_GE:
+ uc_compiler_emit_insn(compiler, 0, I_LT);
+ uc_compiler_emit_insn(compiler, 0, I_NOT);
+ break;
+ case TK_EQ: uc_compiler_emit_insn(compiler, 0, I_EQ); break;
+ case TK_NE: uc_compiler_emit_insn(compiler, 0, I_NE); break;
+ case TK_EQS: uc_compiler_emit_insn(compiler, 0, I_EQS); break;
+ case TK_NES: uc_compiler_emit_insn(compiler, 0, I_NES); break;
+ case TK_IN: uc_compiler_emit_insn(compiler, 0, I_IN); break;
+ default:
+ return;
+ }
+}
+
+static enum insn_type
+uc_compiler_emit_variable_rw(uc_compiler *compiler, json_object *varname, uc_tokentype_t type)
+{
+ enum insn_type insn;
+ uint32_t sub_insn;
+ ssize_t idx;
+
+ switch (type) {
+ case TK_ASADD: sub_insn = I_ADD; break;
+ case TK_ASSUB: sub_insn = I_SUB; break;
+ case TK_ASMUL: sub_insn = I_MUL; break;
+ case TK_ASDIV: sub_insn = I_DIV; break;
+ case TK_ASMOD: sub_insn = I_MOD; break;
+ case TK_ASBAND: sub_insn = I_BAND; break;
+ case TK_ASBXOR: sub_insn = I_BXOR; break;
+ case TK_ASBOR: sub_insn = I_BOR; break;
+ case TK_ASLEFT: sub_insn = I_LSHIFT; break;
+ case TK_ASRIGHT: sub_insn = I_RSHIFT; break;
+ default: sub_insn = 0; break;
+ }
+
+ if (!varname) {
+ insn = sub_insn ? I_UVAL : (type ? I_SVAL : I_LVAL);
+
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, insn);
+
+ if (sub_insn)
+ uc_compiler_emit_u8(compiler, compiler->parser->prev.pos, sub_insn);
+ }
+ else if ((idx = uc_compiler_resolve_local(compiler, varname)) > -1) {
+ insn = sub_insn ? I_ULOC : (type ? I_SLOC : I_LLOC);
+
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, insn);
+ uc_compiler_emit_u32(compiler, compiler->parser->prev.pos,
+ ((sub_insn & 0xff) << 24) | idx);
+ }
+ else if ((idx = uc_compiler_resolve_upval(compiler, varname)) > -1) {
+ insn = sub_insn ? I_UUPV : (type ? I_SUPV : I_LUPV);
+
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, insn);
+ uc_compiler_emit_u32(compiler, compiler->parser->prev.pos,
+ ((sub_insn & 0xff) << 24) | idx);
+ }
+ else {
+ idx = uc_chunk_add_constant(uc_compiler_current_chunk(compiler), varname);
+ insn = sub_insn ? I_UVAR : (type ? I_SVAR : I_LVAR);
+
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, insn);
+ uc_compiler_emit_u32(compiler, compiler->parser->prev.pos,
+ ((sub_insn & 0xff) << 24) | idx);
+ }
+
+ return insn;
+}
+
+static void
+uc_compiler_compile_expression(uc_compiler *compiler)
+{
+ uc_compiler_parse_precedence(compiler, P_COMMA);
+}
+
+static bool
+uc_compiler_compile_assignment(uc_compiler *compiler, json_object *var)
+{
+ uc_tokentype_t type = compiler->parser->curr.type;
+
+ if (uc_compiler_parse_at_assignment_op(compiler)) {
+ uc_compiler_parse_advance(compiler);
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+ uc_compiler_emit_variable_rw(compiler, var, type);
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+uc_compiler_compile_arrowfn(uc_compiler *compiler, json_object *args, bool restarg)
+{
+ bool array = json_object_is_type(args, json_type_array);
+ uc_compiler fncompiler = {};
+ size_t i, pos, load_off;
+ uc_function *fn;
+ ssize_t slot;
+
+ if (!uc_compiler_parse_match(compiler, TK_ARROW))
+ return false;
+
+ pos = compiler->parser->prev.pos;
+
+ uc_compiler_init(&fncompiler, NULL, compiler->parser->prev.pos,
+ compiler->function->source);
+
+ fncompiler.parent = compiler;
+ fncompiler.parser = compiler->parser;
+
+ fncompiler.function->arrow = true;
+ fncompiler.function->vararg = args ? restarg : false;
+ fncompiler.function->nargs = array ? json_object_array_length(args) : !!args;
+
+ uc_compiler_enter_scope(&fncompiler);
+
+ /* declare local variables for arguments */
+ for (i = 0; i < fncompiler.function->nargs; i++) {
+ slot = uc_compiler_declare_local(&fncompiler,
+ array ? json_object_array_get_idx(args, i) : args);
+
+ if (slot != -1)
+ uc_compiler_syntax_error(&fncompiler, pos,
+ "Duplicate argument names are not allowed in this context");
+
+ uc_compiler_initialize_local(&fncompiler);
+ }
+
+ /* parse and compile body */
+ if (uc_compiler_parse_match(&fncompiler, TK_LBRACE)) {
+ while (!uc_compiler_parse_check(&fncompiler, TK_RBRACE) &&
+ !uc_compiler_parse_check(&fncompiler, TK_EOF))
+ uc_compiler_compile_declaration(&fncompiler);
+
+ uc_compiler_parse_consume(&fncompiler, TK_RBRACE);
+
+ /* overwrite last pop result with return */
+ if (fncompiler.function->chunk.count) {
+ uc_chunk_pop(&fncompiler.function->chunk);
+ uc_compiler_emit_insn(&fncompiler, 0, I_RETURN);
+ }
+ }
+ else {
+ uc_compiler_compile_expression(&fncompiler);
+ uc_compiler_emit_insn(&fncompiler, 0, I_RETURN);
+ }
+
+ /* emit load instruction for function value */
+ uc_compiler_emit_insn(compiler, pos, I_ARFN);
+ load_off = uc_compiler_emit_u32(compiler, 0, 0);
+
+ /* encode upvalue information */
+ for (i = 0; i < fncompiler.function->nupvals; i++)
+ uc_compiler_emit_s32(compiler, 0,
+ fncompiler.upvals.entries[i].local
+ ? -(fncompiler.upvals.entries[i].index + 1)
+ : fncompiler.upvals.entries[i].index);
+
+ /* finalize function compiler */
+ fn = uc_compiler_finish(&fncompiler);
+
+ if (fn)
+ uc_compiler_set_u32(compiler, load_off,
+ uc_chunk_add_constant(uc_compiler_current_chunk(compiler),
+ fn->header.jso));
+
+ return true;
+}
+
+static uc_tokentype_t
+uc_compiler_compile_var_or_arrowfn(uc_compiler *compiler, bool assignable, json_object *name)
+{
+ uc_tokentype_t rv;
+
+ if (assignable && uc_compiler_compile_assignment(compiler, name)) {
+ rv = TK_ASSIGN;
+ }
+ else if (uc_compiler_compile_arrowfn(compiler, name, false)) {
+ rv = TK_ARROW;
+ }
+ else {
+ uc_compiler_emit_variable_rw(compiler, name, 0);
+ rv = TK_LABEL;
+ }
+
+ return rv;
+}
+
+static void
+uc_compiler_compile_paren(uc_compiler *compiler, bool assignable)
+{
+ json_object *varnames = NULL, *varname;
+ bool maybe_arrowfn = false;
+ bool restarg = false;
+
+ /* First try to parse a complete parameter expression and remember the
+ * consumed label tokens as we go. */
+ while (true) {
+ if (uc_compiler_parse_match(compiler, TK_LABEL)) {
+ if (!varnames)
+ varnames = xjs_new_array();
+
+ json_object_array_add(varnames, uc_value_get(compiler->parser->prev.val));
+ }
+ else if (uc_compiler_parse_match(compiler, TK_ELLIP)) {
+ uc_compiler_parse_consume(compiler, TK_LABEL);
+
+ if (!varnames)
+ varnames = xjs_new_array();
+
+ json_object_array_add(varnames, uc_value_get(compiler->parser->prev.val));
+
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+
+ maybe_arrowfn = true;
+ restarg = true;
+
+ break;
+ }
+ else if (uc_compiler_parse_check(compiler, TK_COMMA)) {
+ /* Reject consecutive commas */
+ if (compiler->parser->prev.type == TK_COMMA)
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos,
+ "Expecting expression");
+
+ uc_compiler_parse_advance(compiler);
+
+ continue;
+ }
+ else {
+ maybe_arrowfn = uc_compiler_parse_match(compiler, TK_RPAREN);
+ break;
+ }
+ }
+
+ /* The lhs we parsed so far is elligible for an arrow function arg list,
+ * try to continue compiling into arrow function... */
+ if (maybe_arrowfn) {
+ /* If we can parse the remainder as arrow function, we're done */
+ if (uc_compiler_compile_arrowfn(compiler, varnames, restarg))
+ goto out;
+
+ /* ... otherwise disallow the `...` spread operator and empty
+ * parenthesized expressions */
+ if (restarg || !varnames) {
+ uc_compiler_syntax_error(compiler, compiler->parser->prev.pos,
+ "Expecting '=>' after parameter list");
+
+ goto out;
+ }
+ }
+
+ /* If we reach this, the expression we parsed so far cannot be a parameter
+ * list for an arrow function and we might have consumed one or multiple
+ * consecutive labels. */
+ if (varnames) {
+ /* Get last variable name */
+ varname = json_object_array_get_idx(varnames,
+ json_object_array_length(varnames) - 1);
+
+ /* If we consumed the right paren, the expression is complete and we
+ * only need to emit a variable read operation for the last parsed
+ * label since previous read operations are shadowed by subsequent ones
+ * in comma expressions and since pure variable reads are without
+ * side effects. */
+ if (maybe_arrowfn) {
+ uc_compiler_emit_variable_rw(compiler, varname, 0);
+
+ goto out;
+ }
+
+ /* ... otherwise if the last token was a label, try continue parsing as
+ * assignment or arrow function expression and if that fails, as
+ * relational one */
+ if (compiler->parser->prev.type == TK_LABEL) {
+ if (uc_compiler_compile_var_or_arrowfn(compiler, true, varname) == TK_LABEL) {
+ /* parse operand and rhs */
+ while (P_TERNARY <= uc_compiler_parse_rule(compiler->parser->curr.type)->precedence) {
+ uc_compiler_parse_advance(compiler);
+ uc_compiler_parse_rule(compiler->parser->prev.type)->infix(compiler, true);
+ }
+ }
+
+ /* If we're not at the end of the expression, we require a comma.
+ * Also pop intermediate result in this case. */
+ if (!uc_compiler_parse_check(compiler, TK_RPAREN)) {
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ uc_compiler_parse_consume(compiler, TK_COMMA);
+ }
+ }
+ }
+
+ /* When we reach this point, all already complete expression possibilities
+ * have been eliminated and we either need to compile the next, non-label
+ * expression or reached the closing paren. If neither applies, we have a
+ * syntax error. */
+ if (!uc_compiler_parse_check(compiler, TK_RPAREN))
+ uc_compiler_compile_expression(compiler);
+
+ /* At this point we expect the end of the parenthesized expression, anything
+ * else is a syntax error */
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+
+out:
+ uc_value_put(varnames);
+}
+
+static void
+uc_compiler_compile_call(uc_compiler *compiler, bool assignable)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ uc_jmplist spreads = {};
+ enum insn_type type;
+ size_t i, nargs = 0;
+
+ /* determine the kind of the lhs */
+ type = chunk->entries[compiler->last_insn];
+
+ /* if lhs is a dot or bracket expression, pop the LVAL instruction */
+ if (type == I_LVAL)
+ uc_chunk_pop(chunk);
+
+ /* compile arguments */
+ if (!uc_compiler_parse_check(compiler, TK_RPAREN)) {
+ do {
+ /* if this is a spread arg, remember the argument index */
+ if (uc_compiler_parse_match(compiler, TK_ELLIP)) {
+ uc_vector_grow(&spreads);
+ spreads.entries[spreads.count++] = nargs;
+ }
+
+ /* compile argument expression */
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+ nargs++;
+ }
+ while (uc_compiler_parse_match(compiler, TK_COMMA));
+ }
+
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+
+ /* if lhs is a dot or bracket expression, emit a method call */
+ if (type == I_LVAL)
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_MCALL);
+ /* else ordinary call */
+ else
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_CALL);
+
+ if (nargs > 0xffff || spreads.count > 0xffff)
+ uc_compiler_syntax_error(compiler, compiler->parser->prev.pos,
+ "Too many function call arguments");
+
+ /* encode ordinary (low 16 bit) and spread argument (high 16 bit) count */
+ uc_compiler_emit_u32(compiler, 0, ((spreads.count & 0xffff) << 16) | nargs);
+
+ /* encode spread arg positions */
+ for (i = 0; i < spreads.count; i++)
+ uc_compiler_emit_u16(compiler, 0, nargs - spreads.entries[i] - 1);
+
+ uc_vector_clear(&spreads);
+}
+
+static void
+uc_compiler_compile_post_inc(uc_compiler *compiler, bool assignable)
+{
+ uc_compiler_emit_inc_dec(compiler, compiler->parser->prev.type, true);
+}
+
+static void
+uc_compiler_compile_constant(uc_compiler *compiler, bool assignable)
+{
+ int64_t n;
+
+ switch (compiler->parser->prev.type) {
+ case TK_THIS:
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LTHIS);
+ break;
+
+ case TK_NULL:
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LNULL);
+ break;
+
+ case TK_BOOL:
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos,
+ json_object_get_boolean(compiler->parser->prev.val) ? I_LTRUE : I_LFALSE);
+ break;
+
+ case TK_DOUBLE:
+ case TK_STRING:
+ uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.val);
+ break;
+
+ case TK_REGEXP:
+ uc_compiler_emit_regexp(compiler, compiler->parser->prev.pos, compiler->parser->prev.val);
+ break;
+
+ case TK_NUMBER:
+ n = json_object_get_int64(compiler->parser->prev.val);
+
+ if (n >= -0x7f && n <= 0x7f) {
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LOAD8);
+ uc_compiler_emit_s8(compiler, compiler->parser->prev.pos, n);
+ }
+ else if (n >= -0x7fff && n <= 0x7fff) {
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LOAD16);
+ uc_compiler_emit_s16(compiler, compiler->parser->prev.pos, n);
+ }
+ else if (n >= -0x7fffffff && n <= 0x7fffffff) {
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LOAD32);
+ uc_compiler_emit_s32(compiler, compiler->parser->prev.pos, n);
+ }
+ else {
+ uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.val);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+uc_compiler_compile_comma(uc_compiler *compiler, bool assignable)
+{
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+}
+
+static void
+uc_compiler_compile_labelexpr(uc_compiler *compiler, bool assignable)
+{
+ json_object *label = uc_value_get(compiler->parser->prev.val);
+
+ uc_compiler_compile_var_or_arrowfn(compiler, assignable, label);
+ uc_value_put(label);
+}
+
+static bool
+uc_compiler_compile_delimitted_block(uc_compiler *compiler, uc_tokentype_t endtype)
+{
+ while (!uc_compiler_parse_check(compiler, endtype) &&
+ !uc_compiler_parse_check(compiler, TK_EOF))
+ uc_compiler_compile_declaration(compiler);
+
+ return uc_compiler_parse_check(compiler, endtype);
+}
+
+static void
+uc_compiler_compile_function(uc_compiler *compiler, bool assignable)
+{
+ uc_compiler fncompiler = {};
+ json_object *name = NULL;
+ ssize_t slot = -1, pos;
+ uc_tokentype_t type;
+ size_t i, load_off;
+ uc_function *fn;
+
+ pos = compiler->parser->prev.pos;
+ type = compiler->parser->prev.type;
+
+ if (uc_compiler_parse_match(compiler, TK_LABEL)) {
+ name = compiler->parser->prev.val;
+
+ /* Named functions are syntactic sugar for local variable declaration
+ * with function value assignment. If a name token was encountered,
+ * initialize a local variable for it... */
+ slot = uc_compiler_declare_local(compiler, name);
+
+ if (slot == -1)
+ uc_compiler_initialize_local(compiler);
+ }
+
+ uc_compiler_init(&fncompiler,
+ name ? json_object_get_string(name) : NULL, compiler->parser->prev.pos,
+ compiler->function->source);
+
+ fncompiler.parent = compiler;
+ fncompiler.parser = compiler->parser;
+
+ uc_compiler_parse_consume(&fncompiler, TK_LPAREN);
+
+ uc_compiler_enter_scope(&fncompiler);
+
+ /* compile argument specification */
+ while (true) {
+ if (uc_compiler_parse_check(&fncompiler, TK_RPAREN))
+ break;
+
+ if (uc_compiler_parse_match(&fncompiler, TK_ELLIP))
+ fncompiler.function->vararg = true;
+
+ if (uc_compiler_parse_match(&fncompiler, TK_LABEL)) {
+ fncompiler.function->nargs++;
+
+ uc_compiler_declare_local(&fncompiler, fncompiler.parser->prev.val);
+ uc_compiler_initialize_local(&fncompiler);
+
+ if (fncompiler.function->vararg ||
+ !uc_compiler_parse_match(&fncompiler, TK_COMMA))
+ break;
+ }
+ else {
+ uc_compiler_syntax_error(&fncompiler, fncompiler.parser->curr.pos,
+ "Expecting Label");
+
+ return;
+ }
+ }
+
+ uc_compiler_parse_consume(&fncompiler, TK_RPAREN);
+
+ /* parse and compile function body */
+ if (uc_compiler_parse_match(&fncompiler, TK_COLON)) {
+ uc_compiler_compile_delimitted_block(&fncompiler, TK_ENDFUNC);
+ uc_compiler_parse_consume(&fncompiler, TK_ENDFUNC);
+ }
+ else if (uc_compiler_parse_match(&fncompiler, TK_LBRACE)) {
+ uc_compiler_compile_delimitted_block(&fncompiler, TK_RBRACE);
+ uc_compiler_parse_consume(&fncompiler, TK_RBRACE);
+ }
+ else {
+ uc_compiler_syntax_error(&fncompiler, fncompiler.parser->curr.pos,
+ "Expecting '{' or ':' after function parameters");
+ }
+
+ /* emit load instruction for function value */
+ uc_compiler_emit_insn(compiler, pos, (type == TK_ARROW) ? I_ARFN : I_CLFN);
+ load_off = uc_compiler_emit_u32(compiler, 0, 0);
+
+ /* encode upvalue information */
+ for (i = 0; i < fncompiler.function->nupvals; i++)
+ uc_compiler_emit_s32(compiler, 0,
+ fncompiler.upvals.entries[i].local
+ ? -(fncompiler.upvals.entries[i].index + 1)
+ : fncompiler.upvals.entries[i].index);
+
+ /* finalize function compiler */
+ fn = uc_compiler_finish(&fncompiler);
+
+ if (fn)
+ uc_compiler_set_u32(compiler, load_off,
+ uc_chunk_add_constant(uc_compiler_current_chunk(compiler),
+ fn->header.jso));
+
+ /* if a local variable of the same name already existed, overwrite its value
+ * with the compiled function here */
+ if (slot != -1) {
+ uc_compiler_emit_insn(compiler, 0, I_SLOC);
+ uc_compiler_emit_u32(compiler, 0, slot);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ }
+}
+
+static void
+uc_compiler_compile_and(uc_compiler *compiler, bool assignable)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t jmpz_off;
+
+ uc_compiler_emit_insn(compiler, 0, I_COPY);
+ uc_compiler_emit_u8(compiler, 0, 0);
+ jmpz_off = uc_compiler_emit_jmpz(compiler, 0, 0);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ uc_compiler_parse_precedence(compiler, P_AND);
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+}
+
+static void
+uc_compiler_compile_or(uc_compiler *compiler, bool assignable)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t jmpz_off, jmp_off;
+
+ uc_compiler_emit_insn(compiler, 0, I_COPY);
+ uc_compiler_emit_u8(compiler, 0, 0);
+ jmpz_off = uc_compiler_emit_jmpz(compiler, 0, 0);
+ jmp_off = uc_compiler_emit_jmp(compiler, 0, 0);
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ uc_compiler_parse_precedence(compiler, P_OR);
+ uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count);
+}
+
+static void
+uc_compiler_compile_dot(uc_compiler *compiler, bool assignable)
+{
+ /* parse label lhs */
+ uc_compiler_parse_consume(compiler, TK_LABEL);
+ uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.val);
+
+ /* depending on context, compile into I_UVAL, I_SVAL or I_LVAL operation */
+ if (!assignable || !uc_compiler_compile_assignment(compiler, NULL))
+ uc_compiler_emit_variable_rw(compiler, NULL, 0);
+}
+
+static void
+uc_compiler_compile_subscript(uc_compiler *compiler, bool assignable)
+{
+ /* compile lhs */
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_parse_consume(compiler, TK_RBRACK);
+
+ /* depending on context, compile into I_UVAL, I_SVAL or I_LVAL operation */
+ if (!assignable || !uc_compiler_compile_assignment(compiler, NULL))
+ uc_compiler_emit_variable_rw(compiler, NULL, 0);
+}
+
+static void
+uc_compiler_compile_ternary(uc_compiler *compiler, bool assignable)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t jmpz_off, jmp_off;
+
+ /* jump to false branch */
+ jmpz_off = uc_compiler_emit_jmpz(compiler, 0, 0);
+
+ /* compile true branch */
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+
+ /* jump after false branch */
+ jmp_off = uc_compiler_emit_jmp(compiler, 0, 0);
+
+ uc_compiler_parse_consume(compiler, TK_COLON);
+
+ /* compile false branch */
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+ uc_compiler_parse_precedence(compiler, P_TERNARY);
+ uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count);
+}
+
+static void
+uc_compiler_compile_array(uc_compiler *compiler, bool assignable)
+{
+ size_t hint_off, hint_count = 0, len = 0;
+
+ /* create empty array on stack */
+ uc_compiler_emit_insn(compiler, 0, I_NARR);
+ hint_off = uc_compiler_emit_u32(compiler, 0, 0);
+
+ /* parse initializer values */
+ do {
+ if (uc_compiler_parse_check(compiler, TK_RBRACK)) {
+ break;
+ }
+ else if (uc_compiler_parse_match(compiler, TK_ELLIP)) {
+ /* push items on stack so far... */
+ if (len > 0) {
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_PARR);
+ uc_compiler_emit_u32(compiler, 0, len);
+ len = 0;
+ }
+
+ /* compile spread value expression */
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+
+ /* emit merge operation */
+ uc_compiler_emit_insn(compiler, 0, I_MARR);
+ }
+ else {
+ /* push items on stack so far... */
+ if (len >= 0xffffffff) {
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_PARR);
+ uc_compiler_emit_u32(compiler, 0, len);
+ len = 0;
+ }
+
+ /* compile item value expression */
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+
+ hint_count++;
+ len++;
+ }
+ }
+ while (uc_compiler_parse_match(compiler, TK_COMMA));
+
+ uc_compiler_parse_consume(compiler, TK_RBRACK);
+
+ /* push items on stack */
+ if (len > 0) {
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_PARR);
+ uc_compiler_emit_u32(compiler, 0, len);
+ }
+
+ /* set initial size hint */
+ uc_compiler_set_u32(compiler, hint_off, hint_count);
+}
+
+static void
+uc_compiler_compile_object(uc_compiler *compiler, bool assignable)
+{
+ size_t hint_off, hint_count = 0, len = 0;
+
+ /* create empty object on stack */
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_NOBJ);
+ hint_off = uc_compiler_emit_u32(compiler, 0, 0);
+
+ /* parse initializer values */
+ do {
+ /* End of object literal */
+ if (uc_compiler_parse_check(compiler, TK_RBRACE))
+ break;
+
+ /* Spread operator */
+ if (uc_compiler_parse_match(compiler, TK_ELLIP)) {
+ /* set items on stack so far... */
+ if (len > 0) {
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_SOBJ);
+ uc_compiler_emit_u32(compiler, 0, len);
+ len = 0;
+ }
+
+ /* compile spread value expression */
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+
+ /* emit merge operation */
+ uc_compiler_emit_insn(compiler, 0, I_MOBJ);
+
+ continue;
+ }
+
+ /* Computed property name */
+ if (uc_compiler_parse_match(compiler, TK_LBRACK)) {
+ /* parse property name expression */
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+
+ /* cosume closing bracket and colon */
+ uc_compiler_parse_consume(compiler, TK_RBRACK);
+ uc_compiler_parse_consume(compiler, TK_COLON);
+
+ /* parse value expression */
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+ }
+
+ /* Property/value tuple or property shorthand */
+ else {
+ /* parse key expression */
+ if (!uc_compiler_parse_match(compiler, TK_LABEL) &&
+ !uc_compiler_parse_match(compiler, TK_STRING))
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos,
+ "Expecting label");
+
+ /* load label */
+ uc_compiler_emit_constant(compiler, compiler->parser->prev.pos,
+ compiler->parser->prev.val);
+
+ /* If the property name is a plain label followed by a comma or
+ * closing curly brace, treat it as ES2015 property shorthand
+ * notation... */
+ if (compiler->parser->prev.type == TK_LABEL &&
+ (uc_compiler_parse_check(compiler, TK_COMMA) ||
+ uc_compiler_parse_check(compiler, TK_RBRACE))) {
+ uc_compiler_emit_variable_rw(compiler,
+ compiler->parser->prev.val, 0);
+ }
+
+ /* ... otherwise treat it as ordinary `key: value` tuple */
+ else {
+ uc_compiler_parse_consume(compiler, TK_COLON);
+
+ /* parse value expression */
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+ }
+ }
+
+ /* set items on stack so far... */
+ if (len >= 0xfffffffe) {
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_SOBJ);
+ uc_compiler_emit_u32(compiler, 0, len);
+ len = 0;
+ }
+
+ hint_count += 2;
+ len += 2;
+ }
+ while (uc_compiler_parse_match(compiler, TK_COMMA));
+
+ uc_compiler_parse_consume(compiler, TK_RBRACE);
+
+ /* set items on stack */
+ if (len > 0) {
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_SOBJ);
+ uc_compiler_emit_u32(compiler, 0, len);
+ len = 0;
+ }
+
+ /* set initial size hint */
+ uc_compiler_set_u32(compiler, hint_off, hint_count);
+}
+
+
+static void
+uc_compiler_declare_local_null(uc_compiler *compiler, size_t srcpos, json_object *varname)
+{
+ ssize_t existing_slot = uc_compiler_declare_local(compiler, varname);
+
+ uc_compiler_emit_insn(compiler, srcpos, I_LNULL);
+
+ if (existing_slot == -1) {
+ uc_compiler_initialize_local(compiler);
+ }
+ else {
+ uc_compiler_emit_insn(compiler, 0, I_SLOC);
+ uc_compiler_emit_u32(compiler, 0, existing_slot);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ }
+}
+
+static size_t
+uc_compiler_declare_internal(uc_compiler *compiler, size_t srcpos, const char *name)
+{
+#if 0
+ ssize_t existing_slot;
+ json_object *n;
+ bool strict;
+
+ n = xjs_new_string(name);
+ strict = compiler->strict_declarations;
+ compiler->strict_declarations = false;
+ existing_slot = uc_compiler_declare_local(compiler, n);
+ compiler->strict_declarations = strict;
+
+ uc_compiler_emit_insn(compiler, srcpos, I_LNULL);
+
+ if (existing_slot == -1) {
+ uc_value_put(n);
+
+ return uc_compiler_initialize_local(compiler);
+ }
+ else {
+ uc_value_put(n);
+
+ uc_compiler_emit_insn(compiler, 0, I_SLOC);
+ uc_compiler_emit_u32(compiler, 0, existing_slot);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+
+ return existing_slot;
+ }
+#else
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ uc_locals *locals = &compiler->locals;
+
+ //uc_compiler_emit_insn(compiler, srcpos, I_LNULL);
+
+ uc_vector_grow(locals);
+
+ locals->entries[locals->count].name = xjs_new_string(name);
+ locals->entries[locals->count].depth = compiler->scope_depth;
+ locals->entries[locals->count].captured = false;
+ locals->entries[locals->count].from = chunk->count;
+
+ return locals->count++;
+#endif
+}
+
+static void
+uc_compiler_compile_local(uc_compiler *compiler)
+{
+ ssize_t slot;
+
+ do {
+ /* parse variable name */
+ uc_compiler_parse_consume(compiler, TK_LABEL);
+
+ /* declare local variable */
+ slot = uc_compiler_declare_local(compiler, compiler->parser->prev.val);
+
+ /* if followed by '=', parse initializer expression */
+ if (uc_compiler_parse_match(compiler, TK_ASSIGN))
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+ /* otherwise load implicit null */
+ else
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LNULL);
+
+ /* initialize local */
+ if (slot == -1) {
+ uc_compiler_initialize_local(compiler);
+ }
+ /* if the variable was redeclared, overwrite it */
+ else {
+ uc_compiler_emit_insn(compiler, 0, I_SLOC);
+ uc_compiler_emit_u32(compiler, 0, slot);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ }
+ }
+ while (uc_compiler_parse_match(compiler, TK_COMMA));
+
+ uc_compiler_parse_consume(compiler, TK_SCOL);
+}
+
+static uc_tokentype_t
+uc_compiler_compile_altifblock(uc_compiler *compiler)
+{
+ while (true) {
+ switch (compiler->parser->curr.type) {
+ case TK_ELIF:
+ case TK_ELSE:
+ case TK_ENDIF:
+ case TK_EOF:
+ return compiler->parser->curr.type;
+
+ default:
+ uc_compiler_compile_declaration(compiler);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void
+uc_compiler_compile_if(uc_compiler *compiler)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t jmpz_off, jmp_off, i;
+ bool expect_endif = false;
+ uc_jmplist elifs = {};
+ uc_tokentype_t type;
+
+ /* parse & compile condition expression */
+ uc_compiler_parse_consume(compiler, TK_LPAREN);
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+
+ /* conditional jump to else/elif branch */
+ jmpz_off = uc_compiler_emit_jmpz(compiler, 0, 0);
+
+ if (uc_compiler_parse_match(compiler, TK_COLON)) {
+ while (true) {
+ /* compile elsif or else branch */
+ type = uc_compiler_compile_altifblock(compiler);
+
+ /* we just compiled an elsif block */
+ if (!expect_endif && type == TK_ELIF) {
+ /* emit jump to skip to the end */
+ uc_vector_grow(&elifs);
+ elifs.entries[elifs.count++] = uc_compiler_emit_jmp(compiler, 0, 0);
+
+ /* point previous conditional jump to beginning of branch */
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+
+ /* parse & compile elsif condition */
+ uc_compiler_parse_advance(compiler);
+ uc_compiler_parse_consume(compiler, TK_LPAREN);
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+ uc_compiler_parse_consume(compiler, TK_COLON);
+
+ /* conditional jump to else/elif branch */
+ jmpz_off = uc_compiler_emit_jmpz(compiler, 0, 0);
+ }
+ else if (!expect_endif && type == TK_ELSE) {
+ /* emit jump to skip to the end */
+ uc_vector_grow(&elifs);
+ elifs.entries[elifs.count++] = uc_compiler_emit_jmp(compiler, 0, 0);
+
+ /* point previous conditional jump to beginning of branch */
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+ jmpz_off = 0;
+
+ /* skip "else" keyword */
+ uc_compiler_parse_advance(compiler);
+
+ expect_endif = true;
+ }
+ else if (type == TK_ENDIF) {
+ /* if no else clause, point previous conditional jump after block */
+ if (jmpz_off)
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+
+ /* patch the elif branch jumps to point here after the else */
+ for (i = 0; i < elifs.count; i++)
+ uc_compiler_set_jmpaddr(compiler, elifs.entries[i],
+ chunk->count);
+
+ /* skip the "endif" keyword */
+ uc_compiler_parse_advance(compiler);
+ break;
+ }
+ else {
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos,
+ expect_endif
+ ? "Expecting 'endif'"
+ : "Expecting 'elif', 'else' or 'endif'");
+
+ break;
+ }
+ }
+
+ uc_vector_clear(&elifs);
+ }
+ else {
+ /* compile true branch */
+ uc_compiler_compile_statement(compiler);
+
+ /* ... when present, handle false branch */
+ if (uc_compiler_parse_match(compiler, TK_ELSE)) {
+ /* jump to skip else branch */
+ jmp_off = uc_compiler_emit_jmp(compiler, 0, 0);
+
+ /* set conditional jump address */
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+
+ /* compile false branch */
+ uc_compiler_compile_statement(compiler);
+
+ /* set else skip jump address */
+ uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count);
+ }
+ /* ... otherwise point the conditional jump after the true branch */
+ else {
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+ }
+ }
+}
+
+static void
+uc_compiler_compile_while(uc_compiler *compiler)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t cond_off, jmpz_off, end_off;
+ uc_patchlist p = {};
+
+ p.parent = compiler->patchlist;
+ compiler->patchlist = &p;
+
+ cond_off = chunk->count;
+
+ /* parse & compile loop condition */
+ uc_compiler_parse_consume(compiler, TK_LPAREN);
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+
+ /* conditional jump to end */
+ jmpz_off = uc_compiler_emit_jmpz(compiler, 0, 0);
+
+ /* compile loop body */
+ if (uc_compiler_parse_match(compiler, TK_COLON)) {
+ if (!uc_compiler_compile_delimitted_block(compiler, TK_ENDWHILE))
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos,
+ "Expecting 'endwhile'");
+ else
+ uc_compiler_parse_advance(compiler);
+ }
+ else {
+ uc_compiler_compile_statement(compiler);
+ }
+
+ end_off = chunk->count;
+
+ /* jump back to condition */
+ uc_compiler_emit_jmp(compiler, 0, cond_off);
+
+ /* set conditional jump target */
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+
+ /* patch up break/continue */
+ uc_compiler_backpatch(compiler, chunk->count, end_off);
+}
+
+static void
+uc_compiler_compile_for_in(uc_compiler *compiler, bool local, uc_token *kvar, uc_token *vvar)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t skip_jmp, test_jmp, key_slot, val_slot;
+ uc_patchlist p = {};
+
+ p.parent = compiler->patchlist;
+ compiler->patchlist = &p;
+
+ uc_compiler_enter_scope(compiler);
+
+ /* declare internal loop variables */
+ uc_compiler_emit_insn(compiler, 0, I_LNULL);
+ key_slot = uc_compiler_declare_internal(compiler, 0, "(for in key)");
+
+ uc_compiler_emit_insn(compiler, 0, I_LNULL);
+ val_slot = uc_compiler_declare_internal(compiler, 0, "(for in value)");
+
+ /* declare loop variables */
+ if (local) {
+ uc_compiler_declare_local_null(compiler, kvar->pos, kvar->val);
+
+ if (vvar)
+ uc_compiler_declare_local_null(compiler, vvar->pos, vvar->val);
+ }
+
+ /* value to iterate */
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+ uc_compiler_emit_insn(compiler, 0, I_SLOC);
+ uc_compiler_emit_u32(compiler, 0, val_slot);
+
+ /* initial key value */
+ uc_compiler_emit_insn(compiler, 0, I_LNULL);
+ uc_compiler_emit_insn(compiler, 0, I_SLOC);
+ uc_compiler_emit_u32(compiler, 0, key_slot);
+
+ /* jump over variable read for first cycle */
+ skip_jmp = uc_compiler_emit_jmp(compiler, 0, 0);
+
+ /* read value */
+ uc_compiler_emit_insn(compiler, 0, I_LLOC);
+ uc_compiler_emit_u32(compiler, 0, val_slot);
+
+ /* read key */
+ uc_compiler_emit_insn(compiler, 0, I_LLOC);
+ uc_compiler_emit_u32(compiler, 0, key_slot);
+
+ /* backpatch skip jump */
+ uc_compiler_set_jmpaddr(compiler, skip_jmp, chunk->count);
+
+ /* load loop variable and get next key from object */
+ uc_compiler_emit_insn(compiler, 0, vvar ? I_NEXTKV : I_NEXTK);
+
+ /* set internal key variable */
+ uc_compiler_emit_insn(compiler, 0, I_SLOC);
+ uc_compiler_emit_u32(compiler, 0, key_slot);
+
+ /* test for != null */
+ uc_compiler_emit_insn(compiler, 0, I_LNULL);
+ uc_compiler_emit_insn(compiler, 0, I_NES);
+
+ /* jump after loop body if no next key */
+ test_jmp = uc_compiler_emit_jmpz(compiler, 0, 0);
+
+ /* set key and value variables */
+ if (vvar) {
+ uc_compiler_emit_variable_rw(compiler, vvar->val, TK_ASSIGN);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ }
+
+ /* set key variable */
+ uc_compiler_emit_variable_rw(compiler, kvar->val, TK_ASSIGN);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+
+ /* compile loop body */
+ if (uc_compiler_parse_match(compiler, TK_COLON)) {
+ if (!uc_compiler_compile_delimitted_block(compiler, TK_ENDFOR))
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos,
+ "Expecting 'endfor'");
+ else
+ uc_compiler_parse_advance(compiler);
+ }
+ else {
+ uc_compiler_compile_statement(compiler);
+ }
+
+ /* jump back to retrieve next key */
+ uc_compiler_emit_jmp(compiler, 0, skip_jmp + 5);
+
+ /* back patch conditional jump */
+ uc_compiler_set_jmpaddr(compiler, test_jmp, chunk->count);
+
+ /* patch up break/continue */
+ uc_compiler_backpatch(compiler, chunk->count, skip_jmp + 5);
+
+ /* pop loop variables */
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+
+ if (vvar)
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+
+ uc_compiler_leave_scope(compiler);
+}
+
+static void
+uc_compiler_compile_for_count(uc_compiler *compiler, bool local, uc_token *var)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t test_off = 0, incr_off, skip_off, cond_off = 0;
+ uc_patchlist p = {};
+
+ p.parent = compiler->patchlist;
+ compiler->patchlist = &p;
+
+ uc_compiler_enter_scope(compiler);
+
+ /* Initializer ---------------------------------------------------------- */
+
+ /* We parsed a `local x` or `local x, y` expression, so (re)declare
+ * last label as local initializer variable */
+ if (local)
+ uc_compiler_declare_local_null(compiler, var->pos, var->val);
+
+ /* If we parsed at least on label, try continue parsing as variable
+ * expression... */
+ if (var) {
+ uc_compiler_compile_labelexpr(compiler, true);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+
+ /* If followed by a comma, continue parsing expression */
+ if (uc_compiler_parse_match(compiler, TK_COMMA)) {
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ }
+ }
+ /* ... otherwise try parsing an entire expression (which might be absent) */
+ else if (!uc_compiler_parse_check(compiler, TK_SCOL)) {
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ }
+
+ uc_compiler_parse_consume(compiler, TK_SCOL);
+
+
+ /* Condition ------------------------------------------------------------ */
+ if (!uc_compiler_parse_check(compiler, TK_SCOL)) {
+ cond_off = chunk->count;
+
+ uc_compiler_compile_expression(compiler);
+
+ test_off = uc_compiler_emit_jmpz(compiler, 0, 0);
+ }
+
+ uc_compiler_parse_consume(compiler, TK_SCOL);
+
+ /* jump over incrementer */
+ skip_off = uc_compiler_emit_jmp(compiler, 0, 0);
+
+
+ /* Incrementer ---------------------------------------------------------- */
+ incr_off = chunk->count;
+
+ if (!uc_compiler_parse_check(compiler, TK_RPAREN)) {
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ }
+
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+
+ /* if we have a condition, jump back to it, else continue to the loop body */
+ if (cond_off)
+ uc_compiler_emit_jmp(compiler, 0, cond_off);
+
+ /* back patch skip address */
+ uc_compiler_set_jmpaddr(compiler, skip_off, chunk->count);
+
+
+ /* Body ----------------------------------------------------------------- */
+ if (uc_compiler_parse_match(compiler, TK_COLON)) {
+ if (!uc_compiler_compile_delimitted_block(compiler, TK_ENDFOR))
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos,
+ "Expecting 'endfor'");
+ else
+ uc_compiler_parse_advance(compiler);
+ }
+ else {
+ uc_compiler_compile_statement(compiler);
+ }
+
+ /* jump back to incrementer */
+ uc_compiler_emit_jmp(compiler, 0, incr_off);
+
+ /* back patch conditional jump */
+ if (test_off)
+ uc_compiler_set_jmpaddr(compiler, test_off, chunk->count);
+
+ /* patch up break/continue */
+ uc_compiler_backpatch(compiler, chunk->count, incr_off);
+
+ uc_compiler_leave_scope(compiler);
+}
+
+static void
+uc_compiler_compile_for(uc_compiler *compiler)
+{
+ uc_token keyvar = {}, valvar = {};
+ bool local;
+
+ uc_compiler_parse_consume(compiler, TK_LPAREN);
+
+ /* check the next few tokens and see if we have either a
+ * `let x in` / `let x, y` expression or an ordinary initializer
+ * statement */
+
+ local = uc_compiler_parse_match(compiler, TK_LOCAL);
+
+ if (local && !uc_compiler_parse_check(compiler, TK_LABEL))
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos,
+ "Expecting label after 'local'");
+
+ if (uc_compiler_parse_match(compiler, TK_LABEL)) {
+ keyvar = compiler->parser->prev;
+ uc_value_get(keyvar.val);
+
+ if (uc_compiler_parse_match(compiler, TK_COMMA)) {
+ uc_compiler_parse_consume(compiler, TK_LABEL);
+
+ valvar = compiler->parser->prev;
+ uc_value_get(valvar.val);
+ }
+
+ /* is a for-in loop */
+ if (uc_compiler_parse_match(compiler, TK_IN)) {
+ uc_compiler_compile_for_in(compiler, local, &keyvar,
+ valvar.type ? &valvar : NULL);
+
+ goto out;
+ }
+ }
+
+ /*
+ * The previous expression ruled out a for-in loop, so continue parsing
+ * as counting for loop...
+ */
+ uc_compiler_compile_for_count(compiler, local,
+ valvar.val ? &valvar : (keyvar.val ? &keyvar : NULL));
+
+out:
+ uc_value_put(keyvar.val);
+ uc_value_put(valvar.val);
+}
+
+static void
+uc_compiler_compile_switch(uc_compiler *compiler)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t i, first_jmp, skip_jmp, next_jmp, default_jmp = 0;
+ bool in_case = false;
+ uc_jmplist jmps = {};
+ uc_patchlist p = {};
+
+ p.parent = compiler->patchlist;
+ compiler->patchlist = &p;
+
+ uc_compiler_enter_scope(compiler);
+
+ /* parse and compile match value */
+ uc_compiler_parse_consume(compiler, TK_LPAREN);
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+ uc_compiler_parse_consume(compiler, TK_LBRACE);
+ uc_compiler_declare_internal(compiler, 0, "(switch value)");
+
+ /* skip over first condition */
+ first_jmp = uc_compiler_emit_jmp(compiler, 0, 0);
+
+ /* parse and compile case matches */
+ while (!uc_compiler_parse_check(compiler, TK_RBRACE) &&
+ !uc_compiler_parse_check(compiler, TK_EOF)) {
+ /* handle `default:` */
+ if (uc_compiler_parse_match(compiler, TK_DEFAULT)) {
+ if (default_jmp) {
+ uc_compiler_syntax_error(compiler, compiler->parser->prev.pos,
+ "more than one switch default case");
+
+ return;
+ }
+
+ uc_compiler_parse_consume(compiler, TK_COLON);
+
+ /* jump over default case, can only be reached by fallthrough or
+ * conditional jump after last failed case condition */
+ default_jmp = uc_compiler_emit_jmp(compiler, 0, 0);
+
+ in_case = true;
+ }
+
+ /* handle `case …:` */
+ else if (uc_compiler_parse_match(compiler, TK_CASE)) {
+ /* jump over `case …:` label */
+ uc_vector_grow(&jmps);
+ jmps.entries[jmps.count++] = uc_compiler_emit_jmp(compiler, 0, 0);
+
+ /* copy condition value */
+ uc_compiler_emit_insn(compiler, 0, I_COPY);
+ uc_compiler_emit_u8(compiler, 0, 0);
+
+ /* compile case value expression */
+ uc_compiler_compile_expression(compiler);
+ uc_compiler_parse_consume(compiler, TK_COLON);
+
+ /* strict equality test */
+ uc_compiler_emit_insn(compiler, 0, I_EQS);
+
+ /* on inequality, jump to next condition */
+ uc_vector_grow(&jmps);
+ jmps.entries[jmps.count++] = uc_compiler_emit_jmpz(compiler, 0, 0);
+
+ in_case = true;
+ }
+
+ /* handle interleaved statement */
+ else if (in_case) {
+ uc_compiler_compile_declaration(compiler);
+ }
+
+ /* a statement or expression preceeding any `default` or `case` is a
+ * syntax error */
+ else {
+ uc_compiler_syntax_error(compiler, compiler->parser->curr.pos,
+ "Expecting 'case' or 'default'");
+
+ return;
+ }
+ }
+
+ uc_compiler_parse_consume(compiler, TK_RBRACE);
+
+ /* patch jump targets for cases */
+ for (i = 0; i < jmps.count; i += 2) {
+ skip_jmp = jmps.entries[i + 0];
+ next_jmp = jmps.entries[i + 1];
+
+ uc_compiler_set_jmpaddr(compiler, skip_jmp, next_jmp + 5);
+
+ /* have a subsequent case, patch next jump to it */
+ if (i + 2 < jmps.count)
+ uc_compiler_set_jmpaddr(compiler, next_jmp, jmps.entries[i + 2] + 5);
+ /* case was last in switch, jump to default */
+ else if (default_jmp)
+ uc_compiler_set_jmpaddr(compiler, next_jmp, default_jmp + 5);
+ /* if no default, jump to end */
+ else
+ uc_compiler_set_jmpaddr(compiler, next_jmp, chunk->count);
+ }
+
+ /* if we have a default case, set target for the skip jump */
+ if (default_jmp) {
+ /* if we have cases, jump to the first one */
+ if (jmps.count)
+ uc_compiler_set_jmpaddr(compiler, default_jmp, jmps.entries[0] + 5);
+ /* ... otherwise turn jump into no-op */
+ else
+ uc_compiler_set_jmpaddr(compiler, default_jmp, default_jmp + 5);
+ }
+
+ /* if we have cases, patch initial jump after the first case condition */
+ if (jmps.count)
+ uc_compiler_set_jmpaddr(compiler, first_jmp, jmps.entries[0] + 5);
+ /* ... otherwise jump into default */
+ else if (default_jmp)
+ uc_compiler_set_jmpaddr(compiler, first_jmp, default_jmp + 5);
+ /* ... otherwise if no defualt, turn into no-op */
+ else
+ uc_compiler_set_jmpaddr(compiler, first_jmp, first_jmp + 5);
+
+ uc_vector_clear(&jmps);
+
+ uc_compiler_leave_scope(compiler);
+
+ uc_compiler_backpatch(compiler, chunk->count, 0);
+
+}
+
+static void
+uc_compiler_compile_try(uc_compiler *compiler)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t try_from = 0, try_to = 0, jmp_off = 0, ehvar_slot = 0;
+ uc_ehranges *ranges = &chunk->ehranges;
+
+ try_from = chunk->count;
+ ehvar_slot = compiler->locals.count;
+
+ /* Try block ------------------------------------------------------------ */
+ uc_compiler_enter_scope(compiler);
+
+ uc_compiler_parse_consume(compiler, TK_LBRACE);
+
+ while (!uc_compiler_parse_check(compiler, TK_RBRACE) &&
+ !uc_compiler_parse_check(compiler, TK_EOF))
+ uc_compiler_compile_declaration(compiler);
+
+ /* jump beyond catch branch */
+ try_to = chunk->count;
+ jmp_off = uc_compiler_emit_jmp(compiler, 0, 0);
+
+ uc_compiler_parse_consume(compiler, TK_RBRACE);
+
+ uc_compiler_leave_scope(compiler);
+
+
+ /* Catch block ---------------------------------------------------------- */
+ if (try_to > try_from) {
+ uc_vector_grow(ranges);
+
+ ranges->entries[ranges->count].from = try_from;
+ ranges->entries[ranges->count].to = try_to;
+ ranges->entries[ranges->count].target = chunk->count;
+ ranges->entries[ranges->count].slot = ehvar_slot;
+ ranges->count++;
+ }
+
+ uc_compiler_enter_scope(compiler);
+
+ uc_compiler_parse_consume(compiler, TK_CATCH);
+
+ /* have exception variable */
+ if (uc_compiler_parse_match(compiler, TK_LPAREN)) {
+ uc_compiler_parse_consume(compiler, TK_LABEL);
+
+ uc_compiler_declare_local(compiler, compiler->parser->prev.val);
+ uc_compiler_initialize_local(compiler);
+
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+ }
+ /* ... else pop exception object from stack */
+ else {
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ }
+
+ uc_compiler_parse_consume(compiler, TK_LBRACE);
+
+ while (!uc_compiler_parse_check(compiler, TK_RBRACE) &&
+ !uc_compiler_parse_check(compiler, TK_EOF))
+ uc_compiler_compile_declaration(compiler);
+
+ uc_compiler_parse_consume(compiler, TK_RBRACE);
+
+ uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count);
+
+ uc_compiler_leave_scope(compiler);
+}
+
+static void
+uc_compiler_compile_control(uc_compiler *compiler)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ uc_tokentype_t type = compiler->parser->prev.type;
+ uc_patchlist *p = compiler->patchlist;
+ uc_locals *locals = &compiler->locals;
+ size_t i, pos = compiler->parser->prev.pos;
+
+ if (!p) {
+ uc_compiler_syntax_error(compiler, pos,
+ (type == TK_BREAK)
+ ? "break must be inside loop or switch"
+ : "continue must be inside loop");
+
+ return;
+ }
+
+ /* pop locals in scope up to this point */
+ for (i = locals->count; i > 0 && locals->entries[i - 1].depth == compiler->scope_depth; i--)
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+
+ uc_vector_grow(p);
+
+ p->entries[p->count++] =
+ uc_compiler_emit_jmp(compiler, pos, chunk->count + type);
+
+ uc_compiler_parse_consume(compiler, TK_SCOL);
+}
+
+static void
+uc_compiler_compile_return(uc_compiler *compiler)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t off = chunk->count;
+
+ uc_compiler_compile_expstmt(compiler);
+
+ /* if we compiled an empty expression statement (`;`), load implicit null */
+ if (chunk->count == off)
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LNULL);
+ /* otherwise overwrite the final I_POP instruction with I_RETURN */
+ else
+ uc_chunk_pop(chunk);
+
+ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_RETURN);
+}
+
+static void
+uc_compiler_compile_tplexp(uc_compiler *compiler)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ size_t off = chunk->count;
+
+ uc_compiler_compile_expression(compiler);
+
+ /* XXX: the lexer currently emits a superfluous trailing semicolon... */
+ uc_compiler_parse_match(compiler, TK_SCOL);
+
+ uc_compiler_parse_consume(compiler, TK_REXP);
+
+ if (chunk->count > off)
+ uc_compiler_emit_insn(compiler, 0, I_PRINT);
+}
+
+static void
+uc_compiler_compile_text(uc_compiler *compiler)
+{
+ uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.val);
+ uc_compiler_emit_insn(compiler, 0, I_PRINT);
+}
+
+static void
+uc_compiler_compile_block(uc_compiler *compiler)
+{
+ uc_compiler_enter_scope(compiler);
+
+ while (!uc_compiler_parse_check(compiler, TK_RBRACE) &&
+ !uc_compiler_parse_check(compiler, TK_EOF))
+ uc_compiler_compile_declaration(compiler);
+
+ uc_compiler_parse_consume(compiler, TK_RBRACE);
+
+ uc_compiler_leave_scope(compiler);
+}
+
+static void
+uc_compiler_compile_expstmt(uc_compiler *compiler)
+{
+ /* empty statement */
+ if (uc_compiler_parse_match(compiler, TK_SCOL))
+ return;
+
+ uc_compiler_compile_expression(compiler);
+
+ /* allow omitting final semicolon */
+ switch (compiler->parser->curr.type) {
+ case TK_RBRACE:
+ case TK_ELSE: /* fixme: only in altblockmode */
+ case TK_ELIF:
+ case TK_ENDIF:
+ case TK_ENDFOR:
+ case TK_ENDWHILE:
+ case TK_ENDFUNC:
+ case TK_EOF:
+ break;
+
+ default:
+ uc_compiler_parse_consume(compiler, TK_SCOL);
+
+ break;
+ }
+
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+}
+
+static void
+uc_compiler_compile_statement(uc_compiler *compiler)
+{
+ if (uc_compiler_parse_match(compiler, TK_IF))
+ uc_compiler_compile_if(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_WHILE))
+ uc_compiler_compile_while(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_FOR))
+ uc_compiler_compile_for(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_SWITCH))
+ uc_compiler_compile_switch(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_TRY))
+ uc_compiler_compile_try(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_FUNC))
+ uc_compiler_compile_function(compiler, false);
+ else if (uc_compiler_parse_match(compiler, TK_BREAK))
+ uc_compiler_compile_control(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_CONTINUE))
+ uc_compiler_compile_control(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_RETURN))
+ uc_compiler_compile_return(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_TEXT))
+ uc_compiler_compile_text(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_LEXP))
+ uc_compiler_compile_tplexp(compiler);
+ else if (uc_compiler_parse_match(compiler, TK_LBRACE))
+ uc_compiler_compile_block(compiler);
+ else
+ uc_compiler_compile_expstmt(compiler);
+}
+
+static void
+uc_compiler_compile_declaration(uc_compiler *compiler)
+{
+ if (uc_compiler_parse_match(compiler, TK_LOCAL))
+ uc_compiler_compile_local(compiler);
+ else
+ uc_compiler_compile_statement(compiler);
+
+ if (compiler->parser->synchronizing)
+ uc_compiler_parse_synchronize(compiler);
+}
+
+uc_function *
+uc_compile(uc_parse_config *config, uc_source *source, char **errp)
+{
+ uc_parser parser = { .config = config };
+ uc_compiler compiler = { .parser = &parser };
+ uc_function *fn;
+
+ uc_lexer_init(&parser.lex, config, source);
+ uc_compiler_init(&compiler, "main", 0, source);
+
+ uc_compiler_parse_advance(&compiler);
+
+ while (!uc_compiler_parse_match(&compiler, TK_EOF))
+ uc_compiler_compile_declaration(&compiler);
+
+ fn = uc_compiler_finish(&compiler);
+
+ if (errp)
+ *errp = parser.error;
+
+ uc_lexer_free(&parser.lex);
+
+ return fn;
+}