summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2021-05-04 16:01:13 +0200
committerGitHub <noreply@github.com>2021-05-04 16:01:13 +0200
commit799c9f4dbe123536e1dde1639a122f48dcf75a05 (patch)
treeedcf626dc7a68398e8ac9a9793aa0ebb9a9cf042
parenta5aead5161821b2f5be9ae76f1bd76e8814147bd (diff)
parenta36e0dfd8432a0c345ab3a710280f6d4f663bddc (diff)
Merge pull request #8 from jow-/introduce-use-strict
syntax: implement support for 'use strict' pragma
-rw-r--r--compiler.c53
-rw-r--r--tests/custom/00_syntax/22_strict_mode92
-rw-r--r--types.h4
-rw-r--r--vm.c14
4 files changed, 151 insertions, 12 deletions
diff --git a/compiler.c b/compiler.c
index 8c30d84..bbd6dbc 100644
--- a/compiler.c
+++ b/compiler.c
@@ -94,9 +94,10 @@ static ssize_t
uc_compiler_initialize_local(uc_compiler *compiler);
static void
-uc_compiler_init(uc_compiler *compiler, const char *name, size_t srcpos, uc_source *source)
+uc_compiler_init(uc_compiler *compiler, const char *name, size_t srcpos, uc_source *source, bool strict)
{
uc_value_t *varname = ucv_string_new("(callee)");
+ uc_function_t *fn;
compiler->scope_depth = 0;
@@ -114,6 +115,9 @@ uc_compiler_init(uc_compiler *compiler, const char *name, size_t srcpos, uc_sour
compiler->current_srcpos = srcpos;
+ fn = (uc_function_t *)compiler->function;
+ fn->strict = strict;
+
/* reserve stack slot 0 */
uc_compiler_declare_local(compiler, varname);
uc_compiler_initialize_local(compiler);
@@ -625,6 +629,14 @@ uc_compiler_leave_scope(uc_compiler *compiler)
}
}
+static bool
+uc_compiler_is_strict(uc_compiler *compiler)
+{
+ uc_function_t *fn = (uc_function_t *)compiler->function;
+
+ return fn->strict;
+}
+
static ssize_t
uc_compiler_declare_local(uc_compiler *compiler, uc_value_t *name)
{
@@ -653,8 +665,7 @@ uc_compiler_declare_local(uc_compiler *compiler, uc_value_t *name)
len2 = ucv_string_length(locals->entries[i - 1].name);
if (len1 == len2 && !strcmp(str1, str2)) {
- if (compiler->parser->config &&
- compiler->parser->config->strict_declarations) {
+ if (uc_compiler_is_strict(compiler)) {
uc_compiler_syntax_error(compiler, 0, "Variable '%s' redeclared", str2);
return -1;
@@ -1047,7 +1058,8 @@ uc_compiler_compile_arrowfn(uc_compiler *compiler, uc_value_t *args, bool restar
pos = compiler->parser->prev.pos;
uc_compiler_init(&fncompiler, NULL, compiler->parser->prev.pos,
- uc_compiler_current_source(compiler));
+ uc_compiler_current_source(compiler),
+ uc_compiler_is_strict(compiler));
fncompiler.parent = compiler;
fncompiler.parser = compiler->parser;
@@ -1322,9 +1334,26 @@ uc_compiler_compile_post_inc(uc_compiler *compiler, bool assignable)
uc_compiler_emit_inc_dec(compiler, compiler->parser->prev.type, true);
}
+static bool
+uc_compiler_is_use_strict_pragma(uc_compiler *compiler)
+{
+ uc_value_t *v;
+
+ if (uc_compiler_current_chunk(compiler)->count > 0)
+ return false;
+
+ if (compiler->parser->lex.block != STATEMENTS)
+ return false;
+
+ v = compiler->parser->prev.uv;
+
+ return (strcmp(ucv_string_get(v), "use strict") == 0);
+}
+
static void
uc_compiler_compile_constant(uc_compiler *compiler, bool assignable)
{
+ uc_function_t *fn;
int64_t n;
switch (compiler->parser->prev.type) {
@@ -1341,8 +1370,16 @@ uc_compiler_compile_constant(uc_compiler *compiler, bool assignable)
ucv_boolean_get(compiler->parser->prev.uv) ? I_LTRUE : I_LFALSE);
break;
- case TK_DOUBLE:
case TK_STRING:
+ if (uc_compiler_is_use_strict_pragma(compiler)) {
+ fn = (uc_function_t *)compiler->function;
+ fn->strict = true;
+ break;
+ }
+
+ /* fall through */
+
+ case TK_DOUBLE:
uc_compiler_emit_constant(compiler, compiler->parser->prev.pos, compiler->parser->prev.uv);
break;
@@ -1429,7 +1466,8 @@ uc_compiler_compile_function(uc_compiler *compiler, bool assignable)
uc_compiler_init(&fncompiler,
name ? ucv_string_get(name) : NULL, compiler->parser->prev.pos,
- uc_compiler_current_source(compiler));
+ uc_compiler_current_source(compiler),
+ uc_compiler_is_strict(compiler));
fncompiler.parent = compiler;
fncompiler.parser = compiler->parser;
@@ -2706,7 +2744,8 @@ uc_compile(uc_parse_config *config, uc_source *source, char **errp)
uc_function_t *fn;
uc_lexer_init(&parser.lex, config, source);
- uc_compiler_init(&compiler, "main", 0, source);
+ uc_compiler_init(&compiler, "main", 0, source,
+ config && config->strict_declarations);
uc_compiler_parse_advance(&compiler);
diff --git a/tests/custom/00_syntax/22_strict_mode b/tests/custom/00_syntax/22_strict_mode
new file mode 100644
index 0000000..73f399c
--- /dev/null
+++ b/tests/custom/00_syntax/22_strict_mode
@@ -0,0 +1,92 @@
+Ucode borrows the `"use strict";` statement from ECMA script to enable
+strict variable semantics for the entire script or for the enclosing
+function.
+
+With strict mode enabled, attempts to use undeclared local variables
+or attempts to read global variables which have not been assigned yet
+will raise an exception.
+
+
+1. To enable strict mode for the entire script, it should be the first
+statement of the program.
+
+-- Expect stderr --
+Reference error: access to undeclared variable x
+In line 4, byte 8:
+
+ ` print(x);`
+ ^-- Near here
+
+
+-- End --
+
+-- Testcase --
+{%
+ "use strict";
+
+ print(x);
+%}
+-- End --
+
+
+2. To enable strict mode for a single function, the "use strict" expression
+should be the first statement of the function body.
+
+-- Expect stdout --
+a() = null
+-- End --
+
+-- Expect stderr --
+Reference error: access to undeclared variable x
+In b(), line 9, byte 24:
+ called from anonymous function ([stdin]:13:4)
+
+ ` printf("b() = %J\n", x);`
+ Near here -------------------^
+
+
+-- End --
+
+-- Testcase --
+{%
+ function a() {
+ printf("a() = %J\n", x);
+ }
+
+ function b() {
+ "use strict";
+
+ printf("b() = %J\n", x);
+ }
+
+ a();
+ b();
+%}
+-- End --
+
+
+3. When "use strict" is not the first statement, it has no effect.
+
+-- Expect stdout --
+b=null
+c=null
+-- End --
+
+-- Testcase --
+{%
+ function t() {
+ a = 1;
+
+ "use strict";
+
+ printf("b=%J\n", b);
+ }
+
+ t();
+
+ "use strict";
+
+ printf("c=%J\n", c);
+
+%}
+-- End --
diff --git a/types.h b/types.h
index 8e1ba5f..eec0099 100644
--- a/types.h
+++ b/types.h
@@ -150,7 +150,7 @@ typedef struct {
typedef struct {
uc_value_t header;
- bool arrow, vararg;
+ bool arrow, vararg, strict;
size_t nargs;
size_t nupvals;
size_t srcpos;
@@ -231,7 +231,7 @@ typedef struct {
uc_cfunction_t *cfunction;
size_t stackframe;
uc_value_t *ctx;
- bool mcall;
+ bool mcall, strict;
} uc_callframe;
uc_declare_vector(uc_callframes, uc_callframe);
diff --git a/vm.c b/vm.c
index e48afb0..5f5f785 100644
--- a/vm.c
+++ b/vm.c
@@ -158,6 +158,12 @@ uc_vm_current_chunk(uc_vm *vm)
return uc_vm_frame_chunk(uc_vm_current_frame(vm));
}
+static bool
+uc_vm_is_strict(uc_vm *vm)
+{
+ return uc_vm_current_frame(vm)->strict;
+}
+
static enum insn_type
uc_vm_decode_insn(uc_vm *vm, uc_callframe *frame, uc_chunk *chunk)
{
@@ -503,6 +509,7 @@ uc_vm_call_function(uc_vm *vm, uc_value_t *ctx, uc_value_t *fno, bool mcall, siz
frame->ctx = ctx;
frame->ip = function->chunk.entries;
frame->mcall = mcall;
+ frame->strict = function->strict;
if (vm->trace)
uc_vm_frame_dump(vm, frame);
@@ -919,7 +926,7 @@ uc_vm_insn_load_var(uc_vm *vm, enum insn_type insn)
next = ucv_prototype_get(scope);
if (!next) {
- if (vm->config->strict_declarations) {
+ if (uc_vm_is_strict(vm)) {
uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
"access to undeclared variable %s",
ucv_string_get(name));
@@ -1092,7 +1099,7 @@ uc_vm_insn_store_var(uc_vm *vm, enum insn_type insn)
next = ucv_prototype_get(scope);
if (!next) {
- if (vm->config->strict_declarations) {
+ if (uc_vm_is_strict(vm)) {
uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
"access to undeclared variable %s",
ucv_string_get(name));
@@ -1335,7 +1342,7 @@ uc_vm_insn_update_var(uc_vm *vm, enum insn_type insn)
next = ucv_prototype_get(scope);
if (!next) {
- if (vm->config->strict_declarations) {
+ if (uc_vm_is_strict(vm)) {
uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
"access to undeclared variable %s",
ucv_string_get(name));
@@ -2250,6 +2257,7 @@ uc_vm_execute(uc_vm *vm, uc_function_t *fn, uc_value_t *globals, uc_value_t *mod
frame->closure = closure;
frame->stackframe = 0;
frame->ip = uc_vm_frame_chunk(frame)->entries;
+ frame->strict = fn->strict;
if (vm->trace) {
buf = xprintbuf_new();