Skip to content

Commit

Permalink
generate transpilation unit tests from data files
Browse files Browse the repository at this point in the history
  • Loading branch information
viktorstrate committed Aug 7, 2023
1 parent 0b718bb commit e307098
Show file tree
Hide file tree
Showing 16 changed files with 168 additions and 145 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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!"
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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 }
Expand Down
52 changes: 52 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -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();
}
138 changes: 0 additions & 138 deletions src/code_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
}
}
2 changes: 1 addition & 1 deletion src/ir/test_utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#[cfg(test)]
//#[cfg(test)]
pub mod utils {
use std::assert_matches::assert_matches;

Expand Down
2 changes: 2 additions & 0 deletions tests/transpilations.rs
Original file line number Diff line number Diff line change
@@ -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"));
5 changes: 5 additions & 0 deletions tests/transpilations/assign_func_call_expr.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let f = () { return true }
let x: Boolean = f()
---
const f = (() => {return true;});
const x = f();
6 changes: 6 additions & 0 deletions tests/transpilations/assign_func_call_stmt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
func f() -> Boolean { return true }
let x: Boolean = f()
---
function f() {
return true;}
const x = f();
8 changes: 8 additions & 0 deletions tests/transpilations/enum_implicit.txt
Original file line number Diff line number Diff line change
@@ -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]];
10 changes: 10 additions & 0 deletions tests/transpilations/enum_simple.txt
Original file line number Diff line number Diff line change
@@ -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]];
6 changes: 6 additions & 0 deletions tests/transpilations/func_call.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
func f() {};
f()
---
function f() {
}
f();
14 changes: 14 additions & 0 deletions tests/transpilations/func_call_inside_struct.txt
Original file line number Diff line number Diff line change
@@ -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();
3 changes: 3 additions & 0 deletions tests/transpilations/let_declare_simple.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
let val: Number = 23.4
---
const val = 23.4;
12 changes: 12 additions & 0 deletions tests/transpilations/struct_implicit.txt
Original file line number Diff line number Diff line change
@@ -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);
16 changes: 16 additions & 0 deletions tests/transpilations/struct_simple.txt
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 5 additions & 0 deletions tests/transpilations/tuple_simple.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let val: (Boolean, Number) = (true, 42)
let val2: Number = val.1
---
const val = [true, 42];
const val2 = val[1];
22 changes: 22 additions & 0 deletions tests/transpile_template.txt
Original file line number Diff line number Diff line change
@@ -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);
}}

0 comments on commit e307098

Please sign in to comment.