diff --git a/chunk.c b/chunk.c index 63a70648..92c7efb7 100644 --- a/chunk.c +++ b/chunk.c @@ -20,14 +20,11 @@ #include "ucode/types.h" #include "ucode/util.h" -#define OFFSETINFO_BITS (sizeof(((uc_offsetinfo_t *)NULL)->entries[0]) * 8) -#define OFFSETINFO_BYTE_BITS 3 -#define OFFSETINFO_INSN_BITS (OFFSETINFO_BITS - OFFSETINFO_BYTE_BITS) -#define OFFSETINFO_MAX_BYTES ((1 << OFFSETINFO_BYTE_BITS) - 1) -#define OFFSETINFO_MAX_INSNS ((1 << OFFSETINFO_INSN_BITS) - 1) -#define OFFSETINFO_NUM_BYTES(n) ((n) & OFFSETINFO_MAX_BYTES) -#define OFFSETINFO_NUM_INSNS(n) ((n) >> OFFSETINFO_BYTE_BITS) -#define OFFSETINFO_ENCODE(line, insns) ((line & OFFSETINFO_MAX_BYTES) | (((insns) << OFFSETINFO_BYTE_BITS) & ~OFFSETINFO_MAX_BYTES)) +#define OFFSETINFO_MAX_BYTES 127 +#define OFFSETINFO_MAX_INSNS 127 +#define OFFSETINFO_NUM_BYTES(o) ((o)->bytes & OFFSETINFO_MAX_BYTES) +#define OFFSETINFO_NUM_INSNS(o) ((o)->insns & OFFSETINFO_MAX_INSNS) +#define OFFSETINFO_IS_END(o) ((o)->insns & 0x80) void @@ -69,38 +66,39 @@ uc_chunk_add(uc_chunk_t *chunk, uint8_t byte, size_t offset) uc_vector_push(chunk, byte); - /* offset info is encoded in bytes, for each byte, the first three bits - * specify the number of source text bytes to advance since the last entry - * and the remaining five bits specify the amount of instructions belonging - * to any given source text offset */ + /* Offset info is encoded in byte pairs, the first byte specifies the number + * of source text bytes to advance since the last entry and the second byte + * specifies the amount of instructions belonging to the source text offset. + * Byte and instruction count values are limited to 7 bits (0x00..0x7f), + * the most significant bit in each byte is reserved as flag value; if the + * bit is set in the first byte, it signals the begin of a logical statement + * while a set bit in the second byte denotes the end of the statement. */ if (offset > 0 || offsets->count == 0) { - /* if this offset is farther than seven (2 ** 3 - 1) bytes apart from + /* If this offset is farther than 127 (2 ** 7 - 1) bytes apart from * the last one, we need to emit intermediate "jump" bytes with zero * instructions each */ for (i = offset; i > OFFSETINFO_MAX_BYTES; i -= OFFSETINFO_MAX_BYTES) { - /* advance by 7 bytes */ - uc_vector_push(offsets, OFFSETINFO_ENCODE(OFFSETINFO_MAX_BYTES, 0)); + /* advance by 127 bytes */ + uc_vector_push(offsets, { OFFSETINFO_MAX_BYTES, 0 }); } /* advance by `i` bytes, count one instruction */ - uc_vector_push(offsets, OFFSETINFO_ENCODE(i, 1)); + uc_vector_push(offsets, { i, 1 }); } /* update instruction count at current offset entry */ else { - /* since we encode the per-offset instruction count in five bits, we - * can only count up to 31 instructions. If we exceed that limit, - * emit another offset entry with the initial three bits set to zero */ - if (OFFSETINFO_NUM_INSNS(offsets->entries[offsets->count - 1]) >= OFFSETINFO_MAX_INSNS) { + uc_offset_t *o = uc_vector_last(offsets); + + /* since we encode the per-offset instruction count in seven bits, we + * can only count up to 127 instructions. If we exceed that limit, + * emit another offset entry with the byte offset set to zero */ + if (OFFSETINFO_NUM_INSNS(o) >= OFFSETINFO_MAX_INSNS) { /* advance by 0 bytes, count one instruction */ - uc_vector_push(offsets, OFFSETINFO_ENCODE(0, 1)); + uc_vector_push(offsets, { 0, 1 }); } else { - uint8_t *prev = uc_vector_last(offsets); - - *prev = OFFSETINFO_ENCODE( - OFFSETINFO_NUM_BYTES(*prev), - OFFSETINFO_NUM_INSNS(*prev) + 1); + o->insns++; } } @@ -108,24 +106,56 @@ uc_chunk_add(uc_chunk_t *chunk, uint8_t byte, size_t offset) } void -uc_chunk_pop(uc_chunk_t *chunk) +uc_chunk_stmt_start(uc_chunk_t *chunk, size_t offset) { uc_offsetinfo_t *offsets = &chunk->debuginfo.offsets; - int n_insns; + size_t i; - assert(chunk->count > 0); + for (i = offset; i > OFFSETINFO_MAX_BYTES; i -= OFFSETINFO_MAX_BYTES) { + /* advance by 127 bytes */ + uc_vector_push(offsets, { OFFSETINFO_MAX_BYTES, 0 }); + } - chunk->count--; + /* advance by `i` bytes, set start of statement flag */ + uc_vector_push(offsets, { i | 0x80, 0 }); +} - n_insns = OFFSETINFO_NUM_INSNS(offsets->entries[offsets->count - 1]); +void +uc_chunk_stmt_end(uc_chunk_t *chunk, size_t offset) +{ + uc_offsetinfo_t *offsets = &chunk->debuginfo.offsets; + uc_offset_t *o = offsets->count ? uc_vector_last(offsets) : NULL; + size_t i; - if (n_insns > 0) { - uint8_t *prev = uc_vector_last(offsets); + for (i = offset; i > OFFSETINFO_MAX_BYTES; i -= OFFSETINFO_MAX_BYTES) { + /* advance by 127 bytes */ + uc_vector_push(offsets, { OFFSETINFO_MAX_BYTES, 0 }); + } - *prev = OFFSETINFO_ENCODE(OFFSETINFO_NUM_BYTES(*prev), n_insns - 1); + if (i > 0 || o == NULL || OFFSETINFO_IS_END(o)) { + /* advance by `i` bytes, set start of statement flag */ + uc_vector_push(offsets, { i, 0x80 }); } else { - offsets->count--; + /* set end flag on last offset entry */ + o->insns |= 0x80; + } +} + +void +uc_chunk_pop(uc_chunk_t *chunk) +{ + assert(chunk->count > 0); + + chunk->count--; + + for (size_t i = chunk->debuginfo.offsets.count; i > 0; i--) { + uc_offset_t *o = &chunk->debuginfo.offsets.entries[i - 1]; + + if (o->insns & 127) { + o->insns = ((o->insns & 127) - 1) | (o->insns & 128); + break; + } } } @@ -133,17 +163,17 @@ size_t uc_chunk_debug_get_srcpos(uc_chunk_t *chunk, size_t off) { uc_offsetinfo_t *offsets = &chunk->debuginfo.offsets; - size_t i, inum = 0, lnum = 0; + size_t i, inum = 0, bnum = 0; if (!offsets->count) return 0; for (i = 0; i < offsets->count && inum < off; i++) { - lnum += OFFSETINFO_NUM_BYTES(offsets->entries[i]); - inum += OFFSETINFO_NUM_INSNS(offsets->entries[i]); + bnum += OFFSETINFO_NUM_BYTES(&offsets->entries[i]); + inum += OFFSETINFO_NUM_INSNS(&offsets->entries[i]); } - return lnum; + return bnum; } void diff --git a/compiler.c b/compiler.c index 6e417d2b..dafec632 100644 --- a/compiler.c +++ b/compiler.c @@ -465,6 +465,22 @@ uc_compiler_reladdr(uc_compiler_t *compiler, size_t from, size_t to) return (size_t)(delta + 0x7fffffff); } +static void +uc_compiler_emit_stmt_start(uc_compiler_t *compiler, uc_token_t *tok) +{ + uc_chunk_stmt_start( + uc_compiler_current_chunk(compiler), + uc_compiler_set_srcpos(compiler, tok->pos)); +} + +static void +uc_compiler_emit_stmt_end(uc_compiler_t *compiler) +{ + uc_chunk_stmt_end( + uc_compiler_current_chunk(compiler), + uc_compiler_set_srcpos(compiler, compiler->parser->prev.end)); +} + static size_t uc_compiler_emit_insn(uc_compiler_t *compiler, size_t srcpos, uc_vm_insn_t insn) { @@ -1309,9 +1325,15 @@ uc_compiler_compile_nullish_assignment(uc_compiler_t *compiler, uc_value_t *var) } static void -uc_compiler_compile_expression(uc_compiler_t *compiler) +uc_compiler_compile_expression(uc_compiler_t *compiler, bool tag_stmt) { + if (tag_stmt) + uc_compiler_emit_stmt_start(compiler, &compiler->parser->curr); + uc_compiler_parse_precedence(compiler, P_COMMA); + + if (tag_stmt) + uc_compiler_emit_stmt_end(compiler); } static bool @@ -1406,8 +1428,10 @@ uc_compiler_compile_arrowfn(uc_compiler_t *compiler, uc_value_t *args, bool rest } } else { + uc_compiler_emit_stmt_start(&fncompiler, &compiler->parser->curr); uc_compiler_parse_precedence(&fncompiler, P_ASSIGN); uc_compiler_emit_insn(&fncompiler, 0, I_RETURN); + uc_compiler_emit_stmt_end(&fncompiler); } /* emit load instruction for function value */ @@ -1581,7 +1605,7 @@ uc_compiler_compile_paren(uc_compiler_t *compiler) * 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); + uc_compiler_compile_expression(compiler, false); /* A subsequent slash cannot be a regular expression literal */ compiler->parser->lex.no_regexp = true; @@ -1751,7 +1775,7 @@ uc_compiler_compile_template(uc_compiler_t *compiler) uc_compiler_emit_insn(compiler, 0, I_ADD); } else if (uc_compiler_parse_match(compiler, TK_PLACEH)) { - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, true); uc_compiler_emit_insn(compiler, 0, I_ADD); uc_compiler_parse_consume(compiler, TK_RBRACE); } @@ -1764,7 +1788,7 @@ uc_compiler_compile_template(uc_compiler_t *compiler) static void uc_compiler_compile_comma(uc_compiler_t *compiler) { - uc_compiler_emit_insn(compiler, 0, I_POP); + uc_compiler_emit_insn(compiler, compiler->parser->curr.pos, I_POP); uc_compiler_parse_precedence(compiler, P_ASSIGN); } @@ -1944,7 +1968,7 @@ uc_compiler_compile_subscript(uc_compiler_t *compiler) compiler->exprstack->flags |= optional_chaining ? F_OPTCHAINING : 0; /* compile lhs */ - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, false); /* no regexp literal possible after computed property access */ compiler->parser->lex.no_regexp = true; @@ -2227,15 +2251,19 @@ uc_compiler_compile_declexpr(uc_compiler_t *compiler, bool constant) static void uc_compiler_compile_local(uc_compiler_t *compiler) { + uc_compiler_emit_stmt_start(compiler, &compiler->parser->prev); uc_compiler_compile_declexpr(compiler, false); uc_compiler_parse_consume(compiler, TK_SCOL); + uc_compiler_emit_stmt_end(compiler); } static void uc_compiler_compile_const(uc_compiler_t *compiler) { + uc_compiler_emit_stmt_start(compiler, &compiler->parser->prev); uc_compiler_compile_declexpr(compiler, true); uc_compiler_parse_consume(compiler, TK_SCOL); + uc_compiler_emit_stmt_end(compiler); } static uc_tokentype_t @@ -2273,7 +2301,7 @@ uc_compiler_compile_if(uc_compiler_t *compiler) /* parse & compile condition expression */ uc_compiler_parse_consume(compiler, TK_LPAREN); - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, true); uc_compiler_parse_consume(compiler, TK_RPAREN); /* conditional jump to else/elif branch */ @@ -2297,7 +2325,7 @@ uc_compiler_compile_if(uc_compiler_t *compiler) /* parse & compile elsif condition */ uc_compiler_parse_advance(compiler); uc_compiler_parse_consume(compiler, TK_LPAREN); - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, true); uc_compiler_parse_consume(compiler, TK_RPAREN); uc_compiler_parse_consume(compiler, TK_COLON); @@ -2382,7 +2410,7 @@ uc_compiler_compile_while(uc_compiler_t *compiler) /* parse & compile loop condition */ uc_compiler_parse_consume(compiler, TK_LPAREN); - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, true); uc_compiler_parse_consume(compiler, TK_RPAREN); /* conditional jump to end */ @@ -2444,7 +2472,8 @@ uc_compiler_compile_for_in(uc_compiler_t *compiler, bool local, uc_token_t *kvar } /* value to iterate */ - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, false); + uc_compiler_emit_stmt_end(compiler); uc_compiler_parse_consume(compiler, TK_RPAREN); uc_compiler_emit_insn(compiler, 0, I_SLOC); uc_compiler_emit_u32(compiler, 0, val_slot); @@ -2561,7 +2590,7 @@ uc_compiler_compile_for_count(uc_compiler_t *compiler, bool local, uc_token_t *v } /* ... otherwise an unrelated expression */ else { - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, false); uc_compiler_emit_insn(compiler, 0, I_POP); } } @@ -2570,10 +2599,11 @@ uc_compiler_compile_for_count(uc_compiler_t *compiler, bool local, uc_token_t *v } /* ... 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_compile_expression(compiler, false); uc_compiler_emit_insn(compiler, 0, I_POP); } + uc_compiler_emit_stmt_end(compiler); uc_compiler_parse_consume(compiler, TK_SCOL); @@ -2581,7 +2611,7 @@ uc_compiler_compile_for_count(uc_compiler_t *compiler, bool local, uc_token_t *v if (!uc_compiler_parse_check(compiler, TK_SCOL)) { cond_off = chunk->count; - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, true); test_off = uc_compiler_emit_jmpz(compiler, 0); } @@ -2596,7 +2626,7 @@ uc_compiler_compile_for_count(uc_compiler_t *compiler, bool local, uc_token_t *v incr_off = chunk->count; if (!uc_compiler_parse_check(compiler, TK_RPAREN)) { - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, true); uc_compiler_emit_insn(compiler, 0, I_POP); } @@ -2647,6 +2677,8 @@ uc_compiler_compile_for(uc_compiler_t *compiler) uc_compiler_parse_consume(compiler, TK_LPAREN); + uc_compiler_emit_stmt_start(compiler, &compiler->parser->curr); + /* check the next few tokens and see if we have either a * `let x in` / `let x, y` expression or an ordinary initializer * statement */ @@ -2707,7 +2739,7 @@ uc_compiler_compile_switch(uc_compiler_t *compiler) /* parse and compile match value */ uc_compiler_parse_consume(compiler, TK_LPAREN); - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, true); uc_compiler_parse_consume(compiler, TK_RPAREN); uc_compiler_parse_consume(compiler, TK_LBRACE); @@ -2753,7 +2785,7 @@ uc_compiler_compile_switch(uc_compiler_t *compiler) skip_jmp = uc_compiler_emit_jmp(compiler, 0); /* compile case value expression */ - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, false); uc_compiler_parse_consume(compiler, TK_COLON); /* Store three values in case offset list: @@ -3003,7 +3035,7 @@ 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); + uc_compiler_compile_expression(compiler, true); /* XXX: the lexer currently emits a superfluous trailing semicolon... */ uc_compiler_parse_match(compiler, TK_SCOL); @@ -3046,7 +3078,7 @@ uc_compiler_compile_expstmt(uc_compiler_t *compiler) if (uc_compiler_parse_match(compiler, TK_SCOL)) return TK_NULL; - uc_compiler_compile_expression(compiler); + uc_compiler_compile_expression(compiler, false); /* allow omitting final semicolon */ switch (compiler->parser->curr.type) { @@ -3087,32 +3119,39 @@ uc_compiler_compile_statement(uc_compiler_t *compiler) 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)) + 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); + } + else { + uc_compiler_emit_stmt_start(compiler, &compiler->parser->curr); + + 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 + last_statement_type = uc_compiler_compile_expstmt(compiler); + + uc_compiler_emit_stmt_end(compiler); + } compiler->exprstack = expr.parent; @@ -3201,8 +3240,11 @@ uc_compiler_compile_export(uc_compiler_t *compiler) return; } + uc_compiler_emit_stmt_start(compiler, &compiler->parser->prev); + if (uc_compiler_parse_match(compiler, TK_LBRACE)) { uc_compiler_compile_exportlist(compiler); + uc_compiler_emit_stmt_end(compiler); return; } @@ -3214,7 +3256,7 @@ uc_compiler_compile_export(uc_compiler_t *compiler) 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); + uc_compiler_compile_expression(compiler, false); else uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Unexpected token\nExpecting 'let', 'const', 'function', 'default' or '{'"); @@ -3236,6 +3278,8 @@ uc_compiler_compile_export(uc_compiler_t *compiler) } uc_compiler_parse_consume(compiler, TK_SCOL); + + uc_compiler_emit_stmt_end(compiler); } static uc_program_t * @@ -3640,6 +3684,8 @@ uc_compiler_compile_import(uc_compiler_t *compiler) return; } + uc_compiler_emit_stmt_start(compiler, &compiler->parser->prev); + /* import { ... } from */ if (uc_compiler_parse_match(compiler, TK_LBRACE)) { uc_compiler_compile_importlist(compiler, namelist); @@ -3696,6 +3742,8 @@ uc_compiler_compile_import(uc_compiler_t *compiler) uc_compiler_parse_consume(compiler, TK_SCOL); + uc_compiler_emit_stmt_end(compiler); + ucv_put(namelist); } diff --git a/include/ucode/chunk.h b/include/ucode/chunk.h index 1e6ab1f9..804a1225 100644 --- a/include/ucode/chunk.h +++ b/include/ucode/chunk.h @@ -26,10 +26,13 @@ __hidden void uc_chunk_init(uc_chunk_t *chunk); __hidden void uc_chunk_free(uc_chunk_t *chunk); -__hidden size_t uc_chunk_add(uc_chunk_t *chunk, uint8_t byte, size_t line); +__hidden size_t uc_chunk_add(uc_chunk_t *chunk, uint8_t byte, size_t offset); __hidden void uc_chunk_pop(uc_chunk_t *chunk); +__hidden void uc_chunk_stmt_start(uc_chunk_t *chunk, size_t offset); +__hidden void uc_chunk_stmt_end(uc_chunk_t *chunk, size_t offset); + size_t uc_chunk_debug_get_srcpos(uc_chunk_t *chunk, size_t offset); __hidden void uc_chunk_debug_add_variable(uc_chunk_t *chunk, size_t from, size_t to, size_t slot, bool upval, uc_value_t *name); uc_value_t *uc_chunk_debug_get_variable(uc_chunk_t *chunk, size_t offset, size_t slot, bool upval); diff --git a/include/ucode/compiler.h b/include/ucode/compiler.h index db3d6768..f6e4bfd6 100644 --- a/include/ucode/compiler.h +++ b/include/ucode/compiler.h @@ -101,8 +101,8 @@ typedef struct { uc_parse_config_t *config; uc_lexer_t lex; uc_token_t prev, curr; - bool synchronizing; uc_stringbuf_t *error; + bool synchronizing; } uc_parser_t; typedef struct uc_compiler { diff --git a/include/ucode/types.h b/include/ucode/types.h index f149b98b..0fc92630 100644 --- a/include/ucode/types.h +++ b/include/ucode/types.h @@ -91,9 +91,14 @@ typedef struct { size_t from, to, slot, nameidx; } uc_varrange_t; +typedef struct { + uint8_t bytes; + uint8_t insns; +} uc_offset_t; + uc_declare_vector(uc_ehranges_t, uc_ehrange_t); uc_declare_vector(uc_variables_t, uc_varrange_t); -uc_declare_vector(uc_offsetinfo_t, uint8_t); +uc_declare_vector(uc_offsetinfo_t, uc_offset_t); typedef struct { size_t count; @@ -305,8 +310,14 @@ typedef struct { bool mcall, strict; } uc_callframe_t; +typedef struct uc_breakpoint { + uint8_t *ip; + void (*cb)(uc_vm_t *, struct uc_breakpoint *); +} uc_breakpoint_t; + uc_declare_vector(uc_callframes_t, uc_callframe_t); uc_declare_vector(uc_stack_t, uc_value_t *); +uc_declare_vector(uc_breakpoints_t, uc_breakpoint_t *); typedef struct printbuf uc_stringbuf_t; @@ -323,7 +334,7 @@ struct uc_vm { uc_source_t *sources; uc_weakref_t values; uc_resource_types_t restypes; - char _reserved[sizeof(uc_modexports_t)]; + uc_breakpoints_t breakpoints; union { uint32_t u32; int32_t s32; diff --git a/include/ucode/vm.h b/include/ucode/vm.h index 3cb6dc07..2d8b9dc3 100644 --- a/include/ucode/vm.h +++ b/include/ucode/vm.h @@ -122,7 +122,7 @@ typedef enum { #define GC_DEFAULT_INTERVAL 1000 -extern uint32_t insns[__I_MAX]; +extern const int8_t uc_vm_insn_format[__I_MAX]; void uc_vm_init(uc_vm_t *vm, uc_parse_config_t *config); void uc_vm_free(uc_vm_t *vm); diff --git a/lib.c b/lib.c index 6415e3c7..98277b4e 100644 --- a/lib.c +++ b/lib.c @@ -2709,9 +2709,11 @@ uc_require_ucode(uc_vm_t *vm, const char *path, uc_value_t *scope, uc_value_t ** *res = uc_callfunc(vm, 3); - uc_vm_stack_pop(vm); - uc_vm_stack_pop(vm); - uc_vm_stack_pop(vm); + if (vm->stack.count >= 3) { + uc_vm_stack_pop(vm); + uc_vm_stack_pop(vm); + uc_vm_stack_pop(vm); + } } vm->config = prev_config; diff --git a/lib/debug.c b/lib/debug.c index 0b227d9f..06f65b66 100644 --- a/lib/debug.c +++ b/lib/debug.c @@ -60,7 +60,13 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include #ifdef HAVE_ULOOP #include @@ -71,6 +77,7 @@ #include "ucode/module.h" #include "ucode/platform.h" +#include "ucode/compiler.h" static char *memdump_signal = "USR2"; @@ -615,6 +622,7 @@ debug_setup_memdump(uc_vm_t *vm) { uc_cfn_ptr_t ucsignal = uc_stdlib_function("signal"); uc_value_t *memdump = ucv_cfunction_new("memdump", debug_handle_memdump); + uc_value_t *handler; char *ev; ev = getenv("UCODE_DEBUG_MEMDUMP_PATH"); @@ -628,11 +636,14 @@ debug_setup_memdump(uc_vm_t *vm) uc_vm_stack_push(vm, ucv_string_new(memdump_signal)); uc_vm_stack_push(vm, memdump); - if (ucsignal(vm, 2) != memdump) + handler = ucsignal(vm, 2); + + if (handler != memdump) fprintf(stderr, "Unable to install debug signal handler\n"); ucv_put(uc_vm_stack_pop(vm)); ucv_put(uc_vm_stack_pop(vm)); + ucv_put(handler); } static void @@ -1614,20 +1625,4887 @@ uc_setupval(uc_vm_t *vm, size_t nargs) } -static const uc_function_list_t debug_fns[] = { - { "memdump", uc_memdump }, - { "traceback", uc_traceback }, - { "sourcepos", uc_sourcepos }, - { "getinfo", uc_getinfo }, - { "getlocal", uc_getlocal }, - { "setlocal", uc_setlocal }, - { "getupval", uc_getupval }, - { "setupval", uc_setupval }, +/* ========================================================================== */ +/* Interactive debugger implementation follows */ +/* ========================================================================== */ + +typedef enum { + BK_ONCE, + BK_USER, + BK_STEP, + BK_CATCH, +} debug_breakpoint_kind_t; + +typedef struct debug_breakpoint { + uc_breakpoint_t bk; + uc_function_t *fn; + size_t depth; + debug_breakpoint_kind_t kind; +} debug_breakpoint_t; + +typedef struct { + size_t nesting; + size_t off_start, off_end; + size_t pos_start, pos_end, pos_ip; + uint8_t *ip_start, *ip_end; +} insn_span_t; + +typedef struct { + const char *path; + size_t line; + size_t column; + size_t offset; + uc_program_t *program; + uc_source_t *source; + uc_function_t *function; +} location_t; + +typedef enum { + ARGTYPE_NONE, + ARGTYPE_ERROR, + ARGTYPE_STRING, + ARGTYPE_NUMBER, +} argtype_t; + +typedef struct { + argtype_t type; + size_t off; + size_t nv; + char *sv; +} arg_t; + +typedef struct { + size_t count; + char **entries; +} suggestions_t; + +typedef struct { + size_t pos, len, size, width; + uint32_t *chars; +} termline_t; + +static struct { + bool initialized; + char data[128]; + size_t pos, fill; + size_t rows, cols, col_offset; + struct termios orig_settings, curr_settings; + struct { + size_t count; + termline_t *entries; + } history; + struct { + size_t count; + regex_t *entries; + } patterns; +} termstate; + +enum { + HOME_KEY = 0x110000, + END_KEY, + DEL_KEY, + PAGE_UP, + PAGE_DOWN, + ARROW_UP, + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, + CTRL_UP, + CTRL_DOWN, + CTRL_LEFT, + CTRL_RIGHT, }; -void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +#define HISTORY_SIZE 100 + +enum { + BOLD = (1 << 0), + FAINT = (1 << 1), + ULINE = (1 << 2), +}; + +typedef enum { + FG_BLACK = 30, + FG_RED = 31, + FG_GREEN = 32, + FG_YELLOW = 33, + FG_BLUE = 34, + FG_MAGENTA = 35, + FG_CYAN = 36, + FG_GRAY = 37, + FG_BBLACK = 90, + FG_BRED = 91, + FG_BGREEN = 92, + FG_BYELLOW = 93, + FG_BBLUE = 94, + FG_BMAGENT = 95, + FG_BCYAN = 96, + FG_BWHITE = 97, +} fg_color_t; + +typedef enum { + BG_BLACK = 40, + BG_GRAY = 100, +} bg_color_t; + +typedef struct { + fg_color_t fg; + bg_color_t bg; + uint32_t styles; +} style_t; + +#define uc_vector_add(vec, ...) ({ \ + uc_vector_push((vec), ((typeof((vec)->entries[0]))__VA_ARGS__)); \ + uc_vector_last(vec); \ +}) + +static void +cs(uc_stringbuf_t *sb, style_t *style) { - uc_function_list_register(scope, debug_fns); + int codes[8] = { 0 }; + size_t i = 0; - debug_setup(vm); + if (style == NULL) { + printbuf_strappend(sb, "\033[0m"); + return; + } + + if ((style->styles & (BOLD|FAINT|ULINE)) == 0) + codes[i++] = 0; + + if (style->styles & BOLD) codes[i++] = 1; + if (style->styles & FAINT) codes[i++] = 2; + if (style->styles & ULINE) codes[i++] = 4; + + codes[i++] = style->fg ? style->fg : 39; + codes[i++] = style->bg ? style->bg : 49; + + printbuf_strappend(sb, "\033["); + + for (size_t n = 0; n < i; n++) + sprintbuf(sb, "%s%d", n ? ";" : "", codes[n]); + + printbuf_strappend(sb, "m"); +} + +static uc_callframe_t * +uc_debug_curr_frame(uc_vm_t *vm, size_t off) +{ + if (off > vm->callframes.count) + return NULL; + + for (size_t i = vm->callframes.count - off; i > 0; i--) + if (vm->callframes.entries[i-1].closure) + return &vm->callframes.entries[i-1]; + + return NULL; +} + +static bool cmd_help(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_break(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_delete(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_list(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_next(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_step(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_continue(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_return(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_backtrace(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_variables(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_sources(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_quit(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_print(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_lines(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_throw(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); +static bool cmd_disasm(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv); + +static const struct { + const char *command; + bool (*cb)(uc_vm_t *, debug_breakpoint_t *, size_t, arg_t *); + const char *help; +} commands[] = { + { "help\0", cmd_help, + "Print help information." }, + { "break\0", cmd_break, + "The break command sets a breakpoint at the given location, " + "instructing the virtual machine to stop execution at this " + "point and handing control to the debugger.\n\n" + "Breakpoint locations may be specified either as filename, " + "line number and optional character offset within the line " + "or as a ucode expression that evaluates to a function in " + "which a breakpoint is set.\n\n" + "Examples:\n" + " break example.uc:13 # Set breakpoint in line 13 of example.uc\n" + " break 4:17 # Break in line in 4, char 17 of current file\n" + " break myobj.method # Break in function `method` of `myobj`\n" + " break (string.uc) # Parens to disambiguate expression from path" + }, + { "delete\0", cmd_delete, + "Delete a breakpoint. When no argument is given, the current " + "breakpoint is deleted, otherwise this function deletes the breakpoint " + "with the given index.\n\n" + "Examples:\n" + " delete # Delete current breakpoint\n" + " delete 2 # Delete breakpoint #2" + }, + { "list\0ls\0", cmd_list, + "List all currently set breakpoints. User defined breakpoints are " + "prefixed with a number identifying the breakpoint, internal " + "breakpoints used by the debugger are prefixed with a breakpoint type " + "enclosed in parens, e.g. '(step)'." + }, + { "next\0", cmd_next, + "Execute the next statement and stop again." + }, + { "step\0", cmd_step, + "Execute the next statement, in case of function calls step into the " + "called function and stop there." + }, + { "continue\0", cmd_continue, + "Continue execution until the next breakpoint or end of program." + }, + { "return\0", cmd_return, + "Continue executing the current function until it returns, then stop. " + "in the calling function. If the current function is the program entry " + "function, then run until the end of the program." + }, + { "backtrace\0bt\0", cmd_backtrace, + "Print a trace of the current callstack, with most recent callframes " + "output first. If the optional 'full' argument is specified, " + "additional information about each call frame is printed.\n\n" + "Examples:\n" + " backtrace # Print backtrace\n" + " backtrace full # Print backtrace with additional information" + }, + { "variables\0", cmd_variables, + "Print local variables and their contents for the current execution " + "context. Internal variables which are unreachable by script code " + "are colored grey, upvalues (variables captured from parent scopes) " + "are colored blue and ordinary variables use the default color.\n\n" + "If the optional 'full' argument is specified, the complete value for " + "each variable is shown, instead of an abbreviated line truncated to " + "the current terminal width.\n\n" + "Examples:\n" + " variables # Print local variables\n" + " variables full # Print variables with complete content" + }, + { "sources\0src\0", cmd_sources, + "Print a list of loaded source buffers.\n" + }, + { "print\0", cmd_print, + "Evaluate an ucode expression and print the resulting value.\n\n" + "Examples:\n" + " print varname # Print value of variable 'varname'\n" + " print myobj.prop # Print `prop` property of `myobj`\n" + " print keys(myobj) # Invoke a stdlib function" + }, + { "lines\0ln\0", cmd_lines, + "Print source code lines surrounding the given location specified " + "either as filename with line number or as expression evaluating to a " + "function value.\n\n" + "The amount of preceeding and following lines to print may be " + "specified as second and third argument respecitely. By default, two " + "lines of context are printed before and after the location.\n\n" + "Examples:\n" + " lines # Output lines surrounding current line\n" + " lines example.uc # Print first three lines of example.uc\n" + " lines (obj.func) # Parens to disambiguate expression from path\n" + " lines foo 5 8 # Print 5 lines before foo() till 8 lines in\n" + " lines #123 # Print source of instruction offset 123\n" + " lines +0 3 3 # Print 3 lines before and after current line\n" + " lines -5 # Print source 5 lines before current line\n" + " lines +3 # Print source 3 lines after current line" + }, + { "throw\0", cmd_throw, + "Raise an exception at the current instruction offset.\n\n" + "Examples:\n" + " throw \"Message\" # Throw exception with given message" + }, + { "disassemble\0disasm\0", cmd_disasm, + "Disassembe the given function or statement location and output the " + "corresponding byte code in a human readable manner. The location to " + "disassemble may be either a function name, a single instruction " + "offset, an instruction offset range or a ucode expression.\n\n" + "Examples:\n" + " disassemble # Disassemble current statment\n" + " disassemble foo # Disassemble body of foo()\n" + " disassemble foo+100 # Disassemble first 100 byte of function foo()\n" + " disassemble #5 # Disassemble statement containing instruction 5\n" + " disassemble #2-10 # Disassemble instructions 2 to 10\n" + " disassemble #22+100 # Disassemble instructions 22 to 122\n" + " disassemble (12/3*4) # Disassemble ucode expression\n" + }, + { "quit\0", cmd_quit, + "Forcibly terminate the currently running program. The termination " + "happens in the same manner as if 'exit()' has been called from " + "script code." + } +}; + +/* -- convert file path to module name -------------------------------------- */ +static char * +filename_to_modulename(uc_vm_t *vm, const char *filename) +{ + char *module_path = realpath(filename, NULL); + char *rv = NULL; + + if (!module_path) + module_path = (char *)filename; + + size_t len_module_path = strlen(module_path); + + uc_value_t *search = + ucv_object_get(uc_vm_scope_get(vm), "REQUIRE_SEARCH_PATH", NULL); + + for (size_t i = 0; rv == NULL && i < ucv_array_length(search); i++) { + uc_value_t *p = ucv_array_get(search, i); + + if (ucv_type(p) != UC_STRING) + continue; + + char *search_spec = xstrdup(ucv_string_get(p)); + char *search_ext = strchr(search_spec, '*'); + + if (!search_ext) { + free(search_spec); + continue; + } + + *search_ext++ = 0; + + char *search_path = realpath(search_spec, NULL); + + if (!search_path) { + free(search_spec); + continue; + } + + size_t len_search_path = strlen(search_path); + size_t len_search_ext = strlen(search_ext); + + if (!strncmp(module_path, search_path, len_search_path) && + module_path[len_search_path] == '/' && + len_module_path > len_search_ext && + !strcmp(module_path + len_module_path - len_search_ext, search_ext)) + { + xasprintf(&rv, "%.*s", + (int)(len_module_path - (len_search_path + 1 + len_search_ext)), + module_path + len_search_path + 1); + + for (char *p = rv; *p; p++) + if (*p == '/') + *p = '.'; + } + + free(search_spec); + free(search_path); + } + + free(module_path); + + return rv; +} + +/* -- helper routines to deal with print buffers ---------------------------- */ +static size_t +utf8_sequence_length(const char *s) +{ + const uint8_t *c = (const uint8_t *)s; + + if ((c[0] & 0xe0) == 0xc0 && + (c[1] & 0xc0) == 0x80) + return 2; + + if ((c[0] & 0xf0) == 0xe0 && + (c[1] & 0xc0) == 0x80 && + (c[2] & 0xc0) == 0x80) + return 3; + + if ((c[0] & 0xf8) == 0xf0 && + (c[1] & 0xc0) == 0x80 && + (c[2] & 0xc0) == 0x80 && + (c[3] & 0xc0) == 0x80) + return 4; + + return (*c != 0); +} + +static size_t +esc_sequence_length(const char *s) +{ + if (s[0] == '\033' && s[1] == '[') { + size_t i = 2; + + while (s[i] != '\0' && s[i] != 'm') + i++; + + return i + (s[i] == 'm'); + } + + return 0; +} + +static size_t +strwidth(const char *s) +{ + size_t len = 0; + + while (*s) { + s += esc_sequence_length(s); + + size_t n = utf8_sequence_length(s); + + if (n) { + s += n; + len++; + } + } + + return len; +} + +static bool +str_startswith(const char *s, const char *substr) +{ + if (substr == NULL) + return true; + + return strncmp(s, substr, strlen(substr)) == 0; +} + +static size_t +printbuf_truncate(uc_stringbuf_t *sb, size_t off, size_t maxcols, bool tail) +{ + if (maxcols == 0) { + sb->bpos = off; + sb->buf[off] = 0; + + return 0; + } + + size_t len = strwidth(sb->buf + off); + char *s = sb->buf + off; + + if (tail == false && len > maxcols) { + for (size_t i = 0; i < len - maxcols + 1; i++) { + s += esc_sequence_length(s); + s += utf8_sequence_length(s); + } + + size_t keeplen = (sb->buf + sb->bpos) - s; + size_t trunclen = s - (sb->buf + off); + size_t elliplen = sizeof("…") - 1; + + /* Reserve enough additional space for ellipsis mb sequence. */ + if (trunclen < elliplen) + printbuf_memset(sb, -1, ' ', elliplen - trunclen); + + memmove(sb->buf + off + elliplen, s, keeplen); + memcpy(sb->buf + off, "…", elliplen); + + sb->bpos += elliplen; + sb->bpos -= trunclen; + sb->buf[sb->bpos] = 0; + + return maxcols; + } + + if (tail == true && len > maxcols) { + for (size_t i = 0; i < maxcols - 1; i++) { + s += esc_sequence_length(s); + s += utf8_sequence_length(s); + } + + sb->bpos = s - sb->buf; + printbuf_strappend(sb, "…"); + + return maxcols; + } + + return len; +} + +static size_t +printbuf_append_uv(uc_stringbuf_t *sb, uc_vm_t *vm, uc_value_t *val, + size_t maxcols) +{ + int pos = sb->bpos; + const char *end; + size_t len; + + ucv_to_stringbuf(vm, sb, val, false); + + len = strwidth(sb->buf + pos); + + if (len > maxcols) { + switch (sb->buf[pos]) { + case '{': len = maxcols - 3; end = "… }"; break; + case '[': len = maxcols - 3; end = "… ]"; break; + case '"': len = maxcols - 2; end = "…\""; break; + default: len = maxcols - 1; end = "…"; break; + } + + for (sb->bpos = pos; len > 0; len--) + sb->bpos += utf8_sequence_length(sb->buf + sb->bpos); + + printbuf_memappend_fast(sb, end, strlen(end)); + + return maxcols; + } + + return len; +} + +static size_t +printbuf_append_funcname(uc_stringbuf_t *sb, uc_vm_t *vm, uc_value_t *val, + size_t maxcols) +{ + char *placeholder = NULL; + int off = sb->bpos; + + for (size_t i = 0; i < vm->restypes.count; i++) { + uc_resource_type_t *rt = vm->restypes.entries[i]; + + ucv_object_foreach(rt->proto, k, v) { + (void)k; + + if (v == val) { + printbuf_memappend_fast(sb, rt->name, strlen(rt->name)); + printbuf_strappend(sb, "#"); + goto name; + } + } + } + + uc_value_t *modtable = ucv_object_get(uc_vm_scope_get(vm), "modules", NULL); + + ucv_object_foreach(modtable, modname, modscope) { + ucv_object_foreach(modscope, symname, symval) { + (void)symname; + + if (symval == val) { + printbuf_memappend_fast(sb, modname, strlen(modname)); + printbuf_strappend(sb, "."); + goto name; + } + } + } + +name: + if (ucv_type(val) == UC_CLOSURE) { + uc_function_t *fn = ((uc_closure_t *)val)->function; + + if (fn->name[0]) { + printbuf_memappend_fast(sb, fn->name, strlen(fn->name)); + goto done; + } + + placeholder = fn->arrow ? "λ" : "𝑓"; + } + else if (ucv_type(val) == UC_CFUNCTION) { + uc_cfunction_t *cf = (uc_cfunction_t *)val; + + if (cf->name[0]) { + printbuf_memappend_fast(sb, cf->name, strlen(cf->name)); + goto done; + } + + placeholder = "𝑓"; + } + else { + return 0; + } + + /* no prefix and no name yet, try to name by containing property name */ + for (uc_weakref_t *ref = vm->values.next; + ref != &vm->values && sb->bpos == off; + ref = ref->next) + { + uc_object_t *obj = + (uc_object_t *)((char *)ref - offsetof(uc_object_t, ref)); + + if (obj->header.type != UC_OBJECT) + continue; + + ucv_object_foreach(&obj->header, k, v) { + if (v == val) { + printbuf_memappend_fast(sb, k, strlen(k)); + printbuf_strappend(sb, ":"); + break; + } + } + } + + printbuf_memappend_fast(sb, placeholder, strlen(placeholder)); + +done: + return printbuf_truncate(sb, off, maxcols, true); +} + +static size_t +printbuf_append_function(uc_stringbuf_t *sb, uc_vm_t *vm, uc_value_t *val, + uc_callframe_t *frame, size_t maxcols) +{ + uc_type_t t = ucv_type(val); + int off = sb->bpos; + + if (t == UC_CFUNCTION) { + printbuf_append_funcname(sb, vm, val, SIZE_MAX); + printbuf_strappend(sb, "("); + + if (frame) { + size_t prev_frame = vm->stack.count; + + for (size_t i = vm->callframes.count; i > 0; i--) { + if (&vm->callframes.entries[i - 1] == frame) + break; + + prev_frame = vm->callframes.entries[i - 1].stackframe; + } + + for (size_t j = 1; j < prev_frame - frame->stackframe; j++) { + if (j > 1) + printbuf_strappend(sb, ", "); + + uc_value_t *argval = + (frame->stackframe + j < vm->stack.count) + ? vm->stack.entries[frame->stackframe + j] + : NULL; + + printbuf_append_uv(sb, vm, argval, 32); + } + } + + printbuf_strappend(sb, ")"); + } + else if (t == UC_CLOSURE) { + uc_closure_t *cl = (uc_closure_t *)val; + uc_source_t *source = uc_program_function_source(cl->function); + + if (cl->function->module) { + char *s = filename_to_modulename(vm, source->filename); + sprintbuf(sb, "module(%s)", s ? s : ""); + free(s); + } + else { + printbuf_append_funcname(sb, vm, val, SIZE_MAX); + printbuf_strappend(sb, "("); + + if (frame) { + for (size_t i = 0; i < cl->function->nargs; i++) { + uc_value_t *argname = uc_chunk_debug_get_variable( + &cl->function->chunk, i, i + 1, false); + + if (i > 0) + printbuf_strappend(sb, ", "); + + if (i + 1 == cl->function->nargs && cl->function->vararg) + printbuf_strappend(sb, "..."); + + if (argname) { + printbuf_memappend_fast(sb, + ucv_string_get(argname), + ucv_string_length(argname)); + + printbuf_strappend(sb, "="); + ucv_put(argname); + } + else { + sprintbuf(sb, "$%zu=", i + 1); + } + + uc_value_t *argval = + (frame->stackframe + i + 1 < vm->stack.count) + ? vm->stack.entries[frame->stackframe + i + 1] + : NULL; + + printbuf_append_uv(sb, vm, argval, 32); + } + } + + printbuf_strappend(sb, ")"); + } + } + + return printbuf_truncate(sb, off, maxcols, true); +} + +static size_t +printbuf_append_srcpath(uc_stringbuf_t *sb, uc_source_t *source, size_t maxcols) +{ + int off = sb->bpos; + + printbuf_memset(sb, off + PATH_MAX, 0, 1); + + if (realpath(source->filename, sb->buf + off)) { + size_t pathlen = strlen(sb->buf + off); + char cwd[PATH_MAX]; + + if (getcwd(cwd, sizeof(cwd))) { + size_t cwdlen = strlen(cwd); + + if (strncmp(sb->buf + off, cwd, cwdlen) == 0 && + sb->buf[off + cwdlen] == '/') + { + pathlen -= cwdlen + 1; + memmove(sb->buf + off, sb->buf + off + cwdlen + 1, pathlen); + } + } + + sb->bpos = off + pathlen; + sb->buf[sb->bpos] = 0; + } + else { + sb->bpos = off; + printbuf_memappend_fast(sb, + source->filename, strlen(source->filename)); + } + + return printbuf_truncate(sb, off, maxcols, false); +} + +static size_t +printbuf_cs(uc_stringbuf_t *sb, const char *fmt, ...) +{ + uc_stringbuf_t fmtbuf = { 0 }; + style_t *styles[8] = { 0 }; + uint8_t nstyles = 0; + va_list ap, ap1; + + for (const char *p = fmt; *p; p++) + if (*p >= '\1' && *p <= '\7' && *p > nstyles) + nstyles = *p; + + va_start(ap, fmt); + + for (uint8_t i = 0; i < nstyles; i++) + styles[i] = va_arg(ap, style_t *); + + const char *p, *l; + + for (p = l = fmt; *p; p++) { + if ((*p >= '\1' && *p <= '\7') || *p == '\177') { + printbuf_memappend_fast((&fmtbuf), l, p - l); + cs(&fmtbuf, (*p <= '\7' ? styles[(size_t)*p - 1] : NULL)); + l = p + 1; + } + } + + printbuf_memappend_fast((&fmtbuf), l, p - l); + + va_copy(ap1, ap); + int len = vsnprintf(NULL, 0, fmtbuf.buf, ap1); + va_end(ap1); + + if (len > 0) { + printbuf_memset(sb, sb->bpos + len - 1, '\0', 1); + vsnprintf(sb->buf + sb->bpos - len, len + 1, fmtbuf.buf, ap); + } + + va_end(ap); + + free(fmtbuf.buf); + + return (len > 0) ? len : 0; +} + + +static void +bk_enter_cli(uc_vm_t *vm, uc_breakpoint_t *bk); + +static void +bk_handle_catch(uc_vm_t *vm, uc_breakpoint_t *bk); + +static debug_breakpoint_t * +get_breakpoint(uc_vm_t *vm, debug_breakpoint_kind_t kind) +{ + debug_breakpoint_t *dbk; + + for (size_t i = 0; i < vm->breakpoints.count; i++) { + dbk = (debug_breakpoint_t *)vm->breakpoints.entries[i]; + + if (dbk != NULL && dbk->kind == kind) + return dbk; + } + + dbk = xalloc(sizeof(*dbk)); + dbk->kind = kind; + uc_vector_push(&vm->breakpoints, &dbk->bk); + + return dbk; +} + +static void +update_breakpoint(uc_vm_t *vm, debug_breakpoint_kind_t kind, + void (*cb)(uc_vm_t *, uc_breakpoint_t *), uint8_t *ip, + uc_function_t *fn, size_t depth) +{ + debug_breakpoint_t *dbk = get_breakpoint(vm, kind); + + dbk->bk.cb = cb; + dbk->depth = depth; + dbk->fn = fn; + + /* If the target instruction is the same then invoke handler directly */ + if (dbk->bk.ip == ip) + dbk->bk.cb(vm, &dbk->bk); + else + dbk->bk.ip = ip; +} + +static bool +free_breakpoint(uc_vm_t *vm, uc_breakpoint_t *bk) +{ + uc_breakpoints_t *bks = &vm->breakpoints; + bool found = false; + + /* Blank out breakpoint slot */ + for (size_t i = bks->count; i > 0; i--) { + if (bks->entries[i - 1] == bk) { + bks->entries[i - 1] = NULL; + found = true; + break; + } + } + + /* Cleanup empty tail of the breakpoint vector */ + while (bks->count > 0 && bks->entries[bks->count - 1] == NULL) + bks->count--; + + free(bk); + + return found; +} + +static size_t +patch_breakpoint(uc_vm_t *vm, uc_function_t *fn, size_t insnoff, + debug_breakpoint_kind_t kind, size_t depth) +{ + debug_breakpoint_t *dbk = xalloc(sizeof(debug_breakpoint_t)); + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uc_breakpoints_t *bks = &vm->breakpoints; + + dbk->bk.ip = fn ? &fn->chunk.entries[insnoff] : NULL; + dbk->bk.cb = bk_enter_cli; + dbk->fn = fn; + dbk->kind = kind; + dbk->depth = depth; + + /* When the user breakpoint to be installed is at the same instruction + offset as the current VM instruction pointer then ensure to append it + to the breakpoint stack, otherwise reclaim free entry. */ + if (frame == NULL || frame->ip != dbk->bk.ip) { + for (size_t i = 0; i < bks->count; i++) { + if (bks->entries[i] == NULL) { + bks->entries[i] = &dbk->bk; + + return i + 1; + } + } + } + + uc_vector_push(bks, &dbk->bk); + + return bks->count; +} + +static bool +filename_matches_pattern(const char *filename, const char *pattern) +{ + if (strchr(pattern, '/') || strchr(pattern, '*')) + return (fnmatch(filename, pattern, 0) == 0); + + const char *basename = strrchr(filename, '/'); + + if (basename) + return (strcmp(basename + 1, pattern) == 0); + + return false; +} + +static bool +lookup_source(uc_vm_t *vm, location_t *loc) +{ + uc_stringbuf_t pattern = { 0 }, filename = { 0 }; + uc_weakref_t *ref; + uc_closure_t *uc; + + if (loc->program != NULL && loc->source != NULL) + return true; + + if (loc->path == NULL) + return false; + + printbuf_append_srcpath(&pattern, + &((uc_source_t){ .filename = (char *)loc->path }), SIZE_MAX); + + /* iterate all existing closures to find programs */ + for (ref = vm->values.next; ref != &vm->values; ref = ref->next) { + uc = (uc_closure_t *)((uintptr_t)ref - offsetof(uc_closure_t, ref)); + + if (uc->header.type != UC_CLOSURE) + continue; + + if (!uc->function || !uc->function->program) + continue; + + uc_program_t *program = uc->function->program; + + /* iterate all program sources looking for a patchname match */ + for (size_t i = 0; i < program->sources.count; i++) { + uc_source_t *source = program->sources.entries[i]; + + printbuf_append_srcpath(&filename, source, SIZE_MAX); + + if (filename_matches_pattern(filename.buf, pattern.buf)) { + size_t col = (loc->column > 0) ? loc->column - 1 : 0; + size_t rem = (loc->line > 0) ? loc->line - 1 : 0; + uc_lineinfo_t *lines = &source->lineinfo; + + /* iterate line lengths looking for exact offset */ + for (size_t j = 0, llen = 0, off = 0; j < lines->count; j++) { + size_t bytes = lines->entries[j] & 0x7f; + + if (rem == 0 && col >= llen && col <= llen + bytes) { + loc->program = program; + loc->source = source; + loc->offset = off + llen + col; + + free(filename.buf); + free(pattern.buf); + + return true; + } + + llen += bytes; + + if (j > 0 && lines->entries[j] & 0x80) { + off += llen + 1; + llen = 0; + rem--; + } + } + } + + printbuf_reset(&filename); + } + } + + free(filename.buf); + free(pattern.buf); + + return false; +} + +static bool +lookup_offset(uc_vm_t *vm, location_t *loc) +{ + if (!lookup_source(vm, loc)) + return false; + + size_t column = (loc->column > 0) ? loc->column - 1 : 0; + size_t remaining = (loc->line > 0) ? loc->line - 1 : 0; + uc_lineinfo_t *lines = &loc->source->lineinfo; + + /* iterate line lengths looking for exact offset */ + for (size_t j = 0, linelen = 0, offset = 0; j < lines->count; j++) { + size_t bytes = lines->entries[j] & 0x7f; + + if (remaining == 0 && column >= linelen && column <= linelen + bytes) { + loc->offset = offset + linelen + column; + + return true; + } + + linelen += bytes; + + if (j > 0 && lines->entries[j] & 0x80) { + offset += linelen + 1; + linelen = 0; + remaining--; + } + } + + return false; +} + +static bool +lookup_function(uc_vm_t *vm, location_t *loc) +{ + if (loc->function != NULL) + return true; + + if (!lookup_offset(vm, loc)) + return false; + + uc_program_function_foreach(loc->program, fn) { + if (uc_program_function_source(fn) != loc->source) + continue; + + size_t beg = uc_program_function_srcpos(fn, 0); + size_t end = uc_program_function_srcpos(fn, SIZE_MAX); + + if (beg <= loc->offset && end >= loc->offset) { + loc->function = fn; + + return true; + } + } + + return false; +} + +static bool +lookup_stmt_boundary(uc_vm_t *vm, location_t *loc, insn_span_t *sp) +{ + if (!lookup_function(vm, loc)) + return false; + + struct { insn_span_t *entries; size_t count; } sp_stack = { 0 }; + uc_chunk_t *chunk = &loc->function->chunk; + uc_offsetinfo_t *offsets = &chunk->debuginfo.offsets; + size_t bytes = loc->function->srcpos; + insn_span_t *s = NULL; + + for (size_t i = 0, insns = 0; i < offsets->count; i++) { + uc_offset_t *o = &offsets->entries[i]; + + if (o->bytes & 0x80) { + size_t nesting = sp_stack.count + 1; + + s = uc_vector_add(&sp_stack, { + .nesting = nesting, + .off_start = i, + .pos_start = bytes, + .pos_ip = bytes, + .ip_start = chunk->entries + insns + }); + } + + bytes += o->bytes & 0x7f; + insns += o->insns & 0x7f; + + if (insns > chunk->count) + goto not_found; /* out of range / invalid offset coding */ + + if (o->insns & 0x80) { + if (sp_stack.count == 0) + goto not_found; /* invalid offset coding */ + + s->off_end = i; + s->pos_end = bytes; + s->ip_end = chunk->entries + insns; + + if (s->pos_start <= loc->offset && s->pos_end >= loc->offset) + goto found; + + s = --sp_stack.count ? uc_vector_last(&sp_stack) : NULL; + } + + if (bytes > loc->offset && s == NULL) + goto not_found; /* past searched offset w/o matching range start */ + } + +not_found: + memset(sp, 0, sizeof(*sp)); + uc_vector_clear(&sp_stack); + + return false; + +found: + *sp = *uc_vector_last(&sp_stack); + uc_vector_clear(&sp_stack); + + return true; +} + +static size_t +add_breakpoint(uc_vm_t *vm, const char *path, size_t line, size_t byte, + debug_breakpoint_kind_t kind) +{ + location_t loc = { .path = path, .line = line, .column = byte }; + insn_span_t stmt; + + if (!lookup_stmt_boundary(vm, &loc, &stmt)) + return 0; + + return patch_breakpoint(vm, loc.function, + stmt.ip_start - loc.function->chunk.entries, kind, stmt.nesting); +} + +static uint8_t * +next_parent(uc_vm_t *vm, uc_function_t **fnp) +{ + for (size_t i = vm->callframes.count - 1; i > 0; i--) { + uc_callframe_t *pframe = &vm->callframes.entries[i - 1]; + + if (!pframe->closure) + continue; + + *fnp = pframe->closure->function; + + return pframe->ip; + } + + return NULL; +} + +static bool +find_statement_boundaries(uc_function_t *fn, uint8_t *ip, size_t depth, insn_span_t *sp) +{ + struct { insn_span_t *entries; size_t count; } sp_stack = { 0 }; + uc_offsetinfo_t *offsets = &fn->chunk.debuginfo.offsets; + size_t off = ip - fn->chunk.entries; + size_t i = 0, bytes = 0, insns = 0; + insn_span_t *s = NULL; + + for (i = 0; i < offsets->count; i++) { + uc_offset_t *o = &offsets->entries[i]; + + bytes += o->bytes & 0x7f; + + if (o->bytes & 0x80) { + size_t nesting = sp_stack.count + 1; + + s = uc_vector_add(&sp_stack, { + .nesting = nesting, + .off_start = i, + .pos_start = fn->srcpos + bytes, + .pos_ip = fn->srcpos + bytes, + .ip_start = &fn->chunk.entries[insns] + }); + } + + if (insns <= off && insns + (o->insns & 0x7f) > off && s != NULL) + s->pos_ip = fn->srcpos + bytes; + + insns += o->insns & 0x7f; + + if (insns > fn->chunk.count) + goto not_found; /* out of range / invalid offset codiing */ + + if (o->insns & 0x80) { + if (sp_stack.count == 0) + goto not_found; /* invalid offset coding */ + + if (depth == 0 || sp_stack.count == depth) { + s->off_end = i; + s->pos_end = fn->srcpos + bytes; + s->ip_end = &fn->chunk.entries[insns]; + + if (s->ip_start <= ip && s->ip_end > ip) + goto found; + } + + s = --sp_stack.count ? uc_vector_last(&sp_stack) : NULL; + } + + if (insns > off && s == NULL) + goto not_found; /* past searched offset w/o matching range start */ + } + +not_found: + memset(sp, 0, sizeof(*sp)); + uc_vector_clear(&sp_stack); + + return false; + +found: + *sp = *uc_vector_last(&sp_stack); + uc_vector_clear(&sp_stack); + + return true; +} + +static void +term_dimensions(void) +{ + struct winsize w; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) { + termstate.rows = w.ws_row; + termstate.cols = w.ws_col; + } + else { + termstate.rows = 26; + termstate.cols = 80; + } +} + +static size_t +term_width(void) +{ + if (termstate.cols == 0) + term_dimensions(); + + return termstate.cols; +} + +static void +term_reset(void) +{ + if (tcsetattr(STDOUT_FILENO, TCSAFLUSH, &termstate.orig_settings) == -1) + fprintf(stderr, "tcsetattr(): %m\n"); + + while (termstate.patterns.count > 0) { + regex_t *re = &termstate.patterns.entries[--termstate.patterns.count]; + if (re) regfree(re); + } + + while (termstate.history.count > 0) + free(termstate.history.entries[--termstate.history.count].chars); + + uc_vector_clear(&termstate.patterns); + uc_vector_clear(&termstate.history); +} + +static bool +term_raw(void) +{ + if (tcgetattr(STDOUT_FILENO, &termstate.orig_settings) == -1) { + fprintf(stderr, "tcgetattr(): %m\n"); + + return false; + } + + atexit(term_reset); + + termstate.curr_settings = termstate.orig_settings; + + termstate.curr_settings.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + termstate.curr_settings.c_cflag |= (CS8); + termstate.curr_settings.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + termstate.curr_settings.c_cc[VMIN] = 0; + termstate.curr_settings.c_cc[VTIME] = 1; + + if (tcsetattr(STDOUT_FILENO, TCSAFLUSH, &termstate.curr_settings) == -1) { + fprintf(stderr, "tcsetattr(): %m\n"); + + return false; + } + + return true; +} + +static bool +term_isig(bool enable) +{ + struct termios t; + + if (tcgetattr(STDOUT_FILENO, &t) == -1) { + fprintf(stderr, "tcgetattr(): %m\n"); + + return false; + } + + if (enable) + t.c_lflag |= ISIG; + else + t.c_lflag &= ~ISIG; + + if (tcsetattr(STDOUT_FILENO, TCSAFLUSH, &t) == -1) { + fprintf(stderr, "tcsetattr(): %m\n"); + + return false; + } + + return true; +} + +static ssize_t +fgetline(FILE *stream, char **buf, size_t *bufsize) +{ + ssize_t n = 0; + + while (true) { + n = getline(buf, bufsize, stream); + + if (n == -1 && errno == EINTR) { + clearerr(stream); + continue; + } + + break; + } + + return n; +} + +static int +term_getc_raw(void) +{ + ssize_t rlen; + + if (termstate.pos >= termstate.fill) { + while (true) { + rlen = read(STDIN_FILENO, termstate.data, sizeof(termstate.data)); + + if (rlen == -1) { + if (errno == EINTR) + continue; + + return -1; + } + + if (rlen == 0) + continue; + + termstate.fill = rlen; + termstate.pos = 0; + break; + } + } + + return termstate.data[termstate.pos++]; +} + +static bool is_utf8_2b(char c) { return (c & 0xe0) == 0xc0; } +static bool is_utf8_3b(char c) { return (c & 0xf0) == 0xe0; } +static bool is_utf8_4b(char c) { return (c & 0xf8) == 0xf0; } +static bool is_utf8_ct(char c) { return (c & 0xc0) == 0x80; } + +static int +term_getc(void) +{ + int chr = term_getc_raw(); + int seq[5]; + + /* escape sequence */ + if (chr == '\033') { + if ((seq[0] = term_getc_raw()) == -1) return '\033'; + if ((seq[1] = term_getc_raw()) == -1) return '\033'; + + switch (seq[0]) { + case '[': + switch (seq[1]) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if ((seq[2] = term_getc_raw()) == -1) return '\033'; + + switch (seq[2]) { + case '~': + switch (seq[1]) { + case '1': return HOME_KEY; + case '3': return DEL_KEY; + case '4': return END_KEY; + case '5': return PAGE_UP; + case '6': return PAGE_DOWN; + case '7': return HOME_KEY; + case '8': return END_KEY; + } + break; + + case ';': + if ((seq[3] = term_getc_raw()) == -1) return '\033'; + + switch (seq[3]) { + case '5': + if ((seq[4] = term_getc_raw()) == -1) return '\033'; + + switch (seq[4]) { + case 'A': return CTRL_UP; + case 'B': return CTRL_DOWN; + case 'C': return CTRL_RIGHT; + case 'D': return CTRL_LEFT; + } + break; + } + break; + } + break; + + case 'A': return ARROW_UP; + case 'B': return ARROW_DOWN; + case 'C': return ARROW_RIGHT; + case 'D': return ARROW_LEFT; + case 'H': return HOME_KEY; + case 'F': return END_KEY; + } + break; + + case 'O': + switch (seq[1]) { + case 'H': return HOME_KEY; + case 'F': return END_KEY; + } + break; + } + + return '\033'; + } + + /* two byte utf-8 sequence */ + if (is_utf8_2b(chr) && + is_utf8_ct(seq[0] = term_getc_raw())) + { + return ((chr & 0x1f) << 6) | + (seq[0] & 0x3f); + } + + /* three byte utf-8 sequence */ + if (is_utf8_3b(chr) && + is_utf8_ct(seq[0] = term_getc_raw()) && + is_utf8_ct(seq[1] = term_getc_raw())) + { + return ((chr & 0x0f) << 12) | + ((seq[0] & 0x3f) << 6) | + (seq[1] & 0x3f); + } + + /* four byte utf-8 sequence */ + if (is_utf8_4b(chr) && + is_utf8_ct(seq[0] = term_getc_raw()) && + is_utf8_ct(seq[1] = term_getc_raw()) && + is_utf8_ct(seq[2] = term_getc_raw())) + { + return ((chr & 0x07) << 18) | + ((seq[0] & 0x3f) << 12) | + ((seq[1] & 0x3f) << 6) | + (seq[2] & 0x3f); + } + + return chr; +} + +static bool +term_write(const char *s, size_t len) +{ + ssize_t wlen = write(STDOUT_FILENO, s, len); + + return (wlen > -1 && (size_t)wlen == len); +} + +#define term_print(x) term_write(x, sizeof(x) - 1) +#define term_printf(fmt, ...) dprintf(STDOUT_FILENO, fmt, __VA_ARGS__) + +static void +uc_vector_addcp(void *vec, uint32_t cp) +{ + struct { size_t count; char *entries; } *v = vec; + + if (cp <= 0x7F) { + uc_vector_add(v, cp); + } + else if (cp <= 0x7FF) { + uc_vector_add(v, ((cp >> 6) & 0x1F) | 0xC0); + uc_vector_add(v, ( cp & 0x3F) | 0x80); + } + else if (cp <= 0xFFFF) { + uc_vector_add(v, ((cp >> 12) & 0x0F) | 0xE0); + uc_vector_add(v, ((cp >> 6) & 0x3F) | 0x80); + uc_vector_add(v, ( cp & 0x3F) | 0x80); + } + else if (cp <= 0x10FFFF) { + uc_vector_add(v, ((cp >> 18) & 0x07) | 0xF0); + uc_vector_add(v, ((cp >> 12) & 0x3F) | 0x80); + uc_vector_add(v, ((cp >> 6) & 0x3F) | 0x80); + uc_vector_add(v, ( cp & 0x3F) | 0x80); + } +} + +static bool +term_line_parsearg(termline_t *line, size_t *off, arg_t *arg, bool silent) +{ + struct { size_t count; char *entries; } buf = { 0 }, nesting = { 0 }; + uint32_t *end, *cp, q; + unsigned long n; + bool esc; + + if (line == NULL || *off >= line->width) { + arg->type = ARGTYPE_NONE; + arg->off = line->width; + arg->sv = NULL; + arg->nv = 0; + + return false; + } + + end = line->chars + line->width; + cp = line->chars + *off; + + while (cp < end && strchr(" \t\r\n", *cp) != NULL) + cp++; + + arg->off = cp - line->chars; + + if (cp < end && strchr("\"'", *cp) != NULL) { + for (esc = false, q = *cp++; cp < end; cp++) { + if (esc) { + if (cp[0] >= '0' && cp[0] <= '7') { + int n = cp[0] - '0'; + int i = 0; + + if (cp[1] >= '0' && cp[1] <= '7') { + n = n * 8 + (cp[1] - '0'); + i++; + + if (cp[2] >= '0' && cp[2] <= '7') { + n = n * 8 + (cp[2] - '0'); + i++; + } + } + + if (n <= 255) { + uc_vector_addcp(&buf, n); + } + else { + uc_vector_add(&buf, cp[-1]); + uc_vector_add(&buf, cp[0]); + if (i > 0) uc_vector_addcp(&buf, cp[1]); + if (i > 1) uc_vector_addcp(&buf, cp[2]); + } + + cp += i; + } + else if (cp[0] == 'x') { + char c = cp[1]|32; + char d = c ? cp[2]|32 : 0; + + if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) && + ((d >= '0' && d <= '9') || (d >= 'a' && d <= 'f'))) + { + uc_vector_add(&buf, + (c > '9' ? 10 + c - 'a' : c - '0') * 16 + + (d > '9' ? 10 + d - 'a' : d - '0')); + } + else { + uc_vector_add(&buf, cp[-1]); + uc_vector_add(&buf, cp[0]); + if (c) uc_vector_addcp(&buf, cp[1]); + if (d) uc_vector_addcp(&buf, cp[2]); + } + + cp += !!c + !!d; + } + else { + switch (cp[0]) { + case 'n': uc_vector_add(&buf, '\n'); break; + case 't': uc_vector_add(&buf, '\t'); break; + case 'r': uc_vector_add(&buf, '\r'); break; + case 'b': uc_vector_add(&buf, '\b'); break; + default: uc_vector_addcp(&buf, *cp); break; + } + } + + esc = false; + continue; + } + + if (*cp == '\\') { + esc = true; + continue; + } + + if (*cp == q) + break; + + uc_vector_addcp(&buf, *cp); + } + + *off = cp - line->chars; + + uc_vector_add(&buf, 0); + + arg->sv = buf.entries, buf.entries = NULL; + arg->nv = buf.count; + + uc_vector_clear(&buf); + + if (esc == true || cp == end || *cp != q) { + if (!silent) + term_print("Unterminated string\n"); + + arg->type = ARGTYPE_ERROR; + } + else { + arg->type = ARGTYPE_STRING; + } + + return true; + } + + for (n = 0; cp < end && *cp >= '0' && *cp <= '9'; cp++) { + uint32_t d = *cp - '0'; + + uc_vector_add(&buf, *cp); + + if (n > ULONG_MAX / 10) { + n = ULONG_MAX; + continue; + } + + n *= 10; + + if (n > ULONG_MAX - d) { + n = ULONG_MAX; + continue; + } + + n += d; + } + + *off = cp - line->chars; + + if (buf.count > 0 && (cp == end || strchr(" \t\r\n", *cp) != NULL)) { + uc_vector_add(&buf, 0); + + arg->type = ARGTYPE_NUMBER; + arg->sv = buf.entries, buf.entries = NULL; + arg->nv = n; + + uc_vector_clear(&buf); + + return true; + } + + for (esc = false, q = 0; cp < end; cp++) { + if (esc) { + esc = false; + } + else if (*cp == '\\') { + esc = true; + } + else if (q != 0 && *cp == q) { + q = 0; + } + else if (q == 0) { + switch (*cp) { + case '(': uc_vector_push(&nesting, ')'); break; + case '{': uc_vector_push(&nesting, '}'); break; + case '[': uc_vector_push(&nesting, ']'); break; + + case '"': + case '\'': + q = *cp; + break; + + case ']': + case '}': + case ')': + if (nesting.count > 0 && *uc_vector_last(&nesting) == (char)*cp) + nesting.count--; + + break; + } + } + + if (strchr(" \t\r\n", *cp) && nesting.count == 0 && esc == false) + break; + + uc_vector_addcp(&buf, *cp); + } + + uc_vector_clear(&nesting); + + *off = cp - line->chars; + + n = buf.count; + + uc_vector_add(&buf, 0); + + arg->sv = buf.entries, buf.entries = NULL; + arg->nv = n; + + uc_vector_clear(&buf); + + if (esc == true || q != 0) { + if (!silent) + term_print("Unterminated string\n"); + + arg->type = ARGTYPE_ERROR; + } + else if (n > 0 || silent == true) { + arg->type = ARGTYPE_STRING; + } + else { + free(arg->sv); + + arg->type = ARGTYPE_NONE; + arg->sv = NULL; + + return false; + } + + return true; +} + +static size_t +term_line_toargv(termline_t *line, bool silent, arg_t **argp) +{ + struct { size_t count; arg_t *entries; } argv = { 0 }; + size_t off = 0; + + while (true) { + arg_t arg; + argtype_t t = term_line_parsearg(line, &off, &arg, silent); + + if (t == ARGTYPE_NONE) + break; + + uc_vector_add(&argv, arg); + } + + *argp = argv.entries; + + return argv.count; +} + +static size_t +term_line_fromstr(termline_t *line, size_t from, char *s, size_t len) +{ + size_t needed = 0; + + for (const char *p = s, *e = s + len; p < e; needed++) { + if (is_utf8_2b(p[0]) && is_utf8_ct(p[1])) + p += 2; + else if (is_utf8_3b(p[0]) && is_utf8_ct(p[1]) && + is_utf8_ct(p[2])) + p += 3; + else if (is_utf8_4b(p[0]) && is_utf8_ct(p[1]) && + is_utf8_ct(p[2]) && is_utf8_ct(p[3])) + p += 4; + else + p++; + } + + if (from + needed > line->size) { + line->size = ((from + needed + 127) >> 7) << 7; + line->chars = xrealloc(line->chars, line->size * sizeof(*line->chars)); + } + + uint32_t *cp = line->chars + from; + + for (const char *p = s, *e = s + len; p < e; cp++) { + if (is_utf8_2b(p[0]) && is_utf8_ct(p[1])) { + *cp = ((*p++ & 0x1f) << 6); + *cp |= (*p++ & 0x3f); + } + else if (is_utf8_3b(p[0]) && is_utf8_ct(p[1]) && + is_utf8_ct(p[2])) { + *cp = ((*p++ & 0x0f) << 12); + *cp |= ((*p++ & 0x3f) << 6); + *cp |= (*p++ & 0x3f); + } + else if (is_utf8_4b(p[0]) && is_utf8_ct(p[1]) && + is_utf8_ct(p[2]) && is_utf8_ct(p[3])) { + *cp = ((*p++ & 0x07) << 18); + *cp |= ((*p++ & 0x3f) << 12); + *cp |= ((*p++ & 0x3f) << 6); + *cp |= (*p++ & 0x3f); + } + else { + *cp = *p++; + } + } + + line->width = cp - line->chars; + + return line->width; +} + +static bool +term_line_setcur(termline_t *line, size_t pos) +{ + size_t columns = term_width(); + size_t from_row = (termstate.col_offset + line->pos) / columns; + size_t from_col = ((termstate.col_offset + line->pos) % columns) + 1; + size_t to_row = (termstate.col_offset + pos) / columns; + size_t to_col = ((termstate.col_offset + pos) % columns) + 1; + size_t len = 0; + char buf[64]; + + if (from_row > to_row) + len = snprintf(buf, sizeof(buf), "\033[%zuA", from_row - to_row); + else if (from_row < to_row) + len = snprintf(buf, sizeof(buf), "\033[%zuB", to_row - from_row); + + if (from_col > to_col) + len += snprintf(buf + len, sizeof(buf) - len, + "\033[%zuD", from_col - to_col); + else if (from_col < to_col) + len += snprintf(buf + len, sizeof(buf) - len, + "\033[%zuC", to_col - from_col); + + line->pos = pos; + + return (len == 0 || term_write(buf, len) == true); +} + +static bool +term_line_clear(termline_t *line, size_t from) +{ + /* move cursor to initial position, erase screen after curser */ + return term_line_setcur(line, from) && term_print("\033[0J"); +} + +static bool +term_line_needlf(termline_t *line) +{ + return (((termstate.col_offset + line->width) % term_width()) == 0); +} + +static bool +term_line_write(termline_t *line, size_t off) +{ + struct { size_t count; char *entries; } buf = { 0 }; + bool ret; + + for (size_t i = off; i < line->width; i++) + uc_vector_addcp(&buf, line->chars[i]); + + ret = (buf.count == 0 || term_write(buf.entries, buf.count) == true); + + uc_vector_clear(&buf); + + /* if the printed string filled the entire line then print one more + character and erase it again in order to force scrolling to the next + line */ + if (term_line_needlf(line)) + ret &= term_print(" \033[1D\033[0K"); + + line->pos = line->width; + + return ret; +} + +static bool +term_line_cancel(termline_t *line) +{ + term_print("^C"); + term_line_setcur(line, line->width); + term_print("\n"); + + return true; +} + +static bool +term_line_prevword(termline_t *line) +{ + if (line->width == 0) + return true; + + /* find offset */ + size_t off = (line->pos < line->width) ? line->pos : line->width - 1; + + /* skip spaces before cursor */ + while (off > 0 && strchr(" \t", line->chars[off - 1]) != NULL) + off--; + + /* skip non-whitespace before cursor */ + while (off > 0 && strchr(" \t", line->chars[off - 1]) == NULL) + off--; + + return term_line_setcur(line, off); +} + +static bool +term_line_nextword(termline_t *line) +{ + if (line->width == 0) + return true; + + /* find offset */ + size_t off = (line->pos < line->width) ? line->pos : line->width - 1; + + /* skip spaces after cursor */ + while (off < line->width && strchr(" \t", line->chars[off]) != NULL) + off++; + + /* skip non-whitespace after cursor */ + while (off < line->width && strchr(" \t", line->chars[off]) == NULL) + off++; + + return term_line_setcur(line, off); +} + +static bool +term_line_delchr(termline_t *line) +{ + if (line->pos >= line->width || line->width == 0) + return true; + + /* remember original position */ + size_t pos = line->pos; + + /* move cursor before last char, erase */ + term_line_setcur(line, line->width - 1); + term_print("\033[0K"); + + /* move cursor to original position */ + term_line_setcur(line, pos); + + /* rearrange char buffer */ + for (size_t i = pos + 1; i < line->width; i++) + line->chars[i-1] = line->chars[i]; + + line->width--; + + /* re-write tail, will move cursor to eol */ + term_line_write(line, pos); + + /* reset cursor to original position */ + term_line_setcur(line, pos); + + return true; +} + +static bool +term_line_delword(termline_t *line) +{ + if (line->width == 0) + return true; + + /* find offset */ + size_t off = (line->pos < line->width) ? line->pos : line->width - 1; + + /* skip spaces before cursor */ + while (off > 0 && strchr(" \t", line->chars[off - 1]) != NULL) + off--; + + /* skip non-whitespace before cursor */ + while (off > 0 && strchr(" \t", line->chars[off - 1]) == NULL) + off--; + + /* calculate shift offset */ + size_t shift = line->pos - off; + + if (shift > 0) { + /* erase everything after offset */ + term_line_clear(line, off); + + /* rearrange char buffer */ + for (size_t i = off + shift; i < line->width; i++) + line->chars[i - shift] = line->chars[i]; + + line->width -= shift; + + /* re-write tail, will move cursor to eol */ + term_line_write(line, off); + + /* reset cursor to offset position */ + term_line_setcur(line, off); + } + + return true; +} + +static bool +term_line_addchr(termline_t *line, uint32_t chr) +{ + if (line->width == line->size) { + line->size += 128; + line->chars = xrealloc(line->chars, line->size * sizeof(*line->chars)); + } + + size_t pos = line->pos; + + for (size_t i = line->width; i > pos; i--) + line->chars[i] = line->chars[i-1]; + + line->chars[pos] = chr; + line->width++; + + /* write tail, will move cursor to eol */ + term_line_write(line, pos); + + /* restore cursor to original position + 1 */ + term_line_setcur(line, pos + 1); + + return true; +} + +static int +qsort_strcmp(const void *a, const void *b) +{ + return strcmp(*(const char **)a, *(const char **)b); +} + +static char * +common_prefix(suggestions_t *suggests) +{ + if (!suggests || suggests->count == 0 || *suggests->entries[0] == '\0') + return NULL; + + char *prefix = xstrdup(suggests->entries[0]); + size_t prefixlen = strlen(prefix); + + for (size_t i = 1; i < suggests->count; i++) { + while (strncmp(suggests->entries[i], prefix, prefixlen) != 0) { + prefix[--prefixlen] = '\0'; + + if (prefixlen == 0) { + free(prefix); + + return NULL; + } + } + } + + return prefix; +} + +static void +term_line_tabcomplete(termline_t *line, const char *prompt, + void (*cb)(size_t, arg_t *, suggestions_t *, void *), + void *ud) +{ + arg_t *argv = NULL; + size_t argc = term_line_toargv(line, true, &argv); + + suggestions_t suggests = { 0 }; + + cb(argc, argv, &suggests, ud); + + if (suggests.count > 1) { + size_t longest = 0; + + for (size_t i = 0; i < suggests.count; i++) { + size_t itemlen = strlen(suggests.entries[i]) + 2; + + if (itemlen > longest) + longest = itemlen; + } + + size_t cols = term_width() / longest; + + if (cols == 0) + cols = 1; + + qsort(suggests.entries, suggests.count, + sizeof(suggests.entries[0]), qsort_strcmp); + + term_print("\n"); + + for (size_t row = 0; row < suggests.count; row += cols) { + for (size_t col = 0; col < cols && row + col < suggests.count; col++) + term_printf("%-*s", (int)longest, suggests.entries[row + col]); + + term_print("\n"); + } + + fflush(stdout); + + if (prompt) + term_write(prompt, termstate.col_offset); + } + else { + term_line_clear(line, 0); + } + + char *prefix = common_prefix(&suggests); + + if (prefix) { + if (argc > 0) { + arg_t *partial = argv + argc - 1; + + term_line_fromstr(line, partial->off, prefix, strlen(prefix)); + } + else { + term_line_fromstr(line, line->width, prefix, strlen(prefix)); + } + + if (suggests.count == 1) + term_line_fromstr(line, line->width, " ", 1); + + free(prefix); + } + + term_line_write(line, 0); + + for (size_t i = 0; i < suggests.count; i++) + free(suggests.entries[i]); + + uc_vector_clear(&suggests); + + for (size_t i = 0; i < argc; i++) + free(argv[i].sv); + + free(argv); +} + +static ssize_t +term_getline(const char *prompt, arg_t **argv, + void (*completion_cb)(size_t, arg_t *, suggestions_t *, void *), + void *ud) +{ + termline_t line = { 0 }; + termline_t *curr_line = &line; + termline_t *next_line; + + if (prompt != NULL) { + termstate.col_offset = strwidth(prompt); + term_write(prompt, termstate.col_offset); + } + else { + termstate.col_offset = 0; + } + + while (true) { + int chr = term_getc(); + + switch (chr) { + case HOME_KEY: + case CTRL_UP: + term_line_setcur(curr_line, 0); + break; + + case END_KEY: + case CTRL_DOWN: + term_line_setcur(curr_line, curr_line->width); + break; + + case DEL_KEY: + term_line_delchr(curr_line); + break; + + case PAGE_UP: + case ARROW_UP: + if (termstate.history.count > 0 && + curr_line != uc_vector_first(&termstate.history)) { + + if (curr_line == &line) + next_line = uc_vector_last(&termstate.history); + else + next_line = curr_line - 1; + + term_line_clear(curr_line, 0); + term_line_write(next_line, 0); + curr_line = next_line; + } + break; + + case PAGE_DOWN: + case ARROW_DOWN: + if (termstate.history.count > 0 && curr_line != &line) { + if (curr_line == uc_vector_last(&termstate.history)) + next_line = &line; + else + next_line = curr_line + 1; + + term_line_clear(curr_line, 0); + term_line_write(next_line, 0); + curr_line = next_line; + } + break; + + case ARROW_LEFT: + if (curr_line->pos > 0) + term_line_setcur(curr_line, curr_line->pos - 1); + break; + + case ARROW_RIGHT: + if (curr_line->pos < curr_line->width) + term_line_setcur(curr_line, curr_line->pos + 1); + break; + + case CTRL_LEFT: + term_line_prevword(curr_line); + break; + + case CTRL_RIGHT: + term_line_nextword(curr_line); + break; + + case '\3': /* Ctrl-C */ + term_line_cancel(curr_line); + + *argv = NULL; + + return 0; + + case '\11': /* tab */ + if (completion_cb != NULL) + term_line_tabcomplete(curr_line, prompt, completion_cb, ud); + break; + + case '\15': /* carriage return */ + /* save to history if no other line was selected */ + if (curr_line == &line && curr_line->width > 0) { + if (termstate.history.count >= HISTORY_SIZE) { + free(termstate.history.entries[0].chars); + + for (size_t i = 1; i < termstate.history.count; i++) + termstate.history.entries[i-1] = + termstate.history.entries[i]; + + termstate.history.count--; + } + + uc_vector_push(&termstate.history, line); + } + + term_print("\n"); + + return term_line_toargv(curr_line, false, argv); + + case '\27': /* Ctrl-W */ + term_line_delword(curr_line); + break; + + case '\177': /* backspace */ + if (curr_line->pos > 0) { + term_line_setcur(curr_line, curr_line->pos - 1); + term_line_delchr(curr_line); + } + break; + + default: + if (chr >= ' ') + term_line_addchr(curr_line, chr); + break; + } + } + + *argv = NULL; + + return -1; +} + +static uc_value_t * +uc_debug_sigint_handler(uc_vm_t *vm, size_t nargs); + +static size_t +format_context_breadcrumb(uc_stringbuf_t *sb, uc_vm_t *vm, size_t maxcols) +{ + int off = sb->bpos; + + for (size_t i = 0; i < vm->callframes.count; i++) { + uc_callframe_t *frame = &vm->callframes.entries[i]; + + if (frame->cfunction != NULL && + frame->cfunction->cfn == uc_debug_sigint_handler) + continue; + + if (sb->bpos > off) + printbuf_strappend(sb, " » "); + + printbuf_append_function(sb, vm, + frame->closure + ? &frame->closure->header : &frame->cfunction->header, + NULL, SIZE_MAX); + } + + return printbuf_truncate(sb, off, maxcols, false); +} + +static void +format_context_header_backtrace(uc_stringbuf_t *sb, uc_vm_t *vm) +{ + size_t columns = term_width(); + size_t filename_width = (columns >= 42) ? (columns - 2) / 4 : columns - 2; + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uc_source_t *source = uc_program_function_source(frame->closure->function); + size_t printed = 0; + + cs(sb, &((style_t){ FG_BWHITE, BG_GRAY, 0 })); + + printbuf_strappend(sb, "["); + printed += 2 + printbuf_append_srcpath(sb, source, filename_width); + printbuf_strappend(sb, "]"); + + if (columns - printed - 2 > 10) { + printbuf_strappend(sb, " "); + printed += 2 + format_context_breadcrumb(sb, vm, columns - printed - 2); + printbuf_strappend(sb, " "); + } + + printbuf_memset(sb, -1, ' ', columns - printed); + + cs(sb, NULL); +} + +static void +format_context_header_callframe(uc_stringbuf_t *sb, uc_vm_t *vm, + uc_callframe_t *frame, size_t left_pad) +{ + size_t columns = term_width() - left_pad; + size_t filename_width = (columns >= 42) ? (columns - 2) / 4 : columns - 2; + size_t printed = 0; + + printbuf_memset(sb, -1, ' ', left_pad); + + cs(sb, &((style_t){ FG_BWHITE, BG_GRAY, 0 })); + + if (frame->closure) { + uc_source_t *source = uc_program_function_source(frame->closure->function); + + printbuf_strappend(sb, "["); + printed += 2 + printbuf_append_srcpath(sb, source, filename_width); + printbuf_strappend(sb, "]"); + } + else { + printbuf_strappend(sb, "[C]"); + printed += 3; + } + + if (columns - printed - 2 > 10) { + printbuf_strappend(sb, " "); + printed += 2 + printbuf_append_function(sb, vm, + frame->closure ? &frame->closure->header : &frame->cfunction->header, + frame, columns - printed - 2); + printbuf_strappend(sb, " "); + } + + printbuf_memset(sb, -1, ' ', columns - printed); + + cs(sb, NULL); +} + +static bool have_highlighting = false; + +static struct { + fg_color_t color; + char *start, *end; +} highlight_rules[] = { + { FG_GRAY, "^#!.*", NULL }, + + /* declarations */ + { FG_GREEN, "\\<(let|const|function|this)\\>", NULL }, + + /* arrow functions */ + { FG_GREEN, "(\\<\\w+\\>|\\([[:alnum:][:space:]_,.]*\\))[[:space:]]*=>", NULL }, + + /* flow control */ + { FG_BYELLOW, "\\<(while|if|else|elif|switch|case|default|for|in|endif|endfor|endwhile|endfunction)\\>", NULL }, + + /* keywords */ + { FG_BYELLOW, "\\<(export|import|try|catch|delete)\\>", NULL }, + + /* exit points */ + { FG_MAGENTA, "\\<(break|continue|return)\\>", NULL }, + + /* numeric literals */ + { FG_CYAN, "\\<([0-9]+\\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)\\>", NULL }, + { FG_CYAN, "\\<0[xX][[:xdigit:]]+(\\.[[:xdigit:]]+)?\\>", NULL }, + { FG_CYAN, "\\<(0[oO][0-7]+|0[bB][01]+|[0-9]+)\\>", NULL }, + + /* special values */ + { FG_CYAN, "\\<(true|false|null|NaN|Infinity)\\>", NULL }, + + /* strings */ + { FG_BMAGENT, "\"([^\"\\{%#}]|\\\\.|\\{[^\"\\{%#]|[%#}][^\"\\}]|[{%#}]\\\\.)*[{%#}]?\"", NULL }, + { FG_BMAGENT, "'([^'\\{%#}]|\\\\.|\\{[^'\\{%#]|[%#}][^'\\}]|[{%#}]\\\\.)*[{%#}]?'", NULL }, + { FG_BMAGENT, "`([^`\\{%#}]|\\\\.|\\{[^`\\{%#]|[%#}][^`\\}]|[{%#}]\\\\.)*[{%#}]?`", NULL }, + + /* template string expressions */ + { FG_BWHITE, "\\$\\{", "}" }, + + /* comments */ + { FG_BBLUE, "(^|[[:blank:]])//.*", NULL }, + { FG_BBLUE, "(^|[[:space:]])/\\*", "\\*/" }, + { FG_BBLUE, "\\{#", "#\\}" }, + + /* text outside template directives */ + { FG_GRAY, "[}%#]\\}", "\\{[{%#]" }, + { FG_GRAY, "^#!.*(\\|[[:space:]]-[[:alnum:]]*T[[:alnum:]]*\\>)", "\\{[{%#]" }, + { FG_GRAY, "^([^{%#}]|\\{[^{%#]|[%#}][^}])+\\{[{%#]", NULL }, + + /* template tags */ + { FG_BWHITE, "\\{[{%][+-]?|-?[%}]\\}", NULL }, + { FG_BBLUE, "\\{#[+-]?|-?#\\}", NULL }, +}; + +static bool +compile_patterns(void) +{ + regex_t *re = NULL; + int err = 0; + + if (termstate.patterns.count > 0) + return true; + + for (size_t i = 0; i < ARRAY_SIZE(highlight_rules); i++) { + re = uc_vector_add(&termstate.patterns, { 0 }); + err = regcomp(re, highlight_rules[i].start, REG_EXTENDED); + + if (err != 0) + goto err; + + re = uc_vector_add(&termstate.patterns, { 0 }); + + if (highlight_rules[i].end) { + err = regcomp(re, highlight_rules[i].end, REG_EXTENDED); + + if (err != 0) + goto err; + } + } + + return true; + +err: + char errbuf[128]; + regerror(err, re, errbuf, sizeof(errbuf)); + fprintf(stderr, "Regex error: %s\n", errbuf); + + for (size_t i = 0; i < termstate.patterns.count; i++) { + regex_t *re = &termstate.patterns.entries[i]; + if (re) regfree(re); + } + + uc_vector_clear(&termstate.patterns); + + return false; +} + +typedef struct { + uint32_t style; + size_t from, to; +} style_range_t; + +typedef struct { + size_t count; + style_range_t *entries; +} style_ranges_t; + +typedef struct { + size_t from, to; +} line_range_t; + +static void +print_source_location(uc_stringbuf_t *sb, uc_vm_t *vm, uc_source_t *source, + size_t nranges, line_range_t *ranges, insn_span_t *hl, + size_t left_pad) +{ + size_t columns = term_width() - left_pad; + off_t offset = ftello(source->fp); + + fseeko(source->fp, 0, SEEK_SET); + + size_t linesize = 0, byte_pos = 0, start_line = SIZE_MAX, end_line = 0; + size_t hl_start = hl ? hl->pos_start : SIZE_MAX; + size_t cursor_pos = hl ? hl->pos_ip : SIZE_MAX; + size_t hl_end = hl ? hl->pos_end : SIZE_MAX; + style_t style = { FG_BWHITE, BG_BLACK, 0 }; + regex_t *ml_rule_re_end = NULL; + uint32_t ml_rule_color = 0; + ssize_t last_indent = -1; + char *linestr = NULL; + + for (size_t i = 0; i < nranges; i++) { + if (ranges[i].from == 0 || ranges[i].to == 0) + continue; + + if (ranges[i].from < start_line) + start_line = ranges[i].from; + + if (ranges[i].to > end_line) + end_line = ranges[i].to; + } + + for (size_t linenum = 1; linenum <= end_line; linenum++) { + ssize_t linelen = fgetline(source->fp, &linestr, &linesize); + + struct { + size_t count; + struct { fg_color_t color; ssize_t from, to; } *entries; + } colors = { 0 }; + + if (linelen == -1) + break; + + /* apply highlighting rules */ + if (have_highlighting) { + size_t ml_rule_from; + regmatch_t m; + char *p; + int rf; + + /* apply single line matches */ + for (size_t i = 0; i < ARRAY_SIZE(highlight_rules); i++) { + regex_t *re = &termstate.patterns.entries[i * 2]; + + /* only consider single line matches */ + if (highlight_rules[i].end != NULL) + continue; + + for (rf = 0, p = linestr; + regexec(re, p, 1, &m, rf) == 0; + rf = REG_NOTBOL, p += m.rm_eo) + { + uc_vector_add(&colors, { + .color = highlight_rules[i].color, + .from = p + m.rm_so - linestr, + .to = p + m.rm_eo - linestr + }); + } + } + + /* apply multi line matches */ + for (rf = 0, p = linestr, ml_rule_from = 0; + rf == 0 || ml_rule_re_end != NULL; + rf = REG_NOTBOL) { + + /* handle unterminated multiline matches */ + if (ml_rule_re_end != NULL) { + /* end match found on this line, colorize until match */ + if (regexec(ml_rule_re_end, p, 1, &m, 0) == 0) { + uc_vector_add(&colors, { + .color = ml_rule_color, + .from = ml_rule_from, + .to = p + m.rm_eo - linestr + }); + + ml_rule_re_end = NULL; + ml_rule_color = 0; + ml_rule_from = 0; + p += m.rm_eo; + } + + /* no end match, colorize entire remainder and skip rest */ + else { + uc_vector_add(&colors, { + .color = ml_rule_color, + .from = ml_rule_from, + .to = linelen + }); + + break; + } + } + + /* look for next multiline start match */ + for (size_t i = 0; i < ARRAY_SIZE(highlight_rules); i++) { + regex_t *re_start = &termstate.patterns.entries[i * 2]; + regex_t *re_end = &termstate.patterns.entries[i * 2 + 1]; + + /* only consider multi line rules */ + if (highlight_rules[i].end == NULL) + continue; + + /* found another multi line start */ + if (regexec(re_start, p, 1, &m, rf) == 0) { + ml_rule_re_end = re_end; + ml_rule_color = highlight_rules[i].color; + ml_rule_from = p + m.rm_so - linestr; + p += m.rm_eo; + break; + } + } + } + } + + bool print_line = false, more_lines = false; + + for (size_t i = 0; i < nranges; i++) { + if (ranges[i].from == 0 || ranges[i].to == 0) + continue; + + print_line |= (linenum >= ranges[i].from && linenum <= ranges[i].to); + more_lines |= (ranges[i].from > start_line && ranges[i].from == linenum + 1); + } + + if (!print_line) { + uc_vector_clear(&colors); + byte_pos += linelen; + + if (more_lines) { + printbuf_memset(sb, -1, ' ', left_pad); + cs(sb, &((style_t){ FG_GRAY, BG_BLACK, FAINT })); + printbuf_strappend(sb, " … "); + printbuf_memset(sb, -1, ' ', last_indent); + printbuf_strappend(sb, "…"); + printbuf_memset(sb, -1, ' ', columns - 6 - last_indent); + cs(sb, &((style_t){ FG_BWHITE, BG_BLACK, 0 })); + printbuf_strappend(sb, "\n"); + } + + continue; + } + + if (linelen > 0 && linestr[linelen - 1] == '\n') + linelen--; + + size_t trunc = 0; + + /* determine display width of line and whether it is too long */ + for (size_t i = 0, c = 0; i < (size_t)linelen; i++) { + c += (linestr[i] == '\t') ? 4 : 1; + + if (columns > 6 && c > columns - 6) { + trunc = linelen - i; + linelen = i; + break; + } + } + + size_t linecols = 0; + + printbuf_memset(sb, -1, ' ', left_pad); + cs(sb, &((style_t){ FG_GRAY, BG_BLACK, FAINT })); + sprintbuf(sb, "%4zu ", linenum); + cs(sb, &style); + + last_indent = -1; + + /* format line (substitute tabs and ctrls with placeholders) */ + for (ssize_t i = 0; i < linelen; i++, byte_pos++) { + style_t newstyle = { + .fg = FG_BWHITE, + .bg = (byte_pos >= hl_start && byte_pos < hl_end) + ? BG_GRAY : BG_BLACK, + .styles = (cursor_pos == byte_pos) ? ULINE : 0 + }; + + for (size_t j = 0; j < colors.count; j++) + if (colors.entries[j].from <= i && colors.entries[j].to > i) + newstyle.fg = colors.entries[j].color; + + if (memcmp(&style, &newstyle, sizeof(style))) { + style = newstyle; + cs(sb, &style); + } + + if (linestr[i] == '\t') { + linecols += 4; + cs(sb, &((style_t){ FG_BBLACK, style.bg, FAINT })); + printbuf_strappend(sb, "<-> "); + cs(sb, &style); + } + else if (linestr[i] < ' ' || linestr[i] == 0x7f) { + linecols++; + cs(sb, &((style_t){ FG_BBLACK, style.bg, FAINT })); + printbuf_strappend(sb, "."); + cs(sb, &style); + } + else { + if (last_indent == -1) + last_indent = linecols; + + linecols++; + printbuf_memappend_fast(sb, linestr + i, 1); + } + } + + /* reset char styles */ + style.styles = 0; + style.bg = (byte_pos >= hl_start && + byte_pos + trunc <= hl_end) ? BG_GRAY : BG_BLACK; + cs(sb, &style); + + /* if truncated, add ellipsis */ + if (trunc > 0) { + if (linecols < columns - 6) + printbuf_memset(sb, -1, ' ', (columns - 6) - linecols); + + printbuf_strappend(sb, "…"); + byte_pos += trunc; + } + + /* if shorter than display width, pad with trailing spaces */ + else if (linecols < columns - 5) { + if (linestr[linelen] == '\n') { + printbuf_memset(sb, -1, ' ', 1); + linecols++; + } + + if (style.bg != BG_BLACK) { + style.bg = BG_BLACK; + cs(sb, &style); + } + + printbuf_memset(sb, -1, ' ', (columns - 5) - linecols); + } + + cs(sb, &((style_t){ 0, 0, 0 })); + printbuf_strappend(sb, "\n"); + + uc_vector_clear(&colors); + + byte_pos++; + } + + free(linestr); + + fseeko(source->fp, offset, SEEK_SET); +} + +static void +format_context_statement(uc_stringbuf_t *sb, uc_vm_t *vm, + uc_function_t *fn, insn_span_t *stmt, + size_t ctx_before, size_t ctx_after, size_t left_pad) +{ + size_t beg_line = 1, beg_off = 0, end_line = 1, end_off = 0, ip_line = 1; + uc_source_t *source = uc_program_function_source(fn); + uc_lineinfo_t *lines = &source->lineinfo; + + /* determine start and end byte position of first and last statement line */ + for (size_t i = 0, lineoff = 0; i < lines->count; i++) { + // FIXME: >= stmt->pos_start ? + if (end_off <= stmt->pos_start && + end_off + (lines->entries[i] & 0x7f) > stmt->pos_start) + { + beg_line = end_line; + beg_off = lineoff; + } + + if (end_off <= stmt->pos_ip && + end_off + (lines->entries[i] & 0x7f) >= stmt->pos_ip) + { + ip_line = end_line; + } + + if (i > 0 && lines->entries[i] & 0x80) { + end_line++; + end_off++; + lineoff = end_off; + + if (end_off >= stmt->pos_end) + break; + } + + end_off += lines->entries[i] & 0x7f; + } + + if (beg_off >= end_off) + return; + + line_range_t ranges[3] = { 0 }; + + if (end_line - beg_line <= 4) { + ranges[0].from = beg_line; + ranges[0].to = end_line; + } + else { + if (ip_line - beg_line <= (ctx_before + ctx_after + 2)) { + ranges[1].from = beg_line; + } + else { + ranges[0].from = beg_line; + ranges[0].to = beg_line + ctx_after; + + ranges[1].from = ip_line - ctx_before; + } + + if (end_line - ip_line <= (ctx_before + ctx_after + 2)) { + ranges[1].to = end_line; + } + else { + ranges[1].to = ip_line + ctx_after; + + ranges[2].from = end_line - ctx_before; + ranges[2].to = end_line; + } + } + + print_source_location(sb, vm, source, 3, ranges, stmt, left_pad); +} + +static void +format_context_cfunction(uc_stringbuf_t *sb, uc_vm_t *vm, + uc_cfunction_t *cfn, size_t left_pad) +{ + void *loadaddr = NULL, *symaddr = NULL; + const char *filename = "Not available"; + const char *symname = "Not available"; + size_t columns = term_width() - left_pad; + Dl_info dli; + int n; + + if (dladdr(cfn->cfn, &dli)) { + if (dli.dli_fname) + filename = dli.dli_fname; + + if (dli.dli_sname) + symname = dli.dli_sname; + + loadaddr = dli.dli_fbase; + symaddr = dli.dli_saddr; + } + + printbuf_memset(sb, -1, ' ', left_pad); + cs(sb, &((style_t){ FG_BWHITE, BG_BLACK, FAINT })); + n = sprintbuf(sb, " Dynamic library: %s (%p)", filename, loadaddr); + printbuf_memset(sb, -1, ' ', columns - n); + cs(sb, NULL); + printbuf_strappend(sb, "\n"); + + printbuf_memset(sb, -1, ' ', left_pad); + cs(sb, &((style_t){ FG_BWHITE, BG_BLACK, FAINT })); + n = sprintbuf(sb, " Symbol name: %s (%p)", symname, symaddr); + printbuf_memset(sb, -1, ' ', columns - n); + cs(sb, NULL); + printbuf_strappend(sb, "\n"); +} + +// FIXME: read beyond end of array +static int32_t +insn_s32(uint8_t *ip) +{ + return ( + ip[0] * 0x1000000UL + + ip[1] * 0x10000UL + + ip[2] * 0x100UL + + ip[3] + ) - 0x7fffffff; +} + +static uint32_t +insn_u32(uint8_t *ip) +{ + return ( + ip[0] * 0x1000000UL + + ip[1] * 0x10000UL + + ip[2] * 0x100UL + + ip[3] + ); +} + +static uint32_t +insn_u16(uint8_t *ip) +{ + return ( + ip[0] * 0x100UL + + ip[1] + ); +} + +static size_t +insn_length(uint8_t *ip, uc_program_t *prog) +{ + if (*ip == I_CALL || *ip == I_QCALL || *ip == I_MCALL || *ip == I_QMCALL) + return 5 + insn_u16(ip + 1) * 2; + + if (*ip == I_CLFN || *ip == I_ARFN) { + uint32_t u32 = insn_u32(ip + 1); + size_t i = 1; + uc_program_function_foreach(prog, fn) + if (i++ == u32) + return 5 + fn->nupvals * 4; + } + + return 1 + abs(uc_vm_insn_format[*ip]); +} + +static void +bk_enter_function(uc_vm_t *vm, uc_breakpoint_t *bk) +{ + debug_breakpoint_t *dbk = (debug_breakpoint_t *)bk; + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uint8_t *ip = frame->ip; + uint32_t argspec = 0; + bool enter = false; + + assert(dbk->kind == BK_STEP); + + if (*ip == I_MCALL || *ip == I_QMCALL) { + argspec = insn_u32(ip + 1); + + size_t nargs = argspec & 0xffff; + + if (nargs + 2 < vm->stack.count) { + uc_value_t *ctx = vm->stack.entries[vm->stack.count - nargs - 2]; + uc_value_t *key = vm->stack.entries[vm->stack.count - nargs - 1]; + uc_value_t *fno = ucv_key_get(vm, ctx, key); + + ucv_put(fno); /* ucv_get_get() increases refcount */ + + if (ucv_type(fno) == UC_UPVALUE) { + uc_upvalref_t *ref = (uc_upvalref_t *)fno; + + if (ref->closed) + fno = ref->value; + else + fno = vm->stack.entries[ref->slot]; + } + + if (ucv_type(fno) == UC_CLOSURE) { + uc_function_t *fn = ((uc_closure_t *)fno)->function; + + dbk->bk.cb = bk_enter_cli; + dbk->bk.ip = fn->chunk.entries; + dbk->depth = 1; + dbk->fn = fn; + enter = true; + } + } + } + else if (*ip == I_CALL || *ip == I_QCALL) { + argspec = insn_u32(ip + 1); + + size_t nargs = argspec & 0xffff; + + if (nargs + 1 < vm->stack.count) { + uc_value_t *fno = vm->stack.entries[vm->stack.count - nargs - 1]; + + if (ucv_type(fno) == UC_CLOSURE) { + uc_function_t *fn = ((uc_closure_t *)fno)->function; + + dbk->bk.cb = bk_enter_cli; + dbk->bk.ip = fn->chunk.entries; + dbk->depth = 1; + dbk->fn = fn; + enter = true; + } + } + } + + if (!enter) { + dbk->bk.cb = bk_enter_cli; + dbk->bk.ip = NULL; + dbk->depth = 0; + dbk->fn = NULL; + } +} + +static void +bk_leave_function(uc_vm_t *vm, uc_breakpoint_t *bk) +{ + debug_breakpoint_t *dbk = (debug_breakpoint_t *)bk; + uc_callframe_t *frame = uc_debug_curr_frame(vm, 1); + + assert(dbk->kind == BK_STEP); + + term_print("Leaving function!\n"); + + if (!frame) + return; + + dbk->bk.cb = bk_enter_cli; + dbk->bk.ip = frame->ip; + dbk->depth = 0; + dbk->fn = frame->closure->function; +} + +static void +bk_follow_jump(uc_vm_t *vm, uc_breakpoint_t *bk) +{ + debug_breakpoint_t *dbk = (debug_breakpoint_t *)bk; + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uc_program_t *prog = frame->closure->function->program; + uc_chunk_t *chunk = &frame->closure->function->chunk; + size_t off = frame->ip - chunk->entries; + uint8_t *ip = frame->ip; + + assert(dbk->kind == BK_STEP); + + /* skip conditional jmpz if conditition is true */ + if (*ip == I_JMPZ && ucv_is_truish(uc_vm_stack_peek(vm, 0))) { + off += insn_length(ip, prog); + } + + /* otherwise follow jump */ + else { + int32_t addr = insn_s32(ip + 1); + + if ((addr < 0 && (size_t)-addr > off) || + (addr >= 0 && (size_t)addr >= chunk->count)) + { + term_print("Jump target out of range\n"); + off += insn_length(ip, prog); + } + else { + off += addr; + } + } + + /* if the next offset is a jump instruction as well, then don't install + interactive breakpoint but re-invoke this breakpoint handler */ + if (chunk->entries[off] == I_JMP || chunk->entries[off] == I_JMPZ) + dbk->bk.cb = bk_follow_jump; + else + dbk->bk.cb = bk_enter_cli; + + dbk->bk.ip = chunk->entries + off; + dbk->depth = 0; + dbk->fn = frame->closure->function; +} + +static void +bk_handle_catch(uc_vm_t *vm, uc_breakpoint_t *bk) +{ +#define exname(x) [EXCEPTION_##x] = "EXCEPTION_" #x + const char *exnames[] = { + exname(NONE), + exname(SYNTAX), + exname(RUNTIME), + exname(TYPE), + exname(REFERENCE), + exname(USER), + exname(EXIT) + }; +#undef exname + + term_print("Exception occurred!\n"); + term_printf("Type: %s\n", exnames[vm->exception.type]); + term_printf("Message: %s\n", vm->exception.message); + + bk_enter_cli(vm, bk); +} + +static uint8_t * +next_step(uc_vm_t *vm, uc_function_t **fnp, uint8_t *ip, bool single, size_t *depthp) +{ + insn_span_t stmt, next; + + if (find_statement_boundaries(*fnp, ip, 0, &stmt)) { + uc_program_t *prog = (*fnp)->program; + + for (uint8_t *p = ip; p < stmt.ip_end; p += insn_length(p, prog)) { + switch (*p) { + case I_CALL: + case I_QCALL: + case I_MCALL: + case I_QMCALL: + if (single) { + update_breakpoint(vm, BK_STEP, bk_enter_function, p, *fnp, 0); + + return NULL; + } + + break; + + case I_RETURN: + if (single) { + update_breakpoint(vm, BK_STEP, bk_leave_function, p, *fnp, 0); + + return NULL; + } + + break; + + case I_JMP: + case I_JMPZ: + update_breakpoint(vm, BK_STEP, bk_follow_jump, p, *fnp, 0); + + return NULL; + } + } + + while (find_statement_boundaries(*fnp, stmt.ip_end, 0, &next)) { + /* if next statement fully contains our statement, continue */ + if (next.ip_start <= stmt.ip_start && next.ip_end >= stmt.ip_end) { + fprintf(stderr, "Redo %zu..%zu -> %zu..%zu\n", + stmt.pos_start, stmt.pos_end, next.pos_start, next.pos_end); + stmt = next; + continue; + } + + *depthp = next.nesting; + + return next.ip_start; + } + } + + term_print("No next statement, continuing in parent\n"); + + *depthp = 0; + + return next_parent(vm, fnp); +} + +static uc_value_t * +load_constval(uc_value_list_t *vallist, size_t cidx) +{ + uc_value_type_t t = (cidx < vallist->isize) + ? (vallist->index[cidx] & 7) : TAG_INVAL; + + if (t == TAG_STR) { + char buf[sizeof(vallist->index[0])] = { 0 }; + size_t len = (vallist->index[cidx] >> 3) & 31; + + for (size_t j = 1; j <= len; j++) + buf[j-1] = (vallist->index[cidx] >> (j << 3)); + + return ucv_string_new_length(buf, len); + } + else if (t == TAG_LSTR) { + size_t off = (vallist->index[cidx] >> 3); + + if (off + sizeof(uint32_t) <= vallist->dsize) { + char *p = vallist->data + off; + size_t len = be32toh(*(uint32_t *)p); + + if (off + sizeof(uint32_t) + len <= vallist->dsize) + return ucv_string_new_length(p + sizeof(uint32_t), len); + } + } + else if (t == TAG_DBL) { + size_t off = (vallist->index[cidx] >> 3); + + if (off + sizeof(double) <= vallist->dsize) + return ucv_double_new(uc_double_unpack(vallist->data + off, false)); + } + else if (t == TAG_NUM) { + return ucv_uint64_new(vallist->index[cidx] >> 3); + } + else if (t == TAG_LNUM) { + size_t off = (vallist->index[cidx] >> 3); + + if (off + sizeof(uint64_t) <= vallist->dsize) + return ucv_uint64_new(be64toh(*(uint64_t *)(vallist->data + off))); + } + + return NULL; +} + +static void +print_variables(uc_stringbuf_t *buf, uc_vm_t *vm, uc_callframe_t *frame, + bool verbose, const char *indent) +{ + uc_chunk_t *chunk = &frame->closure->function->chunk; + uc_variables_t *decls = &chunk->debuginfo.variables; + uc_value_list_t *names = &chunk->debuginfo.varnames; + size_t columns = term_width() - strlen(indent); + size_t pos = frame->ip - chunk->entries; + + if (frame->ctx) { + printbuf_memappend_fast(buf, indent, strlen(indent)); + + cs(buf, &((style_t){ FG_BWHITE, 0, FAINT })); + printbuf_strappend(buf, "(this) : "); + cs(buf, NULL); + + if (verbose) + ucv_to_stringbuf_formatted(vm, buf, frame->ctx, 0, ' ', 2); + else + printbuf_append_uv(buf, vm, frame->ctx, columns - 19); + + printbuf_strappend(buf, "\n"); + } + + for (size_t i = 0; i < decls->count; i++) { + if (decls->entries[i].from > pos || decls->entries[i].to < pos) + continue; + + uc_value_t *vname = load_constval(names, decls->entries[i].nameidx); + size_t slot = decls->entries[i].slot; + + printbuf_memappend_fast(buf, indent, strlen(indent)); + + /* is local variable */ + if (slot < (size_t)-1 / 2) { + bool is_internal = (vname && *ucv_string_get(vname) == '('); + + if (is_internal) + cs(buf, &((style_t){ FG_BWHITE, 0, FAINT })); + + int n, off = buf->bpos; + + if (vname) + n = sprintbuf(buf, "%s", ucv_string_get(vname)); + else + n = sprintbuf(buf, "$%zu", slot); + + printbuf_truncate(buf, off, 16, true); + + if (is_internal) + cs(buf, NULL); + + if (n < 16) + printbuf_memset(buf, -1, ' ', 16 - n); + + cs(buf, &((style_t){ FG_BWHITE, 0, FAINT })); + printbuf_strappend(buf, " : "); + cs(buf, NULL); + + if (frame->stackframe + slot < vm->stack.count) { + uc_value_t *vval = vm->stack.entries[frame->stackframe + slot]; + + if (verbose) + ucv_to_stringbuf_formatted(vm, buf, vval, 0, ' ', 2); + else + printbuf_append_uv(buf, vm, vval, columns - 19); + } + else { + cs(buf, &((style_t){ FG_RED, 0, BOLD })); + printbuf_strappend(buf, ""); + cs(buf, NULL); + } + } + + /* is upvalue */ + else { + cs(buf, &((style_t){ FG_CYAN, 0, BOLD })); + + int n, off = buf->bpos; + + if (vname) + n = sprintbuf(buf, "%s", ucv_string_get(vname)); + else + n = sprintbuf(buf, "$%zu", slot); + + printbuf_truncate(buf, off, 16, true); + cs(buf, NULL); + + if (n < 16) + printbuf_memset(buf, -1, ' ', 16 - n); + + cs(buf, &((style_t){ FG_BWHITE, 0, FAINT })); + printbuf_strappend(buf, " : "); + cs(buf, NULL); + + slot -= ((size_t)-1 / 2); + + if (slot < frame->closure->function->nupvals) { + uc_upvalref_t *ref = frame->closure->upvals[slot]; + + if (!ref) { + cs(buf, &((style_t){ FG_BWHITE, 0, FAINT })); + printbuf_strappend(buf, ""); + cs(buf, NULL); + } + else if (ref->closed) { + uc_value_t *vval = ref->value; + + if (verbose) + ucv_to_stringbuf_formatted(vm, buf, vval, 0, ' ', 2); + else + printbuf_append_uv(buf, vm, vval, columns - 19); + } + else if (ref->slot < vm->stack.count) { + uc_value_t *vval = vm->stack.entries[ref->slot]; + + if (verbose) + ucv_to_stringbuf_formatted(vm, buf, vval, 0, ' ', 2); + else + printbuf_append_uv(buf, vm, vval, columns - 19); + } + else { + cs(buf, &((style_t){ FG_RED, 0, BOLD })); + printbuf_strappend(buf, ""); + cs(buf, NULL); + } + } + else { + cs(buf, &((style_t){ FG_RED, 0, BOLD })); + printbuf_strappend(buf, ""); + cs(buf, NULL); + } + } + + ucv_put(vname); + + printbuf_strappend(buf, "\n"); + } +} + +static bool +eval_expr(uc_vm_t *vm, uc_callframe_t *frame, char *expr, uc_value_t **res) +{ + uc_chunk_t *caller_chunk = &frame->closure->function->chunk; + uc_variables_t *decls = &caller_chunk->debuginfo.variables; + uc_value_list_t *names = &caller_chunk->debuginfo.varnames; + size_t pos = frame->ip - caller_chunk->entries; + char *err = NULL; + + uc_source_t *source = + uc_source_new_buffer("[eval expression]", xstrdup(expr), strlen(expr)); + + uc_parse_config_t conf = { .raw_mode = true }; + uc_program_t *prog = uc_compile(&conf, source, &err); + + uc_source_put(source); + + if (!prog) { + term_printf("%s", err); + free(err); + *res = NULL; + + return false; + } + + uc_value_t *exprfn = ucv_closure_new(vm, uc_program_entry(prog), false); + uc_chunk_t *chunk = &((uc_closure_t *)exprfn)->function->chunk; + + if (chunk->entries[0] != I_LVAR && chunk->entries[0] != I_LTHIS) { + term_print("Expecting expression\n"); + uc_program_put(prog); + ucv_put(exprfn); + *res = NULL; + + return false; + } + + uc_value_t *scope = ucv_object_new(NULL); + + /* determine referenced variables */ + for (size_t i = 0; i < chunk->count; i += insn_length(&chunk->entries[i], prog)) { + if (chunk->entries[i] != I_LVAR) + continue; + + uc_value_t *varname = load_constval( + &prog->constants, + insn_u32(chunk->entries + i + 1)); + + if (!varname) + continue; + + uc_value_t *varval = NULL; + + for (size_t j = 0; !varval && j < decls->count; j++) { + if (decls->entries[j].from > pos || decls->entries[j].to < pos) + continue; + + uc_value_t *vname = load_constval(names, decls->entries[j].nameidx); + bool match = ucv_is_equal(varname, vname); + + ucv_put(vname); + + if (!match) + continue; + + size_t slot = decls->entries[j].slot; + + /* is local var */ + if (slot < (size_t)-1 / 2) { + slot += frame->stackframe; + + if (slot < vm->stack.count) + varval = ucv_get(vm->stack.entries[slot]); + } + + /* is upvalue */ + else { + slot -= ((size_t)-1 / 2); + + if (slot < frame->closure->function->nupvals) { + uc_upvalref_t *ref = frame->closure->upvals[slot]; + + if (ref && ref->closed) + varval = ucv_get(ref->value); + else if (ref && ref->slot < vm->stack.count) + varval = ucv_get(vm->stack.entries[ref->slot]); + } + } + } + + if (varval) + ucv_object_add(scope, ucv_string_get(varname), varval); + + ucv_put(varname); + } + + uc_value_t *prev_scope = ucv_get(uc_vm_scope_get(vm)); + + ucv_prototype_set(scope, ucv_get(prev_scope)); + + uc_vm_scope_set(vm, scope); + + /* Save VM callframes and stack */ + uc_upvalref_t *upvals = vm->open_upvals; + uc_callframes_t frames = vm->callframes; + uc_stack_t stack = vm->stack; + + vm->open_upvals = NULL; + + vm->callframes.count = 0; + vm->callframes.entries = NULL; + + vm->stack.count = 0; + vm->stack.entries = NULL; + + uc_vm_stack_push(vm, ucv_get(frame->ctx)); + uc_vm_stack_push(vm, ucv_get(exprfn)); + + bool rv; + + if (uc_vm_call(vm, true, 0) == EXCEPTION_NONE) { + *res = uc_vm_stack_pop(vm); + rv = true; + } + else { + term_printf("Exception: %s\n", vm->exception.message); + vm->exception.type = EXCEPTION_NONE; + *res = NULL; + rv = false; + } + + uc_vector_clear(&vm->callframes); + uc_vector_clear(&vm->stack); + + /* Restore VM callframes and stack */ + vm->open_upvals = upvals; + vm->callframes = frames; + vm->stack = stack; + + uc_vm_scope_set(vm, prev_scope); + uc_program_put(prog); + ucv_put(exprfn); + + return rv; +} + +static void +update_catchpoint(uc_vm_t *vm, uc_function_t *fn, uint8_t *ip) +{ + uc_ehranges_t *eh = &fn->chunk.ehranges; + size_t off = ip - fn->chunk.entries; + + for (size_t i = 0; i < eh->count; i++) { + if (off >= eh->entries[i].from && off < eh->entries[i].to) { + update_breakpoint(vm, BK_CATCH, bk_handle_catch, + fn->chunk.entries + eh->entries[i].target, fn, 0); + + break; + } + } +} + +static void +print_location(uc_vm_t *vm, const char *prefix, debug_breakpoint_t *dbk) +{ + uc_callframe_t *topframe = NULL, *funframe = NULL; + size_t depth = dbk->depth; + + for (size_t i = vm->callframes.count; i > 0; i--) { + if (!topframe || (topframe->cfunction && + topframe->cfunction->cfn == uc_debug_sigint_handler)) + topframe = &vm->callframes.entries[i - 1]; + + if (vm->callframes.entries[i - 1].closure) { + funframe = &vm->callframes.entries[i - 1]; + + /* Update location in automatic function breakpoint */ + if (dbk->fn == NULL) { + dbk->fn = funframe->closure->function; + dbk->bk.ip = funframe->ip; + } + + /* Update exception catch point */ + update_catchpoint(vm, funframe->closure->function, funframe->ip); + break; + } + } + + uc_stringbuf_t *pb = xprintbuf_new(); + + printbuf_memappend_fast(pb, prefix, strlen(prefix)); + + if (funframe) { + uc_function_t *function = funframe->closure->function; + uc_source_t *source = uc_program_function_source(function); + insn_span_t stmt; + + if (find_statement_boundaries(function, funframe->ip, depth, &stmt)) { + size_t byte = stmt.pos_start; + size_t line = uc_source_get_line(source, &byte); + + sprintbuf(pb, "%s, line %zu:%zu\n", + source->filename, line, byte); + + format_context_header_backtrace(pb, vm); + format_context_statement(pb, vm, function, &stmt, 2, 2, 0); + } + } + else if (topframe) { + if (topframe->cfunction->name[0]) + sprintbuf(pb, "native function %s()\n", topframe->cfunction->name); + else + printbuf_strappend(pb, "unnamed native function\n"); + } + else { + printbuf_strappend(pb, "[unknown location]\n"); + } + + printbuf_strappend(pb, "\n"); + term_write(pb->buf, pb->bpos); + + printbuf_free(pb); +} + +static bool +cmd_help(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + char *cmd = (argc > 1) ? argv[1].sv : NULL; + size_t columns = term_width(); + + for (size_t i = 0; i < ARRAY_SIZE(commands); i++) { + bool match = !cmd; + + if (cmd) { + for (const char *c = commands[i].command; *c; c += strlen(c) + 1) { + if (str_startswith(c, argv[1].sv)) { + match = true; + break; + } + } + } + + if (match == false) + continue; + + term_printf("\033[1m%s\033[0m\n\n", commands[i].command); + + const char *p = commands[i].help; + + while (*p != '\0') { + size_t pad = strspn(p, " "); + size_t len = strcspn(p, "\r\n") - pad; + + if (pad + len <= columns) { + term_printf("%.*s\n", (int)(pad + len), p); + p += pad + len + (p[pad + len] == '\n'); + } + else { + if (pad > columns) + pad = 1; + + const char *l = p + pad; + + while (len > columns - pad) { + term_printf("%.*s", (int)pad, p); + + for (size_t j = columns - pad; j > 0; j--) { + if (l[j-1] == ' ') { + term_printf("%.*s\n", (int)j, l); + l += j; + len -= j; + break; + } + } + } + + term_printf("%.*s", (int)pad, p); + term_printf("%.*s\n", (int)len, l); + p = l + len + (l[len] == '\n'); + } + } + + term_print("\n\n"); + } + + return true; +} + +static bool +cmd_break(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + char *spec = (argc == 2) ? argv[1].sv : NULL; + size_t id = 0; + + if (spec == NULL || *spec == '\0') { + term_print("Usage:\n"); + term_print(" break path[:line[:offset]]\n"); + term_print(" break expr\n"); + + return true; + } + + /* path spec */ + if ((strchr(spec, '/') || strchr(spec, ':') || + (*spec >= '0' && *spec <= '9')) && *spec != '(') { + + char *path, *line, *byte; + + if (*spec == ':' || (*spec >= '0' && *spec <= '9')) { + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uc_function_t *function = frame->closure->function; + + path = uc_program_function_source(function)->filename; + line = strtok(spec, ": \t"); + byte = strtok(NULL, ": \t"); + } + else { + path = strtok(spec, ": \t"); + line = strtok(NULL, ": \t"); + byte = strtok(NULL, ": \t"); + } + + if (!path && !line && !byte) { + term_print("Usage: break path[:line[:offset]]\n"); + + return true; + } + + id = add_breakpoint(vm, path, + line ? strtoul(line, NULL, 10) : 0, + byte ? strtoul(byte, NULL, 10) : 0, + BK_USER); + } + + /* expression spec or function name */ + else { + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uc_value_t *val = NULL; + + /* Before evaluating as code, try looking up function name directly. */ + if (frame != NULL) { + uc_program_function_foreach(frame->closure->function->program, fn) { + if (!strcmp(fn->name, spec)) { + id = patch_breakpoint(vm, fn, 0, BK_USER, 1); + break; + } + } + } + + if (id == 0 && frame != NULL && eval_expr(vm, frame, spec, &val)) { + if (ucv_type(val) == UC_CLOSURE) { + id = patch_breakpoint(vm, + ((uc_closure_t *)val)->function, 0, BK_USER, 1); + } + else { + char *s = ucv_to_string(vm, val); + int len = strlen(s); + + term_printf("Value `%s` (%.*s%s) is not a function\n", + spec, + len > 32 ? 31 : len, + s, + len > 32 ? "…" : ""); + } + + ucv_put(val); + } + } + + if (id) + term_printf("Breakpoint #%zu added\n", id); + else + term_print("Unable to resolve source location\n"); + + return true; +} + +static bool +cmd_delete(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + uc_breakpoints_t *bks = &vm->breakpoints; + + if (argc > 2 || (argc == 2 && argv[1].type != ARGTYPE_NUMBER)) { + term_print("Usage: delete [id]\n"); + } + else if (argc == 2) { + size_t n = 0; + + for (size_t i = 0; i < bks->count; i++) { + debug_breakpoint_t *dbk = (debug_breakpoint_t *)bks->entries[i]; + + if (dbk == NULL || dbk->kind != BK_USER) + continue; + + if (++n == argv[1].nv) { + free_breakpoint(vm, &dbk->bk); + + return term_printf("Breakpoint #%zu deleted\n", argv[1].nv); + } + } + + term_printf("No breakpoint #%zu set\n", argv[1].nv); + } + else { + if (dbk->kind == BK_USER) { + free_breakpoint(vm, &dbk->bk); + term_print("Current breakpoint deleted\n"); + } + else { + term_print("Automatic breakpoint cannot be deleted\n"); + } + } + + return true; +} + +static bool +cmd_list(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + uc_breakpoints_t *bks = &vm->breakpoints; + uc_stringbuf_t buf = { 0 }; + size_t n = 0; + + const char *kinds[] = { + [BK_ONCE] = "(once)", + [BK_USER] = "(user)", + [BK_STEP] = "(step)", + [BK_CATCH] = "(catch)", + }; + + for (size_t i = 0; i < ARRAY_SIZE(kinds); i++) { + for (size_t j = 0; j < bks->count; j++) { + debug_breakpoint_t *p = (debug_breakpoint_t *)bks->entries[j]; + + if (p == NULL || p->kind != i) + continue; + + if (p->kind == BK_USER) + sprintbuf(&buf, "#%-6zu ", ++n); + else + sprintbuf(&buf, "%-7s ", kinds[p->kind]); + + if (p->fn) { + uc_source_t *source = uc_program_function_source(p->fn); + size_t byte = uc_program_function_srcpos(p->fn, + p->bk.ip - p->fn->chunk.entries); + + size_t line = uc_source_get_line(source, &byte); + + if (source) + printbuf_append_srcpath(&buf, source, SIZE_MAX); + else + printbuf_strappend(&buf, "[unknown source]"); + + sprintbuf(&buf, ":%zu:%zu - ", line, byte > 1 ? byte : 1); + + uc_closure_t cl = { + .header = { .type = UC_CLOSURE }, + .function = p->fn + }; + + printbuf_append_function(&buf, vm, &cl.header, NULL, SIZE_MAX); + + + } + else { + printbuf_strappend(&buf, ""); + } + + printbuf_strappend(&buf, "\n"); + term_write(buf.buf, buf.bpos); + printbuf_reset(&buf); + } + } + + if (n == 0) + term_print("No user breakpoints set\n"); + + free(buf.buf); + + return true; +} + +static bool +cmd_step_common(uc_vm_t *vm, debug_breakpoint_t *dbk, bool single) +{ + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + + if (!frame) + return false; + + uc_function_t *fn = frame->closure->function; + size_t depth = dbk->depth; + uint8_t *nextinsn = next_step(vm, &fn, frame->ip, single, &depth); + + /* no next instruction, run until completion */ + if (!nextinsn) + return false; + + uc_source_t *source = uc_program_function_source(fn); + + size_t byte = uc_program_function_srcpos(fn, + nextinsn - fn->chunk.entries); + + size_t line = uc_source_get_line( + uc_program_function_source(fn), &byte); + + if (fn != frame->closure->function) + term_printf("Entering %s()...\n", + fn->name[0] + ? fn->name : fn->arrow + ? "[arrow function]" : "[unnamed function]"); + else + term_printf("Continuing in %s:%zu:%zu...\n", + source->filename, line, byte); + + update_breakpoint(vm, BK_STEP, bk_enter_cli, nextinsn, fn, depth); + + return false; +} + +static bool +cmd_next(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + return cmd_step_common(vm, dbk, false); +} + +static bool +cmd_step(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + return cmd_step_common(vm, dbk, true); +} + +static bool +cmd_continue(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + term_print("Continuing...\n"); + + return false; +} + +static bool +cmd_return(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + uc_callframe_t *frame = uc_debug_curr_frame(vm, 1); + + if (frame) { + update_breakpoint(vm, BK_STEP, bk_enter_cli, frame->ip, + frame->closure->function, 0); /* XXX: fixup depth? */ + } + else { + term_print("In topmost function, running until completion...\n"); + } + + return false; +} + +static bool +cmd_backtrace(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + bool verbose = false; + + if (argc > 3 || (argc == 2 && argv[1].type != ARGTYPE_STRING)) + return term_print("Usage: backtrace [full]\n"); + + if (argc == 2 && str_startswith("full", argv[1].sv)) + verbose = true; + + uc_stringbuf_t buf = { 0 }; + uc_function_t *function; + uc_callframe_t *frame; + bool adjust_ip = true; + size_t i; + + for (i = vm->callframes.count; i > 0; i--) { + frame = &vm->callframes.entries[i - 1]; + + if (frame->closure) { + function = frame->closure->function; + + printbuf_cs(&buf, "\1#%-2zu\177 in ", + &((style_t){ 0, 0, BOLD }), + i); + + printbuf_append_srcpath(&buf, + uc_program_function_source(function), SIZE_MAX); + + size_t insn = frame->ip - function->chunk.entries; + size_t byte = insn; + size_t line = insnoff_to_srcpos(function, &byte); + + sprintbuf(&buf, ":%zu:%zu at insn #%zu in ", line, byte, insn); + + cs(&buf, &((style_t){ 0, 0, BOLD })); + printbuf_append_funcname(&buf, + vm, &frame->closure->header, SIZE_MAX); + printbuf_strappend(&buf, "()\n"); + cs(&buf, NULL); + + uint8_t *ip = frame->ip; + insn_span_t stmt; + + if (adjust_ip && i < vm->callframes.count) + ip -= 5 - 2 * (vm->arg.u32 >> 16); + + if (find_statement_boundaries(function, ip, 0, &stmt)) { + format_context_header_callframe(&buf, vm, frame, 2); + format_context_statement(&buf, vm, function, &stmt, 2, 2, 2); + } + + if (verbose) { + printbuf_cs(&buf, "\n \1Local variables:\177\n", + &((style_t){ 0, 0, BOLD })); + + print_variables(&buf, vm, frame, false, " - "); + } + } + else if (frame->cfunction) { + uc_cfunction_t *cfn = frame->cfunction; + Dl_info dli; + + printbuf_cs(&buf, "\1#%-2zu\177 in ", + &((style_t){ 0, 0, BOLD }), + i); + + if (dladdr(cfn->cfn, &dli) != 0 && dli.dli_fname != NULL) + printbuf_memappend_fast((&buf), + dli.dli_fname, strlen(dli.dli_fname)); + else + printbuf_strappend(&buf, "[unknown shared object]"); + + printbuf_strappend(&buf, ", function "); + + cs(&buf, &((style_t){ 0, 0, BOLD })); + printbuf_append_funcname(&buf, vm, &cfn->header, SIZE_MAX); + printbuf_strappend(&buf, "()\n"); + cs(&buf, NULL); + + format_context_header_callframe(&buf, vm, frame, 2); + format_context_cfunction(&buf, vm, cfn, 2); + + adjust_ip = false; + } + + printbuf_strappend(&buf, "\n"); + term_write(buf.buf, buf.bpos); + printbuf_reset(&buf); + } + + free(buf.buf); + + return true; +} + +static bool +cmd_variables(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uc_stringbuf_t buf = { 0 }; + bool verbose = false; + + if (argc > 3 || (argc == 2 && argv[1].type != ARGTYPE_STRING)) + return term_print("Usage: backtrace [full]\n"); + + if (argc == 2 && str_startswith("full", argv[1].sv)) + verbose = true; + + if (!frame) + return term_print("No local variables in current context\n"); + + print_variables(&buf, vm, frame, verbose, ""); + + term_write(buf.buf, buf.bpos); + free(buf.buf); + + return true; +} + +static bool +cmd_sources(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + struct lh_table *sources = lh_kptr_table_new(16, NULL); + uc_weakref_t *ref; + + for (ref = vm->values.next; ref != &vm->values; ref = ref->next) { + uc_closure_t *uc = + (uc_closure_t *)((uintptr_t)ref - offsetof(uc_closure_t, ref)); + + if (uc->header.type != UC_CLOSURE) + continue; + + if (!uc->function || !uc->function->program) + continue; + + for (size_t i = 0; i < uc->function->program->sources.count; i++) { + uc_source_t *source = uc->function->program->sources.entries[i]; + unsigned long hash = lh_get_hash(sources, source); + + if (!lh_table_lookup_entry_w_hash(sources, source, hash)) + lh_table_insert_w_hash(sources, source, NULL, hash, 0); + } + } + + struct lh_entry *e; + size_t i = 0; + + lh_foreach(sources, e) { + uc_source_t *source = lh_entry_k(e); + + term_printf("#%2zu %s\n", i++, source->filename); + } + + lh_table_free(sources); + + return true; +} + +static bool +cmd_print(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uc_stringbuf_t buf = { 0 }; + + if (argc < 2) + return term_print("Usage: print expr\n"); + + for (size_t i = 1; i < argc; i++) { + if (i > 1) + printbuf_strappend(&buf, " "); + + printbuf_memappend_fast((&buf), argv[i].sv, strlen(argv[i].sv)); + } + + uc_value_t *res = NULL; + + if (eval_expr(vm, frame, buf.buf, &res)) { + printbuf_reset(&buf); + ucv_to_stringbuf_formatted(vm, &buf, res, 0, ' ', 2); + printbuf_strappend(&buf, "\n"); + + ucv_put(res); + + term_write(buf.buf, buf.bpos); + } + + free(buf.buf); + + return true; +} + +static bool +cmd_lines(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uc_function_t *fn = frame->closure->function; + size_t insn = frame->ip - fn->chunk.entries; + bool ctx_is_range = false; + size_t ctx_before = 2; + size_t ctx_after = 2; + + location_t loc = { + .program = fn->program, + .source = uc_program_function_source(fn), + .function = fn, + .offset = uc_program_function_srcpos(fn, insn), + }; + + insn_span_t stmt = { + .pos_start = SIZE_MAX, + .pos_end = SIZE_MAX, + .pos_ip = SIZE_MAX + }; + + /* no argument */ + if (argc == 1) { + if (find_statement_boundaries(fn, frame->ip, 0, &stmt)) + loc.offset = stmt.pos_start; + + loc.column = loc.offset; + loc.line = uc_source_get_line(loc.source, &loc.column); + } + + /* absolute line number */ + else if (argc >= 2 && argv[1].type == ARGTYPE_NUMBER) { + loc.line = (argv[1].nv > 0) ? argv[1].nv : 1; + } + + /* line or instruction offset */ + else if (argc >= 2 && argv[1].type == ARGTYPE_STRING && + strchr("+-#", argv[1].sv[0]) != NULL && + argv[1].sv[1] >= '0' && argv[1].sv[1] <= '9') { + char *e; + unsigned long n = strtoul(argv[1].sv + 1, &e, 0); + + if (*e != '\0') + return term_print("Invalid offset\n"); + + if (argv[1].sv[0] == '+' || argv[1].sv[0] == '-') { + if (find_statement_boundaries(fn, frame->ip, 0, &stmt)) + loc.offset = stmt.pos_start; + + loc.column = loc.offset; + loc.line = uc_source_get_line(loc.source, &loc.column); + + if (argv[1].sv[0] == '+') + loc.line += n; + else if (n < loc.line) + loc.line -= n; + else + loc.line = 1; + } + else { + loc.offset = uc_program_function_srcpos(fn, n); + loc.column = loc.offset; + loc.line = uc_source_get_line(loc.source, &loc.column); + + stmt.pos_ip = loc.offset; + } + } + + /* source path, function name or function expression */ + else if (argc >= 2 && argv[1].type == ARGTYPE_STRING) { + bool found = false; + + if (argv[1].sv[0] != '(') { + loc = ((location_t){ + .path = argv[1].sv, + .line = 1, + .column = 1 + }); + + found = lookup_source(vm, &loc); + ctx_is_range = true; + + if (!found) { + uc_program_function_foreach(fn->program, pfn) { + if (!strcmp(pfn->name, argv[1].sv)) { + loc = ((location_t){ .function = pfn }); + found = true; + ctx_is_range = false; + break; + } + } + } + } + + if (!found) { + uc_value_t *val; + + if (!eval_expr(vm, frame, argv[1].sv, &val)) + return true; + + if (ucv_type(val) != UC_CLOSURE) { + char *s = ucv_to_string(vm, val); + int len = strlen(s); + + term_printf("Value `%s` (%.*s%s) is not a function\n", + argv[1].sv, + len > 32 ? 31 : len, + s, + len > 32 ? "…" : ""); + + ucv_put(val); + free(s); + + return true; + } + + loc = ((location_t){ .function = ((uc_closure_t *)val)->function }); + found = true; + ctx_is_range = false; + + ucv_put(val); + } + + if (loc.function) { + size_t beg = uc_program_function_srcpos(loc.function, 0); + size_t end = uc_program_function_srcpos(loc.function, SIZE_MAX); + + loc.program = loc.function->program; + loc.source = uc_program_function_source(loc.function); + loc.offset = loc.column = beg; + loc.line = uc_source_get_line(loc.source, &loc.column); + + ctx_before = 1; + ctx_after = uc_source_get_line(loc.source, &end) + 2 - loc.line; + } + else { + ctx_before = 0; + ctx_after = 5; + } + } + + if (argc >= 3 && argv[2].type != ARGTYPE_NUMBER) + return term_print("Invalid amount of context lines\n"); + + if (argc >= 4 && argv[3].type != ARGTYPE_NUMBER) + return term_print("Invalid amount of following context lines\n"); + + if (argc >= 4) { + if (ctx_is_range) { + loc.line = argv[2].nv > 0 ? argv[2].nv : 1; + ctx_before = 0; + ctx_after = (argv[3].nv > argv[2].nv) ? argv[3].nv - argv[2].nv : 1; + } + else { + ctx_before = argv[2].nv; + ctx_after = argv[3].nv; + } + } + else if (argc >= 3) { + ctx_before = 0, ctx_after = argv[2].nv; + } + + if (!lookup_function(vm, &loc)) + return term_print("Unable to resolve source code location\n"); + + uc_stringbuf_t buf = { 0 }; + + line_range_t lines = { + .from = (loc.line > ctx_before) ? loc.line - ctx_before : 1, + .to = loc.line + ctx_after + + }; + + print_source_location(&buf, vm, loc.source, 1, &lines, + (loc.source == uc_program_function_source(fn)) ? &stmt : NULL, 0); + + printbuf_strappend(&buf, "\n"); + + term_write(buf.buf, buf.bpos); + free(buf.buf); + + return true; +} + +static bool +cmd_throw(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + uc_exception_type_t et = EXCEPTION_USER; + + if (argc < 2 || argv[1].type != ARGTYPE_STRING || argc > 3) + return term_print("Usage: throw [type] message\n"); + + if (argc == 3) { + if (str_startswith("syntax", argv[1].sv)) + et = EXCEPTION_SYNTAX; + else if (str_startswith("runtime", argv[1].sv)) + et = EXCEPTION_RUNTIME; + else if (str_startswith("type", argv[1].sv)) + et = EXCEPTION_TYPE; + else if (str_startswith("reference", argv[1].sv)) + et = EXCEPTION_REFERENCE; + else if (str_startswith("user", argv[1].sv)) + et = EXCEPTION_USER; + else if (str_startswith("exit", argv[1].sv)) + et = EXCEPTION_EXIT; + else + return term_printf("Unrecognized exception type '%s'\n", argv[1].sv); + } + + uc_vm_raise_exception(vm, et, "%s", argv[argc - 1].sv); + + return true; +} + +#undef __insn +#define __insn(_name) #_name, + +static const char *insn_names[__I_MAX] = { + __insns +}; + +static bool +cmd_disasm(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + uc_function_t *target = NULL; + uc_stringbuf_t buf = { 0 }; + uc_program_t *prog = NULL; + size_t from = 0, to = 0; + size_t columns = term_width(); + + if (argc > 2 || (argc == 2 && argv[1].type != ARGTYPE_STRING)) + return term_print("Usage: disassemble [target]\n"); + + if (argc == 2) { + if (*argv[1].sv == '#') { + char *e; + + from = strtoul(argv[1].sv + 1, &e, 10); + + if (*e == '-') { + to = strtoul(e + 1, &e, 10); + + if (*e != '\0' || to < from) + return term_printf("Invalid instruction range '%s'\n", argv[1].sv); + } + else if (*e == '+') { + to = from + strtoul(e + 1, &e, 10); + + if (*e != '\0') + return term_printf("Invalid instruction count '%s'\n", argv[1].sv); + } + else if (*e == '\0') { + to = from; + } + else { + return term_printf("Invalid instruction offset '%s'\n", argv[1].sv); + } + + target = frame->closure->function; + + if (from >= target->chunk.count || to >= target->chunk.count) + return term_printf("Instruction offset '%s' out of range 0..%zu\n", + argv[1].sv, target->chunk.count - 1); + } + else if (*argv[1].sv == '(') { + uc_parse_config_t conf = { .raw_mode = true }; + uc_source_t *source = uc_source_new_buffer("[disasm expression]", + xstrdup(argv[1].sv), strlen(argv[1].sv)); + + char *err; + prog = uc_compile(&conf, source, &err); + + uc_source_put(source); + + if (!prog) { + term_write(err, strlen(err)); + free(err); + + return term_print("Invalid expression\n"); + } + + target = uc_program_entry(prog); + from = 0; + to = target->chunk.count - 1; + } + else { + char *p = strchr(argv[1].sv, '+'); + size_t limit = SIZE_MAX; + + if (p) { + char *e; + limit = strtoul(p + 1, &e, 10); + + if (e == p + 1 || *e != '\0' || limit == 0) + return term_printf("Invalid instruction count '%s'\n", p + 1); + + *p++ = 0; + } + + uc_program_function_foreach(frame->closure->function->program, fn) { + if (!strcmp(fn->name, argv[1].sv)) { + target = fn; + from = 0; + to = (limit < target->chunk.count) + ? limit : target->chunk.count - 1; + break; + } + } + + if (!target) + return term_printf("Unable to find function '%s'\n", argv[1].sv); + } + } + else { + insn_span_t stmt; + + target = frame->closure->function; + + if (!find_statement_boundaries(target, frame->ip, 0, &stmt)) + return term_print("Unable to determine current statement boundaries\n"); + + from = stmt.ip_start - target->chunk.entries; + to = (stmt.ip_end - target->chunk.entries) - 1; + } + + uint8_t *bytecode = target->chunk.entries; + + /* find nearest instruction start */ + for (size_t i = 0; i < target->chunk.count; ) { + size_t len = insn_length(bytecode + i, target->program); + + if (i + len > from) { + from = i; + break; + } + + i += len; + } + + for (size_t i = from; i <= to; ) { + union { uint8_t u8; uint16_t u16; uint32_t u32; int32_t s32; } arg; + size_t n = insn_length(bytecode + i, target->program); + uint8_t insn = bytecode[i]; + int off = buf.bpos; + fg_color_t color; + + sprintbuf(&buf, "%06zu:", i); + + for (size_t j = 0; j <= (size_t)abs(uc_vm_insn_format[insn]); j++) { + if (j == 0) + color = 0; + else if (j <= (size_t)abs(uc_vm_insn_format[insn])) + color = FG_BMAGENT; + else + color = FG_BYELLOW; + + printbuf_cs(&buf, " \001%02hhx\177", + &((style_t){ color, 0, 0 }), + bytecode[i + j]); + } + + printbuf_memset(&buf, -1, ' ', 3 * (4 - abs(uc_vm_insn_format[insn]))); + + sprintbuf(&buf, " %7s", insn_names[insn]); + + switch (uc_vm_insn_format[insn]) { + case 0: + break; + + case -4: + arg.s32 = insn_s32(bytecode + i + 1); + + printbuf_cs(&buf, " {\1%c0x%x\177}", + &((style_t){ FG_BMAGENT, 0, 0 }), + arg.s32 < 0 ? '-' : '+', + (uint32_t)(arg.s32 < 0 ? -arg.s32 : arg.s32)); + + break; + + case 1: + arg.u8 = bytecode[i + 1]; + + printbuf_cs(&buf, " {\1%hhu\177}", + &((style_t){ FG_BMAGENT, 0, 0 }), + arg.u8); + + break; + + case 2: + arg.u16 = insn_u16(bytecode + i + 1); + + printbuf_cs(&buf, " {\0010x%hx\177}", + &((style_t){ FG_BMAGENT, 0, 0 }), + arg.u16); + + break; + + case 4: + arg.u32 = insn_u32(bytecode + i + 1); + + if (insn == I_LOAD) { + uc_value_t *cv = load_constval(&target->program->constants, arg.u32); + + char *s = ucv_to_jsonstring(vm, cv); + printbuf_cs(&buf, " {\0010x%x\177 : \002%s\177}", + &((style_t){ FG_BMAGENT, 0, 0 }), + &((style_t){ ucv_type(cv) == UC_STRING ? FG_BMAGENT : FG_CYAN, 0, 0 }), + arg.u32, s); + free(s); + } + else if (insn == I_LLOC || insn == I_SLOC || insn == I_LUPV || insn == I_SUPV) { + bool upval = (insn == I_LUPV || insn == I_SUPV); + uc_value_t *vn = uc_chunk_debug_get_variable( + &target->chunk, i, arg.u32, upval); + + printbuf_cs(&buf, " {\0010x%x\177 : %s \002%s\177}", + &((style_t){ FG_BMAGENT, 0, 0 }), + &((style_t){ upval ? FG_CYAN : FG_BWHITE, 0, 0 }), + arg.u32, upval ? "upval" : "local", + vn ? ucv_string_get(vn) : "(unknown)"); + } + else if (insn == I_LVAR || insn == I_SVAR) { + uc_value_t *vn = load_constval(&target->program->constants, arg.u32); + + printbuf_cs(&buf, " {\0010x%x\177 : global \002%s\177}", + &((style_t){ FG_BMAGENT, 0, 0 }), + &((style_t){ FG_BWHITE, 0, 0 }), + arg.u32, vn ? ucv_string_get(vn) : "(unknown)"); + } + else if (insn == I_CLFN || insn == I_ARFN) { + printbuf_cs(&buf, " {\0010x%x\177 : %s \001#%u\177}", + &((style_t){ FG_BMAGENT, 0, 0 }), + arg.u32, + (insn == I_CLFN) ? "closure" : "arrow", + arg.u32); + } + else { + printbuf_cs(&buf, " {\0010x%x\177}", + &((style_t){ FG_BMAGENT, 0, 0 }), + arg.u32); + } + + break; + + default: + printbuf_cs(&buf, " \1(unknown operand format: %hhu)\177", + &((style_t){ FG_RED, 0, 0 }), + uc_vm_insn_format[insn]); + + break; + } + + printbuf_truncate(&buf, off, columns, true); + cs(&buf, NULL); + + printbuf_strappend(&buf, "\n"); + + if (insn == I_CLFN || insn == I_ARFN) { + size_t id = 1, nupvals = 0; + uc_program_function_foreach(target->program, fn) { + if (id++ == arg.u32) { + nupvals = fn->nupvals; + break; + } + } + + for (size_t j = 0; j < nupvals; j++) { + int32_t slot = insn_s32(bytecode + i + 5 + j * 4); + bool upval = (slot >= 0); + uc_value_t *vn = uc_chunk_debug_get_variable( + &target->chunk, i, (slot < 0) ? -(slot + 1) : slot, upval); + + int off = buf.bpos; + + printbuf_cs(&buf, " … \1%02hhx %02hhx %02hhx %02hhx\177", + &((style_t){ FG_YELLOW, 0, 0 }), + bytecode[i + 5 + j * 4 + 0], bytecode[i + 5 + j * 4 + 1], + bytecode[i + 5 + j * 4 + 2], bytecode[i + 5 + j * 4 + 3]); + + printbuf_cs(&buf, + " capture {\001%c0x%x\177 : %s \002%s\177}", + &((style_t){ FG_YELLOW, 0, 0 }), + &((style_t){ upval ? FG_CYAN : FG_BWHITE, 0, 0 }), + (slot < 0) ? '-' : '+', + (slot < 0) ? -slot : slot, + upval ? "upval" : "local", + vn ? ucv_string_get(vn) : "(unknown)"); + + printbuf_truncate(&buf, off, columns, true); + printbuf_strappend(&buf, "\n"); + } + } + else if (insn == I_CALL || insn == I_QCALL || insn == I_MCALL || insn == I_QMCALL) { + for (size_t j = 0; j < arg.u32 >> 16; j++) { + uint16_t slot = insn_u16(bytecode + i + 5 + j * 2); + int off = buf.bpos; + + printbuf_cs(&buf, " … \1%02hhx %02hhx\177", + &((style_t){ FG_YELLOW, 0, 0 }), + bytecode[i + 5 + j * 2 + 0], bytecode[i + 5 + j * 2 + 1]); + + printbuf_cs(&buf, + " unpack {\0010x%hx\177 : stack slot \002-%hx\177}", + &((style_t){ FG_YELLOW, 0, 0 }), + &((style_t){ FG_BMAGENT, 0, 0 }), + slot, slot + 1); + + printbuf_truncate(&buf, off, columns, true); + printbuf_strappend(&buf, "\n"); + } + } + + term_write(buf.buf, buf.bpos); + printbuf_reset(&buf); + + i += n; + } + + free(buf.buf); + + return true; +} + +static bool +cmd_quit(uc_vm_t *vm, debug_breakpoint_t *dbk, size_t argc, arg_t *argv) +{ + bool proceed = true; + ssize_t c; + arg_t *v; + + while ((c = term_getline("Terminate program? (y/n) > ", &v, NULL, NULL)) != -1) { + if (c > 0 && v[0].sv[0] == 'y') { + vm->arg.s32 = -1; + uc_vm_raise_exception(vm, EXCEPTION_EXIT, "Terminated"); + proceed = false; + break; + } + + if (c > 0 && v[0].sv[0] == 'n') + break; + } + + while (c > 0) + free(v[--c].sv); + + free(v); + + return proceed; +} + +static void +cli_tab_complete(size_t nargs, arg_t *args, suggestions_t *suggests, void *ud) +{ + uc_vm_t *vm = ud; + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + char *cmd = (nargs > 0) ? args[0].sv : NULL; + + /* no completions beyond first arg */ + if (nargs > 2) + return; + + /* no completions without stackframe info */ + if (frame == NULL) + return; + + /* complete command itself */ + if (nargs <= 1) { + for (size_t i = 0; i < ARRAY_SIZE(commands); i++) + if (nargs == 0 || str_startswith(commands[i].command, args[0].sv)) + uc_vector_add(suggests, xstrdup(commands[i].command)); + + return; + } + + /* completions for `break` and `disassemble` */ + if (str_startswith("break", cmd) || + str_startswith("disasm", cmd) || + str_startswith("disassemble", cmd)) { + + size_t len = strlen(args[1].sv); + + uc_program_function_foreach(frame->closure->function->program, fn) { + if (fn->name[0] == '\0') + continue; + + if (len > 0 && strncmp(fn->name, args[1].sv, len) != 0) + continue; + + uc_vector_add(suggests, xstrdup(fn->name)); + } + + return; + } + + /* completions for `lines` */ + if (str_startswith("lines", cmd) || str_startswith("ln", cmd)) { + uc_program_t *prog = frame->closure->function->program; + size_t len = strlen(args[1].sv); + + /* suggest function names */ + uc_program_function_foreach(prog, fn) { + if (fn->name[0] == '\0') + continue; + + if (len > 0 && strncmp(fn->name, args[1].sv, len) != 0) + continue; + + uc_vector_add(suggests, xstrdup(fn->name)); + } + + /* suggest file names */ + for (size_t i = 0; i < prog->sources.count; i++) { + uc_stringbuf_t buf = { 0 }; + printbuf_append_srcpath(&buf, prog->sources.entries[i], SIZE_MAX); + + if (len > 0 && strncmp(buf.buf, args[1].sv, len) != 0) { + free(buf.buf); + continue; + } + + uc_vector_add(suggests, buf.buf); + } + + return; + } + + /* completions for `help` */ + if (str_startswith("help", cmd)) { + size_t len = strlen(args[1].sv); + + /* suggest command names */ + for (size_t i = 0; i < ARRAY_SIZE(commands); i++) { + if (len > 0 && strncmp(commands[i].command, args[1].sv, len) != 0) + continue; + + uc_vector_add(suggests, xstrdup(commands[i].command)); + } + + return; + } + + /* completions for `print` */ + if (str_startswith("print", cmd)) { + uc_chunk_t *chunk = &frame->closure->function->chunk; + uc_variables_t *decls = &chunk->debuginfo.variables; + uc_value_list_t *names = &chunk->debuginfo.varnames; + size_t len = strlen(args[1].sv); + + /* suggest local variable names */ + for (size_t i = 0; i < decls->count; i++) { + uc_value_t *vname = load_constval(names, decls->entries[i].nameidx); + char *s = ucv_string_get(vname); + + if (*s != '(' && (len == 0 || strncmp(s, args[1].sv, len) == 0)) + uc_vector_add(suggests, xstrdup(s)); + + ucv_put(vname); + } + + /* suggest global variables */ + ucv_object_foreach(uc_vm_scope_get(vm), k, v) { + /* skip functions */ + if (ucv_is_callable(v)) + continue; + + if (len > 0 && strncmp(k, args[1].sv, len) != 0) + continue; + + uc_vector_add(suggests, xstrdup(k)); + } + } +} + +static void +bk_enter_cli(uc_vm_t *vm, uc_breakpoint_t *bk) +{ + debug_breakpoint_t *dbk = (debug_breakpoint_t *)bk; + arg_t *argv = NULL; + ssize_t argc = 0; + + term_isig(false); + print_location(vm, "Paused execution in ", dbk); + + while ((argc = term_getline("dbg > ", &argv, cli_tab_complete, vm)) > -1) { + size_t l = (argc > 0) ? strlen(argv[0].sv) : 0, i; + bool proceed = true; + + for (i = 0; l > 0 && i < ARRAY_SIZE(commands); i++) { + bool match = false; + + for (const char *c = commands[i].command; *c; c += strlen(c) + 1) { + if (strncmp(c, argv[0].sv, l) == 0) { + match = true; + break; + } + } + + if (!match) + continue; + + proceed = commands[i].cb(vm, dbk, argc, argv); + break; + } + + if (l > 0 && i == ARRAY_SIZE(commands)) + term_printf("Unrecognized command '%s'\n", argv[0].sv); + + while (argc > 0) + free(argv[--argc].sv); + + free(argv); + + if (!proceed) + break; + } + + if (dbk->kind == BK_ONCE) + free_breakpoint(vm, &dbk->bk); + + term_isig(true); +} + +static uc_value_t * +uc_debug_sigint_handler(uc_vm_t *vm, size_t nargs) +{ + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + + if (!frame) + return NULL; + + debug_breakpoint_t dbk = { + .bk = { .ip = frame->ip }, + .fn = frame->closure->function, + .kind = BK_USER + }; + + bk_enter_cli(vm, &dbk.bk); + + uc_value_t *sigint_handler = + uc_vm_registry_get(vm, "debug.orig_int_signal"); + + if (ucv_is_callable(sigint_handler)) { + uc_vm_stack_push(vm, ucv_get(sigint_handler)); + uc_vm_stack_push(vm, ucv_get(uc_fn_arg(0))); + + if (uc_vm_call(vm, false, 1) == EXCEPTION_NONE) + return uc_vm_stack_pop(vm); + } + + return NULL; +} + +static uc_value_t * +uc_debug_sigwinch_handler(uc_vm_t *vm, size_t nargs) +{ + term_dimensions(); + + uc_value_t *sigwinch_handler = + uc_vm_registry_get(vm, "debug.orig_winch_signal"); + + if (ucv_is_callable(sigwinch_handler)) { + uc_vm_stack_push(vm, ucv_get(sigwinch_handler)); + uc_vm_stack_push(vm, ucv_get(uc_fn_arg(0))); + + if (uc_vm_call(vm, false, 1) == EXCEPTION_NONE) + return uc_vm_stack_pop(vm); + } + + return NULL; +} + +/** + * Initialize interactive debugger. + * + * The `debugger()` function sets up the interactive command line debugger and + * immediately starts it, or - when a function argument is provided - defers the + * debugger invocation until the given function is called. + * + * This function does not return any value. + * + * @function module:debug#debugger + * + * @param {function} [target] + * An optional function to attach the debugger to. When provided, a debug + * breakpoint is installed at the first instruction of the given function, + * causing the debug cli to get launched as soon as this function is entered. + * + * @example + * // Launch debugger immediately + * debug.debugger(); + * + * + * // Attach debugger to function + * function test(a, b) { + * print(`Result is ${a * b}\n`); + * } + * + * debug.debugger(test); // Install debug breakpoint in `test()` function + * test(); // Starts debugger, breaking before `print(…)` + */ +static uc_value_t * +uc_debugger(uc_vm_t *vm, size_t nargs) +{ + uc_cfn_ptr_t ucsignal = uc_stdlib_function("signal"); + uc_value_t *mainfn = uc_fn_arg(0); + + if (termstate.initialized == false) { + uc_vm_stack_push(vm, ucv_string_new("SIGINT")); + uc_vm_registry_set(vm, "debug.orig_int_signal", ucsignal(vm, 1)); + ucv_put(uc_vm_stack_pop(vm)); + + uc_vm_stack_push(vm, ucv_string_new("SIGINT")); + uc_vm_stack_push(vm, + ucv_cfunction_new("debug_sigint_handler", uc_debug_sigint_handler)); + ucv_put(ucsignal(vm, 2)); + ucv_put(uc_vm_stack_pop(vm)); + ucv_put(uc_vm_stack_pop(vm)); + + uc_vm_stack_push(vm, ucv_string_new("SIGWINCH")); + uc_vm_registry_set(vm, "debug.orig_winch_signal", ucsignal(vm, 1)); + ucv_put(uc_vm_stack_pop(vm)); + + uc_vm_stack_push(vm, ucv_string_new("SIGWINCH")); + uc_vm_stack_push(vm, + ucv_cfunction_new("debug_sigwinch_handler", uc_debug_sigwinch_handler)); + ucv_put(ucsignal(vm, 2)); + ucv_put(uc_vm_stack_pop(vm)); + ucv_put(uc_vm_stack_pop(vm)); + + term_raw(); + term_isig(true); + + termstate.initialized = true; + } + + if (ucv_type(mainfn) == UC_CLOSURE) { + uc_function_t *fn = ((uc_closure_t *)mainfn)->function; + update_breakpoint(vm, BK_STEP, bk_enter_cli, fn->chunk.entries, fn, 1); + } + else { + uc_callframe_t *frame = uc_debug_curr_frame(vm, 0); + + if (frame) { + debug_breakpoint_t dbk = { + .bk = { .ip = frame->ip }, + .fn = frame->closure->function, + .kind = BK_USER + }; + + bk_enter_cli(vm, &dbk.bk); + } + } + + return NULL; +} + + +static const uc_function_list_t debug_fns[] = { + { "memdump", uc_memdump }, + { "traceback", uc_traceback }, + { "sourcepos", uc_sourcepos }, + { "getinfo", uc_getinfo }, + { "getlocal", uc_getlocal }, + { "setlocal", uc_setlocal }, + { "getupval", uc_getupval }, + { "setupval", uc_setupval }, + { "debugger", uc_debugger }, +}; + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, debug_fns); + + debug_setup(vm); + + have_highlighting = compile_patterns(); } diff --git a/main.c b/main.c index 1faaba5e..7389de94 100644 --- a/main.c +++ b/main.c @@ -106,13 +106,19 @@ print_usage(const char *app) "-s\n" " Omit (strip) debug information when compiling files.\n" - " Only meaningful in conjunction with `-c`.\n\n", + " Only meaningful in conjunction with `-c`.\n\n" + + "-x\n" + " Start program in interactive debugger.\n\n", app); } +static bool +parse_library_load(char *opt, uc_vm_t *vm); static int -compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip, char *interp, bool print_result) +compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip, + char *interp, bool print_result, bool debugger) { uc_value_t *res = NULL; uc_program_t *program; @@ -140,6 +146,30 @@ compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip, char *inter if (vm->gc_interval) uc_vm_gc_start(vm, vm->gc_interval); + if (debugger) { + if (!parse_library_load("debug", vm)) { + fprintf(stderr, "Unable to load debug module\n"); + rc = -2; + goto out; + } + + uc_value_t *dbgmod = ucv_object_get(uc_vm_scope_get(vm), "debug", NULL); + uc_value_t *dbgfn = ucv_object_get(dbgmod, "debugger", NULL); + + if (ucv_type(dbgfn) != UC_CFUNCTION) { + fprintf(stderr, "Unable to locate debugger function\n"); + rc = -2; + goto out; + } + + uc_vm_stack_push(vm, ucv_get(dbgfn)); + uc_vm_stack_push(vm, + ucv_closure_new(vm, uc_program_entry(program), false)); + + if (uc_vm_call(vm, false, 1) == EXCEPTION_NONE) + ucv_put(uc_vm_stack_pop(vm)); + } + rc = uc_vm_execute(vm, program, &res); switch (rc) { @@ -499,8 +529,8 @@ appname(const char *argv0) int main(int argc, char **argv) { - const char *optspec = "he:p:tg:ST::RD:F:U:l:L:c::o:s"; - bool strip = false, print_result = false; + const char *optspec = "he:p:tg:ST::RD:F:U:l:L:c::o:sx"; + bool strip = false, print_result = false, debugger = false; char *interp = "/usr/bin/env ucode"; uc_source_t *source = NULL; FILE *precompile = NULL; @@ -635,6 +665,10 @@ main(int argc, char **argv) case 'o': outfile = optarg; break; + + case 'x': + debugger = true; + break; } } @@ -684,7 +718,7 @@ main(int argc, char **argv) ucv_put(o); - rv = compile(&vm, source, precompile, strip, interp, print_result); + rv = compile(&vm, source, precompile, strip, interp, print_result, debugger); out: uc_search_path_free(&config.module_search_path); diff --git a/platform.c b/platform.c index 63a79d4f..6a42672e 100644 --- a/platform.c +++ b/platform.c @@ -109,6 +109,9 @@ const char *uc_system_signal_names[UC_SYSTEM_SIGNAL_COUNT] = { #if defined(SIGUSR2) [SIGUSR2] = "USR2", #endif +#if defined(SIGWINCH) + [SIGWINCH] = "WINCH", +#endif }; diff --git a/tests/cram/test_basic.t b/tests/cram/test_basic.t index 798436e3..8547547c 100644 --- a/tests/cram/test_basic.t +++ b/tests/cram/test_basic.t @@ -79,6 +79,9 @@ check that ucode provides exepected help: Omit (strip) debug information when compiling files. Only meaningful in conjunction with `-c`. + -x + Start program in interactive debugger. + check that ucode prints greetings: diff --git a/vm.c b/vm.c index fc322c5d..ee4cae30 100644 --- a/vm.c +++ b/vm.c @@ -37,7 +37,7 @@ static const char *insn_names[__I_MAX] = { __insns }; -static const int8_t insn_operand_bytes[__I_MAX] = { +const int8_t uc_vm_insn_format[__I_MAX] = { [I_LOAD] = 4, [I_LOAD8] = 1, [I_LOAD16] = 2, @@ -271,6 +271,11 @@ void uc_vm_free(uc_vm_t *vm) free(vm->restypes.entries[i]); uc_vector_clear(&vm->restypes); + + for (i = 0; i < vm->breakpoints.count; i++) + free(vm->breakpoints.entries[i]); + + uc_vector_clear(&vm->breakpoints); } static uc_chunk_t * @@ -312,6 +317,7 @@ uc_vm_is_strict(uc_vm_t *vm) static uc_vm_insn_t uc_vm_decode_insn(uc_vm_t *vm, uc_callframe_t *frame, uc_chunk_t *chunk) { + uc_breakpoints_t *bks = &vm->breakpoints; uc_vm_insn_t insn; #ifndef NDEBUG @@ -320,12 +326,19 @@ uc_vm_decode_insn(uc_vm_t *vm, uc_callframe_t *frame, uc_chunk_t *chunk) assert(frame->ip < end); + for (size_t i = 0; i < bks->count; i++) { + uc_breakpoint_t *bk = bks->entries[i]; + + if (bk != NULL && (bk->ip == NULL || bk->ip == frame->ip)) + bk->cb(vm, bk); + } + insn = frame->ip[0]; frame->ip++; - assert(frame->ip + abs(insn_operand_bytes[insn]) <= end); + assert(frame->ip + abs(uc_vm_insn_format[insn]) <= end); - switch (insn_operand_bytes[insn]) { + switch (uc_vm_insn_format[insn]) { case 0: break; @@ -363,7 +376,7 @@ uc_vm_decode_insn(uc_vm_t *vm, uc_callframe_t *frame, uc_chunk_t *chunk) break; default: - fprintf(stderr, "Unhandled operand format: %" PRId8 "\n", insn_operand_bytes[insn]); + fprintf(stderr, "Unhandled operand format: %" PRId8 "\n", uc_vm_insn_format[insn]); abort(); } @@ -717,7 +730,7 @@ uc_dump_insn(uc_vm_t *vm, uint8_t *pos, uc_vm_insn_t insn) fprintf(stderr, "%08zx %s", pos - chunk->entries, insn_names[insn]); - switch (insn_operand_bytes[insn]) { + switch (uc_vm_insn_format[insn]) { case 0: break; @@ -750,7 +763,7 @@ uc_dump_insn(uc_vm_t *vm, uint8_t *pos, uc_vm_insn_t insn) break; default: - fprintf(stderr, " (unknown operand format: %" PRId8 ")", insn_operand_bytes[insn]); + fprintf(stderr, " (unknown operand format: %" PRId8 ")", uc_vm_insn_format[insn]); break; } @@ -2768,6 +2781,17 @@ uc_vm_signal_dispatch(uc_vm_t *vm) return EXCEPTION_NONE; } +static uc_vm_status_t +uc_vm_exception_type_to_status(uc_vm_t *vm) +{ + switch (vm->exception.type) { + case EXCEPTION_NONE: return STATUS_OK; + case EXCEPTION_EXIT: return STATUS_EXIT; + case EXCEPTION_SYNTAX: return ERROR_COMPILE; + default: return ERROR_RUNTIME; + } +} + static uc_vm_status_t uc_vm_execute_chunk(uc_vm_t *vm) { @@ -2968,6 +2992,10 @@ uc_vm_execute_chunk(uc_vm_t *vm) case I_CALL: case I_QCALL: uc_vm_insn_call(vm, insn); + + if (vm->callframes.count == 0) + return uc_vm_exception_type_to_status(vm); + frame = uc_vm_current_frame(vm); chunk = frame->closure ? uc_vm_frame_chunk(frame) : NULL; break; @@ -2975,6 +3003,10 @@ uc_vm_execute_chunk(uc_vm_t *vm) case I_MCALL: case I_QMCALL: uc_vm_insn_mcall(vm, insn); + + if (vm->callframes.count == 0) + return uc_vm_exception_type_to_status(vm); + frame = uc_vm_current_frame(vm); chunk = frame->closure ? uc_vm_frame_chunk(frame) : NULL; break;