summaryrefslogtreecommitdiffhomepage
path: root/tests/custom/04_modules
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-07-17 23:21:03 +0200
committerJo-Philipp Wich <jo@mein.io>2022-07-30 13:46:23 +0200
commit10e056d3744384a029f05de5903c489898722fc3 (patch)
treee6621194f1053fdc314dfee02358972028a6a5ff /tests/custom/04_modules
parent862e49de33bd07daea129d553968579019c79b59 (diff)
compiler: add support for import/export statements
This commit introduces syntax level support for ES6 style module import and export statements. Imports are resolved at compile time and the corresponding module code is compiled into the main program. Also add testcases to cover import and export statement semantics. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'tests/custom/04_modules')
-rw-r--r--tests/custom/04_modules/01_export_variable_declaration29
-rw-r--r--tests/custom/04_modules/02_export_function_declaration22
-rw-r--r--tests/custom/04_modules/03_export_list27
-rw-r--r--tests/custom/04_modules/04_export_rename28
-rw-r--r--tests/custom/04_modules/05_export_default38
-rw-r--r--tests/custom/04_modules/06_export_errors89
-rw-r--r--tests/custom/04_modules/07_import_default99
-rw-r--r--tests/custom/04_modules/08_import_list105
-rw-r--r--tests/custom/04_modules/09_import_wildcard73
-rw-r--r--tests/custom/04_modules/10_import_none18
-rw-r--r--tests/custom/04_modules/11_import_many_exec_once28
-rw-r--r--tests/custom/04_modules/12_import_immutability52
-rw-r--r--tests/custom/04_modules/13_import_liveness29
13 files changed, 637 insertions, 0 deletions
diff --git a/tests/custom/04_modules/01_export_variable_declaration b/tests/custom/04_modules/01_export_variable_declaration
new file mode 100644
index 0000000..19a1c11
--- /dev/null
+++ b/tests/custom/04_modules/01_export_variable_declaration
@@ -0,0 +1,29 @@
+Variable declarations can be prepended with `export` to automatically
+export each variable using the same name as the variable itself.
+
+Updates to the variable after the export are reflected properly in
+the including scope.
+
+-- File test-var-decl.uc --
+export let a, b, c;
+export let d = 4, e = 5, f = 6;
+export const g = 7, h = 8, i = 9;
+
+a = 1;
+b = 2;
+c = 3;
+-- End --
+
+-- Testcase --
+import { a, b, c, d, e, f, g, h, i } from "./files/test-var-decl.uc";
+
+print([ a, b, c, d, e, f, g, h, i ], "\n");
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
+-- End --
diff --git a/tests/custom/04_modules/02_export_function_declaration b/tests/custom/04_modules/02_export_function_declaration
new file mode 100644
index 0000000..4067da9
--- /dev/null
+++ b/tests/custom/04_modules/02_export_function_declaration
@@ -0,0 +1,22 @@
+A named function declaration can be prepended with `export` to
+automatically export the function.
+
+-- File test-func-decl.uc --
+export function func() {
+ print("Hello, world!\n");
+};
+-- End --
+
+-- Testcase --
+import { func } from "./files/test-func-decl.uc";
+
+func();
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+Hello, world!
+-- End --
diff --git a/tests/custom/04_modules/03_export_list b/tests/custom/04_modules/03_export_list
new file mode 100644
index 0000000..8f93f08
--- /dev/null
+++ b/tests/custom/04_modules/03_export_list
@@ -0,0 +1,27 @@
+Already declared local variables and functions may be exported using the
+curly brace export list syntax.
+
+-- File test-var-decl.uc --
+let testvar = 123;
+const testconst = "Test";
+
+function testfunc() {
+ print("Hello, world!\n");
+}
+
+export { testvar, testconst, testfunc };
+-- End --
+
+-- Testcase --
+import { testvar, testconst, testfunc } from "./files/test-var-decl.uc";
+
+print([ testvar, testconst, testfunc ], "\n");
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+[ 123, "Test", "function testfunc() { ... }" ]
+-- End --
diff --git a/tests/custom/04_modules/04_export_rename b/tests/custom/04_modules/04_export_rename
new file mode 100644
index 0000000..49057fd
--- /dev/null
+++ b/tests/custom/04_modules/04_export_rename
@@ -0,0 +1,28 @@
+By using the `as` keyword, exports may be renamed when using the export
+list syntax. It is also possible to specify string aliases which are not
+valid variable names, in this case a rename on import is mandatory.
+
+-- File test.uc --
+let testvar = 123;
+const testconst = "Test";
+
+function testfunc() {
+ print("Hello, world!\n");
+}
+
+export { testvar as modvar, testconst as 'define', testfunc as "module-function" };
+-- End --
+
+-- Testcase --
+import { modvar, define, "module-function" as func } from "./files/test.uc";
+
+print([ modvar, define, func ], "\n");
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+[ 123, "Test", "function testfunc() { ... }" ]
+-- End --
diff --git a/tests/custom/04_modules/05_export_default b/tests/custom/04_modules/05_export_default
new file mode 100644
index 0000000..a4c8a43
--- /dev/null
+++ b/tests/custom/04_modules/05_export_default
@@ -0,0 +1,38 @@
+The `export default` statement can be used to declare a default export
+value for a module. The value for `export default` can be an arbitrary
+expression, it must not refer to a local variable.
+
+When using the export list syntax, the alias "default" can be used to
+designate the default export.
+
+-- File test-default-expr.uc --
+export default 7 * 21;
+-- End --
+
+-- File test-default-func.uc --
+export default function() {
+ return "Hello, world!";
+};
+-- End --
+
+-- File test-default-alias.uc --
+let a = 1, b = 2, c = 3;
+
+export { a, b as default, c };
+-- End --
+
+-- Testcase --
+import def1 from "./files/test-default-expr.uc";
+import def2 from "./files/test-default-func.uc";
+import def3 from "./files/test-default-alias.uc";
+
+print([ def1, def2(), def3 ], "\n");
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+[ 147, "Hello, world!", 2 ]
+-- End --
diff --git a/tests/custom/04_modules/06_export_errors b/tests/custom/04_modules/06_export_errors
new file mode 100644
index 0000000..c02a547
--- /dev/null
+++ b/tests/custom/04_modules/06_export_errors
@@ -0,0 +1,89 @@
+Export statements are only allowed at the toplevel of a module.
+
+-- Testcase --
+export let x = 1;
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stderr --
+Syntax error: Exports may only appear at top level of a module
+
+ `export let x = 1;`
+ ^-- Near here
+
+
+-- End --
+
+
+Export statements are not allowed within functions or nested blocks.
+
+-- Testcase --
+import "./files/test.uc";
+-- End --
+
+-- File test.uc --
+{
+ export let x = 1;
+}
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- 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
+
+
+
+In line 1, byte 25:
+
+ `import "./files/test.uc";`
+ Near here --------------^
+
+
+-- End --
+
+
+Duplicate export names should result in an error.
+
+-- Testcase --
+import "./files/test-duplicate.uc";
+-- End --
+
+-- File test-duplicate.uc --
+let x = 1, y = 2;
+
+export { x };
+export { y as x };
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- 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 ----^
+
+
+
+In line 1, byte 35:
+
+ `import "./files/test-duplicate.uc";`
+ Near here ------------------------^
+
+
+-- End --
diff --git a/tests/custom/04_modules/07_import_default b/tests/custom/04_modules/07_import_default
new file mode 100644
index 0000000..7190a22
--- /dev/null
+++ b/tests/custom/04_modules/07_import_default
@@ -0,0 +1,99 @@
+An `import` statement with a sole label will import the modules default
+export and bind it to a local variable named after the label.
+
+-- Testcase --
+import defVal from "./files/test1.uc";
+
+print(defVal, "\n");
+-- End --
+
+-- File test1.uc --
+export default "This is the default export";
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+This is the default export
+-- End --
+
+
+Attemping to import a default export from a module without default
+export will raise an error.
+
+-- Testcase --
+import defVal from "./files/test2.uc";
+
+print(defVal, "\n");
+-- End --
+
+-- File test2.uc --
+export const x = "This is a non-default export";
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stderr --
+Syntax error: Module ./files/test2.uc has no default export
+In line 1, byte 20:
+
+ `import defVal from "./files/test2.uc";`
+ Near here ---------^
+
+
+-- End --
+
+
+In import statements usign the list syntax, the `default` keyword can be
+used to refer to default exports.
+
+-- Testcase --
+import { default as defVal } from "./files/test3.uc";
+
+print(defVal, "\n");
+-- End --
+
+-- File test3.uc --
+export default "This is the default export";
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+This is the default export
+-- End --
+
+
+When using the default keyword within the list syntax, the `as` keyword is
+mandatory to assign a non-reserved keyword as name.
+
+-- Testcase --
+import { default } from "./files/test4.uc";
+
+print(defVal, "\n");
+-- End --
+
+-- File test4.uc --
+export default "This is the default export";
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stderr --
+Syntax error: Unexpected token
+Expecting 'as'
+In line 1, byte 18:
+
+ `import { default } from "./files/test4.uc";`
+ Near here -------^
+
+
+-- End --
diff --git a/tests/custom/04_modules/08_import_list b/tests/custom/04_modules/08_import_list
new file mode 100644
index 0000000..1a4f116
--- /dev/null
+++ b/tests/custom/04_modules/08_import_list
@@ -0,0 +1,105 @@
+An `import` statement followed by a curly brace enclosed list of names
+will import the corresponding exports from the module.
+
+-- Testcase --
+import { a, b, c } from "./files/test1.uc";
+
+print([ a, b, c ], "\n");
+-- End --
+
+-- File test1.uc --
+export const a = 1, b = 2, c = 3;
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+[ 1, 2, 3 ]
+-- End --
+
+
+Attemping to import a not exported name will raise an error.
+
+-- Testcase --
+import y from "./files/test2.uc";
+
+print(y, "\n");
+-- End --
+
+-- File test2.uc --
+export const x = "This is a test";
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stderr --
+Syntax error: Module ./files/test2.uc has no default export
+In line 1, byte 15:
+
+ `import y from "./files/test2.uc";`
+ Near here ----^
+
+
+-- End --
+
+
+Imports may be renamed to assign an alternative local name to the
+exported module symbols. Renaming is also required for string export
+names which are no valid variable identifiers.
+
+-- Testcase --
+import { a as var1, bool as var2, "my function" as var3 } from "./files/test3.uc";
+
+print([ var1, var2, var3 ], "\n");
+-- End --
+
+-- File test3.uc --
+const a = "A string";
+
+let b = 123;
+
+function c() {
+ return "A function"
+}
+
+export {
+ a,
+ b as bool,
+ c as "my function"
+};
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+[ "A string", 123, "function c() { ... }" ]
+-- End --
+
+
+A list expression may follow a default import expression in an `import`
+statment.
+
+-- Testcase --
+import defVal, { a as x, b as y, c as z } from "./files/test4.uc";
+
+print([defVal, x, y, z], "\n");
+-- End --
+
+-- File test4.uc --
+export const a = 1, b = 2, c = 3;
+export default a + b + c;
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+[ 6, 1, 2, 3 ]
+-- End --
diff --git a/tests/custom/04_modules/09_import_wildcard b/tests/custom/04_modules/09_import_wildcard
new file mode 100644
index 0000000..aa3dc82
--- /dev/null
+++ b/tests/custom/04_modules/09_import_wildcard
@@ -0,0 +1,73 @@
+By specifying `*` instead of a label or an import list after an `import`
+keyword, all of the modules exports are aggregated into an object whose
+keys and values refer to the exported names and their corresponding
+values respectively.
+
+-- Testcase --
+import * as mod from "./files/test1.uc";
+
+print(mod, "\n");
+-- End --
+
+-- File test1.uc --
+export const a = 1, b = 2, c = 3;
+export default a + b + c;
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+{ "a": 1, "b": 2, "c": 3, "default": 6 }
+-- End --
+
+
+When using the wildcard import syntax, assigning a name using the `as`
+expression is mandatory.
+
+-- Testcase --
+import * from "./files/test2.uc";
+-- End --
+
+-- File test2.uc --
+export const x = "This is a test";
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stderr --
+Syntax error: Unexpected token
+Expecting 'as'
+In line 1, byte 10:
+
+ `import * from "./files/test2.uc";`
+ ^-- Near here
+
+
+-- End --
+
+
+A wildcard expression may follow a default import expression in an `import`
+statment.
+
+-- Testcase --
+import defVal, * as mod from "./files/test3.uc";
+
+print([defVal, mod], "\n");
+-- End --
+
+-- File test3.uc --
+export const a = 1, b = 2, c = 3;
+export default a + b + c;
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+[ 6, { "a": 1, "b": 2, "c": 3, "default": 6 } ]
+-- End --
diff --git a/tests/custom/04_modules/10_import_none b/tests/custom/04_modules/10_import_none
new file mode 100644
index 0000000..be30106
--- /dev/null
+++ b/tests/custom/04_modules/10_import_none
@@ -0,0 +1,18 @@
+An `import` statement may omit a default name, wildcard expression or name
+lsit entirely to execute a module code solely for its side effects.
+
+-- Testcase --
+import "./files/test.uc";
+-- End --
+
+-- File test.uc --
+print("This is the test module running\n");
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+This is the test module running
+-- End --
diff --git a/tests/custom/04_modules/11_import_many_exec_once b/tests/custom/04_modules/11_import_many_exec_once
new file mode 100644
index 0000000..f469c7f
--- /dev/null
+++ b/tests/custom/04_modules/11_import_many_exec_once
@@ -0,0 +1,28 @@
+When multiple imports refer to the same module, the module will only be
+executed once. The equivalence of module paths is tested after canonicalizing
+the requested path.
+
+-- Testcase --
+import { counter as counter1 } from "./files/test/example.uc";
+import { counter as counter2 } from "files/test/example.uc";
+import { counter as counter3 } from "test.example";
+
+print([ counter1, counter2, counter3 ], "\n");
+-- End --
+
+-- File test/example.uc --
+print("This is the test module running\n");
+
+export let counter = 0;
+
+counter++;
+-- End --
+
+-- Args --
+-R -L ./files
+-- End --
+
+-- Expect stdout --
+This is the test module running
+[ 1, 1, 1 ]
+-- End --
diff --git a/tests/custom/04_modules/12_import_immutability b/tests/custom/04_modules/12_import_immutability
new file mode 100644
index 0000000..37c0bc6
--- /dev/null
+++ b/tests/custom/04_modules/12_import_immutability
@@ -0,0 +1,52 @@
+Module imports are read-only bindings to the exported module variables.
+
+-- Testcase --
+import { a } from "./files/test.uc";
+
+a = 2;
+-- End --
+
+-- File test.uc --
+export let a = 1;
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stderr --
+Syntax error: Invalid assignment to constant 'a'
+In line 3, byte 5:
+
+ `a = 2;`
+ ^-- Near here
+
+
+-- End --
+
+
+Aggregated module objects are read-only as well.
+
+-- Testcase --
+import * as mod from "./files/test.uc";
+
+mod.a = 2;
+-- End --
+
+-- File test.uc --
+export let a = 1;
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stderr --
+Type error: object value is immutable
+In line 3, byte 9:
+
+ `mod.a = 2;`
+ ^-- Near here
+
+
+-- End --
diff --git a/tests/custom/04_modules/13_import_liveness b/tests/custom/04_modules/13_import_liveness
new file mode 100644
index 0000000..ca7ff35
--- /dev/null
+++ b/tests/custom/04_modules/13_import_liveness
@@ -0,0 +1,29 @@
+Imported bindings to exported module variables are live, they'll reflect
+every change to the exported variable values.
+
+-- Testcase --
+import { counter, count } from "./files/test.uc";
+
+print(counter, "\n");
+count();
+print(counter, "\n");
+-- End --
+
+-- File test.uc --
+let counter = 1;
+
+function count() {
+ counter++;
+}
+
+export { counter, count };
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+1
+2
+-- End --