Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing framework #18

Merged
merged 4 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading