Reorganized parser/tests and included new test for generating ASTs without spans

This commit is contained in:
Pranav Gaddamadugu 2021-09-22 10:41:47 -07:00
parent 73ff1d85e0
commit 28d6aeb47d
13 changed files with 5503 additions and 3 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,308 @@
{
"aliases": {},
"circuits": {
"{\"name\":\"PedersenHash\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":9,\\\"col_stop\\\":21,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\"circuit PedersenHash {\\\"}\"}": {
"circuit_name": "{\"name\":\"PedersenHash\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":9,\\\"col_stop\\\":21,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\"circuit PedersenHash {\\\"}\"}",
"core_mapping": null,
"members": [
{
"CircuitVariable": [
"{\"name\":\"parameters\",\"span\":\"{\\\"line_start\\\":2,\\\"line_stop\\\":2,\\\"col_start\\\":5,\\\"col_stop\\\":15,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" parameters: [group; 256];\\\"}\"}",
{
"Array": [
"Group",
[
{
"value": "256"
}
]
]
}
]
},
{
"CircuitFunction": {
"annotations": [],
"block": {
"statements": [
{
"Return": {
"expression": {
"CircuitInit": {
"members": [
{
"expression": {
"Identifier": "{\"name\":\"parameters\",\"span\":\"{\\\"line_start\\\":6,\\\"line_stop\\\":6,\\\"col_start\\\":35,\\\"col_stop\\\":45,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" return Self { parameters: parameters };\\\"}\"}"
},
"identifier": "{\"name\":\"parameters\",\"span\":\"{\\\"line_start\\\":6,\\\"line_stop\\\":6,\\\"col_start\\\":23,\\\"col_stop\\\":33,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" return Self { parameters: parameters };\\\"}\"}"
}
],
"name": "{\"name\":\"Self\",\"span\":\"{\\\"line_start\\\":6,\\\"line_stop\\\":6,\\\"col_start\\\":16,\\\"col_stop\\\":20,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" return Self { parameters: parameters };\\\"}\"}"
}
}
}
}
]
},
"identifier": "{\"name\":\"new\",\"span\":\"{\\\"line_start\\\":5,\\\"line_stop\\\":5,\\\"col_start\\\":14,\\\"col_stop\\\":17,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" function new(parameters: [group; 256]) -> Self {\\\"}\"}",
"input": [
{
"Variable": {
"const_": false,
"identifier": "{\"name\":\"parameters\",\"span\":\"{\\\"line_start\\\":5,\\\"line_stop\\\":5,\\\"col_start\\\":18,\\\"col_stop\\\":28,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" function new(parameters: [group; 256]) -> Self {\\\"}\"}",
"mutable": true,
"type_": {
"Array": [
"Group",
[
{
"value": "256"
}
]
]
}
}
}
],
"output": "SelfType"
}
},
{
"CircuitFunction": {
"annotations": [],
"block": {
"statements": [
{
"Definition": {
"declaration_type": "Let",
"type_": "Group",
"value": {
"Value": {
"Group": {
"Single": [
"0",
{}
]
}
}
},
"variable_names": [
{
"identifier": "{\"name\":\"digest\",\"span\":\"{\\\"line_start\\\":10,\\\"line_stop\\\":10,\\\"col_start\\\":13,\\\"col_stop\\\":19,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" let digest: group = 0group;\\\"}\"}",
"mutable": true
}
]
}
},
{
"Iteration": {
"block": {
"statements": [
{
"Conditional": {
"block": {
"statements": [
{
"Assign": {
"assignee": {
"accesses": [],
"identifier": "{\"name\":\"digest\",\"span\":\"{\\\"line_start\\\":13,\\\"line_stop\\\":13,\\\"col_start\\\":17,\\\"col_stop\\\":23,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" digest += self.parameters[i];\\\"}\"}"
},
"operation": "Add",
"value": {
"ArrayAccess": {
"array": {
"CircuitMemberAccess": {
"circuit": {
"Identifier": "{\"name\":\"self\",\"span\":\"{\\\"line_start\\\":13,\\\"line_stop\\\":13,\\\"col_start\\\":27,\\\"col_stop\\\":31,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" digest += self.parameters[i];\\\"}\"}"
},
"name": "{\"name\":\"parameters\",\"span\":\"{\\\"line_start\\\":13,\\\"line_stop\\\":13,\\\"col_start\\\":32,\\\"col_stop\\\":42,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" digest += self.parameters[i];\\\"}\"}",
"type_": null
}
},
"index": {
"Identifier": "{\"name\":\"i\",\"span\":\"{\\\"line_start\\\":13,\\\"line_stop\\\":13,\\\"col_start\\\":43,\\\"col_stop\\\":44,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" digest += self.parameters[i];\\\"}\"}"
}
}
}
}
}
]
},
"condition": {
"ArrayAccess": {
"array": {
"Identifier": "{\"name\":\"bits\",\"span\":\"{\\\"line_start\\\":12,\\\"line_stop\\\":12,\\\"col_start\\\":16,\\\"col_stop\\\":20,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" if bits[i] {\\\"}\"}"
},
"index": {
"Identifier": "{\"name\":\"i\",\"span\":\"{\\\"line_start\\\":12,\\\"line_stop\\\":12,\\\"col_start\\\":21,\\\"col_stop\\\":22,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" if bits[i] {\\\"}\"}"
}
}
},
"next": null
}
}
]
},
"inclusive": false,
"start": {
"Value": {
"Implicit": [
"0",
{}
]
}
},
"stop": {
"Value": {
"Implicit": [
"256",
{}
]
}
},
"variable": "{\"name\":\"i\",\"span\":\"{\\\"line_start\\\":11,\\\"line_stop\\\":11,\\\"col_start\\\":13,\\\"col_stop\\\":14,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" for i in 0..256 {\\\"}\"}"
}
},
{
"Return": {
"expression": {
"Identifier": "{\"name\":\"digest\",\"span\":\"{\\\"line_start\\\":16,\\\"line_stop\\\":16,\\\"col_start\\\":16,\\\"col_stop\\\":22,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" return digest;\\\"}\"}"
}
}
}
]
},
"identifier": "{\"name\":\"hash\",\"span\":\"{\\\"line_start\\\":9,\\\"line_stop\\\":9,\\\"col_start\\\":14,\\\"col_stop\\\":18,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" function hash(self, bits: [bool; 256]) -> group {\\\"}\"}",
"input": [
{
"SelfKeyword": "{\"name\":\"self\",\"span\":\"{\\\"line_start\\\":9,\\\"line_stop\\\":9,\\\"col_start\\\":19,\\\"col_stop\\\":23,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" function hash(self, bits: [bool; 256]) -> group {\\\"}\"}"
},
{
"Variable": {
"const_": false,
"identifier": "{\"name\":\"bits\",\"span\":\"{\\\"line_start\\\":9,\\\"line_stop\\\":9,\\\"col_start\\\":25,\\\"col_stop\\\":29,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" function hash(self, bits: [bool; 256]) -> group {\\\"}\"}",
"mutable": true,
"type_": {
"Array": [
"Boolean",
[
{
"value": "256"
}
]
]
}
}
}
],
"output": "Group"
}
}
]
}
},
"expected_input": [],
"functions": {
"{\"name\":\"main\",\"span\":\"{\\\"line_start\\\":21,\\\"line_stop\\\":21,\\\"col_start\\\":10,\\\"col_stop\\\":14,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\"function main(hash_input: [bool; 256], const parameters: [group; 256]) -> group {\\\"}\"}": {
"annotations": [],
"block": {
"statements": [
{
"Definition": {
"declaration_type": "Const",
"type_": null,
"value": {
"Call": {
"arguments": [
{
"Identifier": "{\"name\":\"parameters\",\"span\":\"{\\\"line_start\\\":22,\\\"line_stop\\\":22,\\\"col_start\\\":40,\\\"col_stop\\\":50,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" const pedersen = PedersenHash::new(parameters);\\\"}\"}"
}
],
"function": {
"CircuitStaticFunctionAccess": {
"circuit": {
"Identifier": "{\"name\":\"PedersenHash\",\"span\":\"{\\\"line_start\\\":22,\\\"line_stop\\\":22,\\\"col_start\\\":22,\\\"col_stop\\\":34,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" const pedersen = PedersenHash::new(parameters);\\\"}\"}"
},
"name": "{\"name\":\"new\",\"span\":\"{\\\"line_start\\\":22,\\\"line_stop\\\":22,\\\"col_start\\\":36,\\\"col_stop\\\":39,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" const pedersen = PedersenHash::new(parameters);\\\"}\"}"
}
}
}
},
"variable_names": [
{
"identifier": "{\"name\":\"pedersen\",\"span\":\"{\\\"line_start\\\":22,\\\"line_stop\\\":22,\\\"col_start\\\":11,\\\"col_stop\\\":19,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" const pedersen = PedersenHash::new(parameters);\\\"}\"}",
"mutable": false
}
]
}
},
{
"Return": {
"expression": {
"Call": {
"arguments": [
{
"Identifier": "{\"name\":\"hash_input\",\"span\":\"{\\\"line_start\\\":23,\\\"line_stop\\\":23,\\\"col_start\\\":26,\\\"col_stop\\\":36,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" return pedersen.hash(hash_input);\\\"}\"}"
}
],
"function": {
"CircuitMemberAccess": {
"circuit": {
"Identifier": "{\"name\":\"pedersen\",\"span\":\"{\\\"line_start\\\":23,\\\"line_stop\\\":23,\\\"col_start\\\":12,\\\"col_stop\\\":20,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" return pedersen.hash(hash_input);\\\"}\"}"
},
"name": "{\"name\":\"hash\",\"span\":\"{\\\"line_start\\\":23,\\\"line_stop\\\":23,\\\"col_start\\\":21,\\\"col_stop\\\":25,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\" return pedersen.hash(hash_input);\\\"}\"}",
"type_": null
}
}
}
}
}
}
]
},
"identifier": "{\"name\":\"main\",\"span\":\"{\\\"line_start\\\":21,\\\"line_stop\\\":21,\\\"col_start\\\":10,\\\"col_stop\\\":14,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\"function main(hash_input: [bool; 256], const parameters: [group; 256]) -> group {\\\"}\"}",
"input": [
{
"Variable": {
"const_": false,
"identifier": "{\"name\":\"hash_input\",\"span\":\"{\\\"line_start\\\":21,\\\"line_stop\\\":21,\\\"col_start\\\":15,\\\"col_stop\\\":25,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\"function main(hash_input: [bool; 256], const parameters: [group; 256]) -> group {\\\"}\"}",
"mutable": true,
"type_": {
"Array": [
"Boolean",
[
{
"value": "256"
}
]
]
}
}
},
{
"Variable": {
"const_": true,
"identifier": "{\"name\":\"parameters\",\"span\":\"{\\\"line_start\\\":21,\\\"line_stop\\\":21,\\\"col_start\\\":46,\\\"col_stop\\\":56,\\\"path\\\":\\\"/Users/pranav/work/Aleo/leo/examples/pedersen-hash/src/main.leo\\\",\\\"content\\\":\\\"function main(hash_input: [bool; 256], const parameters: [group; 256]) -> group {\\\"}\"}",
"mutable": false,
"type_": {
"Array": [
"Group",
[
{
"value": "256"
}
]
]
}
}
}
],
"output": "Group"
}
},
"global_consts": {},
"import_statements": [],
"imports": {},
"name": ""
}

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,9 @@ use leo_ast::Ast;
use leo_ast::Program;
use leo_errors::{LeoError, Result};
use std::fs::File;
use std::io::BufReader;
use std::iter::Iterator;
use std::path::{Path, PathBuf};
fn to_ast(program_filepath: &Path) -> Result<Ast> {
@ -44,7 +47,7 @@ fn test_serialize() {
// Construct an ast from the given test file.
let ast = {
let mut program_filepath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
program_filepath.push("tests/serialization/main.leo");
program_filepath.push("tests/serialization/leo/one_plus_one.leo");
to_ast(&program_filepath).unwrap()
};
@ -53,12 +56,58 @@ fn test_serialize() {
let serialized_ast: Program = serde_json::from_value(serde_json::to_value(ast.as_repr()).unwrap()).unwrap();
// Load the expected ast.
let expected: Program = serde_json::from_str(include_str!("expected_leo_ast.json")).unwrap();
let expected: Program = serde_json::from_str(include_str!("./expected_leo_ast/one_plus_one.json")).unwrap();
clean();
assert_eq!(expected, serialized_ast);
}
#[test]
#[cfg(not(feature = "ci_skip"))]
fn serialize_no_span() {
setup();
let program_paths = vec![
"tests/serialization/leo/linear_regression.leo",
"tests/serialization/leo/palindrome.leo",
"tests/serialization/leo/pedersen_hash.leo",
"tests/serialization/leo/silly_sudoku.leo",
];
let json_paths = vec![
"./expected_leo_ast/linear_regression.json",
"./expected_leo_ast/palindrome.json",
"./expected_leo_ast/pedersen_hash.json",
"./expected_leo_ast/silly_sudoku.json",
];
for (program_path, json_path) in program_paths.into_iter().zip(json_paths) {
// Construct an ast from the given test file.
let ast = {
let mut program_filepath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
program_filepath.push(program_path);
to_ast(&program_filepath).unwrap()
};
let json_reader = {
let mut json_filepath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
json_filepath.push(json_path);
let file = File::open(json_filepath).expect("Failed to read expected ast file");
BufReader::new(file)
};
// Serializes the ast into JSON format.
let serialized_ast: serde_json::Value =
serde_json::from_value(serde_json::to_value(ast.as_repr()).unwrap()).unwrap();
// Load the expected ast.
let expected: serde_json::Value = serde_json::from_reader(json_reader).unwrap();
assert_eq!(expected, serialized_ast);
}
clean();
}
// TODO Renable when we don't write spans to snapshots.
/* #[test]
#[cfg(not(feature = "ci_skip"))]
@ -112,7 +161,7 @@ fn test_generic_parser_error() {
let error_result = {
let mut program_filepath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
program_filepath.push("tests/serialization/parser_error.leo");
program_filepath.push("tests/serialization/leo/parser_error.leo");
to_ast(&program_filepath)
}

View File

@ -0,0 +1,65 @@
circuit Point {
x: i32,
y: i32,
function new(x: i32, y: i32) -> Self {
return Self { x, y };
}
}
circuit LinearRegression {
points: [Point; 5],
// Instantiates a linear regression circuit.
function new(points: [Point; 5]) -> Self {
return Self { points };
}
// Return the slope of the linear regression.
function slope(self) -> i32 {
let num_points = 5i32;
// Calculate the sums.
let x_sum = 0i32;
let y_sum = 0i32;
let xy_sum = 0i32;
let x2_sum = 0i32;
for i in 0..5 {
x_sum += self.points[i].x;
y_sum += self.points[i].y;
xy_sum += self.points[i].x * self.points[i].y;
x2_sum += self.points[i].x * self.points[i].x;
}
let numerator = (num_points * xy_sum) - (x_sum * y_sum);
let denominator = (num_points * x2_sum) - (x_sum * x_sum);
let slope = numerator / denominator;
return slope;
}
// Return the offset of the linear regression.
function offset(self, slope: i32) -> i32 {
let num_points = 5i32;
// Calculate the sum.
let x_sum = 0i32;
let y_sum = 0i32;
for i in 0..5 {
x_sum += self.points[i].x;
y_sum += self.points[i].y;
}
return (y_sum - slope * x_sum) / num_points;
}
}
function main (x: i32, y: i32) -> [i32; 2] {
let points: [Point; 5] = [
Point{x: x + 1, y: y + 1},
Point{x: x + 2, y: y + 2},
Point{x: x + 3, y: y + 3},
Point{x: x + 4, y: y + 4},
Point{x: x + 5, y: y + 5}
];
let reg = LinearRegression::new(points);
let slope = reg.slope();
let offset = reg.offset(slope);
return [slope, offset];
}

View File

@ -0,0 +1,59 @@
// This Program takes in any 20-byte low register string and tells
// whether a string is a palindrome ignoring any spaces.
function main(str: [char; 20]) -> bool {
return is_palindrome(str);
}
function is_palindrome(str: [char; 20]) -> bool {
const str_len = 20u32; // saving const for convenience
// By default we assume that input is a palindrome.
let result = true;
let processed = 0u8;
for start in 0..(str_len / 2) {
let start_sym = str[start];
if start_sym != ' ' {
let skipped = 0u8;
let end_empty = 0u8;
let end_sym = ' ';
for end in (str_len - 1)..start {
if str[end] != ' ' && skipped == processed && end_sym == ' ' {
end_sym = str[end];
} else {
end_empty = end_empty + 1;
if str[end] != ' ' {
skipped = skipped + 1;
}
}
}
// If there are symbols left to the right from the start.
if end_sym != ' ' {
console.log("Comparing: {} ? {}", start_sym, end_sym);
if result {
result = (start_sym == end_sym);
}
processed = processed + 1;
}
}
}
console.log("Result is: {}", result);
return result;
}
@test
function test_is_palindrome() {
console.assert(is_palindrome("a b a "));
console.assert(is_palindrome("😀😀😀😀😀 😀😀😀😀😀"));
console.assert(is_palindrome("borrow or rob "));
console.assert(is_palindrome("bbbb aaaa aaaa bbbb"));
console.assert(is_palindrome("aaaaaaaaaaaaaaaaaaaa"));
console.assert(is_palindrome("taco cat "));
}

View File

@ -0,0 +1,25 @@
circuit PedersenHash {
parameters: [group; 256];
// Instantiates a Pedersen hash circuit
function new(parameters: [group; 256]) -> Self {
return Self { parameters: parameters };
}
function hash(self, bits: [bool; 256]) -> group {
let digest: group = 0group;
for i in 0..256 {
if bits[i] {
digest += self.parameters[i];
}
}
return digest;
}
}
// The 'pedersen-hash' main function.
function main(hash_input: [bool; 256], const parameters: [group; 256]) -> group {
const pedersen = PedersenHash::new(parameters);
return pedersen.hash(hash_input);
}

View File

@ -0,0 +1,71 @@
import lib.SillySudoku;
// The `silly-sudoku` main function
function main(puzzle: [u8; (3, 3)], answer: [u8; (3, 3)]) -> bool {
console.log("Starting Sudoku solver...");
console.log("{}", puzzle);
// Instantiate the Sudoku puzzle.
let sudoku = SillySudoku { puzzle_grid: puzzle };
console.log("Checking Sudoku answer...");
console.log("{}", answer);
// Evaluate the Sudoku puzzle with the given answer.
let result = sudoku.solve(answer);
console.log("The answer is {}.", result);
return result;
}
// Tests that the `silly-sudoku` circuit outputs true on a correct answer.
@test
function test_solve_pass() {
let puzzle: [u8; (3, 3)] = [[0, 2, 0],
[0, 0, 6],
[0, 8, 9]];
let answer: [u8; (3, 3)] = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]];
// Runs the Sudoku checker.
let result = main(puzzle, answer);
// Expects the result to be true.
console.assert(true == result);
}
// Tests that the `silly-sudoku` circuit outputs false on an incorrect answer.
@test
function test_solve_fail() {
let puzzle: [u8; (3, 3)] = [[0, 2, 0],
[0, 0, 6],
[0, 8, 0]];
let answer: [u8; (3, 3)] = [[1, 2, 3],
[4, 5, 6],
[7, 8, 8]]; // We have an extra `8` in this column!
// Runs the Sudoku checker.
let result = main(puzzle, answer);
// Expects the result to be false.
console.assert(false == result);
}
// Test that the `silly-sudoku` circuit outputs the expected value on a custom test input.
@test(test_input)
function test_solve_with_input(
puzzle: [u8; (3, 3)],
answer: [u8; (3, 3)],
expected: bool
) {
// Runs the Sudoku checker.
let result = main(puzzle, answer);
console.log("expected {}, got {}", expected, result);
console.assert(expected == result);
}