Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 79 additions & 6 deletions compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,13 @@ uc_compiler_declare_local(uc_compiler_t *compiler, uc_value_t *name, bool consta
len2 = ucv_string_length(locals->entries[i - 1].name);

if (len1 == len2 && !strcmp(str1, str2)) {
if (locals->entries[i - 1].funcstub) {
uc_compiler_syntax_error(compiler, compiler->parser->prev.pos,
"Variable '%s' redeclared", str2);

return -1;
}

if (uc_compiler_is_strict(compiler)) {
uc_compiler_syntax_error(compiler, 0, "Variable '%s' redeclared", str2);

Expand Down Expand Up @@ -937,6 +944,31 @@ uc_compiler_resolve_local(uc_compiler_t *compiler, uc_value_t *name, bool *const
return -1;
}

static ssize_t
uc_compiler_resolve_funcstub(uc_compiler_t *compiler, uc_value_t *name)
{
uc_locals_t *locals = &compiler->locals;
const char *str1, *str2;
size_t i, len1, len2;

str1 = ucv_string_get(name);
len1 = ucv_string_length(name);

for (i = locals->count; i > 0; i--) {
if (locals->entries[i - 1].depth != -1 &&
locals->entries[i - 1].depth < (ssize_t)compiler->scope_depth)
break;

str2 = ucv_string_get(locals->entries[i - 1].name);
len2 = ucv_string_length(locals->entries[i - 1].name);

if (len1 == len2 && !strcmp(str1, str2))
return locals->entries[i - 1].funcstub ? (ssize_t)(i - 1) : -1;
}

return -1;
}

static ssize_t
uc_compiler_add_upval(uc_compiler_t *compiler, size_t idx, bool local, uc_value_t *name, bool constant)
{
Expand Down Expand Up @@ -1890,13 +1922,48 @@ uc_compiler_compile_funcexpr_common(uc_compiler_t *compiler, bool require_name)
if (uc_compiler_parse_match(compiler, TK_LABEL)) {
name = compiler->parser->prev.uv;

/* Named functions are syntactic sugar for local variable declaration
* with function value assignment. If a name token was encountered,
* initialize a local variable for it... */
slot = uc_compiler_declare_local(compiler, name, false);
if (require_name && uc_compiler_parse_check(compiler, TK_SCOL)) {
slot = uc_compiler_resolve_funcstub(compiler, name);

if (slot > -1) {
uc_compiler_syntax_error(compiler, compiler->parser->prev.pos,
"Function '%s' redeclared", ucv_string_get(name));

return;
}

slot = uc_compiler_declare_local(compiler, name, true);

if (slot > -1) {
uc_compiler_syntax_error(compiler, compiler->parser->prev.pos,
"Variable '%s' redeclared", ucv_string_get(name));

return;
}

if (slot == -1)
compiler->locals.entries[compiler->locals.count - 1].funcstub = true;
uc_compiler_emit_insn(compiler, compiler->parser->prev.pos, I_LNULL);
uc_compiler_initialize_local(compiler);
uc_compiler_parse_consume(compiler, TK_SCOL);

return;
}

slot = uc_compiler_resolve_funcstub(compiler, name);

if (slot > -1) {
compiler->locals.entries[slot].funcstub = false;
}
else {
slot = uc_compiler_declare_local(compiler, name, false);

if (slot == -1)
uc_compiler_initialize_local(compiler);
else if (compiler->locals.entries[slot].constant)
uc_compiler_syntax_error(compiler, compiler->parser->prev.pos,
"Redeclaration of constant '%s'",
ucv_string_get(name));
}
}
else if (require_name) {
uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, "Expecting function name");
Expand Down Expand Up @@ -3302,8 +3369,14 @@ uc_compiler_compile_export(uc_compiler_t *compiler)
uc_compiler_compile_declexpr(compiler, false);
else if (uc_compiler_parse_match(compiler, TK_CONST))
uc_compiler_compile_declexpr(compiler, true);
else if (uc_compiler_parse_match(compiler, TK_FUNC))
else if (uc_compiler_parse_match(compiler, TK_FUNC)) {
uc_compiler_compile_funcdecl(compiler);

for (; off < locals->count; off++)
uc_compiler_export_add(compiler, locals->entries[off].name, off);

return;
}
else if (uc_compiler_parse_match(compiler, TK_DEFAULT))
uc_compiler_compile_expression(compiler);
else
Expand Down
1 change: 1 addition & 0 deletions include/ucode/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ typedef struct {
size_t from;
bool captured;
bool constant;
bool funcstub;
} uc_local_t;

typedef struct {
Expand Down
273 changes: 273 additions & 0 deletions tests/custom/00_syntax/29_function_forward_declarations
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
The `function foo;` syntax allows forward-declaring a function name
before providing its definition. This enables mutual recursion between
functions at the same scope level.


1. A forward-declared function can be defined and called normally.

-- Expect stdout --
hello
-- End --

-- Testcase --
{%
function greet;

function greet() {
return "hello";
}

print(greet(), "\n");
%}
-- End --


2. Forward declarations enable mutual recursion.

-- Expect stdout --
[
true,
true,
true,
true
]
-- End --

-- Testcase --
{%
function is_even;
function is_odd;

function is_even(n) {
if (n == 0) return true;
return is_odd(n - 1);
}

function is_odd(n) {
if (n == 0) return false;
return is_even(n - 1);
}

printf("%.J\n", [
is_even(0),
is_odd(1),
is_even(10),
is_odd(11)
]);
%}
-- End --


3. Assignment to a forward-declared function is forbidden (constant binding).

-- Expect stderr --
Syntax error: Invalid assignment to constant 'foo'
In line 4, byte 8:

` foo = function() { return 2; };`
^-- Near here


-- End --

-- Testcase --
{%
function foo;

foo = function() { return 2; };
%}
-- End --


4. A `let` declaration after a forward declaration is forbidden.

-- Expect stderr --
Syntax error: Variable 'foo' redeclared
In line 4, byte 6:

` let foo = 1;`
^-- Near here


-- End --

-- Testcase --
{%
function foo;

let foo = 1;
%}
-- End --


5. A `const` declaration after a forward declaration is forbidden.

-- Expect stderr --
Syntax error: Variable 'foo' redeclared
In line 4, byte 8:

` const foo = 1;`
^-- Near here


-- End --

-- Testcase --
{%
function foo;

const foo = 1;
%}
-- End --


6. Defining a function twice after forward declaration is forbidden.

-- Expect stderr --
Syntax error: Redeclaration of constant 'foo'
In line 5, byte 11:

` function foo() { return 2; }`
Near here ---^


-- End --

-- Testcase --
{%
function foo;

function foo() { return 1; }
function foo() { return 2; }
%}
-- End --


7. Duplicate forward declarations are forbidden.

-- Expect stderr --
Syntax error: Function 'foo' redeclared
In line 3, byte 11:

` function foo;`
Near here ---^


-- End --

-- Testcase --
{%
function foo;
function foo;
%}
-- End --


8. Forward declaration after a definition is forbidden.

-- Expect stderr --
Syntax error: Variable 'foo' redeclared
In line 3, byte 11:

` function foo;`
Near here ---^


-- End --

-- Testcase --
{%
function foo() { return 1; }
function foo;
%}
-- End --


9. Calling an unfilled forward declaration raises a runtime error.

-- Expect stderr --
Type error: left-hand side is not a function
In line 4, byte 6:

` foo();`
^-- Near here


-- End --

-- Testcase --
{%
function foo;

foo();
%}
-- End --


10. A nested function can capture a forward-declared name as upvalue.

-- Expect stdout --
hello from foo
-- End --

-- Testcase --
{%
function foo;

function call_foo() {
return foo();
}

function foo() {
return "hello from foo";
}

print(call_foo(), "\n");
%}
-- End --


11. Forward declarations work with export.

-- File test-forward-decl.uc --
export function foo;

function foo() {
return "exported";
}
-- End --

-- Args --
-R
-- End --

-- Expect stdout --
exported
-- End --

-- Testcase --
import { foo } from "./files/test-forward-decl.uc";

print(foo(), "\n");
-- End --


12. Increment/decrement of a forward-declared function is forbidden.

-- Expect stderr --
Syntax error: Invalid increment/decrement of constant 'foo'
In line 4, byte 5:

` foo++;`
^-- Near here


-- End --

-- Testcase --
{%
function foo;

foo++;
%}
-- End --
Loading