diff --git a/README.md b/README.md index d8f4112..0cbb985 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ It is designed such that transpilation to javascript is as direct as possible, b The main goal for both Taro and Typescript is to make programming javascript more safe. Typescript does this by adding a type system on top of javascript. Taro on the other hand is a completely different programming language that transpiles to javascript. -This allows for an even stricter and flexible type system by leveraging [algebraic data types](https://en.wikipedia.org/wiki/Algebraic_data_type). +This allows for an even stricter and more flexible type system by leveraging [algebraic data types](https://en.wikipedia.org/wiki/Algebraic_data_type). By making a completely new language, inconsistencies and complexities in Javascript can be removed and new features like [pattern matching](https://en.wikipedia.org/wiki/Pattern_matching) can be added. ## Features @@ -43,7 +43,7 @@ By default a variable is immutable, to declare a mutable variable, the `var` key let greeting = "Hello, World!" ``` -A type signature can optionally be added, either for clarity or special cases where the compiler is unable to infer the type by itself. +A type signature can optionally be added, either for clarity or in special cases where the compiler is unable to infer the type by itself. ``` let greeting: String = "Hello, World!" @@ -67,13 +67,13 @@ struct Car { Next, the Car struct can be initialized and assigned to a variable. Notice that the wheels attribute is not specified and the default value of 4 is used instead. -All attributes without a default value must be specified when instanciating a new instance. +All attributes without a default value must be specified when instantiating a new instance. ``` let deLorean = Car { maxSpeed: 100, model: "DMC DeLorean" } ``` -Attributes can then be retrived as such. +Attributes can then be retrieved as such. ``` let model: String = deLorean.model @@ -118,7 +118,7 @@ let sum = (a: Number, b: Number) -> Number { An enum is declared with a name and then a list of values the enum can take. Each value can have a tuple of data associated with it. -Enum declarations are only used to type check and will be removed when compiled to javascript. +Enum declarations are only used to type-check and will be removed when compiled to javascript. ``` enum IP { @@ -141,7 +141,7 @@ let my_ip: IP = .v4(127, 0, 0, 1) Raw javascript can be inserted as an expression anywhere using an escape block. The content of an escape block is not evaluated or checked by the compiler, -instead it will simply be inserted into the raw javascript output. +it will instead simply be inserted into the raw javascript output. ``` let num = @{ 1 + 2 } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..f49a705 --- /dev/null +++ b/build.rs @@ -0,0 +1,52 @@ +// Test generation build tool is inspired by this blog post +// https://blog.cyplo.dev/posts/2018/12/generate-rust-tests-from-data/ + +use std::env; +use std::fs::read_dir; +use std::fs::DirEntry; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +// build script's entry point +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let destination = Path::new(&out_dir).join("transpilation_tests.rs"); + let mut test_file = File::create(&destination).unwrap(); + + // write test file header, put `use`, `const` etc there + write_header(&mut test_file); + + let test_data_files = read_dir("./tests/transpilations/").unwrap(); + + for entry in test_data_files { + write_test(&mut test_file, &entry.unwrap()); + } +} + +fn write_test(test_file: &mut File, entry: &DirEntry) { + let directory = entry.path().canonicalize().unwrap(); + let path = directory.display(); + let test_name = format!( + "transpile_{}", + directory.file_stem().unwrap().to_string_lossy() + ); + + write!( + test_file, + include_str!("./tests/transpile_template.txt"), + name = test_name, + path = path + ) + .unwrap(); +} + +fn write_header(test_file: &mut File) { + write!( + test_file, + r#"use taro::ir::test_utils::utils::final_codegen; + +"# + ) + .unwrap(); +} diff --git a/src/code_gen/mod.rs b/src/code_gen/mod.rs index 3c2d617..65a5f4c 100644 --- a/src/code_gen/mod.rs +++ b/src/code_gen/mod.rs @@ -407,141 +407,3 @@ where Ok(()) } - -#[cfg(test)] -mod tests { - use std::assert_matches::assert_matches; - - use crate::ir::test_utils::utils::final_codegen; - - #[test] - fn test_let_declare_simple() { - assert_eq!( - final_codegen("let val: Number = 23.4").unwrap(), - "const val = 23.4;\n" - ) - } - - #[test] - fn test_func_call() { - assert_eq!( - final_codegen("func f() {}; f()").unwrap(), - "function f() {\n}\nf();\n" - ); - } - - #[test] - fn test_assign_func_call() { - let output1 = final_codegen("func f() -> Boolean { return true }; let x: Boolean = f()"); - let output2 = final_codegen("let f = () { return true }; let x: Boolean = f()"); - assert_matches!(output1, Ok(_)); - assert_matches!(output2, Ok(_)); - assert_eq!( - output1.unwrap(), - "function f() {\nreturn true;}\nconst x = f();\n" - ); - assert_eq!( - output2.unwrap(), - "const f = (() => {return true;});\nconst x = f();\n" - ); - } - - #[test] - fn test_func_call_inside_struct() { - let output = final_codegen( - "struct Foo {\n\ - let bar: () -> Void\n\ - } - - let x = Foo { bar: () {} }\n\ - x.bar()\n\ - ", - ); - assert_eq!( - output.unwrap(), - "function Foo (bar) {\n\ - this.bar = bar\n\ - }\n\ - const x = new Foo((() => {}));\n\ - x.bar();\n" - ) - } - - #[test] - fn test_struct() { - let output = final_codegen( - "struct Test { let defaultVal = 123; var noDefault: Boolean }\n\ - let testVar = Test { noDefault: false } - let val: Number = testVar.defaultVal - ", - ); - assert_eq!( - output.unwrap(), - "function Test (defaultVal, noDefault) {\n\ - this.defaultVal = defaultVal ?? 123;\n\ - this.noDefault = noDefault\n\ - }\n\ - const testVar = new Test(null, false);\n\ - const val = testVar.defaultVal;\n" - ); - } - - #[test] - fn test_tuple() { - let output = final_codegen( - "let val: (Boolean, Number) = (true, 42)\n\ - let val2: Number = val.1", - ); - assert_eq!( - output.unwrap(), - "const val = [true, 42];\n\ - const val2 = val[1];\n" - ); - } - - #[test] - fn test_enum() { - let output = final_codegen( - "enum IPAddress {\n\ - v4(Number, Number, Number, Number)\n\ - v6(String)\n\ - }\n - let ipValue = IPAddress.v4(192, 168, 0, 1)", - ); - assert_eq!( - output.unwrap(), - "\nconst ipValue = [0, [192, 168, 0, 1]];\n" - ); - } - - #[test] - fn test_implicit_enum() { - let output = final_codegen( - "enum IPAddress {\n\ - v4(Number, Number, Number, Number)\n\ - v6(String)\n\ - }\n - let ipValue: IPAddress = .v4(192, 168, 0, 1)", - ); - assert_eq!( - output.unwrap(), - "\nconst ipValue = [0, [192, 168, 0, 1]];\n" - ); - } - - #[test] - fn test_implicit_struct() { - let output = final_codegen( - " - struct Foo {\n\ - let x: Number\n\ - }\n\ - let a: Foo = { x: 32 }", - ); - - assert_eq!( - output.unwrap(), - "function Foo (x) {\nthis.x = x\n}\nconst a = new Foo(32);\n" - ) - } -} diff --git a/src/ir/test_utils.rs b/src/ir/test_utils.rs index 0986e22..4739a04 100644 --- a/src/ir/test_utils.rs +++ b/src/ir/test_utils.rs @@ -1,4 +1,4 @@ -#[cfg(test)] +//#[cfg(test)] pub mod utils { use std::assert_matches::assert_matches; diff --git a/tests/transpilations.rs b/tests/transpilations.rs new file mode 100644 index 0000000..d91a161 --- /dev/null +++ b/tests/transpilations.rs @@ -0,0 +1,2 @@ +// include tests generated by `build.rs`, one test per directory in tests/data +include!(concat!(env!("OUT_DIR"), "/transpilation_tests.rs")); diff --git a/tests/transpilations/assign_func_call_expr.txt b/tests/transpilations/assign_func_call_expr.txt new file mode 100644 index 0000000..13cce7a --- /dev/null +++ b/tests/transpilations/assign_func_call_expr.txt @@ -0,0 +1,5 @@ +let f = () { return true } +let x: Boolean = f() +--- +const f = (() => {return true;}); +const x = f(); diff --git a/tests/transpilations/assign_func_call_stmt.txt b/tests/transpilations/assign_func_call_stmt.txt new file mode 100644 index 0000000..e41b49b --- /dev/null +++ b/tests/transpilations/assign_func_call_stmt.txt @@ -0,0 +1,6 @@ +func f() -> Boolean { return true } +let x: Boolean = f() +--- +function f() { +return true;} +const x = f(); diff --git a/tests/transpilations/enum_implicit.txt b/tests/transpilations/enum_implicit.txt new file mode 100644 index 0000000..f78343c --- /dev/null +++ b/tests/transpilations/enum_implicit.txt @@ -0,0 +1,8 @@ +enum IPAddress { + v4(Number, Number, Number, Number) + v6(String) +} + +let ipValue: IPAddress = .v4(192, 168, 0, 1) +--- +const ipValue = [0, [192, 168, 0, 1]]; diff --git a/tests/transpilations/enum_simple.txt b/tests/transpilations/enum_simple.txt new file mode 100644 index 0000000..2e9de6e --- /dev/null +++ b/tests/transpilations/enum_simple.txt @@ -0,0 +1,10 @@ +enum IPAddress { + v4(Number, Number, Number, Number) + v6(String) +} + +let ipValue = IPAddress.v4(192, 168, 0, 1) + +--- + +const ipValue = [0, [192, 168, 0, 1]]; diff --git a/tests/transpilations/func_call.txt b/tests/transpilations/func_call.txt new file mode 100644 index 0000000..b767f68 --- /dev/null +++ b/tests/transpilations/func_call.txt @@ -0,0 +1,6 @@ +func f() {}; +f() +--- +function f() { +} +f(); diff --git a/tests/transpilations/func_call_inside_struct.txt b/tests/transpilations/func_call_inside_struct.txt new file mode 100644 index 0000000..d8c9351 --- /dev/null +++ b/tests/transpilations/func_call_inside_struct.txt @@ -0,0 +1,14 @@ +struct Foo { + let bar: () -> Void +} + +let x = Foo { bar: () {} } +x.bar() + +--- + +function Foo (bar) { +this.bar = bar +} +const x = new Foo((() => {})); +x.bar(); diff --git a/tests/transpilations/let_declare_simple.txt b/tests/transpilations/let_declare_simple.txt new file mode 100644 index 0000000..7d1086e --- /dev/null +++ b/tests/transpilations/let_declare_simple.txt @@ -0,0 +1,3 @@ +let val: Number = 23.4 +--- +const val = 23.4; diff --git a/tests/transpilations/struct_implicit.txt b/tests/transpilations/struct_implicit.txt new file mode 100644 index 0000000..697f701 --- /dev/null +++ b/tests/transpilations/struct_implicit.txt @@ -0,0 +1,12 @@ +struct Foo { + let x: Number +} + +let a: Foo = { x: 32 } + +--- + +function Foo (x) { +this.x = x +} +const a = new Foo(32); diff --git a/tests/transpilations/struct_simple.txt b/tests/transpilations/struct_simple.txt new file mode 100644 index 0000000..a1cfe6d --- /dev/null +++ b/tests/transpilations/struct_simple.txt @@ -0,0 +1,16 @@ +struct Test { + let defaultVal = 123 + var noDefault: Boolean +} + +let testVar = Test { noDefault: false } +let val: Number = testVar.defaultVal + +--- + +function Test (defaultVal, noDefault) { +this.defaultVal = defaultVal ?? 123; +this.noDefault = noDefault +} +const testVar = new Test(null, false); +const val = testVar.defaultVal; diff --git a/tests/transpilations/tuple_simple.txt b/tests/transpilations/tuple_simple.txt new file mode 100644 index 0000000..02e3042 --- /dev/null +++ b/tests/transpilations/tuple_simple.txt @@ -0,0 +1,5 @@ +let val: (Boolean, Number) = (true, 42) +let val2: Number = val.1 +--- +const val = [true, 42]; +const val2 = val[1]; diff --git a/tests/transpile_template.txt b/tests/transpile_template.txt new file mode 100644 index 0000000..4ced5bf --- /dev/null +++ b/tests/transpile_template.txt @@ -0,0 +1,22 @@ +#[test] +fn {name}() {{ + let data = include_str!("{path}"); + + let mut splits = data.split("\n---\n"); + + let before = splits.next().unwrap().trim(); + let after = splits + .next() + .expect("Expected test file to contain line with three dashes (---)") + .trim(); + + assert!( + splits.next() == None, + "Test file contains more than one dash separator" + ); + + let transpiled = final_codegen(before).unwrap(); + let transpiled = transpiled.trim(); + + assert_eq!(transpiled, after); +}}