/* * Copyright (C) 2020-2021 Jo-Philipp Wich * * 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 #include #include "ucode/compiler.h" #include "ucode/chunk.h" #include "ucode/vm.h" /* I_* */ #include "ucode/source.h" #include "ucode/program.h" #include "ucode/lib.h" /* uc_error_context_format() */ #ifndef NO_COMPILE static void uc_compiler_compile_unary(uc_compiler_t *compiler); static void uc_compiler_compile_binary(uc_compiler_t *compiler); static void uc_compiler_compile_delete(uc_compiler_t *compiler); static void uc_compiler_compile_paren(uc_compiler_t *compiler); static void uc_compiler_compile_call(uc_compiler_t *compiler); static void uc_compiler_compile_post_inc(uc_compiler_t *compiler); static void uc_compiler_compile_constant(uc_compiler_t *compiler); static void uc_compiler_compile_template(uc_compiler_t *compiler); static void uc_compiler_compile_comma(uc_compiler_t *compiler); static void uc_compiler_compile_labelexpr(uc_compiler_t *compiler); static void uc_compiler_compile_funcexpr(uc_compiler_t *compiler); static void uc_compiler_compile_and(uc_compiler_t *compiler); static void uc_compiler_compile_or(uc_compiler_t *compiler); static void uc_compiler_compile_nullish(uc_compiler_t *compiler); static void uc_compiler_compile_dot(uc_compiler_t *compiler); static void uc_compiler_compile_subscript(uc_compiler_t *compiler); static void uc_compiler_compile_ternary(uc_compiler_t *compiler); static void uc_compiler_compile_array(uc_compiler_t *compiler); static void uc_compiler_compile_object(uc_compiler_t *compiler); static uc_tokentype_t uc_compiler_compile_declaration(uc_compiler_t *compiler); static uc_tokentype_t uc_compiler_compile_statement(uc_compiler_t *compiler); static uc_tokentype_t uc_compiler_compile_expstmt(uc_compiler_t *compiler); static uc_parse_rule_t uc_compiler_parse_rules[TK_ERROR + 1] = { [TK_LPAREN] = { uc_compiler_compile_paren, uc_compiler_compile_call, P_CALL }, [TK_QLPAREN] = { NULL, 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_DELETE] = { uc_compiler_compile_delete, 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_EXP] = { NULL, uc_compiler_compile_binary, P_EXP }, [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_TRUE] = { uc_compiler_compile_constant, NULL, P_NONE }, [TK_FALSE] = { 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_TEMPLATE] = { uc_compiler_compile_template, 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_funcexpr, NULL, P_NONE }, [TK_AND] = { NULL, uc_compiler_compile_and, P_AND }, [TK_OR] = { NULL, uc_compiler_compile_or, P_OR }, [TK_NULLISH] = { NULL, uc_compiler_compile_nullish, 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_QDOT] = { NULL, uc_compiler_compile_dot, P_CALL }, [TK_LBRACK] = { uc_compiler_compile_array, uc_compiler_compile_subscript, P_CALL }, [TK_QLBRACK] = { NULL, 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_t *compiler, uc_value_t *name, bool constant); static ssize_t uc_compiler_initialize_local(uc_compiler_t *compiler); static bool uc_compiler_exprstack_is(uc_compiler_t *compiler, uc_exprflag_t flag) { uc_exprstack_t *expr; for (expr = compiler->exprstack; expr; expr = expr->parent) if (expr->flags & flag) return true; return false; } static void uc_compiler_init(uc_compiler_t *compiler, const char *name, uc_source_t *source, size_t srcpos, uc_program_t *program, bool strict) { uc_value_t *varname = ucv_string_new("(callee)"); uc_function_t *fn; compiler->scope_depth = 0; compiler->program = program; compiler->function = uc_program_function_new(program, name, source, srcpos); 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; fn = (uc_function_t *)compiler->function; fn->strict = strict; /* reserve stack slot 0 */ uc_compiler_declare_local(compiler, varname, false); uc_compiler_initialize_local(compiler); ucv_put(varname); } static uc_chunk_t * uc_compiler_current_chunk(uc_compiler_t *compiler) { uc_function_t *fn = (uc_function_t *)compiler->function; return &fn->chunk; } static uc_source_t * uc_compiler_current_source(uc_compiler_t *compiler) { return uc_program_function_source(compiler->function); } __attribute__((format(printf, 3, 0))) static void uc_compiler_syntax_error(uc_compiler_t *compiler, size_t off, const char *fmt, ...) { uc_source_t *source = uc_compiler_current_source(compiler); uc_stringbuf_t *buf = compiler->parser->error; size_t line = 0, byte = 0, len = 0; va_list ap; char *s; if (compiler->parser->synchronizing) return; compiler->parser->synchronizing = true; if (!buf) buf = compiler->parser->error = xprintbuf_new(); if (!off) off = uc_program_function_srcpos(compiler->function, uc_compiler_current_chunk(compiler)->count); byte = off; line = uc_source_get_line(source, &byte); va_start(ap, fmt); len = xvasprintf(&s, fmt, ap); va_end(ap); ucv_stringbuf_append(buf, "Syntax error: "); ucv_stringbuf_addstr(buf, s, len); ucv_stringbuf_append(buf, "\n"); free(s); if (line) { ucv_stringbuf_append(buf, "In "); if (compiler->program->sources.count > 1) { len = strlen(source->filename); if (len > 48) ucv_stringbuf_printf(buf, "...%s", source->filename + len - 45); else ucv_stringbuf_addstr(buf, source->filename, len); ucv_stringbuf_append(buf, ", "); } ucv_stringbuf_printf(buf, "line %zu, byte %zu:\n", line, byte); } if (uc_error_context_format(buf, source, NULL, off)) ucv_stringbuf_append(buf, "\n\n"); } static size_t uc_compiler_set_srcpos(uc_compiler_t *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_t *compiler) { ucv_put(compiler->parser->prev.uv); 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", ucv_string_get(compiler->parser->curr.uv)); ucv_put(compiler->parser->curr.uv); compiler->parser->curr.uv = NULL; } } static void uc_compiler_parse_consume(uc_compiler_t *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_tokenname(type)); } static bool uc_compiler_parse_check(uc_compiler_t *compiler, uc_tokentype_t type) { return (compiler->parser->curr.type == type); } static bool uc_compiler_parse_match(uc_compiler_t *compiler, uc_tokentype_t type) { if (!uc_compiler_parse_check(compiler, type)) return false; uc_compiler_parse_advance(compiler); return true; } static bool uc_compiler_keyword_check(uc_compiler_t *compiler, const char *keyword) { size_t keywordlen = strlen(keyword); return (compiler->parser->curr.type == TK_LABEL && ucv_string_length(compiler->parser->curr.uv) == keywordlen && strcmp(ucv_string_get(compiler->parser->curr.uv), keyword) == 0); } static bool uc_compiler_keyword_match(uc_compiler_t *compiler, const char *keyword) { if (!uc_compiler_keyword_check(compiler, keyword)) return false; uc_compiler_parse_advance(compiler); return true; } static void uc_compiler_keyword_consume(uc_compiler_t *compiler, const char *keyword) { if (uc_compiler_keyword_check(compiler, keyword)) { uc_compiler_parse_advance(compiler); return; } uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unexpected token\nExpecting '%s'", keyword); } static void uc_compiler_parse_synchronize(uc_compiler_t *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_t * uc_compiler_parse_rule(uc_tokentype_t type) { return &uc_compiler_parse_rules[type]; } static bool uc_compiler_parse_at_assignment_op(uc_compiler_t *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_ASAND: case TK_ASOR: case TK_ASEXP: case TK_ASNULLISH: case TK_ASSIGN: return true; default: return false; } } static void uc_compiler_parse_precedence(uc_compiler_t *compiler, uc_precedence_t precedence) { uc_parse_rule_t *rule; rule = uc_compiler_parse_rule(compiler->parser->curr.type); if (!rule->prefix) { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Expecting expression"); uc_compiler_parse_advance(compiler); return; } uc_compiler_exprstack_push(compiler, compiler->parser->curr.type, (precedence <= P_ASSIGN) ? F_ASSIGNABLE : 0); /* allow reserved words as property names in object literals */ if (rule->prefix == uc_compiler_compile_object) compiler->parser->lex.no_keyword = true; /* unless a sub-expression follows, treat subsequent slash as division * operator and not as beginning of regexp literal */ if (rule->prefix != uc_compiler_compile_paren && rule->prefix != uc_compiler_compile_unary && rule->prefix != uc_compiler_compile_array) compiler->parser->lex.no_regexp = true; uc_compiler_parse_advance(compiler); rule->prefix(compiler); while (precedence <= uc_compiler_parse_rule(compiler->parser->curr.type)->precedence) { compiler->exprstack->token = compiler->parser->curr.type; rule = uc_compiler_parse_rule(compiler->exprstack->token); if (!rule->infix) { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Expecting ';' or binary operator"); uc_compiler_parse_advance(compiler); return; } /* allow reserved words in property accessors */ if (rule->infix == uc_compiler_compile_dot) compiler->parser->lex.no_keyword = true; uc_compiler_parse_advance(compiler); rule->infix(compiler); } if (uc_compiler_exprstack_is(compiler, F_ASSIGNABLE) && uc_compiler_parse_at_assignment_op(compiler)) uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Invalid left-hand side expression for assignment"); uc_compiler_exprstack_pop(compiler); } static size_t uc_compiler_reladdr(uc_compiler_t *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_t *compiler, size_t srcpos, uc_vm_insn_t insn) { uc_chunk_t *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_t *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_u16(uc_compiler_t *compiler, size_t srcpos, uint16_t n) { uc_chunk_t *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_u32(uc_compiler_t *compiler, size_t srcpos, uint32_t n) { uc_chunk_t *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_t *compiler, size_t srcpos, int32_t n) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t lineoff = uc_compiler_set_srcpos(compiler, srcpos); uint32_t v; if (n <= 0) v = n + 0x7fffffff; else v = (uint32_t)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_t *compiler, size_t off) { uc_chunk_t *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_t *compiler, size_t off, uint32_t n) { uc_chunk_t *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_index(uc_compiler_t *compiler, size_t srcpos, uc_value_t *val) { size_t cidx = uc_program_add_constant(compiler->program, val); uc_compiler_emit_u32(compiler, srcpos, cidx); return cidx; } static size_t uc_compiler_emit_constant(uc_compiler_t *compiler, size_t srcpos, uc_value_t *val) { size_t cidx; uc_compiler_emit_insn(compiler, srcpos, I_LOAD); cidx = uc_compiler_emit_constant_index(compiler, srcpos, val); return cidx; } static size_t uc_compiler_emit_regexp(uc_compiler_t *compiler, size_t srcpos, uc_value_t *val) { size_t cidx = uc_program_add_constant(compiler->program, 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_t *compiler, size_t srcpos) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_compiler_emit_insn(compiler, srcpos, I_JMP); uc_compiler_emit_u32(compiler, 0, 0); return chunk->count - 5; } static size_t uc_compiler_emit_jmpz(uc_compiler_t *compiler, size_t srcpos) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_compiler_emit_insn(compiler, srcpos, I_JMPZ); uc_compiler_emit_u32(compiler, 0, 0); return chunk->count - 5; } static size_t uc_compiler_emit_jmp_dest(uc_compiler_t *compiler, size_t srcpos, uint32_t dest) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_compiler_emit_insn(compiler, srcpos, I_JMP); uc_compiler_emit_u32(compiler, 0, uc_compiler_reladdr(compiler, chunk->count - 1, dest)); return chunk->count - 5; } static size_t uc_compiler_emit_copy(uc_compiler_t *compiler, size_t srcpos, uint8_t depth) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_compiler_emit_insn(compiler, srcpos, I_COPY); uc_compiler_emit_u8(compiler, 0, depth); return chunk->count - 2; } static ssize_t uc_compiler_get_jmpaddr(uc_compiler_t *compiler, size_t off) { uc_chunk_t *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] * 0x1000000UL + chunk->entries[off + 2] * 0x10000UL + chunk->entries[off + 3] * 0x100UL + chunk->entries[off + 4] ) - 0x7fffffff; } static void uc_compiler_set_jmpaddr(uc_compiler_t *compiler, size_t off, uint32_t dest) { uc_chunk_t *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 void uc_compiler_inc_exportnum(uc_compiler_t *compiler) { uc_source_t *root = uc_program_function_source(uc_program_entry(compiler->program)); uint64_t u; if (root->exports.count == 0) { uc_vector_push(&root->exports, ucv_uint64_new(1)); } else { u = ucv_uint64_get(root->exports.entries[0]); ucv_put(root->exports.entries[0]); root->exports.entries[0] = ucv_uint64_new(u + 1); } } static size_t uc_compiler_get_exportnum(uc_compiler_t *compiler) { uc_source_t *root = uc_program_function_source(uc_program_entry(compiler->program)); return root->exports.count ? ucv_uint64_get(root->exports.entries[0]) : 0; } static void uc_compiler_emit_exports(uc_compiler_t *compiler) { size_t i; if (!compiler->patchlist || compiler->patchlist->token != TK_EXPORT) return; for (i = 0; i < compiler->patchlist->count; i++) { uc_compiler_emit_insn(compiler, 0, I_EXPORT); uc_compiler_emit_u32(compiler, 0, compiler->patchlist->entries[i]); } } static uc_function_t * uc_compiler_finish(uc_compiler_t *compiler, uc_tokentype_t last_statement_type) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_locals_t *locals = &compiler->locals; uc_upvals_t *upvals = &compiler->upvals; size_t i; if (compiler->function->module) uc_compiler_emit_exports(compiler); 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); ucv_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); ucv_put(upvals->entries[i].name); } uc_vector_clear(locals); uc_vector_clear(upvals); if (compiler->parser->error) { uc_program_function_free(compiler->function); return NULL; } return compiler->function; } static void uc_compiler_enter_scope(uc_compiler_t *compiler) { compiler->scope_depth++; } static void uc_compiler_leave_scope(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_locals_t *locals = &compiler->locals; compiler->scope_depth--; while (locals->count > 0 && locals->entries[locals->count - 1].depth > (ssize_t)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); ucv_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 bool uc_compiler_is_strict(uc_compiler_t *compiler) { uc_function_t *fn = (uc_function_t *)compiler->function; return fn->strict; } static ssize_t uc_compiler_declare_local(uc_compiler_t *compiler, uc_value_t *name, bool constant) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_locals_t *locals = &compiler->locals; const char *str1, *str2; size_t i, len1, len2; if (locals->count >= 0x00FFFFFF) { uc_compiler_syntax_error(compiler, 0, "Too many local variables"); return -1; } str1 = ucv_string_get(name); len1 = ucv_string_length(name); for (i = locals->count; i > 0; i--) { if (locals->entries[i - 1].depth != -1 && locals->entries[i - 1].depth < (ssize_t)compiler->scope_depth) break; str2 = ucv_string_get(locals->entries[i - 1].name); len2 = ucv_string_length(locals->entries[i - 1].name); if (len1 == len2 && !strcmp(str1, str2)) { if (uc_compiler_is_strict(compiler)) { uc_compiler_syntax_error(compiler, 0, "Variable '%s' redeclared", str2); return -1; } return i - 1; } } uc_vector_grow(locals); locals->entries[locals->count].name = ucv_get(name); locals->entries[locals->count].depth = -1; locals->entries[locals->count].captured = false; locals->entries[locals->count].from = chunk->count; locals->entries[locals->count].constant = constant; locals->count++; return -1; } static ssize_t uc_compiler_initialize_local(uc_compiler_t *compiler) { uc_locals_t *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_t *compiler, uc_value_t *name, bool *constant) { uc_locals_t *locals = &compiler->locals; const char *str1, *str2; size_t i, len1, len2; str1 = ucv_string_get(name); len1 = ucv_string_length(name); for (i = locals->count; i > 0; i--) { str2 = ucv_string_get(locals->entries[i - 1].name); len2 = ucv_string_length(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; } *constant = locals->entries[i - 1].constant; return i - 1; } return -1; } static ssize_t uc_compiler_add_upval(uc_compiler_t *compiler, size_t idx, bool local, uc_value_t *name, bool constant) { uc_function_t *function = (uc_function_t *)compiler->function; uc_upvals_t *upvals = &compiler->upvals; uc_upval_t *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 = ucv_get(name); upvals->entries[upvals->count].constant = constant; function->nupvals++; return upvals->count++; } static ssize_t uc_compiler_resolve_upval(uc_compiler_t *compiler, uc_value_t *name, bool *constant) { uc_upvals_t *upvals = &compiler->upvals; uc_upval_t *uv; ssize_t idx; size_t i; if (!compiler->parent) { for (i = 0, uv = upvals->entries; i < upvals->count; i++, uv = upvals->entries + i) { if (ucv_is_equal(uv->name, name) && uv->local == false) { *constant = uv->constant; return i; } } return -1; } idx = uc_compiler_resolve_local(compiler->parent, name, constant); if (idx > -1) { compiler->parent->locals.entries[idx].captured = true; return uc_compiler_add_upval(compiler, idx, true, name, *constant); } idx = uc_compiler_resolve_upval(compiler->parent, name, constant); if (idx > -1) return uc_compiler_add_upval(compiler, idx, false, name, *constant); return -1; } static void uc_compiler_backpatch(uc_compiler_t *compiler, size_t break_addr, size_t next_addr) { uc_patchlist_t *pl = compiler->patchlist; 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; } /* there should be no unhandled instructions */ assert(0); } free(pl->entries); compiler->patchlist = pl->parent; } static void uc_compiler_emit_inc_dec(uc_compiler_t *compiler, uc_tokentype_t toktype, bool is_postfix) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_value_t *varname = NULL; uc_vm_insn_t type; uint32_t cidx = 0; int insn; /* determine kind of emitted load instruction and operand value (if any) */ type = chunk->entries ? chunk->entries[compiler->last_insn] : 0; if (type == I_LVAR || type == I_LLOC || type == I_LUPV) { cidx = uc_compiler_get_u32(compiler, compiler->last_insn + 1); if (type == I_LLOC && compiler->locals.entries[cidx].constant) varname = compiler->locals.entries[cidx].name; else if (type == I_LUPV && compiler->upvals.entries[cidx].constant) varname = compiler->upvals.entries[cidx].name; if (varname) uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Invalid increment/decrement of constant '%s'", ucv_string_get(varname)); 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_compiler_exprstack_is(compiler, F_OPTCHAINING)) { uc_chunk_pop(chunk); } else { uc_compiler_syntax_error(compiler, 0, "Invalid increment/decrement operand"); return; } insn = (toktype == TK_INC) ? I_PLUS : I_MINUS; /* add / substract 1 */ uc_compiler_emit_insn(compiler, 0, I_LOAD8); uc_compiler_emit_u8(compiler, 0, 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, (insn << 24) | cidx); break; case I_LLOC: uc_compiler_emit_insn(compiler, 0, I_ULOC); uc_compiler_emit_u32(compiler, 0, (insn << 24) | cidx); break; case I_LUPV: uc_compiler_emit_insn(compiler, 0, I_UUPV); uc_compiler_emit_u32(compiler, 0, (insn << 24) | cidx); break; case I_LVAL: uc_compiler_emit_insn(compiler, 0, I_UVAL); uc_compiler_emit_u8(compiler, 0, insn); 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_u8(compiler, 0, 1); uc_compiler_emit_insn(compiler, 0, (toktype == TK_INC) ? I_SUB : I_ADD); } } static void uc_compiler_compile_unary(uc_compiler_t *compiler) { 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_t *compiler) { uc_tokentype_t type = compiler->parser->prev.type; uc_compiler_parse_precedence(compiler, uc_compiler_parse_rule(type)->precedence + 1); uc_compiler_emit_insn(compiler, 0, I_BOR + (type - TK_BOR)); } static void uc_compiler_compile_delete(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_vm_insn_t type; uc_compiler_parse_precedence(compiler, P_UNARY); type = chunk->entries[compiler->last_insn]; if (type != I_LVAL) uc_compiler_syntax_error(compiler, 0, "expecting a property access expression"); chunk->entries[compiler->last_insn] = I_DELETE; } static uc_vm_insn_t uc_compiler_emit_variable_rw(uc_compiler_t *compiler, uc_value_t *varname, uc_tokentype_t type) { uc_vm_insn_t insn; uint32_t sub_insn; bool constant; 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; case TK_ASEXP: sub_insn = I_EXP; break; default: sub_insn = 0; break; } if (!varname) { if (sub_insn != 0) insn = I_UVAL; else if (type == TK_QDOT || type == TK_QLBRACK) insn = I_QLVAL; else if (type != 0) insn = I_SVAL; else insn = I_LVAL; if ((insn == I_UVAL || insn == I_SVAL) && uc_compiler_exprstack_is(compiler, F_OPTCHAINING)) uc_compiler_syntax_error(compiler, 0, "Invalid left-hand side expression for assignment"); 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, &constant)) > -1) { insn = sub_insn ? I_ULOC : (type ? I_SLOC : I_LLOC); if (insn != I_LLOC && constant) uc_compiler_syntax_error(compiler, 0, "Invalid assignment to constant '%s'", ucv_string_get(varname)); 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, &constant)) > -1) { insn = sub_insn ? I_UUPV : (type ? I_SUPV : I_LUPV); if (insn != I_LUPV && constant) uc_compiler_syntax_error(compiler, 0, "Invalid assignment to constant '%s'", ucv_string_get(varname)); 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_program_add_constant(compiler->program, 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_emit_variable_copy(uc_compiler_t *compiler, uc_value_t *var) { if (!var) { uc_compiler_emit_copy(compiler, 0, 1); uc_compiler_emit_copy(compiler, 0, 1); } uc_compiler_emit_variable_rw(compiler, var, 0); } static void uc_compiler_compile_and(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t jmpz_off; uc_compiler_emit_copy(compiler, 0, 0); jmpz_off = uc_compiler_emit_jmpz(compiler, 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_and_assignment(uc_compiler_t *compiler, uc_value_t *var) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t jmpz_off; uc_compiler_emit_variable_copy(compiler, var); uc_compiler_emit_copy(compiler, 0, 0); jmpz_off = uc_compiler_emit_jmpz(compiler, 0); uc_compiler_emit_insn(compiler, 0, I_POP); uc_compiler_parse_precedence(compiler, P_ASSIGN); uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN); } static void uc_compiler_compile_or(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t jmpz_off, jmp_off; uc_compiler_emit_copy(compiler, 0, 0); jmpz_off = uc_compiler_emit_jmpz(compiler, 0); jmp_off = uc_compiler_emit_jmp(compiler, 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_or_assignment(uc_compiler_t *compiler, uc_value_t *var) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t jmpz_off, jmp_off; uc_compiler_emit_variable_copy(compiler, var); jmpz_off = uc_compiler_emit_jmpz(compiler, 0); uc_compiler_emit_variable_rw(compiler, var, 0); jmp_off = uc_compiler_emit_jmp(compiler, 0); uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); uc_compiler_parse_precedence(compiler, P_ASSIGN); uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN); uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count); } static void uc_compiler_compile_nullish(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t jmpz_off, jmp_off; uc_compiler_emit_copy(compiler, 0, 0); uc_compiler_emit_insn(compiler, 0, I_LNULL); uc_compiler_emit_insn(compiler, 0, I_NES); jmpz_off = uc_compiler_emit_jmpz(compiler, 0); jmp_off = uc_compiler_emit_jmp(compiler, 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_nullish_assignment(uc_compiler_t *compiler, uc_value_t *var) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t jmpz_off, jmp_off; uc_compiler_emit_variable_copy(compiler, var); uc_compiler_emit_insn(compiler, 0, I_LNULL); uc_compiler_emit_insn(compiler, 0, I_NES); jmpz_off = uc_compiler_emit_jmpz(compiler, 0); uc_compiler_emit_variable_rw(compiler, var, 0); jmp_off = uc_compiler_emit_jmp(compiler, 0); uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); uc_compiler_parse_precedence(compiler, P_ASSIGN); uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN); uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count); } static void uc_compiler_compile_expression(uc_compiler_t *compiler) { uc_compiler_parse_precedence(compiler, P_COMMA); } static bool uc_compiler_compile_assignment(uc_compiler_t *compiler, uc_value_t *var) { uc_tokentype_t type = compiler->parser->curr.type; if (type == TK_ASNULLISH) { uc_compiler_parse_advance(compiler); uc_compiler_compile_nullish_assignment(compiler, var); return true; } else if (type == TK_ASOR) { uc_compiler_parse_advance(compiler); uc_compiler_compile_or_assignment(compiler, var); return true; } else if (type == TK_ASAND) { uc_compiler_parse_advance(compiler); uc_compiler_compile_and_assignment(compiler, var); return true; } else 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_t *compiler, uc_value_t *args, bool restarg) { uc_tokentype_t last_statement_type = TK_NULL; bool array = (ucv_type(args) == UC_ARRAY); uc_compiler_t fncompiler = { 0 }; size_t i, pos, load_off; uc_function_t *fn; ssize_t slot; if (!uc_compiler_parse_match(compiler, TK_ARROW)) return false; pos = compiler->parser->prev.pos; uc_compiler_init(&fncompiler, NULL, uc_compiler_current_source(compiler), compiler->parser->prev.pos, compiler->program, uc_compiler_is_strict(compiler)); fncompiler.parent = compiler; fncompiler.parser = compiler->parser; fncompiler.exprstack = compiler->exprstack; fn = (uc_function_t *)fncompiler.function; fn->arrow = true; fn->vararg = args ? restarg : false; fn->nargs = array ? ucv_array_length(args) : !!args; uc_compiler_enter_scope(&fncompiler); /* declare local variables for arguments */ for (i = 0; i < fn->nargs; i++) { slot = uc_compiler_declare_local(&fncompiler, array ? ucv_array_get(args, i) : args, false); 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); /* emit final return */ if (last_statement_type != TK_RETURN) { uc_compiler_emit_insn(&fncompiler, 0, I_LNULL); uc_compiler_emit_insn(&fncompiler, 0, I_RETURN); } } else { uc_compiler_parse_precedence(&fncompiler, P_ASSIGN); 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 < fn->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, TK_RETURN); if (fn) uc_compiler_set_u32(compiler, load_off, uc_program_function_id(compiler->program, fn)); return true; } static uc_tokentype_t uc_compiler_compile_var_or_arrowfn(uc_compiler_t *compiler, uc_value_t *name) { uc_tokentype_t rv; if (uc_compiler_exprstack_is(compiler, F_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_t *compiler) { uc_value_t *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_check(compiler, TK_LABEL)) { if (!varnames) varnames = ucv_array_new(NULL); ucv_array_push(varnames, ucv_get(compiler->parser->curr.uv)); /* A subsequent slash cannot be a regular expression literal */ compiler->parser->lex.no_regexp = true; uc_compiler_parse_advance(compiler); } else if (uc_compiler_parse_match(compiler, TK_ELLIP)) { uc_compiler_parse_consume(compiler, TK_LABEL); if (!varnames) varnames = ucv_array_new(NULL); ucv_array_push(varnames, ucv_get(compiler->parser->prev.uv)); /* A subsequent slash cannot be a regular expression literal */ compiler->parser->lex.no_regexp = true; 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_check(compiler, TK_RPAREN); if (maybe_arrowfn) { /* A subsequent slash cannot be a regular expression literal */ compiler->parser->lex.no_regexp = true; uc_compiler_parse_advance(compiler); } /* If we encouter a dot, treat potential subsequent keyword as label */ if (uc_compiler_parse_check(compiler, TK_DOT) || uc_compiler_parse_check(compiler, TK_QDOT)) compiler->parser->lex.no_keyword = true; 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 = ucv_array_get(varnames, ucv_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) { uc_compiler_exprstack_push(compiler, TK_LABEL, F_ASSIGNABLE); if (uc_compiler_compile_var_or_arrowfn(compiler, 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); } } /* 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); } uc_compiler_exprstack_pop(compiler); } } /* 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); /* A subsequent slash cannot be a regular expression literal */ compiler->parser->lex.no_regexp = true; /* 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: ucv_put(varnames); } static void uc_compiler_compile_call(uc_compiler_t *compiler) { bool optional_chaining = (compiler->parser->prev.type == TK_QLPAREN); uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_jmplist_t spreads = { 0 }; uc_vm_insn_t type; size_t i, nargs = 0; /* flag optional chaining usage in current expression */ compiler->exprstack->flags |= optional_chaining ? F_OPTCHAINING : 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 || type == I_QLVAL) 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)); } /* after a function call expression, no regexp literal can follow */ compiler->parser->lex.no_regexp = true; uc_compiler_parse_consume(compiler, TK_RPAREN); /* if lhs is a dot or bracket expression, emit a method call */ if (type == I_LVAL || type == I_QLVAL) uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, optional_chaining ? I_QMCALL : I_MCALL); /* else ordinary call */ else uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, optional_chaining ? I_QCALL : 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_t *compiler) { uc_compiler_emit_inc_dec(compiler, compiler->parser->prev.type, true); } static bool uc_compiler_is_use_strict_pragma(uc_compiler_t *compiler) { uc_value_t *v; if (uc_compiler_current_chunk(compiler)->count > 0) return false; if (compiler->parser->lex.block != STATEMENTS) return false; v = compiler->parser->prev.uv; return (strcmp(ucv_string_get(v), "use strict") == 0); } static void uc_compiler_compile_constant(uc_compiler_t *compiler) { uc_function_t *fn; uint64_t u; 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_TRUE: uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LTRUE); break; case TK_FALSE: uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LFALSE); break; case TK_STRING: if (uc_compiler_is_use_strict_pragma(compiler)) { fn = (uc_function_t *)compiler->function; fn->strict = true; } /* fall through */ case TK_DOUBLE: uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.uv); break; case TK_REGEXP: uc_compiler_emit_regexp(compiler, compiler->parser->prev.pos, compiler->parser->prev.uv); break; case TK_NUMBER: u = ucv_uint64_get(compiler->parser->prev.uv); assert(errno == 0); if (u <= 0xff) { uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LOAD8); uc_compiler_emit_u8(compiler, compiler->parser->prev.pos, u); } else if (u <= 0xffff) { uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LOAD16); uc_compiler_emit_u16(compiler, compiler->parser->prev.pos, u); } else if (u <= 0xffffffff) { uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LOAD32); uc_compiler_emit_u32(compiler, compiler->parser->prev.pos, u); } else { uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.uv); } break; default: break; } } static void uc_compiler_compile_template(uc_compiler_t *compiler) { uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.uv); while (true) { if (uc_compiler_parse_match(compiler, TK_TEMPLATE)) { uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.uv); uc_compiler_emit_insn(compiler, 0, I_ADD); } else if (uc_compiler_parse_match(compiler, TK_PLACEH)) { uc_compiler_compile_expression(compiler); uc_compiler_emit_insn(compiler, 0, I_ADD); uc_compiler_parse_consume(compiler, TK_RBRACE); } else { break; } } } static void uc_compiler_compile_comma(uc_compiler_t *compiler) { uc_compiler_emit_insn(compiler, 0, I_POP); uc_compiler_parse_precedence(compiler, P_ASSIGN); } static void uc_compiler_compile_labelexpr(uc_compiler_t *compiler) { uc_value_t *label = ucv_get(compiler->parser->prev.uv); uc_compiler_compile_var_or_arrowfn(compiler, label); ucv_put(label); } static uc_tokentype_t uc_compiler_compile_delimitted_block(uc_compiler_t *compiler, uc_tokentype_t endtype) { uc_tokentype_t last_statement_type = TK_NULL; while (!uc_compiler_parse_check(compiler, endtype) && !uc_compiler_parse_check(compiler, TK_EOF)) last_statement_type = uc_compiler_compile_declaration(compiler); return uc_compiler_parse_check(compiler, endtype) ? last_statement_type : TK_EOF; } static void uc_compiler_compile_funcexpr_common(uc_compiler_t *compiler, bool require_name) { uc_tokentype_t last_statement_type = TK_NULL; uc_compiler_t fncompiler = { 0 }; uc_value_t *name = NULL; ssize_t slot = -1, pos; uc_tokentype_t type; size_t i, load_off; uc_function_t *fn; pos = compiler->parser->prev.pos; type = compiler->parser->prev.type; if (uc_compiler_parse_match(compiler, TK_LABEL)) { name = compiler->parser->prev.uv; /* 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, false); if (slot == -1) uc_compiler_initialize_local(compiler); } else if (require_name) { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Expecting function name"); } uc_compiler_init(&fncompiler, name ? ucv_string_get(name) : NULL, uc_compiler_current_source(compiler), compiler->parser->prev.pos, compiler->program, uc_compiler_is_strict(compiler)); fncompiler.parent = compiler; fncompiler.parser = compiler->parser; fncompiler.exprstack = compiler->exprstack; fn = (uc_function_t *)fncompiler.function; 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)) fn->vararg = true; if (uc_compiler_parse_match(&fncompiler, TK_LABEL)) { fn->nargs++; uc_compiler_declare_local(&fncompiler, fncompiler.parser->prev.uv, false); uc_compiler_initialize_local(&fncompiler); if (fn->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)) { last_statement_type = uc_compiler_compile_delimitted_block(&fncompiler, TK_ENDFUNC); uc_compiler_parse_consume(&fncompiler, TK_ENDFUNC); } else if (uc_compiler_parse_match(&fncompiler, TK_LBRACE)) { last_statement_type = 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 < fn->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, last_statement_type); if (fn) uc_compiler_set_u32(compiler, load_off, uc_program_function_id(compiler->program, fn)); /* 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_funcexpr(uc_compiler_t *compiler) { return uc_compiler_compile_funcexpr_common(compiler, false); } static void uc_compiler_compile_funcdecl(uc_compiler_t *compiler) { return uc_compiler_compile_funcexpr_common(compiler, true); } static void uc_compiler_compile_dot(uc_compiler_t *compiler) { bool optional_chaining = (compiler->parser->prev.type == TK_QDOT); /* flag optional chaining usage in current expression */ compiler->exprstack->flags |= optional_chaining ? F_OPTCHAINING : 0; /* no regexp literal possible after property access */ compiler->parser->lex.no_regexp = true; /* parse label lhs */ uc_compiler_parse_consume(compiler, TK_LABEL); uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.uv); /* depending on context, compile into I_UVAL, I_SVAL or I_LVAL operation */ if (!uc_compiler_exprstack_is(compiler, F_ASSIGNABLE) || !uc_compiler_compile_assignment(compiler, NULL)) uc_compiler_emit_variable_rw(compiler, NULL, optional_chaining ? TK_QDOT : 0); } static void uc_compiler_compile_subscript(uc_compiler_t *compiler) { bool optional_chaining = (compiler->parser->prev.type == TK_QLBRACK); /* flag optional chaining usage in current expression */ compiler->exprstack->flags |= optional_chaining ? F_OPTCHAINING : 0; /* compile lhs */ uc_compiler_compile_expression(compiler); /* no regexp literal possible after computed property access */ compiler->parser->lex.no_regexp = true; uc_compiler_parse_consume(compiler, TK_RBRACK); /* depending on context, compile into I_UVAL, I_SVAL or I_LVAL operation */ if (!uc_compiler_exprstack_is(compiler, F_ASSIGNABLE) || !uc_compiler_compile_assignment(compiler, NULL)) uc_compiler_emit_variable_rw(compiler, NULL, optional_chaining ? TK_QLBRACK : 0); } static void uc_compiler_compile_ternary(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t jmpz_off, jmp_off; /* jump to false branch */ jmpz_off = uc_compiler_emit_jmpz(compiler, 0); /* compile true branch */ uc_compiler_parse_precedence(compiler, P_ASSIGN); /* jump after false branch */ jmp_off = uc_compiler_emit_jmp(compiler, 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_t *compiler) { 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)); /* no regexp literal possible after array literal */ compiler->parser->lex.no_regexp = true; 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_t *compiler) { 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.uv); /* 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))) { /* disallow keywords in this case */ if (uc_lexer_is_keyword(compiler->parser->prev.uv)) uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Invalid identifier"); uc_compiler_emit_variable_rw(compiler, compiler->parser->prev.uv, 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; compiler->parser->lex.no_keyword = true; } while (uc_compiler_parse_match(compiler, TK_COMMA)); /* no regexp literal possible after object literal */ compiler->parser->lex.no_regexp = true; 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); } /* set initial size hint */ uc_compiler_set_u32(compiler, hint_off, hint_count); } static void uc_compiler_declare_local_null(uc_compiler_t *compiler, size_t srcpos, uc_value_t *varname) { ssize_t existing_slot = uc_compiler_declare_local(compiler, varname, false); 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_t *compiler, size_t srcpos, const char *name) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_locals_t *locals = &compiler->locals; uc_vector_grow(locals); locals->entries[locals->count].name = ucv_string_new(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++; } static void uc_compiler_compile_declexpr(uc_compiler_t *compiler, bool constant) { ssize_t slot; do { /* parse variable name */ if (!uc_compiler_parse_match(compiler, TK_LABEL)) { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Expecting variable name"); return; } /* declare local variable */ slot = uc_compiler_declare_local(compiler, compiler->parser->prev.uv, constant); /* if followed by '=', parse initializer expression */ if (uc_compiler_parse_match(compiler, TK_ASSIGN)) uc_compiler_parse_precedence(compiler, P_ASSIGN); /* otherwise, for writable variables, load implicit null */ else if (!constant) uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LNULL); /* for constant variables, a missing initializer is a syntax error */ else uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Expecting initializer expression"); /* 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)); } static void uc_compiler_compile_local(uc_compiler_t *compiler) { uc_compiler_compile_declexpr(compiler, false); uc_compiler_parse_consume(compiler, TK_SCOL); } static void uc_compiler_compile_const(uc_compiler_t *compiler) { uc_compiler_compile_declexpr(compiler, true); uc_compiler_parse_consume(compiler, TK_SCOL); } static uc_tokentype_t uc_compiler_compile_altifblock(uc_compiler_t *compiler) { uc_compiler_enter_scope(compiler); while (true) { switch (compiler->parser->curr.type) { case TK_ELIF: case TK_ELSE: case TK_ENDIF: case TK_EOF: uc_compiler_leave_scope(compiler); return compiler->parser->curr.type; default: uc_compiler_compile_declaration(compiler); break; } } return 0; } static void uc_compiler_compile_if(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t jmpz_off, jmp_off, i; bool expect_endif = false; uc_jmplist_t elifs = { 0 }; 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); if (uc_compiler_parse_match(compiler, TK_COLON)) { compiler->exprstack->flags |= F_ALTBLOCKMODE; 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); /* 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); } 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); /* 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); /* 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_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_patchlist_t p = { .depth = compiler->scope_depth, .token = TK_WHILE }; size_t cond_off, jmpz_off, end_off; 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); /* compile loop body */ if (uc_compiler_parse_match(compiler, TK_COLON)) { uc_compiler_enter_scope(compiler); if (uc_compiler_compile_delimitted_block(compiler, TK_ENDWHILE) == TK_EOF) uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Expecting 'endwhile'"); else uc_compiler_parse_advance(compiler); uc_compiler_leave_scope(compiler); } else { uc_compiler_compile_statement(compiler); } end_off = chunk->count; /* jump back to condition */ uc_compiler_emit_jmp_dest(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_t *compiler, bool local, uc_token_t *kvar, uc_token_t *vvar) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_patchlist_t p = { .depth = compiler->scope_depth + 1, .token = TK_FOR }; size_t skip_jmp, test_jmp, key_slot, val_slot; 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->uv); if (vvar) uc_compiler_declare_local_null(compiler, vvar->pos, vvar->uv); } /* 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); /* 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); /* set key and value variables */ if (vvar) { uc_compiler_emit_variable_rw(compiler, vvar->uv, TK_ASSIGN); uc_compiler_emit_insn(compiler, 0, I_POP); } /* set key variable */ uc_compiler_emit_variable_rw(compiler, kvar->uv, TK_ASSIGN); uc_compiler_emit_insn(compiler, 0, I_POP); /* compile loop body */ if (uc_compiler_parse_match(compiler, TK_COLON)) { uc_compiler_enter_scope(compiler); if (uc_compiler_compile_delimitted_block(compiler, TK_ENDFOR) == TK_EOF) uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Expecting 'endfor'"); else uc_compiler_parse_advance(compiler); uc_compiler_leave_scope(compiler); } else { uc_compiler_compile_statement(compiler); } /* jump back to retrieve next key */ uc_compiler_emit_jmp_dest(compiler, 0, skip_jmp + 5); /* back patch conditional jump */ uc_compiler_set_jmpaddr(compiler, test_jmp, chunk->count); /* pop loop variables */ uc_compiler_emit_insn(compiler, 0, I_POP); if (vvar) uc_compiler_emit_insn(compiler, 0, I_POP); /* patch up break/continue */ uc_compiler_backpatch(compiler, chunk->count, skip_jmp + 5); uc_compiler_leave_scope(compiler); } static void uc_compiler_compile_for_count(uc_compiler_t *compiler, bool local, uc_token_t *var) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t test_off = 0, incr_off, skip_off, cond_off = 0; uc_patchlist_t p = { .depth = compiler->scope_depth + 1, .token = TK_FOR }; p.parent = compiler->patchlist; compiler->patchlist = &p; uc_compiler_enter_scope(compiler); /* Initializer ---------------------------------------------------------- */ /* If we parsed at least one label, try continue parsing as variable * expression... */ if (var) { /* 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->uv); uc_compiler_exprstack_push(compiler, TK_FOR, F_ASSIGNABLE); uc_compiler_compile_labelexpr(compiler); uc_compiler_emit_insn(compiler, 0, I_POP); /* If followed by a comma, continue parsing expression */ if (uc_compiler_parse_match(compiler, TK_COMMA)) { /* Is a continuation of a declaration list... */ if (local) { uc_compiler_compile_declexpr(compiler, false); } /* ... otherwise an unrelated expression */ else { uc_compiler_compile_expression(compiler); uc_compiler_emit_insn(compiler, 0, I_POP); } } uc_compiler_exprstack_pop(compiler); } /* ... 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); } uc_compiler_parse_consume(compiler, TK_SCOL); /* jump over incrementer */ skip_off = uc_compiler_emit_jmp(compiler, 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_dest(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)) { uc_compiler_enter_scope(compiler); if (uc_compiler_compile_delimitted_block(compiler, TK_ENDFOR) == TK_EOF) uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Expecting 'endfor'"); else uc_compiler_parse_advance(compiler); uc_compiler_leave_scope(compiler); } else { uc_compiler_compile_statement(compiler); } /* jump back to incrementer */ uc_compiler_emit_jmp_dest(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_t *compiler) { uc_token_t keyvar = { 0 }, valvar = { 0 }; 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 (uc_compiler_parse_match(compiler, TK_LABEL)) { keyvar = compiler->parser->prev; ucv_get(keyvar.uv); if (uc_compiler_parse_match(compiler, TK_COMMA)) { uc_compiler_parse_consume(compiler, TK_LABEL); valvar = compiler->parser->prev; ucv_get(valvar.uv); } /* 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; } } else if (local) { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Expecting label after 'local'"); 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.uv ? &valvar : (keyvar.uv ? &keyvar : NULL)); out: ucv_put(keyvar.uv); ucv_put(valvar.uv); } static void uc_compiler_compile_switch(uc_compiler_t *compiler) { size_t i, test_jmp, skip_jmp, next_jmp = 0, value_slot, default_off = 0; uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_patchlist_t p = { .depth = compiler->scope_depth, .token = TK_SWITCH }; uc_locals_t *locals = &compiler->locals; uc_jmplist_t cases = { 0 }; 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); value_slot = uc_compiler_declare_internal(compiler, 0, "(switch value)"); /* jump to branch tests */ test_jmp = uc_compiler_emit_jmp(compiler, 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_off) { uc_vector_clear(&cases); uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "more than one switch default case"); break; } uc_compiler_parse_consume(compiler, TK_COLON); /* remember address of default branch */ default_off = chunk->count; /* Store three values in case offset list: * 1) amount of local variables declared so far * 2) beginning of condition expression * 3) end of condition expression * For the `default` case, beginning and end offsets of the * condition expression are equal. */ uc_vector_grow(&cases); cases.entries[cases.count++] = (locals->count - 1) - value_slot; uc_vector_grow(&cases); cases.entries[cases.count++] = chunk->count; uc_vector_grow(&cases); cases.entries[cases.count++] = chunk->count; } /* handle `case …:` */ else if (uc_compiler_parse_match(compiler, TK_CASE)) { /* jump over `case …:` label expression */ skip_jmp = uc_compiler_emit_jmp(compiler, 0); /* compile case value expression */ uc_compiler_compile_expression(compiler); uc_compiler_parse_consume(compiler, TK_COLON); /* Store three values in case offset list: * 1) amount of local variables declared so far * 2) beginning of condition expression * 3) end of condition expression */ uc_vector_grow(&cases); cases.entries[cases.count++] = (locals->count - 1) - value_slot; uc_vector_grow(&cases); cases.entries[cases.count++] = skip_jmp + 5; uc_vector_grow(&cases); cases.entries[cases.count++] = uc_compiler_emit_jmp(compiler, 0); /* patch jump skipping over the case value */ uc_compiler_set_jmpaddr(compiler, skip_jmp, chunk->count); } /* handle interleaved statement */ else if (cases.count) { 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'"); break; } } uc_compiler_parse_consume(compiler, TK_RBRACE); /* evaluate case matches */ if (cases.count) { skip_jmp = uc_compiler_emit_jmp(compiler, 0); uc_compiler_set_jmpaddr(compiler, test_jmp, chunk->count); for (i = 0, default_off = cases.count; i < cases.count; i += 3) { /* remember and skip default case */ if (cases.entries[i + 1] == cases.entries[i + 2]) { default_off = i; continue; } /* read switch match value */ uc_compiler_emit_insn(compiler, 0, I_LLOC); uc_compiler_emit_u32(compiler, 0, value_slot); /* jump to case value expression code */ uc_compiler_emit_jmp_dest(compiler, 0, cases.entries[i + 1]); /* patch final case value expression jump back here */ uc_compiler_set_jmpaddr(compiler, cases.entries[i + 2], chunk->count); /* strict equal test */ uc_compiler_emit_insn(compiler, 0, I_EQS); /* conditional jump to next match */ next_jmp = uc_compiler_emit_jmpz(compiler, 0); /* fill local slots */ while (cases.entries[i + 0] > 0) { uc_compiler_emit_insn(compiler, 0, I_LNULL); cases.entries[i + 0]--; } /* jump to target code */ uc_compiler_emit_jmp_dest(compiler, 0, cases.entries[i + 2] + 5); /* patch next jump */ uc_compiler_set_jmpaddr(compiler, next_jmp, chunk->count); } /* handle default case (if any) */ if (default_off < cases.count) { /* fill local slots */ while (cases.entries[default_off + 0] > 0) { uc_compiler_emit_insn(compiler, 0, I_LNULL); cases.entries[default_off + 0]--; } /* jump to target */ uc_compiler_emit_jmp_dest(compiler, 0, cases.entries[default_off + 2]); /* do not patch final match failure jump later, we handle it here * in the default case */ next_jmp = 0; } uc_compiler_set_jmpaddr(compiler, skip_jmp, chunk->count); } else { uc_compiler_set_jmpaddr(compiler, test_jmp, test_jmp + 5); } uc_vector_clear(&cases); uc_compiler_leave_scope(compiler); /* if no default case exists, patch last case match failure jump */ if (next_jmp) { /* There's pop instructions for all local variables including the * switch test value itself on the stack. Jump onto the last POP * instruction (-1) to get rid of the on-stack switch test value * but skip the POP instructions for all other scoped local variables * which never have been initialized. */ uc_compiler_set_jmpaddr(compiler, next_jmp, chunk->count - 1); } uc_compiler_backpatch(compiler, chunk->count, 0); } static void uc_compiler_compile_try(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t try_from = 0, try_to = 0, jmp_off = 0, ehvar_slot = 0; uc_ehranges_t *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); uc_compiler_parse_consume(compiler, TK_RBRACE); uc_compiler_leave_scope(compiler); /* jump beyond catch branch */ try_to = chunk->count; jmp_off = uc_compiler_emit_jmp(compiler, 0); /* 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.uv, false); 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_leave_scope(compiler); uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count); } static void uc_compiler_compile_control(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); uc_tokentype_t type = compiler->parser->prev.type; uc_patchlist_t *p = compiler->patchlist; uc_locals_t *locals = &compiler->locals; size_t i, pos = compiler->parser->prev.pos; /* select applicable patchlist: for continue statements select the * first non-switch scope */ while (p) { if (type != TK_CONTINUE || p->token != TK_SWITCH) break; p = p->parent; } if (!p || p->token == TK_EXPORT) { 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 all scopes covered by the target patchlist */ for (i = locals->count; i > 0 && (size_t)locals->entries[i - 1].depth > p->depth; i--) uc_compiler_emit_insn(compiler, 0, locals->entries[i - 1].captured ? I_CUPV : I_POP); uc_vector_grow(p); p->entries[p->count++] = uc_compiler_emit_jmp_dest(compiler, pos, chunk->count + type); uc_compiler_parse_consume(compiler, TK_SCOL); } static void uc_compiler_compile_return(uc_compiler_t *compiler) { uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); if (compiler->function->module) { uc_compiler_syntax_error(compiler, 0, "return must be inside function body"); return; } /* if we compiled an empty expression statement (`;`), load implicit null */ if (uc_compiler_compile_expstmt(compiler) == TK_NULL) 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_t *compiler) { uc_chunk_t *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_t *compiler) { uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.uv); uc_compiler_emit_insn(compiler, 0, I_PRINT); } static uc_tokentype_t uc_compiler_compile_block(uc_compiler_t *compiler) { uc_tokentype_t last_statement_type = TK_NULL; uc_compiler_enter_scope(compiler); while (!uc_compiler_parse_check(compiler, TK_RBRACE) && !uc_compiler_parse_check(compiler, TK_EOF)) last_statement_type = uc_compiler_compile_declaration(compiler); uc_compiler_parse_consume(compiler, TK_RBRACE); uc_compiler_leave_scope(compiler); return last_statement_type; } static uc_tokentype_t uc_compiler_compile_expstmt(uc_compiler_t *compiler) { /* empty statement */ if (uc_compiler_parse_match(compiler, TK_SCOL)) return TK_NULL; uc_compiler_compile_expression(compiler); /* allow omitting final semicolon */ switch (compiler->parser->curr.type) { case TK_RBRACE: case TK_ELIF: case TK_ENDIF: case TK_ENDFOR: case TK_ENDWHILE: case TK_ENDFUNC: case TK_EOF: break; case TK_ELSE: if (!uc_compiler_exprstack_is(compiler, F_ALTBLOCKMODE)) uc_compiler_parse_consume(compiler, TK_SCOL); break; default: uc_compiler_parse_consume(compiler, TK_SCOL); break; } uc_compiler_emit_insn(compiler, 0, I_POP); return TK_SCOL; } static uc_tokentype_t uc_compiler_compile_statement(uc_compiler_t *compiler) { uc_tokentype_t last_statement_type = compiler->parser->curr.type; uc_exprstack_t expr = { .token = compiler->parser->curr.type, .parent = compiler->exprstack }; compiler->exprstack = &expr; 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_funcdecl(compiler); 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)) last_statement_type = uc_compiler_compile_block(compiler); else last_statement_type = uc_compiler_compile_expstmt(compiler); compiler->exprstack = expr.parent; return last_statement_type; } static void uc_compiler_export_add(uc_compiler_t *compiler, uc_value_t *name, ssize_t slot) { uc_source_t *source = uc_compiler_current_source(compiler); if (!uc_source_export_add(source, name)) { if (name) uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Duplicate export '%s' for module '%s'", ucv_string_get(name), source->filename); else uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Duplicate default export for module '%s'", source->filename); } else { uc_vector_push(compiler->patchlist, slot); uc_compiler_inc_exportnum(compiler); } } static void uc_compiler_compile_exportlist(uc_compiler_t *compiler) { uc_value_t *label, *name; bool constant; ssize_t slot; /* parse export symbols */ do { uc_compiler_parse_consume(compiler, TK_LABEL); label = ucv_get(compiler->parser->prev.uv); name = NULL; slot = uc_compiler_resolve_local(compiler, label, &constant); if (slot == -1) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Attempt to export undeclared or non-local variable '%s'", ucv_string_get(label)); } if (uc_compiler_keyword_match(compiler, "as")) { if (uc_compiler_parse_match(compiler, TK_LABEL) || uc_compiler_parse_match(compiler, TK_STRING)) { name = ucv_get(compiler->parser->prev.uv); } else if (!uc_compiler_parse_match(compiler, TK_DEFAULT)) { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unexpected token\nExpecting Label, String or 'default'"); } } else { name = ucv_get(label); } uc_compiler_export_add(compiler, name, slot); ucv_put(label); ucv_put(name); if (uc_compiler_parse_match(compiler, TK_RBRACE)) break; } while (uc_compiler_parse_match(compiler, TK_COMMA)); uc_compiler_parse_consume(compiler, TK_SCOL); } static void uc_compiler_compile_export(uc_compiler_t *compiler) { uc_locals_t *locals = &compiler->locals; size_t off = locals->count; uc_value_t *name; ssize_t slot; if (!compiler->function->module || compiler->scope_depth) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Exports may only appear at top level of a module"); return; } if (uc_compiler_parse_match(compiler, TK_LBRACE)) { uc_compiler_compile_exportlist(compiler); return; } if (uc_compiler_parse_match(compiler, TK_LOCAL)) uc_compiler_compile_declexpr(compiler, false); else if (uc_compiler_parse_match(compiler, TK_CONST)) uc_compiler_compile_declexpr(compiler, true); else if (uc_compiler_parse_match(compiler, TK_FUNC)) uc_compiler_compile_funcdecl(compiler); else if (uc_compiler_parse_match(compiler, TK_DEFAULT)) uc_compiler_compile_expression(compiler); else uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unexpected token\nExpecting 'let', 'const', 'function', 'default' or '{'"); if (off == locals->count) { name = ucv_string_new("(module default export)"); slot = uc_compiler_declare_local(compiler, name, true); ucv_put(name); if (slot != -1) uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Duplicate default export statement"); else uc_compiler_export_add(compiler, NULL, compiler->locals.count - 1); } else { for (; off < locals->count; off++) uc_compiler_export_add(compiler, locals->entries[off].name, off); } uc_compiler_parse_consume(compiler, TK_SCOL); } static uc_program_t * uc_compile_from_source(uc_parse_config_t *config, uc_source_t *source, uc_program_t *prog, char **errp); static bool uc_compiler_compile_module_source(uc_compiler_t *compiler, uc_source_t *source, uc_value_t *imports, char **errp) { uc_parse_config_t config = { .raw_mode = true, .strict_declarations = true, .module_search_path = compiler->parser->lex.config->module_search_path }; size_t i, load_idx = 0, n_imports = 0; bool loaded = false; uc_value_t *import; ssize_t slot; uc_program_function_foreach(compiler->program, fn) { if (uc_program_function_source(fn) == source) { if (source->exports.offset == (size_t)-1) uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Circular dependency"); loaded = true; break; } } if (!loaded) { load_idx = uc_program_function_id(compiler->program, uc_program_function_last(compiler->program)) + 1; source->exports.offset = (size_t)-1; if (!uc_compile_from_source(&config, source, compiler->program, errp)) return false; source->exports.offset = uc_compiler_get_exportnum(compiler) - source->exports.count; uc_compiler_current_source(compiler)->exports.offset += source->exports.count; /* emit load, call & pop instructions */ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_CLFN); uc_compiler_emit_u32(compiler, 0, load_idx); uc_compiler_emit_insn(compiler, 0, I_CALL); uc_compiler_emit_u32(compiler, 0, 0); uc_compiler_emit_insn(compiler, 0, I_POP); } /* count imports, handle wildcard imports */ for (i = 0; i < ucv_array_length(imports); i++) { if (ucv_boolean_get(ucv_array_get(imports, i))) { if (source->exports.offset > 0xffff || source->exports.count > 0xffff) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Too many module exports"); } /* emit import instruction... */ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_IMPORT); uc_compiler_emit_u32(compiler, 0, source->exports.count | (0xffff << 16)); /* ... followed by first module export offset ... */ uc_compiler_emit_u16(compiler, 0, source->exports.offset); /* ... and constant indexes for all exported names */ for (load_idx = 0; load_idx < source->exports.count; load_idx++) { if (source->exports.entries[load_idx]) import = ucv_get(source->exports.entries[load_idx]); else import = ucv_string_new("default"); uc_compiler_emit_constant_index(compiler, 0, import); ucv_put(import); } } else { n_imports++; } } /* 0xffff is reserved for wildcard import */ if (n_imports > 0xfffe) uc_compiler_syntax_error(compiler, 0, "Too many imports"); /* emit non-wilcard import instructions */ for (i = 0; i < ucv_array_length(imports); i++) { import = ucv_array_get(imports, i); if (!ucv_boolean_get(import)) { slot = uc_source_export_lookup(source, import); if (slot == -1) { if (import) uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Module %s does not export '%s'", source->filename, ucv_string_get(import)); else uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Module %s has no default export", source->filename); } else if (slot > 0xffff) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Too many module exports"); } else { uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_IMPORT); uc_compiler_emit_u32(compiler, 0, (source->exports.offset + slot) | ((compiler->upvals.count - n_imports + i) << 16)); } } } return true; } static char * uc_compiler_canonicalize_path(const char *path, const char *runpath) { char *p, *resolved; if (*path == '/') xasprintf(&p, "%s", path); else if (runpath && (p = strrchr(runpath, '/')) != NULL) xasprintf(&p, "%.*s/%s", (int)(p - runpath), runpath, path); else xasprintf(&p, "./%s", path); resolved = realpath(p, NULL); free(p); return resolved; } static char * uc_compiler_expand_module_path(const char *name, const char *runpath, const char *template) { int namelen, prefixlen; char *path, *p; p = strchr(template, '*'); if (!p) return NULL; prefixlen = p - template; namelen = strlen(name); xasprintf(&path, "%.*s%.*s%s", prefixlen, template, namelen, name, p + 1); for (p = path + prefixlen; namelen > 0; namelen--, p++) if (*p == '.') *p = '/'; p = uc_compiler_canonicalize_path(path, runpath); free(path); return p; } static char * uc_compiler_resolve_module_path(uc_compiler_t *compiler, const char *name) { uc_search_path_t *search = &compiler->parser->lex.config->module_search_path; uc_source_t *source = uc_compiler_current_source(compiler); char *path = NULL; size_t i; if (strchr(name, '/')) return uc_compiler_canonicalize_path(name, source->runpath); for (i = 0; i < search->count && !path; i++) path = uc_compiler_expand_module_path(name, source->runpath, search->entries[i]); return path; } static uc_source_t * uc_compiler_acquire_source(uc_compiler_t *compiler, const char *path) { size_t i; for (i = 0; i < compiler->program->sources.count; i++) if (!strcmp(compiler->program->sources.entries[i]->filename, path)) return uc_source_get(compiler->program->sources.entries[i]); return uc_source_new_file(path); } static bool uc_compiler_compile_dynload(uc_compiler_t *compiler, const char *name, uc_value_t *imports) { uc_value_t *modname = ucv_string_new(name); size_t i, n_imports; uc_value_t *import; for (i = 0, n_imports = 0; i < ucv_array_length(imports); i++) { import = ucv_array_get(imports, i); if (ucv_boolean_get(import)) { uc_compiler_emit_insn(compiler, 0, I_DYNLOAD); uc_compiler_emit_u32(compiler, 0, 0); uc_compiler_emit_constant_index(compiler, 0, modname); } else { n_imports++; } } if (n_imports > 0) { uc_compiler_emit_insn(compiler, 0, I_DYNLOAD); uc_compiler_emit_u32(compiler, 0, n_imports | ((compiler->upvals.count - n_imports) << 16)); uc_compiler_emit_constant_index(compiler, 0, modname); for (i = 0; i < ucv_array_length(imports); i++) { import = ucv_get(ucv_array_get(imports, i)); if (!import) import = ucv_string_new("default"); if (!ucv_boolean_get(import)) uc_compiler_emit_constant_index(compiler, 0, import); ucv_put(import); } } ucv_put(modname); return true; } static bool uc_compiler_is_dynlink_module(uc_compiler_t *compiler, const char *name, const char *path) { uc_search_path_t *dynlink_list = &compiler->parser->config->force_dynlink_list; size_t i; char *p; for (i = 0; i < dynlink_list->count; i++) if (!strcmp(dynlink_list->entries[i], name)) return true; if (!path) return false; p = strrchr(path, '.'); return (p && !strcmp(p, ".so")); } static bool uc_compiler_compile_module(uc_compiler_t *compiler, const char *name, uc_value_t *imports) { uc_source_t *source; char *path, *err; bool res; if (!name) return false; path = uc_compiler_resolve_module_path(compiler, name); if (uc_compiler_is_dynlink_module(compiler, name, path)) { res = uc_compiler_compile_dynload(compiler, name, imports); } else if (path) { source = uc_compiler_acquire_source(compiler, path); if (source) { err = NULL; res = uc_compiler_compile_module_source(compiler, source, imports, &err); if (!res) { uc_error_message_indent(&err); uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unable to compile module '%s':\n\n%s", source->filename, err); } free(err); } else { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unable to open module '%s': %s", path, strerror(errno)); res = false; } uc_source_put(source); } else { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unable to resolve path for module '%s'", name); return false; } free(path); return res; } static void uc_compiler_import_add(uc_compiler_t *compiler, uc_value_t *name) { bool constant; ssize_t slot; slot = uc_compiler_resolve_local(compiler, name, &constant); if (slot != -1) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Import name '%s' is already declared as local variable", ucv_string_get(name)); return; } slot = uc_compiler_resolve_upval(compiler, name, &constant); if (slot != -1) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Import name '%s' is already used", ucv_string_get(name)); return; } uc_compiler_add_upval(compiler, (2 << 14) + compiler->upvals.count, false, name, true); } static void uc_compiler_compile_importlist(uc_compiler_t *compiler, uc_value_t *namelist) { uc_value_t *label, *name; /* parse export symbols */ do { name = NULL; label = NULL; if (uc_compiler_parse_match(compiler, TK_DEFAULT)) { uc_compiler_keyword_consume(compiler, "as"); uc_compiler_parse_consume(compiler, TK_LABEL); label = ucv_get(compiler->parser->prev.uv); } else if (uc_compiler_parse_match(compiler, TK_STRING)) { name = ucv_get(compiler->parser->prev.uv); uc_compiler_keyword_consume(compiler, "as"); uc_compiler_parse_consume(compiler, TK_LABEL); label = ucv_get(compiler->parser->prev.uv); } else if (uc_compiler_parse_match(compiler, TK_LABEL)) { name = ucv_get(compiler->parser->prev.uv); if (uc_compiler_keyword_match(compiler, "as")) { uc_compiler_parse_consume(compiler, TK_LABEL); label = ucv_get(compiler->parser->prev.uv); } else { label = ucv_get(name); } } else { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unexpected token\nExpecting Label, String or 'default'"); } uc_compiler_import_add(compiler, label); ucv_array_push(namelist, name); ucv_put(label); if (uc_compiler_parse_match(compiler, TK_RBRACE)) return; } while (uc_compiler_parse_match(compiler, TK_COMMA)); uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unexpected token\nExpecting 'as', ',' or '}'"); } static void uc_compiler_compile_import(uc_compiler_t *compiler) { uc_value_t *namelist = ucv_array_new(NULL); if (compiler->scope_depth) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Imports may only appear at top level"); ucv_put(namelist); return; } /* import { ... } from */ if (uc_compiler_parse_match(compiler, TK_LBRACE)) { uc_compiler_compile_importlist(compiler, namelist); uc_compiler_keyword_consume(compiler, "from"); } /* import * as name from */ else if (uc_compiler_parse_match(compiler, TK_MUL)) { uc_compiler_keyword_consume(compiler, "as"); uc_compiler_parse_consume(compiler, TK_LABEL); uc_compiler_declare_local(compiler, compiler->parser->prev.uv, true); uc_compiler_initialize_local(compiler); ucv_array_push(namelist, ucv_boolean_new(true)); uc_compiler_keyword_consume(compiler, "from"); } /* import defaultExport [, ... ] from */ else if (uc_compiler_parse_match(compiler, TK_LABEL)) { uc_compiler_import_add(compiler, compiler->parser->prev.uv); ucv_array_push(namelist, NULL); /* import defaultExport, ... from */ if (uc_compiler_parse_match(compiler, TK_COMMA)) { /* import defaultExport, { ... } from */ if (uc_compiler_parse_match(compiler, TK_LBRACE)) { uc_compiler_compile_importlist(compiler, namelist); } /* import defaultExport, * as name from */ else if (uc_compiler_parse_match(compiler, TK_MUL)) { uc_compiler_keyword_consume(compiler, "as"); uc_compiler_parse_consume(compiler, TK_LABEL); uc_compiler_declare_local(compiler, compiler->parser->prev.uv, true); uc_compiler_initialize_local(compiler); ucv_array_push(namelist, ucv_boolean_new(true)); } /* error */ else { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unexpected token\nExpecting '{' or '*'"); } } uc_compiler_keyword_consume(compiler, "from"); } uc_compiler_parse_consume(compiler, TK_STRING); uc_compiler_compile_module(compiler, ucv_string_get(compiler->parser->prev.uv), namelist); uc_compiler_parse_consume(compiler, TK_SCOL); ucv_put(namelist); } static uc_tokentype_t uc_compiler_compile_declaration(uc_compiler_t *compiler) { uc_tokentype_t last_statement_type = compiler->parser->curr.type; if (uc_compiler_parse_match(compiler, TK_LOCAL)) uc_compiler_compile_local(compiler); else if (uc_compiler_parse_match(compiler, TK_CONST)) uc_compiler_compile_const(compiler); else if (uc_compiler_parse_match(compiler, TK_EXPORT)) uc_compiler_compile_export(compiler); else if (uc_compiler_parse_match(compiler, TK_IMPORT)) uc_compiler_compile_import(compiler); else last_statement_type = uc_compiler_compile_statement(compiler); if (compiler->parser->synchronizing) uc_compiler_parse_synchronize(compiler); return last_statement_type; } #endif /* NO_COMPILE */ static uc_program_t * uc_compile_from_source(uc_parse_config_t *config, uc_source_t *source, uc_program_t *prog, char **errp) { #ifdef NO_COMPILE if (errp) xasprintf(errp, "Source code compilation not supported\n"); return NULL; #else uc_patchlist_t exports = { .token = TK_EXPORT }; uc_exprstack_t expr = { .token = TK_EOF }; uc_parser_t parser = { .config = config }; uc_compiler_t compiler = { .parser = &parser, .exprstack = &expr }; uc_tokentype_t last_statement_type = TK_NULL; uc_program_t *progptr; uc_function_t *fn; const char *name; if (!prog) { progptr = uc_program_new(); name = "main"; } else { progptr = prog; name = "module"; } uc_lexer_init(&parser.lex, config, source); uc_compiler_init(&compiler, name, source, 0, progptr, config && config->strict_declarations); if (progptr == prog) { compiler.patchlist = &exports; compiler.function->module = true; } uc_compiler_parse_advance(&compiler); while (!uc_compiler_parse_match(&compiler, TK_EOF)) last_statement_type = uc_compiler_compile_declaration(&compiler); if (!compiler.function->module && last_statement_type == TK_SCOL) { uc_chunk_pop(uc_compiler_current_chunk(&compiler)); uc_compiler_emit_insn(&compiler, 0, I_RETURN); last_statement_type = TK_RETURN; } fn = uc_compiler_finish(&compiler, last_statement_type); if (errp) { *errp = parser.error ? parser.error->buf : NULL; free(parser.error); } else { printbuf_free(parser.error); } uc_lexer_free(&parser.lex); uc_vector_clear(&exports); if (!fn) { if (progptr != prog) ucv_put(&progptr->header); return NULL; } return progptr; #endif } static uc_program_t * uc_compile_from_bytecode(uc_parse_config_t *config, uc_source_t *source, char **errp) { uc_program_t *prog; prog = uc_program_load(source, errp); if (prog && !uc_program_entry(prog)) { if (errp) xasprintf(errp, "Program file contains no entry function\n"); ucv_put(&prog->header); } return prog; } uc_program_t * uc_compile(uc_parse_config_t *config, uc_source_t *source, char **errp) { uc_program_t *prog = NULL; if (!config) config = &uc_default_parse_config; switch (uc_source_type_test(source)) { case UC_SOURCE_TYPE_PLAIN: prog = uc_compile_from_source(config, source, NULL, errp); break; case UC_SOURCE_TYPE_PRECOMPILED: prog = uc_compile_from_bytecode(config, source, errp); break; default: if (errp) xasprintf(errp, "Unrecognized source type\n"); break; } return prog; }