This commit is contained in:
parent
2dff17144c
commit
856618d113
@ -82,6 +82,8 @@ export class Node {
|
||||
return visit();
|
||||
case "CallExpr":
|
||||
return visit(k.expr, ...k.args);
|
||||
case "UnaryExpr":
|
||||
return visit(k.expr);
|
||||
case "BinaryExpr":
|
||||
return visit(k.left, k.right);
|
||||
case "IdentTy":
|
||||
@ -111,9 +113,14 @@ export type NodeKind =
|
||||
| { tag: "IdentExpr"; ident: string }
|
||||
| { tag: "IntExpr"; value: number }
|
||||
| { tag: "CallExpr"; expr: Node; args: Node[] }
|
||||
| { tag: "UnaryExpr"; op: UnaryOp; expr: Node; tok: string }
|
||||
| { tag: "BinaryExpr"; op: BinaryOp; left: Node; right: Node; tok: string }
|
||||
| { tag: "IdentTy"; ident: string };
|
||||
|
||||
export type UnaryOp =
|
||||
| "Not"
|
||||
| "Negate";
|
||||
|
||||
export type BinaryOp =
|
||||
| "Or"
|
||||
| "And"
|
||||
|
||||
@ -84,6 +84,21 @@ export class Checker {
|
||||
return this.checkCall(node);
|
||||
}
|
||||
|
||||
if (node.is("UnaryExpr")) {
|
||||
const expr = this.check(node.kind.expr);
|
||||
if (node.kind.op === "Negate" && expr.compatibleWith(Ty.Int)) {
|
||||
return Ty.Int;
|
||||
}
|
||||
if (node.kind.op === "Not" && expr.compatibleWith(Ty.Bool)) {
|
||||
return Ty.Bool;
|
||||
}
|
||||
this.error(
|
||||
node.line,
|
||||
`operator '${node.kind.tok}' cannot be applied to type '${expr.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
|
||||
if (node.is("BinaryExpr")) {
|
||||
const left = this.check(node.kind.left);
|
||||
const right = this.check(node.kind.right);
|
||||
|
||||
@ -136,7 +136,7 @@ export class Parser {
|
||||
parseBinary(prec = 7): ast.Node {
|
||||
const loc = this.loc();
|
||||
if (prec == 0) {
|
||||
return this.parsePrefixE();
|
||||
return this.parsePrefix();
|
||||
}
|
||||
const ops: [Tok["type"], ast.BinaryOp, number][] = [
|
||||
["or", "Or", 9],
|
||||
@ -180,7 +180,18 @@ export class Parser {
|
||||
return left;
|
||||
}
|
||||
|
||||
parsePrefixE() {
|
||||
parsePrefix(): ast.Node {
|
||||
const loc = this.loc();
|
||||
const ops: [Tok["type"], ast.UnaryOp][] = [
|
||||
["not", "Not"],
|
||||
["-", "Negate"],
|
||||
];
|
||||
for (const [tok, op] of ops) {
|
||||
if (this.eat(tok)) {
|
||||
const expr = this.parsePrefix();
|
||||
return ast.Node.create(loc, "UnaryExpr", { op, expr, tok });
|
||||
}
|
||||
}
|
||||
return this.parsePostfix();
|
||||
}
|
||||
|
||||
@ -290,7 +301,7 @@ export class Parser {
|
||||
export type Tok = { type: string; value: string; line: number };
|
||||
|
||||
const keywordPattern =
|
||||
/^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)$/;
|
||||
/^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)|(?:not)$/;
|
||||
const operatorPattern =
|
||||
/((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|[\n\(\)\{\}\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g;
|
||||
|
||||
|
||||
@ -180,6 +180,29 @@ class FnLowerer {
|
||||
const callee = this.lowerExpr(expr.kind.expr);
|
||||
return this.pushInst(ty, "Call", { callee, args });
|
||||
}
|
||||
if (expr.is("UnaryExpr")) {
|
||||
const resultTy = this.checker.check(expr);
|
||||
const operandTy = this.checker.check(expr.kind.expr);
|
||||
if (
|
||||
expr.kind.op === "Negate" &&
|
||||
operandTy.compatibleWith(Ty.Int) &&
|
||||
resultTy.compatibleWith(Ty.Int)
|
||||
) {
|
||||
const operand = this.lowerExpr(expr.kind.expr);
|
||||
return this.pushInst(Ty.Int, "Negate", { source: operand });
|
||||
}
|
||||
if (
|
||||
expr.kind.op === "Not" &&
|
||||
operandTy.compatibleWith(Ty.Bool) &&
|
||||
resultTy.compatibleWith(Ty.Bool)
|
||||
) {
|
||||
const operand = this.lowerExpr(expr.kind.expr);
|
||||
return this.pushInst(Ty.Bool, "Not", { source: operand });
|
||||
}
|
||||
throw new Error(
|
||||
`'${expr.kind.op}' with '${resultTy.pretty()}' not handled`,
|
||||
);
|
||||
}
|
||||
if (expr.is("BinaryExpr")) {
|
||||
const resultTy = this.checker.check(expr);
|
||||
const leftTy = this.checker.check(expr.kind.left);
|
||||
@ -349,6 +372,9 @@ export class Inst {
|
||||
}`;
|
||||
case "Return":
|
||||
return ` ${r(k.source)}`;
|
||||
case "Not":
|
||||
case "Negate":
|
||||
return ` ${r(k.source)}`;
|
||||
case "Eq":
|
||||
case "Ne":
|
||||
case "Lt":
|
||||
@ -389,6 +415,8 @@ export type InstKind =
|
||||
| { tag: "Jump"; target: BasicBlock }
|
||||
| { tag: "Branch"; cond: Inst; truthy: BasicBlock; falsy: BasicBlock }
|
||||
| { tag: "Return"; source: Inst }
|
||||
| { tag: "Not"; source: Inst }
|
||||
| { tag: "Negate"; source: Inst }
|
||||
| { tag: BinaryOp; left: Inst; right: Inst }
|
||||
| { tag: "DebugPrint"; args: Inst[] };
|
||||
|
||||
|
||||
@ -82,6 +82,28 @@ export class FnInterpreter {
|
||||
}
|
||||
case "Return":
|
||||
return this.regs.get(k.source)!;
|
||||
case "Not": {
|
||||
const source = this.regs.get(k.source)!;
|
||||
if (source.kind.tag !== "Bool") {
|
||||
throw new Error();
|
||||
}
|
||||
this.regs.set(
|
||||
inst,
|
||||
new Val({ tag: "Bool", value: !source.kind.value }),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "Negate": {
|
||||
const source = this.regs.get(k.source)!;
|
||||
if (source.kind.tag !== "Int") {
|
||||
throw new Error();
|
||||
}
|
||||
this.regs.set(
|
||||
inst,
|
||||
new Val({ tag: "Int", value: -source.kind.value }),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "Eq":
|
||||
case "Ne":
|
||||
case "Lt":
|
||||
|
||||
@ -42,6 +42,11 @@ export class Ty {
|
||||
}
|
||||
|
||||
compatibleWith(other: Ty): boolean {
|
||||
// types are interned--we can just do this
|
||||
if (this === other) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.is("Error")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
// expect: 15
|
||||
// expect: 7
|
||||
// expect: 2
|
||||
// expect: -5
|
||||
// expect: 123
|
||||
|
||||
fn main()
|
||||
{
|
||||
@ -14,5 +16,12 @@ fn main()
|
||||
print_int(a * b);
|
||||
print_int(a * b / 2);
|
||||
print_int(a % b);
|
||||
print_int(-a);
|
||||
|
||||
let c = false;
|
||||
|
||||
if not c {
|
||||
print_int(123);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user