added unary ops
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 8s

This commit is contained in:
sfja 2026-03-11 19:21:22 +01:00
parent 2dff17144c
commit 856618d113
7 changed files with 100 additions and 3 deletions

View File

@ -82,6 +82,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 "UnaryExpr":
return visit(k.expr);
case "BinaryExpr": case "BinaryExpr":
return visit(k.left, k.right); return visit(k.left, k.right);
case "IdentTy": case "IdentTy":
@ -111,9 +113,14 @@ 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: "UnaryExpr"; op: UnaryOp; expr: Node; tok: string }
| { tag: "BinaryExpr"; op: BinaryOp; left: Node; right: Node; tok: string } | { tag: "BinaryExpr"; op: BinaryOp; left: Node; right: Node; tok: string }
| { tag: "IdentTy"; ident: string }; | { tag: "IdentTy"; ident: string };
export type UnaryOp =
| "Not"
| "Negate";
export type BinaryOp = export type BinaryOp =
| "Or" | "Or"
| "And" | "And"

View File

@ -84,6 +84,21 @@ export class Checker {
return this.checkCall(node); 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")) { if (node.is("BinaryExpr")) {
const left = this.check(node.kind.left); const left = this.check(node.kind.left);
const right = this.check(node.kind.right); const right = this.check(node.kind.right);

View File

@ -136,7 +136,7 @@ export class Parser {
parseBinary(prec = 7): ast.Node { parseBinary(prec = 7): ast.Node {
const loc = this.loc(); const loc = this.loc();
if (prec == 0) { if (prec == 0) {
return this.parsePrefixE(); return this.parsePrefix();
} }
const ops: [Tok["type"], ast.BinaryOp, number][] = [ const ops: [Tok["type"], ast.BinaryOp, number][] = [
["or", "Or", 9], ["or", "Or", 9],
@ -180,7 +180,18 @@ export class Parser {
return left; 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(); return this.parsePostfix();
} }
@ -290,7 +301,7 @@ export class Parser {
export type Tok = { type: string; value: string; line: number }; export type Tok = { type: string; value: string; line: number };
const keywordPattern = const keywordPattern =
/^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)$/; /^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)|(?:not)$/;
const operatorPattern = const operatorPattern =
/((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|[\n\(\)\{\}\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g; /((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|[\n\(\)\{\}\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g;

View File

@ -180,6 +180,29 @@ 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("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")) { if (expr.is("BinaryExpr")) {
const resultTy = this.checker.check(expr); const resultTy = this.checker.check(expr);
const leftTy = this.checker.check(expr.kind.left); const leftTy = this.checker.check(expr.kind.left);
@ -349,6 +372,9 @@ export class Inst {
}`; }`;
case "Return": case "Return":
return ` ${r(k.source)}`; return ` ${r(k.source)}`;
case "Not":
case "Negate":
return ` ${r(k.source)}`;
case "Eq": case "Eq":
case "Ne": case "Ne":
case "Lt": case "Lt":
@ -389,6 +415,8 @@ export type InstKind =
| { tag: "Jump"; target: BasicBlock } | { tag: "Jump"; target: BasicBlock }
| { tag: "Branch"; cond: Inst; truthy: BasicBlock; falsy: BasicBlock } | { tag: "Branch"; cond: Inst; truthy: BasicBlock; falsy: BasicBlock }
| { tag: "Return"; source: Inst } | { tag: "Return"; source: Inst }
| { tag: "Not"; source: Inst }
| { tag: "Negate"; source: Inst }
| { tag: BinaryOp; left: Inst; right: Inst } | { tag: BinaryOp; left: Inst; right: Inst }
| { tag: "DebugPrint"; args: Inst[] }; | { tag: "DebugPrint"; args: Inst[] };

View File

@ -82,6 +82,28 @@ export class FnInterpreter {
} }
case "Return": case "Return":
return this.regs.get(k.source)!; 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 "Eq":
case "Ne": case "Ne":
case "Lt": case "Lt":

View File

@ -42,6 +42,11 @@ export class Ty {
} }
compatibleWith(other: Ty): boolean { compatibleWith(other: Ty): boolean {
// types are interned--we can just do this
if (this === other) {
return true;
}
if (this.is("Error")) { if (this.is("Error")) {
return false; return false;
} }

View File

@ -3,6 +3,8 @@
// expect: 15 // expect: 15
// expect: 7 // expect: 7
// expect: 2 // expect: 2
// expect: -5
// expect: 123
fn main() fn main()
{ {
@ -14,5 +16,12 @@ fn main()
print_int(a * b); print_int(a * b);
print_int(a * b / 2); print_int(a * b / 2);
print_int(a % b); print_int(a % b);
print_int(-a);
let c = false;
if not c {
print_int(123);
}
} }