tests + binary ops
This commit is contained in:
parent
3b3a189020
commit
bdee8b5bed
@ -1,13 +1,14 @@
|
|||||||
|
|
||||||
fn add(a: int, b: int) -> int
|
fn add(a: int, b: int) -> int
|
||||||
{
|
{
|
||||||
return __add(a, b);
|
return a + b;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main()
|
fn main()
|
||||||
{
|
{
|
||||||
let sum: void = add(2, 3);
|
let sum = add(2, 3);
|
||||||
print_int(sum);
|
print_int(sum);
|
||||||
|
print_int(2 - 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
23
src/ast.ts
23
src/ast.ts
@ -80,6 +80,8 @@ export class Node {
|
|||||||
return visit();
|
return visit();
|
||||||
case "CallExpr":
|
case "CallExpr":
|
||||||
return visit(k.expr, ...k.args);
|
return visit(k.expr, ...k.args);
|
||||||
|
case "BinaryExpr":
|
||||||
|
return visit(k.left, k.right);
|
||||||
case "IdentTy":
|
case "IdentTy":
|
||||||
return visit();
|
return visit();
|
||||||
}
|
}
|
||||||
@ -106,8 +108,29 @@ export type NodeKind =
|
|||||||
| { tag: "IdentExpr"; ident: string }
|
| { tag: "IdentExpr"; ident: string }
|
||||||
| { tag: "IntExpr"; value: number }
|
| { tag: "IntExpr"; value: number }
|
||||||
| { tag: "CallExpr"; expr: Node; args: Node[] }
|
| { tag: "CallExpr"; expr: Node; args: Node[] }
|
||||||
|
| { tag: "BinaryExpr"; op: BinaryOp; left: Node; right: Node; tok: string }
|
||||||
| { tag: "IdentTy"; ident: string };
|
| { tag: "IdentTy"; ident: string };
|
||||||
|
|
||||||
|
export type BinaryOp =
|
||||||
|
| "Or"
|
||||||
|
| "And"
|
||||||
|
| "Eq"
|
||||||
|
| "Ne"
|
||||||
|
| "Lt"
|
||||||
|
| "Gt"
|
||||||
|
| "Lte"
|
||||||
|
| "Gte"
|
||||||
|
| "BitOr"
|
||||||
|
| "BitXor"
|
||||||
|
| "BitAnd"
|
||||||
|
| "Shl"
|
||||||
|
| "Shr"
|
||||||
|
| "Add"
|
||||||
|
| "Subtract"
|
||||||
|
| "Multiply"
|
||||||
|
| "Divide"
|
||||||
|
| "Remainder";
|
||||||
|
|
||||||
export interface Visitor {
|
export interface Visitor {
|
||||||
visit(node: Node): void | "break";
|
visit(node: Node): void | "break";
|
||||||
}
|
}
|
||||||
|
|||||||
135
src/front.ts
135
src/front.ts
@ -1,23 +1,6 @@
|
|||||||
import * as ast from "./ast.ts";
|
import * as ast from "./ast.ts";
|
||||||
import { Ty } from "./ty.ts";
|
import { Ty } from "./ty.ts";
|
||||||
|
|
||||||
const rootSyms = [
|
|
||||||
{
|
|
||||||
id: "print_int",
|
|
||||||
ty: Ty.create("Fn", {
|
|
||||||
params: [Ty.Int],
|
|
||||||
retTy: Ty.Void,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "__add",
|
|
||||||
ty: Ty.create("Fn", {
|
|
||||||
params: [Ty.Int, Ty.Int],
|
|
||||||
retTy: Ty.Int,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export class Checker {
|
export class Checker {
|
||||||
private nodeTys = new Map<number, Ty>();
|
private nodeTys = new Map<number, Ty>();
|
||||||
|
|
||||||
@ -76,7 +59,7 @@ export class Checker {
|
|||||||
return this.check(sym.stmt);
|
return this.check(sym.stmt);
|
||||||
}
|
}
|
||||||
if (sym.tag === "Builtin") {
|
if (sym.tag === "Builtin") {
|
||||||
return rootSyms.find((s) => s.id === sym.id)!.ty;
|
return builtins.find((s) => s.id === sym.id)!.ty;
|
||||||
}
|
}
|
||||||
if (sym.tag === "FnParam") {
|
if (sym.tag === "FnParam") {
|
||||||
return this.check(sym.param);
|
return this.check(sym.param);
|
||||||
@ -95,6 +78,25 @@ export class Checker {
|
|||||||
return this.checkCall(node);
|
return this.checkCall(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.is("BinaryExpr")) {
|
||||||
|
const left = this.check(node.kind.left);
|
||||||
|
const right = this.check(node.kind.right);
|
||||||
|
const binaryOp = binaryOpPatterns
|
||||||
|
.find((pat) =>
|
||||||
|
pat.op === node.kind.op &&
|
||||||
|
left.compatibleWith(pat.left) &&
|
||||||
|
right.compatibleWith(pat.right)
|
||||||
|
);
|
||||||
|
if (!binaryOp) {
|
||||||
|
this.error(
|
||||||
|
node.line,
|
||||||
|
`operator '${node.kind.tok}' cannot be applied to types '${left.pretty()}' and '${right.pretty()}'`,
|
||||||
|
);
|
||||||
|
this.fail();
|
||||||
|
}
|
||||||
|
return binaryOp.result;
|
||||||
|
}
|
||||||
|
|
||||||
if (node.is("IdentTy")) {
|
if (node.is("IdentTy")) {
|
||||||
switch (node.kind.ident) {
|
switch (node.kind.ident) {
|
||||||
case "void":
|
case "void":
|
||||||
@ -231,6 +233,18 @@ export class Checker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BinaryOpPattern = {
|
||||||
|
op: ast.BinaryOp;
|
||||||
|
left: Ty;
|
||||||
|
right: Ty;
|
||||||
|
result: Ty;
|
||||||
|
};
|
||||||
|
|
||||||
|
const binaryOpPatterns: BinaryOpPattern[] = [
|
||||||
|
{ op: "Add", left: Ty.Int, right: Ty.Int, result: Ty.Int },
|
||||||
|
{ op: "Subtract", left: Ty.Int, right: Ty.Int, result: Ty.Int },
|
||||||
|
];
|
||||||
|
|
||||||
export type Sym =
|
export type Sym =
|
||||||
| { tag: "Error" }
|
| { tag: "Error" }
|
||||||
| { tag: "Builtin"; id: string }
|
| { tag: "Builtin"; id: string }
|
||||||
@ -264,7 +278,7 @@ class ResolverSyms {
|
|||||||
static root(): ResolverSyms {
|
static root(): ResolverSyms {
|
||||||
return new ResolverSyms(
|
return new ResolverSyms(
|
||||||
new Map(
|
new Map(
|
||||||
rootSyms.map<[string, Sym]>((sym) => [
|
builtins.map<[string, Sym]>((sym) => [
|
||||||
sym.id,
|
sym.id,
|
||||||
{ tag: "Builtin", id: sym.id },
|
{ tag: "Builtin", id: sym.id },
|
||||||
]),
|
]),
|
||||||
@ -365,6 +379,28 @@ export function resolve(
|
|||||||
return new ResolveMap(resols);
|
return new ResolveMap(resols);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Builtin = {
|
||||||
|
id: string;
|
||||||
|
ty: Ty;
|
||||||
|
};
|
||||||
|
|
||||||
|
const builtins: Builtin[] = [
|
||||||
|
{
|
||||||
|
id: "print_int",
|
||||||
|
ty: Ty.create("Fn", {
|
||||||
|
params: [Ty.Int],
|
||||||
|
retTy: Ty.Void,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "__add",
|
||||||
|
ty: Ty.create("Fn", {
|
||||||
|
params: [Ty.Int, Ty.Int],
|
||||||
|
retTy: Ty.Int,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function parse(
|
export function parse(
|
||||||
filename: string,
|
filename: string,
|
||||||
text: string,
|
text: string,
|
||||||
@ -480,6 +516,57 @@ export class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseExpr(): ast.Node {
|
parseExpr(): ast.Node {
|
||||||
|
return this.parseBinary();
|
||||||
|
}
|
||||||
|
|
||||||
|
parseBinary(prec = 7): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
if (prec == 0) {
|
||||||
|
return this.parsePrefixE();
|
||||||
|
}
|
||||||
|
const ops: [Tok["type"], ast.BinaryOp, number][] = [
|
||||||
|
["or", "Or", 9],
|
||||||
|
["and", "And", 8],
|
||||||
|
["==", "Eq", 7],
|
||||||
|
["!=", "Ne", 7],
|
||||||
|
["<", "Lt", 7],
|
||||||
|
[">", "Gt", 7],
|
||||||
|
["<=", "Lte", 7],
|
||||||
|
[">=", "Gte", 7],
|
||||||
|
["|", "BitOr", 6],
|
||||||
|
["^", "BitXor", 5],
|
||||||
|
["&", "BitAnd", 4],
|
||||||
|
["<<", "Shl", 3],
|
||||||
|
[">>", "Shr", 3],
|
||||||
|
["+", "Add", 2],
|
||||||
|
["-", "Subtract", 2],
|
||||||
|
["*", "Multiply", 1],
|
||||||
|
["/", "Divide", 1],
|
||||||
|
["%", "Remainder", 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
let left = this.parseBinary(prec - 1);
|
||||||
|
|
||||||
|
let should_continue = true;
|
||||||
|
while (should_continue) {
|
||||||
|
should_continue = false;
|
||||||
|
for (const [tok, op, p] of ops) {
|
||||||
|
if (prec >= p && this.eat(tok)) {
|
||||||
|
const right = this.parseBinary(prec - 1);
|
||||||
|
left = ast.Node.create(
|
||||||
|
loc,
|
||||||
|
"BinaryExpr",
|
||||||
|
{ op, left, right, tok },
|
||||||
|
);
|
||||||
|
should_continue = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePrefixE() {
|
||||||
return this.parsePostfix();
|
return this.parsePostfix();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,13 +675,15 @@ export class Parser {
|
|||||||
|
|
||||||
export type Tok = { type: string; value: string; line: number };
|
export type Tok = { type: string; value: string; line: number };
|
||||||
|
|
||||||
const keywordRegex =
|
const keywordPattern =
|
||||||
/^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)$/;
|
/^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)$/;
|
||||||
|
const operatorPattern =
|
||||||
|
/((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|[\n\(\)\{\}\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g;
|
||||||
|
|
||||||
export function tokenize(text: string): Tok[] {
|
export function tokenize(text: string): Tok[] {
|
||||||
return text
|
return text
|
||||||
.replace(/\/\/[^\n]*/g, "")
|
.replace(/\/\/[^\n]*/g, "")
|
||||||
.replace(/((?:\-\>)|[\n\(\)\{\},.;:])/g, " $1 ")
|
.replace(operatorPattern, " $1 ")
|
||||||
.split(/[ \t\r]/)
|
.split(/[ \t\r]/)
|
||||||
.filter((value) => value !== "")
|
.filter((value) => value !== "")
|
||||||
.reduce<[[string, number][], number]>(
|
.reduce<[[string, number][], number]>(
|
||||||
@ -612,7 +701,7 @@ export function tokenize(text: string): Tok[] {
|
|||||||
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tok.value)
|
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tok.value)
|
||||||
? {
|
? {
|
||||||
...tok,
|
...tok,
|
||||||
type: keywordRegex.test(tok.value) ? tok.value : "ident",
|
type: keywordPattern.test(tok.value) ? tok.value : "ident",
|
||||||
}
|
}
|
||||||
: tok
|
: tok
|
||||||
)
|
)
|
||||||
|
|||||||
@ -34,7 +34,9 @@ if (!mainFn) {
|
|||||||
const m = new middle.MiddleLowerer(resols, checker);
|
const m = new middle.MiddleLowerer(resols, checker);
|
||||||
const mainMiddleFn = m.lowerFn(mainFn);
|
const mainMiddleFn = m.lowerFn(mainFn);
|
||||||
|
|
||||||
console.log(mainMiddleFn.pretty());
|
if (!Deno.args.includes("--test")) {
|
||||||
|
console.log(mainMiddleFn.pretty());
|
||||||
|
}
|
||||||
|
|
||||||
const interp = new MirInterpreter();
|
const interp = new MirInterpreter();
|
||||||
interp.eval(mainMiddleFn);
|
interp.eval(mainMiddleFn);
|
||||||
|
|||||||
@ -63,6 +63,12 @@ class FnLowerer {
|
|||||||
this.pushInst(Ty.Void, "Return", { source });
|
this.pushInst(Ty.Void, "Return", { source });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (stmt.is("AssignStmt")) {
|
||||||
|
const source = this.lowerExpr(stmt.kind.expr);
|
||||||
|
const target = this.lowerAssignPlace(stmt.kind.place);
|
||||||
|
this.pushInst(Ty.Void, "LocalStore", { target, source });
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (stmt.is("ExprStmt")) {
|
if (stmt.is("ExprStmt")) {
|
||||||
this.lowerExpr(stmt.kind.expr);
|
this.lowerExpr(stmt.kind.expr);
|
||||||
return;
|
return;
|
||||||
@ -70,6 +76,21 @@ class FnLowerer {
|
|||||||
throw new Error(`'${stmt.kind.tag}' not handled`);
|
throw new Error(`'${stmt.kind.tag}' not handled`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lowerAssignPlace(place: ast.Node): Inst {
|
||||||
|
if (place.is("IdentExpr")) {
|
||||||
|
const sym = this.resols.get(place);
|
||||||
|
if (sym.tag === "Let") {
|
||||||
|
const local = this.localMap.get(sym.param.id);
|
||||||
|
if (!local) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
throw new Error(`'${sym.tag}' not handled`);
|
||||||
|
}
|
||||||
|
throw new Error(`'${place.kind.tag}' not handled`);
|
||||||
|
}
|
||||||
|
|
||||||
private lowerExpr(expr: ast.Node): Inst {
|
private lowerExpr(expr: ast.Node): Inst {
|
||||||
if (expr.is("IdentExpr")) {
|
if (expr.is("IdentExpr")) {
|
||||||
const sym = this.resols.get(expr);
|
const sym = this.resols.get(expr);
|
||||||
@ -118,6 +139,19 @@ class FnLowerer {
|
|||||||
const callee = this.lowerExpr(expr.kind.expr);
|
const callee = this.lowerExpr(expr.kind.expr);
|
||||||
return this.pushInst(ty, "Call", { callee, args });
|
return this.pushInst(ty, "Call", { callee, args });
|
||||||
}
|
}
|
||||||
|
if (expr.is("BinaryExpr")) {
|
||||||
|
const ty = this.checker.check(expr);
|
||||||
|
const binaryOp = binaryOpPatterns
|
||||||
|
.find((pat) =>
|
||||||
|
expr.kind.op === pat.op && ty.compatibleWith(pat.ty)
|
||||||
|
);
|
||||||
|
if (!binaryOp) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const left = this.lowerExpr(expr.kind.left);
|
||||||
|
const right = this.lowerExpr(expr.kind.right);
|
||||||
|
return this.pushInst(ty, binaryOp.tag, { left, right });
|
||||||
|
}
|
||||||
throw new Error(`'${expr.kind.tag}' not handled`);
|
throw new Error(`'${expr.kind.tag}' not handled`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +172,17 @@ class FnLowerer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BinaryOpPattern = {
|
||||||
|
op: ast.BinaryOp;
|
||||||
|
ty: Ty;
|
||||||
|
tag: BinaryOp;
|
||||||
|
};
|
||||||
|
|
||||||
|
const binaryOpPatterns: BinaryOpPattern[] = [
|
||||||
|
{ op: "Add", ty: Ty.Int, tag: "Add" },
|
||||||
|
{ op: "Subtract", ty: Ty.Int, tag: "Sub" },
|
||||||
|
];
|
||||||
|
|
||||||
export class Fn {
|
export class Fn {
|
||||||
constructor(
|
constructor(
|
||||||
public stmt: ast.FnStmt,
|
public stmt: ast.FnStmt,
|
||||||
@ -237,7 +282,22 @@ export class Inst {
|
|||||||
return ` ${r(k.target)}, ${r(k.source)}`;
|
return ` ${r(k.target)}, ${r(k.source)}`;
|
||||||
case "Return":
|
case "Return":
|
||||||
return ` ${r(k.source)}`;
|
return ` ${r(k.source)}`;
|
||||||
|
case "Eq":
|
||||||
|
case "Ne":
|
||||||
|
case "Lt":
|
||||||
|
case "Gt":
|
||||||
|
case "Lte":
|
||||||
|
case "Gte":
|
||||||
|
case "BitOr":
|
||||||
|
case "BitXor":
|
||||||
|
case "BitAnd":
|
||||||
|
case "Shl":
|
||||||
|
case "Shr":
|
||||||
case "Add":
|
case "Add":
|
||||||
|
case "Sub":
|
||||||
|
case "Mul":
|
||||||
|
case "Div":
|
||||||
|
case "Rem":
|
||||||
return ` ${r(k.left)} ${r(k.right)}`;
|
return ` ${r(k.left)} ${r(k.right)}`;
|
||||||
case "DebugPrint":
|
case "DebugPrint":
|
||||||
return ` ${k.args.map(r).join(", ")}`;
|
return ` ${k.args.map(r).join(", ")}`;
|
||||||
@ -259,5 +319,23 @@ export type InstKind =
|
|||||||
| { tag: "LocalLoad"; source: Inst }
|
| { tag: "LocalLoad"; source: Inst }
|
||||||
| { tag: "LocalStore"; target: Inst; source: Inst }
|
| { tag: "LocalStore"; target: Inst; source: Inst }
|
||||||
| { tag: "Return"; source: Inst }
|
| { tag: "Return"; source: Inst }
|
||||||
| { tag: "Add"; left: Inst; right: Inst }
|
| { tag: BinaryOp; left: Inst; right: Inst }
|
||||||
| { tag: "DebugPrint"; args: Inst[] };
|
| { tag: "DebugPrint"; args: Inst[] };
|
||||||
|
|
||||||
|
export type BinaryOp =
|
||||||
|
| "Eq"
|
||||||
|
| "Ne"
|
||||||
|
| "Lt"
|
||||||
|
| "Gt"
|
||||||
|
| "Lte"
|
||||||
|
| "Gte"
|
||||||
|
| "BitOr"
|
||||||
|
| "BitXor"
|
||||||
|
| "BitAnd"
|
||||||
|
| "Shl"
|
||||||
|
| "Shr"
|
||||||
|
| "Add"
|
||||||
|
| "Sub"
|
||||||
|
| "Mul"
|
||||||
|
| "Div"
|
||||||
|
| "Rem";
|
||||||
|
|||||||
@ -57,26 +57,63 @@ export class MirInterpreter {
|
|||||||
continue;
|
continue;
|
||||||
case "Return":
|
case "Return":
|
||||||
return regs.get(k.source)!;
|
return regs.get(k.source)!;
|
||||||
case "Add": {
|
case "Eq":
|
||||||
|
case "Ne":
|
||||||
|
case "Lt":
|
||||||
|
case "Gt":
|
||||||
|
case "Lte":
|
||||||
|
case "Gte":
|
||||||
|
case "BitOr":
|
||||||
|
case "BitXor":
|
||||||
|
case "BitAnd":
|
||||||
|
case "Shl":
|
||||||
|
case "Shr":
|
||||||
|
case "Add":
|
||||||
|
case "Sub":
|
||||||
|
case "Mul":
|
||||||
|
case "Div":
|
||||||
|
case "Rem": {
|
||||||
const left = regs.get(k.left)!;
|
const left = regs.get(k.left)!;
|
||||||
const right = regs.get(k.right)!;
|
const right = regs.get(k.right)!;
|
||||||
if (left.kind.tag === "Int" && right.kind.tag == "Int") {
|
const lk = left.kind;
|
||||||
regs.set(
|
const rk = right.kind;
|
||||||
inst,
|
|
||||||
new Val({
|
if (lk.tag === "Int" && rk.tag === "Int") {
|
||||||
tag: "Int",
|
const value = (() => {
|
||||||
value: left.kind.value + right.kind.value,
|
switch (k.tag) {
|
||||||
}),
|
case "Eq":
|
||||||
);
|
case "Ne":
|
||||||
|
case "Lt":
|
||||||
|
case "Gt":
|
||||||
|
case "Lte":
|
||||||
|
case "Gte":
|
||||||
|
case "BitOr":
|
||||||
|
case "BitXor":
|
||||||
|
case "BitAnd":
|
||||||
|
case "Shl":
|
||||||
|
case "Shr":
|
||||||
|
break;
|
||||||
|
case "Add":
|
||||||
|
return lk.value + rk.value;
|
||||||
|
case "Sub":
|
||||||
|
return lk.value - rk.value;
|
||||||
|
case "Mul":
|
||||||
|
case "Div":
|
||||||
|
case "Rem":
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw new Error(`'${k.tag}' not handled`);
|
||||||
|
})();
|
||||||
|
regs.set(inst, new Val({ tag: "Int", value }));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw new Error();
|
throw new Error(`'${k.tag}' not handled`);
|
||||||
}
|
}
|
||||||
case "DebugPrint":
|
case "DebugPrint":
|
||||||
console.log(
|
console.log(
|
||||||
`debug: ${
|
k.args
|
||||||
k.args.map((a) => regs.get(a)!.pretty()).join(", ")
|
.map((a) => regs.get(a)!.pretty())
|
||||||
}`,
|
.join(", "),
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
9
tests/assign.ethlang
Normal file
9
tests/assign.ethlang
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// expect: 456
|
||||||
|
|
||||||
|
fn main()
|
||||||
|
{
|
||||||
|
let v: int = 123;
|
||||||
|
v = 456;
|
||||||
|
print_int(v);
|
||||||
|
}
|
||||||
|
|
||||||
10
tests/fn_int.ethlang
Normal file
10
tests/fn_int.ethlang
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
fn my_int_fn() -> int {
|
||||||
|
return 123;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main()
|
||||||
|
{
|
||||||
|
my_int_fn();
|
||||||
|
}
|
||||||
|
|
||||||
12
tests/fn_void.ethlang
Normal file
12
tests/fn_void.ethlang
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
fn my_implicit_void() {}
|
||||||
|
|
||||||
|
fn my_explicit_void() -> void {}
|
||||||
|
|
||||||
|
fn main()
|
||||||
|
{
|
||||||
|
my_implicit_void();
|
||||||
|
my_explicit_void();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
8
tests/let.ethlang
Normal file
8
tests/let.ethlang
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
fn main()
|
||||||
|
{
|
||||||
|
let a = 123;
|
||||||
|
let b: int = 321;
|
||||||
|
let c = b;
|
||||||
|
}
|
||||||
|
|
||||||
12
tests/operators.ethlang
Normal file
12
tests/operators.ethlang
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// expect: 8
|
||||||
|
// expect: 2
|
||||||
|
|
||||||
|
fn main()
|
||||||
|
{
|
||||||
|
let a = 5;
|
||||||
|
let b = 3;
|
||||||
|
|
||||||
|
print_int(a + b);
|
||||||
|
print_int(a - b);
|
||||||
|
}
|
||||||
|
|
||||||
61
tests/test.sh
Executable file
61
tests/test.sh
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TEST_DIR=$(dirname $0)
|
||||||
|
SRC_DIR=$TEST_DIR/../src
|
||||||
|
|
||||||
|
TEST_SRC=$(fd '\.ethlang' $TEST_DIR)
|
||||||
|
|
||||||
|
count_total=0
|
||||||
|
count_succeeded=0
|
||||||
|
|
||||||
|
for test_file in $TEST_SRC
|
||||||
|
do
|
||||||
|
echo "- $(basename $test_file)"
|
||||||
|
set +e
|
||||||
|
output=$(deno run -A $SRC_DIR/main.ts $test_file --test)
|
||||||
|
status=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
|
||||||
|
if [[ status -ne 0 ]]
|
||||||
|
then
|
||||||
|
echo "-- failed: exit code $status --"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ status -eq 0 ]]
|
||||||
|
then
|
||||||
|
if grep -q '// expect:' $test_file
|
||||||
|
then
|
||||||
|
expected=$(grep '// expect:' $test_file | sed -E 's/\/\/ expect: (.*?)/\1/g')
|
||||||
|
if [[ $output != $expected ]]
|
||||||
|
then
|
||||||
|
echo "-- failed: incorrect output --"
|
||||||
|
echo "-- expected --"
|
||||||
|
echo "$expected"
|
||||||
|
echo "-- actual --"
|
||||||
|
echo "$output"
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
count_total=$(($count_total + 1))
|
||||||
|
if [[ status -eq 0 ]]
|
||||||
|
then
|
||||||
|
count_succeeded=$(($count_succeeded + 1))
|
||||||
|
else
|
||||||
|
echo "failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $count_succeeded -eq $count_total ]]
|
||||||
|
then
|
||||||
|
echo "=== all tests passed ($count_succeeded/$count_total passed) ==="
|
||||||
|
else
|
||||||
|
echo "=== tests failed ($count_succeeded/$count_total passed) ==="
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user