Skip to content

Commit

Permalink
Testing framework (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
AjaniBilby authored May 29, 2024
1 parent 73d94b6 commit fad3e20
Show file tree
Hide file tree
Showing 18 changed files with 392 additions and 271 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ jobs:
with:
deno-version: v1.x

- name: Automated tests
run: deno test
- name: Compiler Source tests
run: deno test

- name: Compiler behaviour tests
run: npm run test:reflect
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
"build": "run-s build:*",
"build:syntax": "npx bnf-compile ./source/bnf/",
"build:compiler": "deno compile --output salient.exe -A ./source/cli.ts",
"test": "deno test",
"compile": "deno run -A ./source/cli.ts"
"test": "run-s test:*",
"test:deno": "deno test",
"test:reflect": "npm run sa-test ./tests/reflective",
"cli": "deno run -A ./source/cli.ts",
"sa-test": "deno run -A ./source/cli.ts test",
"compile": "deno run -A ./source/cli.ts compile"
},
"bin": {
"salient": "bin/cli.js"
Expand Down
9 changes: 7 additions & 2 deletions source/bnf/syntax.bnf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
program ::= %w* ( stmt_top %w* )* ;
stmt_top ::= function | structure | external ;
stmt_top ::= function | structure | external | test ;


#=============================
Expand Down Expand Up @@ -134,4 +134,9 @@ external ::= %( "external" w+ ) ( ext_import | ext_export ) ;
ext_import ::= %( "import" w* "{" w* ) ext_imports* %( w* "}" w* "from" w*) string_plain %(w* ";" w*) ;
ext_imports ::= function | ext_import_var ;
ext_import_var ::= %( "let" w* ) name %( w* ":" w* ) access %(w* ";" w*);
ext_export ::= "export" ;
ext_export ::= "export" ;

#=============================
# Test
#=============================
test ::= %( "test" w+ ) string_plain %w* block ;
20 changes: 19 additions & 1 deletion source/bnf/syntax.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export type Term_Stmt_top = {
count: number,
ref: _Shared.ReferenceRange,
value: [
(Term_Function | Term_Structure | Term_External)
(Term_Function | Term_Structure | Term_External | Term_Test)
]
}
export declare function Parse_Stmt_top (i: string, refMapping?: boolean): _Shared.ParseError | {
Expand Down Expand Up @@ -1383,3 +1383,21 @@ export declare function Parse_Ext_export (i: string, refMapping?: boolean): _Sha
reach: null | _Shared.Reference,
isPartial: boolean
}

export type Term_Test = {
type: 'test',
start: number,
end: number,
count: number,
ref: _Shared.ReferenceRange,
value: [
Term_String_plain,
Term_Block
]
}
export declare function Parse_Test (i: string, refMapping?: boolean): _Shared.ParseError | {
root: _Shared.SyntaxNode & Term_Test,
reachBytes: number,
reach: null | _Shared.Reference,
isPartial: boolean
}
5 changes: 4 additions & 1 deletion source/bnf/syntax.js

Large diffs are not rendered by default.

71 changes: 15 additions & 56 deletions source/cli.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,29 @@
/// <reference lib="deno.ns" />

import { resolve, join, relative } from "https://deno.land/[email protected]/path/mod.ts";
import { existsSync } from "https://deno.land/[email protected]/fs/mod.ts";
import * as colors from "https://deno.land/[email protected]/fmt/colors.ts";

import Function from "~/compiler/function.ts";
import Package from "~/compiler/package.ts";
import Project from "~/compiler/project.ts";
import { DisplayTimers, TimerStart, TimerEnd } from "~/helper.ts";
import { Compile } from "~/compile.ts";
import { Panic } from "~/compiler/helper.ts";
import { Test } from "~/test.ts";

if (Deno.args.includes("--version")) {
console.log("version: 0.0.0");
Deno.exit(0);
}

const verb = Deno.args[0];
switch (verb) {
case "compile": {
Compile(Deno.args[1] || "", {
time: Deno.args.includes("--time")
});
break;
}
case "test": { Test(); break; }
default: Panic(
`${colors.red("Error")}: Unknown verb ${verb}`
);
}
if (!Deno.args[0]) {
Panic(`${colors.red("Error")}: Please provide an entry file`);
}

const cwd = resolve("./");
const root = join(cwd, Deno.args[0]);

if (!existsSync(root)) {
Panic(`${colors.red("Error")}: Cannot find entry ${colors.cyan(relative(cwd, root))}`);
}

const project = new Project();
const mainPck = new Package(project, root);
if (project.failed) Panic(`Compilation ${colors.red("Failed")}`);

const mainFile = mainPck.import(root);
const mainFunc = mainFile.namespace["main"];
if (!(mainFunc instanceof Function)) Panic(
`Main namespace is not a function: ${colors.cyan(mainFunc.constructor.name)}`
);

TimerStart("compilation");
mainFunc.compile();
TimerEnd("compilation");

if (project.failed) Panic(`Compilation ${colors.red("Failed")}`);

if (!mainFunc.ref) Panic(`Main function not compiled correctly`);
project.module.exportFunction("_start", mainFunc.ref);
project.module.exportFunction("main", mainFunc.ref);
project.module.startFunction(mainFunc.ref);

TimerStart("serialize");
await Deno.writeFile("out.wasm", project.toBinary());
TimerEnd("serialize");

TimerStart("wasm2wat");
const command = new Deno.Command(
"wasm2wat",
{ args: ["-v", "out.wasm", "-o", "out.wat", "--enable-all"] }
);
const { code, stdout, stderr } = await command.output();
if (code !== 0) {
console.error("Invalid wasm generated");
console.error(new TextDecoder().decode(stderr));
Deno.exit(1);
}
TimerEnd("wasm2wat");
console.log(new TextDecoder().decode(stdout));

console.log(` out: ${"out.wasm"}\n`);

if (Deno.args.includes("--time")) DisplayTimers();
65 changes: 65 additions & 0 deletions source/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/// <reference lib="deno.ns" />

import { resolve, join, relative } from "https://deno.land/[email protected]/path/mod.ts";
import { existsSync } from "https://deno.land/[email protected]/fs/mod.ts";
import * as colors from "https://deno.land/[email protected]/fmt/colors.ts";

import Function from "~/compiler/function.ts";
import Package from "~/compiler/package.ts";
import Project from "~/compiler/project.ts";
import { DisplayTimers, TimerStart, TimerEnd } from "~/helper.ts";
import { Panic } from "~/compiler/helper.ts";


export async function Compile(entry: string, config: {
time: boolean
}) {
const cwd = resolve("./");
const root = join(cwd, entry);

if (!existsSync(root)) Panic(
`${colors.red("Error")}: Cannot find entry ${colors.cyan(relative(cwd, root))}`
);

const project = new Project();
const mainPck = new Package(project, root);

const mainFile = mainPck.import(root);
const mainFunc = mainFile.namespace["main"];
if (!(mainFunc instanceof Function)) Panic(
`Main namespace is not a function: ${colors.cyan(mainFunc.constructor.name)}`
);

if (config.time) TimerStart("compilation");
mainFunc.compile();
if (config.time) TimerEnd("compilation");

if (project.failed) Panic(`Compilation ${colors.red("Failed")}`);

if (!mainFunc.ref) Panic(`Main function not compiled correctly`);
project.module.exportFunction("_start", mainFunc.ref);
project.module.exportFunction("main", mainFunc.ref);
project.module.startFunction(mainFunc.ref);

if (config.time) TimerStart("serialize");
await Deno.writeFile("out.wasm", project.toBinary());
if (config.time) TimerEnd("serialize");

if (config.time) TimerStart("wasm2wat");
const command = new Deno.Command(
"wasm2wat",
{ args: ["-v", "out.wasm", "-o", "out.wat", "--enable-all"] }
);
const { code, stdout, stderr } = await command.output();
if (code !== 0) {
console.error("Invalid wasm generated");
console.error(new TextDecoder().decode(stderr));
Deno.exit(1);
}
if (config.time) TimerEnd("wasm2wat");

console.log(new TextDecoder().decode(stdout));
console.log(` out: "out.wasm" + "out.wat"\n`);

if (config.time) DisplayTimers();
}
29 changes: 14 additions & 15 deletions source/compiler/codegen/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type { File } from "~/compiler/file.ts";

import * as banned from "~/compiler/codegen/banned.ts";
import Structure from "~/compiler/structure.ts";
import Function from "~/compiler/function.ts";
import { BasePointerType, LinearType, OperandType, SolidType, IsRuntimeType, IsSolidType } from "~/compiler/codegen/expression/type.ts";
import { IntrinsicType, IntrinsicValue, none, never } from "~/compiler/intrinsic.ts";
import { Instruction, AnyInstruction } from "~/wasm/index.ts";
Expand All @@ -21,19 +20,19 @@ import { Panic } from "~/compiler/helper.ts";

export class Context {
file: File;
function: Function;
scope: Scope;

exited: boolean;
done: boolean;

returns: SolidType[] | VirtualType;
raiseType: OperandType;

block: AnyInstruction[];

constructor(file: File, func: Function, scope: Scope, block: AnyInstruction[]) {
this.function = func;
constructor(file: File, scope: Scope, block: AnyInstruction[], returnType: Context['returns']) {
this.raiseType = none;
this.returns = returnType;
this.scope = scope;
this.block = block;
this.file = file;
Expand Down Expand Up @@ -75,7 +74,7 @@ export class Context {
}

child() {
return new Context(this.file, this.function, this.scope.child(), []);
return new Context(this.file, this.scope.child(), [], this.returns);
}

cleanup() {
Expand Down Expand Up @@ -312,7 +311,7 @@ function CompileReturn(ctx: Context, syntax: Syntax.Term_Return): typeof never {
}

// Guard: return none
if (ctx.function.returns instanceof VirtualType) {
if (ctx.returns instanceof VirtualType) {
if (maybe_expr) Panic(
`${colors.red("Error")}: This function should have no return value\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
Expand All @@ -322,29 +321,29 @@ function CompileReturn(ctx: Context, syntax: Syntax.Term_Return): typeof never {
ctx.block.push(Instruction.return());
ctx.exited = true;
ctx.done = true;
return ctx.function.returns;
return ctx.returns;
}

if (!maybe_expr) Panic(
`${colors.red("Error")}: Missing return expression\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

if (ctx.function.returns.length !== 1) Panic(
if (ctx.returns.length !== 1) Panic(
`${colors.red("Error")}: Multi value return is currently not supported\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

const goal = ctx.function.returns[0];
const expr = CompileExpr(ctx, maybe_expr, goal.type || none);
const goal = ctx.returns[0];
const expr = CompileExpr(ctx, maybe_expr, goal || none);
if (!IsRuntimeType(expr)) Panic(
`${colors.red("Error")}: You can only return a runtime type, not ${colors.cyan(expr.getTypeName())}\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

// Guard: simple intrinsic return
if (goal.type instanceof IntrinsicType) {
if (!goal.type.like(expr)) ctx.markFailure(`${colors.red("Error")}: Return type miss-match, expected ${colors.cyan(goal.type.getTypeName())} got ${colors.cyan(expr.getTypeName())}\n`, ref);
if (goal instanceof IntrinsicType) {
if (!goal.like(expr)) ctx.markFailure(`${colors.red("Error")}: Return type miss-match, expected ${colors.cyan(goal.getTypeName())} got ${colors.cyan(expr.getTypeName())}\n`, ref);

if (expr instanceof LinearType) {
ResolveLinearType(ctx, expr, ref, true);
Expand All @@ -358,8 +357,8 @@ function CompileReturn(ctx: Context, syntax: Syntax.Term_Return): typeof never {
return never;
}

if (expr instanceof IntrinsicValue || !expr.like(goal.type)) Panic(
`${colors.red("Error")}: Return type miss-match, expected ${colors.cyan(goal.type.getTypeName())} got ${colors.cyan(expr.getTypeName())}\n`,
if (expr instanceof IntrinsicValue || !expr.like(goal)) Panic(
`${colors.red("Error")}: Return type miss-match, expected ${colors.cyan(goal.getTypeName())} got ${colors.cyan(expr.getTypeName())}\n`,
{ path: ctx.file.path, name: ctx.file.name, ref }
);

Expand All @@ -373,7 +372,7 @@ function CompileReturn(ctx: Context, syntax: Syntax.Term_Return): typeof never {
ResolveLinearType(ctx, expr, maybe_expr.ref, true);

// Transfer
ctx.block.push(Instruction.const.i32(goal.type.size));
ctx.block.push(Instruction.const.i32(goal.size));
ctx.block.push(Instruction.copy(0, 0));

expr.dispose();
Expand Down
4 changes: 2 additions & 2 deletions source/compiler/codegen/expression/postfix/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function CompileTailCall(ctx: Context, syntax: Syntax.Term_Expr_call, ope

if (!operand.ref) throw new Error("A function somehow compiled with no reference generated");

const expect = Array.isArray(ctx.function.returns) ? ctx.function.returns[0].type : ctx.function.returns;
const expect = Array.isArray(ctx.returns) ? ctx.returns[0] : ctx.returns;
const returnType = PrepareReturnTail(ctx, operand, syntax.ref);
if (returnType != expect) ctx.markFailure(
`${colors.red("Error")}: Return type miss-match, expected ${colors.cyan(expect.getTypeName())} got ${colors.cyan(returnType.getTypeName())}\n`,
Expand All @@ -143,7 +143,7 @@ export function CompileTailCall(ctx: Context, syntax: Syntax.Term_Expr_call, ope

ctx.scope.cleanup(true);

if (ctx.function.owner.owner.project.flags.tailCall) {
if (ctx.file.owner.project.flags.tailCall) {
ctx.block.push(Instruction.return_call(operand.ref));
} else {
ctx.block.push(Instruction.call(operand.ref));
Expand Down
Loading

0 comments on commit fad3e20

Please sign in to comment.