diff options
-rw-r--r-- | compiler.c | 151 | ||||
-rw-r--r-- | include/ucode/program.h | 2 | ||||
-rw-r--r-- | include/ucode/types.h | 8 | ||||
-rw-r--r-- | program.c | 26 | ||||
-rw-r--r-- | tests/custom/04_modules/06_export_errors | 26 | ||||
-rw-r--r-- | tests/custom/04_modules/07_import_default | 2 | ||||
-rw-r--r-- | tests/custom/04_modules/08_import_list | 2 | ||||
-rw-r--r-- | tests/custom/04_modules/12_import_immutability | 2 | ||||
-rw-r--r-- | tests/custom/04_modules/14_circular_imports | 43 | ||||
-rw-r--r-- | tests/custom/04_modules/15_complex_imports | 151 | ||||
-rw-r--r-- | vm.c | 3 |
11 files changed, 348 insertions, 68 deletions
@@ -169,10 +169,11 @@ uc_compiler_current_source(uc_compiler_t *compiler) __attribute__((format(printf, 3, 0))) static void uc_compiler_syntax_error(uc_compiler_t *compiler, size_t off, const char *fmt, ...) { + uc_source_t *source = uc_compiler_current_source(compiler); uc_stringbuf_t *buf = compiler->parser->error; size_t line = 0, byte = 0, len = 0; + char *s, *p, *nl; va_list ap; - char *s; if (compiler->parser->synchronizing) return; @@ -188,7 +189,7 @@ uc_compiler_syntax_error(uc_compiler_t *compiler, size_t off, const char *fmt, . if (off) { byte = off; - line = uc_source_get_line(uc_compiler_current_source(compiler), &byte); + line = uc_source_get_line(source, &byte); } va_start(ap, fmt); @@ -196,15 +197,53 @@ uc_compiler_syntax_error(uc_compiler_t *compiler, size_t off, const char *fmt, . va_end(ap); ucv_stringbuf_append(buf, "Syntax error: "); - ucv_stringbuf_addstr(buf, s, len); - ucv_stringbuf_append(buf, "\n"); + + p = strstr(s, "\nSyntax error: "); + + if (!p) { + ucv_stringbuf_addstr(buf, s, len); + ucv_stringbuf_append(buf, "\n"); + } + else { + ucv_stringbuf_printf(buf, "%.*s\n\n", (int)(p - s), s); + + while (len > 0 && s[len-1] == '\n') + s[--len] = 0; + + for (p++, nl = strchr(p, '\n'); p != NULL; + p = nl ? nl + 1 : NULL, nl = p ? strchr(p, '\n') : NULL) + { + if (!nl) + ucv_stringbuf_printf(buf, " | %s", p); + else if (nl != p) + ucv_stringbuf_printf(buf, " | %.*s\n", (int)(nl - p), p); + else + ucv_stringbuf_append(buf, " |\n"); + } + + ucv_stringbuf_append(buf, "\n\n"); + } free(s); - if (line) - ucv_stringbuf_printf(buf, "In line %zu, byte %zu:\n", line, byte); + if (line) { + ucv_stringbuf_append(buf, "In "); + + if (compiler->program->sources.count > 1) { + len = strlen(source->filename); + + if (len > 48) + ucv_stringbuf_printf(buf, "...%s", source->filename + len - 45); + else + ucv_stringbuf_addstr(buf, source->filename, len); + + ucv_stringbuf_append(buf, ", "); + } + + ucv_stringbuf_printf(buf, "line %zu, byte %zu:\n", line, byte); + } - if (uc_error_context_format(buf, uc_compiler_current_source(compiler), NULL, off)) + if (uc_error_context_format(buf, source, NULL, off)) ucv_stringbuf_append(buf, "\n\n"); } @@ -593,6 +632,45 @@ uc_compiler_set_jmpaddr(uc_compiler_t *compiler, size_t off, uint32_t dest) chunk->entries[off + 4] = addr % 0x100; } +static void +uc_compiler_inc_exportnum(uc_compiler_t *compiler) +{ + uc_source_t *root = uc_program_function_source(uc_program_entry(compiler->program)); + uint64_t u; + + if (root->exports.count == 0) { + uc_vector_push(&root->exports, ucv_uint64_new(1)); + } + else { + u = ucv_uint64_get(root->exports.entries[0]); + + ucv_put(root->exports.entries[0]); + + root->exports.entries[0] = ucv_uint64_new(u + 1); + } +} + +static size_t +uc_compiler_get_exportnum(uc_compiler_t *compiler) +{ + uc_source_t *root = uc_program_function_source(uc_program_entry(compiler->program)); + + return root->exports.count ? ucv_uint64_get(root->exports.entries[0]) : 0; +} + +static void +uc_compiler_emit_exports(uc_compiler_t *compiler) { + size_t i; + + if (!compiler->patchlist || compiler->patchlist->token != TK_EXPORT) + return; + + for (i = 0; i < compiler->patchlist->count; i++) { + uc_compiler_emit_insn(compiler, 0, I_EXPORT); + uc_compiler_emit_u32(compiler, 0, compiler->patchlist->entries[i]); + } +} + static uc_function_t * uc_compiler_finish(uc_compiler_t *compiler) { @@ -601,6 +679,9 @@ uc_compiler_finish(uc_compiler_t *compiler) uc_upvals_t *upvals = &compiler->upvals; size_t i; + if (compiler->function->module) + uc_compiler_emit_exports(compiler); + uc_compiler_emit_insn(compiler, 0, I_LNULL); uc_compiler_emit_insn(compiler, 0, I_RETURN); @@ -2782,7 +2863,7 @@ uc_compiler_compile_control(uc_compiler_t *compiler) p = p->parent; } - if (!p) { + if (!p || p->token == TK_EXPORT) { uc_compiler_syntax_error(compiler, pos, (type == TK_BREAK) ? "break must be inside loop or switch" @@ -2809,6 +2890,12 @@ uc_compiler_compile_return(uc_compiler_t *compiler) uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); size_t off = chunk->count; + if (compiler->function->module) { + uc_compiler_syntax_error(compiler, 0, "return must be inside function body"); + + return; + } + uc_compiler_compile_expstmt(compiler); /* if we compiled an empty expression statement (`;`), load implicit null */ @@ -2948,8 +3035,8 @@ uc_compiler_export_add(uc_compiler_t *compiler, uc_value_t *name, ssize_t slot) "Duplicate default export for module '%s'", source->filename); } else { - uc_compiler_emit_insn(compiler, 0, I_EXPORT); - uc_compiler_emit_u32(compiler, 0, slot); + uc_vector_push(compiler->patchlist, slot); + uc_compiler_inc_exportnum(compiler); } } @@ -3009,7 +3096,7 @@ uc_compiler_compile_export(uc_compiler_t *compiler) uc_value_t *name; ssize_t slot; - if (compiler->program->sources.count == 1 || compiler->scope_depth) { + if (!compiler->function->module || compiler->scope_depth) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Exports may only appear at top level of a module"); @@ -3072,6 +3159,10 @@ uc_compiler_compile_module_source(uc_compiler_t *compiler, uc_source_t *source, uc_program_function_foreach(compiler->program, fn) { if (uc_program_function_source(fn) == source) { + if (source->exports.offset == (size_t)-1) + uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, + "Circular dependency"); + loaded = true; break; } @@ -3081,9 +3172,14 @@ uc_compiler_compile_module_source(uc_compiler_t *compiler, uc_source_t *source, load_idx = uc_program_function_id(compiler->program, uc_program_function_last(compiler->program)) + 1; + source->exports.offset = (size_t)-1; + if (!uc_compile_from_source(&config, source, compiler->program, errp)) return false; + source->exports.offset = uc_compiler_get_exportnum(compiler) - source->exports.count; + uc_compiler_current_source(compiler)->exports.offset += source->exports.count; + /* emit load, call & pop instructions */ uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_CLFN); uc_compiler_emit_u32(compiler, 0, load_idx); @@ -3097,20 +3193,17 @@ uc_compiler_compile_module_source(uc_compiler_t *compiler, uc_source_t *source, /* count imports, handle wildcard imports */ for (i = 0; i < ucv_array_length(imports); i++) { if (ucv_boolean_get(ucv_array_get(imports, i))) { - /* find index of first module export */ - slot = uc_program_export_lookup(compiler->program, source, source->exports.entries[0]); - - if (slot > 0xffff || source->exports.count > 0xffff) { + if (source->exports.offset > 0xffff || source->exports.count > 0xffff) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "Too many module exports"); } /* emit import instruction... */ - uc_compiler_emit_insn(compiler, 0, I_IMPORT); + uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_IMPORT); uc_compiler_emit_u32(compiler, 0, source->exports.count | (0xffff << 16)); /* ... followed by first module export offset ... */ - uc_compiler_emit_u16(compiler, 0, slot); + uc_compiler_emit_u16(compiler, 0, source->exports.offset); /* ... and constant indexes for all exported names */ for (load_idx = 0; load_idx < source->exports.count; load_idx++) { @@ -3138,7 +3231,7 @@ uc_compiler_compile_module_source(uc_compiler_t *compiler, uc_source_t *source, import = ucv_array_get(imports, i); if (!ucv_boolean_get(import)) { - slot = uc_program_export_lookup(compiler->program, source, import); + slot = uc_source_export_lookup(source, import); if (slot == -1) { if (import) @@ -3153,8 +3246,9 @@ uc_compiler_compile_module_source(uc_compiler_t *compiler, uc_source_t *source, "Too many module exports"); } else { - uc_compiler_emit_insn(compiler, 0, I_IMPORT); - uc_compiler_emit_u32(compiler, 0, slot | ((compiler->upvals.count - n_imports + i) << 16)); + uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_IMPORT); + uc_compiler_emit_u32(compiler, 0, + (source->exports.offset + slot) | ((compiler->upvals.count - n_imports + i) << 16)); } } } @@ -3163,14 +3257,14 @@ uc_compiler_compile_module_source(uc_compiler_t *compiler, uc_source_t *source, } static char * -uc_compiler_canonicalize_path(const char *path, const char *basedir) +uc_compiler_canonicalize_path(const char *path, const char *runpath) { char *p, *resolved; if (*path == '/') xasprintf(&p, "%s", path); - else if (basedir) - xasprintf(&p, "%s/%s", basedir, path); + else if (runpath && (p = strrchr(runpath, '/')) != NULL) + xasprintf(&p, "%.*s/%s", (int)(p - runpath), runpath, path); else xasprintf(&p, "./%s", path); @@ -3182,7 +3276,7 @@ uc_compiler_canonicalize_path(const char *path, const char *basedir) } static char * -uc_compiler_expand_module_path(const char *name, const char *basedir, const char *template) +uc_compiler_expand_module_path(const char *name, const char *runpath, const char *template) { int namelen, prefixlen; char *path, *p; @@ -3201,7 +3295,7 @@ uc_compiler_expand_module_path(const char *name, const char *basedir, const char if (*p == '.') *p = '/'; - p = uc_compiler_canonicalize_path(path, basedir); + p = uc_compiler_canonicalize_path(path, runpath); free(path); @@ -3464,6 +3558,7 @@ uc_compile_from_source(uc_parse_config_t *config, uc_source_t *source, uc_progra return NULL; #else + uc_patchlist_t exports = { .token = TK_EXPORT }; uc_exprstack_t expr = { .token = TK_EOF }; uc_parser_t parser = { .config = config }; uc_compiler_t compiler = { .parser = &parser, .exprstack = &expr }; @@ -3484,6 +3579,11 @@ uc_compile_from_source(uc_parse_config_t *config, uc_source_t *source, uc_progra uc_compiler_init(&compiler, name, source, 0, progptr, config && config->strict_declarations); + if (progptr == prog) { + compiler.patchlist = &exports; + compiler.function->module = true; + } + uc_compiler_parse_advance(&compiler); while (!uc_compiler_parse_match(&compiler, TK_EOF)) @@ -3500,6 +3600,7 @@ uc_compile_from_source(uc_parse_config_t *config, uc_source_t *source, uc_progra } uc_lexer_free(&parser.lex); + uc_vector_clear(&exports); if (!fn) { if (progptr != prog) diff --git a/include/ucode/program.h b/include/ucode/program.h index 9014ae4..19c8bdf 100644 --- a/include/ucode/program.h +++ b/include/ucode/program.h @@ -55,8 +55,6 @@ __hidden uc_source_t *uc_program_function_source(uc_function_t *); __hidden size_t uc_program_function_srcpos(uc_function_t *, size_t); __hidden void uc_program_function_free(uc_function_t *); -__hidden ssize_t uc_program_export_lookup(uc_program_t *, uc_source_t *, uc_value_t *); - __hidden uc_value_t *uc_program_get_constant(uc_program_t *, size_t); __hidden ssize_t uc_program_add_constant(uc_program_t *, uc_value_t *); diff --git a/include/ucode/types.h b/include/ucode/types.h index 0b63501..636d6e4 100644 --- a/include/ucode/types.h +++ b/include/ucode/types.h @@ -65,7 +65,6 @@ typedef struct { /* Source buffer defintions */ uc_declare_vector(uc_lineinfo_t, uint8_t); -uc_declare_vector(uc_exports_t, uc_value_t *); typedef struct { uc_value_t header; @@ -73,7 +72,10 @@ typedef struct { FILE *fp; size_t off; uc_lineinfo_t lineinfo; - uc_exports_t exports; + struct { + size_t count, offset; + uc_value_t **entries; + } exports; } uc_source_t; @@ -112,7 +114,7 @@ typedef struct uc_weakref { typedef struct uc_function { uc_weakref_t progref; - bool arrow, vararg, strict; + bool arrow, vararg, strict, module; size_t nargs; size_t nupvals; size_t srcidx; @@ -228,6 +228,7 @@ enum { UC_FUNCTION_F_HAS_NAME = (1 << 4), UC_FUNCTION_F_HAS_VARDBG = (1 << 5), UC_FUNCTION_F_HAS_OFFSETDBG = (1 << 6), + UC_FUNCTION_F_IS_MODULE = (1 << 7), }; static void @@ -288,6 +289,9 @@ write_function(uc_function_t *func, FILE *file, bool debug) if (func->strict) flags |= UC_FUNCTION_F_IS_STRICT; + if (func->module) + flags |= UC_FUNCTION_F_IS_MODULE; + if (func->chunk.ehranges.count) flags |= UC_FUNCTION_F_HAS_EXCEPTIONS; @@ -780,6 +784,7 @@ read_function(FILE *file, uc_program_t *program, size_t idx, char **errp) func->arrow = (flags & UC_FUNCTION_F_IS_ARROW); func->vararg = (flags & UC_FUNCTION_F_IS_VARARG); func->strict = (flags & UC_FUNCTION_F_IS_STRICT); + func->module = (flags & UC_FUNCTION_F_IS_MODULE); func->nargs = nargs; func->nupvals = nupvals; @@ -846,24 +851,3 @@ uc_program_entry(uc_program_t *program) return (uc_function_t *)program->functions.prev; } - -ssize_t -uc_program_export_lookup(uc_program_t *program, uc_source_t *source, uc_value_t *name) -{ - size_t i, off; - ssize_t slot; - - for (i = 0, off = 0; i < program->sources.count; i++) { - if (program->sources.entries[i] != source) { - off += program->sources.entries[i]->exports.count; - continue; - } - - slot = uc_source_export_lookup(source, name); - - if (slot > -1) - return off + slot; - } - - return -1; -} diff --git a/tests/custom/04_modules/06_export_errors b/tests/custom/04_modules/06_export_errors index c02a547..5c9f676 100644 --- a/tests/custom/04_modules/06_export_errors +++ b/tests/custom/04_modules/06_export_errors @@ -36,15 +36,14 @@ import "./files/test.uc"; -- Expect stderr -- Syntax error: Unable to compile module './files/test.uc': -Syntax error: Exports may only appear at top level of a module -In line 2, byte 2: - - ` export let x = 1;` - ^-- Near here - + | Syntax error: Exports may only appear at top level of a module + | In ./files/test.uc, line 2, byte 2: + | + | ` export let x = 1;` + | ^-- Near here -In line 1, byte 25: +In [stdin], line 1, byte 25: `import "./files/test.uc";` Near here --------------^ @@ -72,15 +71,14 @@ export { y as x }; -- Expect stderr -- Syntax error: Unable to compile module './files/test-duplicate.uc': -Syntax error: Duplicate export 'x' for module './files/test-duplicate.uc' -In line 4, byte 15: - - `export { y as x };` - Near here ----^ - + | Syntax error: Duplicate export 'x' for module './files/test-duplicate.uc' + | In ./files/test-duplicate.uc, line 4, byte 15: + | + | `export { y as x };` + | Near here ----^ -In line 1, byte 35: +In [stdin], line 1, byte 35: `import "./files/test-duplicate.uc";` Near here ------------------------^ diff --git a/tests/custom/04_modules/07_import_default b/tests/custom/04_modules/07_import_default index 7190a22..d9d08b5 100644 --- a/tests/custom/04_modules/07_import_default +++ b/tests/custom/04_modules/07_import_default @@ -39,7 +39,7 @@ export const x = "This is a non-default export"; -- Expect stderr -- Syntax error: Module ./files/test2.uc has no default export -In line 1, byte 20: +In [stdin], line 1, byte 20: `import defVal from "./files/test2.uc";` Near here ---------^ diff --git a/tests/custom/04_modules/08_import_list b/tests/custom/04_modules/08_import_list index 1a4f116..b55ddec 100644 --- a/tests/custom/04_modules/08_import_list +++ b/tests/custom/04_modules/08_import_list @@ -38,7 +38,7 @@ export const x = "This is a test"; -- Expect stderr -- Syntax error: Module ./files/test2.uc has no default export -In line 1, byte 15: +In [stdin], line 1, byte 15: `import y from "./files/test2.uc";` Near here ----^ diff --git a/tests/custom/04_modules/12_import_immutability b/tests/custom/04_modules/12_import_immutability index 37c0bc6..48a7fe2 100644 --- a/tests/custom/04_modules/12_import_immutability +++ b/tests/custom/04_modules/12_import_immutability @@ -16,7 +16,7 @@ export let a = 1; -- Expect stderr -- Syntax error: Invalid assignment to constant 'a' -In line 3, byte 5: +In [stdin], line 3, byte 5: `a = 2;` ^-- Near here diff --git a/tests/custom/04_modules/14_circular_imports b/tests/custom/04_modules/14_circular_imports new file mode 100644 index 0000000..0b6070a --- /dev/null +++ b/tests/custom/04_modules/14_circular_imports @@ -0,0 +1,43 @@ +Circular imports are not possible and will lead to a compilation error. + +-- Testcase -- +import a_val from "./files/a.uc"; +-- End -- + +-- File a.uc -- +import b_val from "./b.uc"; +export default "a"; +-- End -- + +-- File b.uc -- +import a_val from "./a.uc"; +export default "b"; +-- End -- + +-- Args -- +-R +-- End -- + +-- Expect stderr -- +Syntax error: Unable to compile module './files/a.uc': + + | Syntax error: Unable to compile module './files/b.uc': + | + | | Syntax error: Circular dependency + | | In ./files/b.uc, line 1, byte 19: + | | + | | `import a_val from "./a.uc";` + | | Near here --------^ + | + | In ./files/a.uc, line 1, byte 27: + | + | `import b_val from "./b.uc";` + | Near here ----------------^ + +In [stdin], line 1, byte 33: + + `import a_val from "./files/a.uc";` + Near here ----------------------^ + + +-- End -- diff --git a/tests/custom/04_modules/15_complex_imports b/tests/custom/04_modules/15_complex_imports new file mode 100644 index 0000000..f4dd588 --- /dev/null +++ b/tests/custom/04_modules/15_complex_imports @@ -0,0 +1,151 @@ +This testcase implements a somewhat complex dependency chain to stress +test the compiler module resolving. + +The dependency tree is: + +root + + mod1 + + mod4 + + mod8 + + mod2 + + mod4 + + mod6 + + mod8 + + mod9 + + mod3 + + mod4 + + mod6 + + mod4 + + mod5 + + mod1 + + mod4 + + mod8 + + mod2 + + mod4 + + mod6 + + mod8 + + mod9 + + mod4 + + mod6 + + mod8 + + mod9 + + mod4 + + mod6 + + mod6 + + mod7 + + mod5 + + mod1 + + mod4 + + mod8 + + mod2 + + mod4 + + mod6 + + mod8 + + mod9 + + mod4 + + mod6 + + mod8 + + mod9 + + mod4 + + mod6 + + mod6 + + mod8 + +-- Testcase -- +import mod1 from 'mod1'; +import mod2 from 'mod2'; +import mod3 from 'mod3'; +import mod4 from 'mod4'; +import mod5 from 'mod5'; +import mod6 from 'mod6'; +import mod7 from 'mod7'; +import mod8 from 'mod8'; + +print("root: ", [ mod1, mod2, mod3, mod4, mod5, mod6, mod7, mod8 ], "\n"); +-- End -- + +-- File mod1.uc -- +import mod4 from 'mod4'; +import mod8 from 'mod8'; + +print("mod1: ", [ mod4, mod8 ], "\n"); + +export default 'mod1'; +-- End -- + +-- File mod2.uc -- +import mod9 from 'mod9'; +import mod4 from 'mod4'; +import mod8 from 'mod8'; +import mod6 from 'mod6'; + +print("mod2: ", [ mod4, mod6, mod8, mod9 ], "\n"); + +export default 'mod2'; +-- End -- + +-- File mod3.uc -- +import mod4 from 'mod4'; +import mod6 from 'mod6'; + +print("mod3: ", [ mod4, mod6 ], "\n"); + +export default 'mod3'; +-- End -- + +-- File mod4.uc -- +export default 'mod4'; +-- End -- + +-- File mod5.uc -- +import mod1 from 'mod1'; +import mod4 from 'mod4'; +import mod2 from 'mod2'; +import mod9 from 'mod9'; +import mod8 from 'mod8'; +import mod6 from 'mod6'; + +print("mod5: ", [ mod1, mod2, mod4, mod6, mod8, mod9 ], "\n"); + +export default 'mod5'; +-- End -- + +-- File mod6.uc -- +export default 'mod6'; +-- End -- + +-- File mod7.uc -- +import mod6 from 'mod6'; +import mod5 from 'mod5'; + +print("mod7: ", [ mod5, mod6 ], "\n"); + +export default 'mod7'; +-- End -- + +-- File mod8.uc -- +export default 'mod8'; +-- End -- + +-- File mod9.uc -- +import mod4 from 'mod4'; +import mod6 from 'mod6'; + +print("mod9: ", [ mod4, mod6 ], "\n"); + +export default 'mod9'; +-- End -- + +-- Args -- +-R -L files/ +-- End -- + +-- Expect stdout -- +mod1: [ "mod4", "mod8" ] +mod9: [ "mod4", "mod6" ] +mod2: [ "mod4", "mod6", "mod8", "mod9" ] +mod3: [ "mod4", "mod6" ] +mod5: [ "mod1", "mod2", "mod4", "mod6", "mod8", "mod9" ] +mod7: [ "mod5", "mod6" ] +root: [ "mod1", "mod2", "mod3", "mod4", "mod5", "mod6", "mod7", "mod8" ] +-- End -- @@ -1179,6 +1179,9 @@ uc_vm_insn_load_closure(uc_vm_t *vm, uc_vm_insn_t insn) uc_vm_stack_push(vm, &closure->header); + if (function->module) + return; + for (i = 0; i < function->nupvals; i++) { uv = ( frame->ip[0] * 0x1000000 + |