Skip to content

Commit

Permalink
Fix operator precedence not applying correctly (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
AjaniBilby authored Mar 25, 2024
1 parent 1b62e92 commit 0ee3d57
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 135 deletions.
154 changes: 115 additions & 39 deletions source/compiler/codegen/expression/infix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,52 +281,128 @@ function CompileRem(ctx: Context, lhs: IntrinsicValue, rhs: IntrinsicValue, ref:
return lhs;
}

if (lhs === f32.value) {
const regA = ctx.scope.register.allocate(f32.bitcode, false);
const regB = ctx.scope.register.allocate(f32.bitcode, false);
ctx.block.push(Instruction.local.set(regB.ref));
ctx.block.push(Instruction.local.set(regA.ref));

ctx.block.push(Instruction.local.get(regA.ref)); // a -

ctx.block.push(Instruction.local.get(regA.ref)); // floor(a/b)
ctx.block.push(Instruction.local.get(regB.ref));
ctx.block.push(Instruction.f32.div());
ctx.block.push(Instruction.f32.trunc());

ctx.block.push(Instruction.local.get(regB.ref)); // * b
ctx.block.push(Instruction.f32.mul());
if (lhs === f32.value || lhs === f64.value) return CompileFloatRemainder(ctx, lhs, ref);

ctx.block.push(Instruction.f32.sub());

regA.free();
regB.free();
return lhs;
}
Panic(`${colors.red("Error")}: Unhandled type ${lhs.type.name}\n`, {
path: ctx.file.path, name: ctx.file.name, ref
});
}

if (lhs === f64.value) {
const regA = ctx.scope.register.allocate(f64.bitcode, false);
const regB = ctx.scope.register.allocate(f64.bitcode, false);
ctx.block.push(Instruction.local.set(regA.ref));
ctx.block.push(Instruction.local.set(regB.ref));
function CompileFloatRemainder(ctx: Context, type: IntrinsicValue, ref: ReferenceRange) {
/**
* float fmod(float x, float y) {
if (y == 0.0) return NaN;
ctx.block.push(Instruction.local.get(regA.ref));
ctx.block.push(Instruction.local.get(regB.ref));
ctx.block.push(Instruction.f64.div());
ctx.block.push(Instruction.f64.trunc());
float quotient = x / y;
float remainder = x - trunc(quotient) * y;
ctx.block.push(Instruction.local.get(regB.ref));
ctx.block.push(Instruction.f64.mul());
if (remainder == 0.0 && quotient < 0.0) return -0.0;
else return remainder;
}*/

ctx.block.push(Instruction.local.get(regA.ref));
ctx.block.push(Instruction.f64.sub());
if (type === f32.value) {
const x = ctx.scope.register.allocate(f32.bitcode);
const y = ctx.scope.register.allocate(f32.bitcode);
ctx.block.push(Instruction.local.set(y.ref));
ctx.block.push(Instruction.local.set(x.ref));

regA.free();
regB.free();
return lhs;
}
const q = ctx.scope.register.allocate(f32.bitcode);
const r = ctx.scope.register.allocate(f32.bitcode);

Panic(`${colors.red("Error")}: Unhandled type ${lhs.type.name}\n`, {
// if (y == 0) return NaN;
ctx.block.push(Instruction.local.get(y.ref));
ctx.block.push(Instruction.const.f32(0.0));
ctx.block.push(Instruction.f32.eq());
ctx.block.push(Instruction.if(type.type.bitcode, [
Instruction.const.f32(NaN)
], [
Instruction.local.get(x.ref), // q = x / y
Instruction.local.get(y.ref),
Instruction.f32.div(),
Instruction.local.set(q.ref),

Instruction.local.get(x.ref), // x - trunc(q)*y
Instruction.local.get(q.ref),
Instruction.f32.trunc(),
Instruction.local.get(y.ref),
Instruction.f32.mul(),
Instruction.f32.sub(),
Instruction.local.set(r.ref),

Instruction.local.get(r.ref), // remainder == 0.0
Instruction.const.f32(0.0),
Instruction.f32.eq(),

Instruction.local.get(q.ref), // quotient < 0.0
Instruction.const.f32(0.0),
Instruction.f32.lt(),

Instruction.i32.and(), // &&
Instruction.if(f32.bitcode, [
Instruction.const.f32(-0.0)
], [
Instruction.local.get(r.ref)
])
]));

x.free(); y.free();
q.free(); r.free();

return type;
}

if (type === f64.value) {
const x = ctx.scope.register.allocate(f64.bitcode);
const y = ctx.scope.register.allocate(f64.bitcode);
ctx.block.push(Instruction.local.set(y.ref));
ctx.block.push(Instruction.local.set(x.ref));

const q = ctx.scope.register.allocate(f64.bitcode);
const r = ctx.scope.register.allocate(f64.bitcode);

// if (y == 0) return NaN;
ctx.block.push(Instruction.local.get(y.ref));
ctx.block.push(Instruction.const.f64(0.0));
ctx.block.push(Instruction.f64.eq());
ctx.block.push(Instruction.if(type.type.bitcode, [
Instruction.const.f64(NaN)
], [
Instruction.local.get(x.ref), // q = x / y
Instruction.local.get(y.ref),
Instruction.f64.div(),
Instruction.local.set(q.ref),

Instruction.local.get(x.ref), // x - trunc(q)*y
Instruction.local.get(q.ref),
Instruction.f64.trunc(),
Instruction.local.get(y.ref),
Instruction.f64.mul(),
Instruction.f64.sub(),
Instruction.local.set(r.ref),

Instruction.local.get(r.ref), // remainder == 0.0
Instruction.const.f64(0.0),
Instruction.f64.eq(),

Instruction.local.get(q.ref), // quotient < 0.0
Instruction.const.f64(0.0),
Instruction.f64.lt(),

Instruction.i32.and(), // &&
Instruction.if(f64.bitcode, [
Instruction.const.f64(-0.0)
], [
Instruction.local.get(r.ref)
])
]));

x.free(); y.free();
q.free(); r.free();

return type;
}

Panic(`${colors.red("Error")}: Unhandled type ${type.type.name}\n`, {
path: ctx.file.path, name: ctx.file.name, ref
});
}
Expand Down
127 changes: 74 additions & 53 deletions source/compiler/codegen/expression/precedence.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import type { Term_Expr, Term_Expr_arg, _Literal } from "~/bnf/syntax.d.ts";
import { ReferenceRange } from "~/parser.ts";
import { Panic } from "~/compiler/helper.ts";
import { assert } from "https://deno.land/[email protected]/assert/assert.ts";


const precedence = {
".": 1, "->": 1,
"*" : 3, "/" : 3, "%" : 3,
"+" : 4, "-" : 4,
"<<": 5, ">>": 5,
"<" : 6, ">" : 6, "<=": 6, ">=": 6,
"instanceof": 6.5,
"==": 7, "!=": 7,
"as": 7.5,
"&": 8,
"^": 9,
"|": 10,
"&&": 11,
"||": 12,
"**": 2,
"%": 3,
"*" : 4, "/" : 4,
"+" : 5, "-" : 5,
"<<": 6, ">>": 6,
"<" : 7, ">" : 7, "<=": 7, ">=": 7,
"instanceof": 8,
"==": 9, "!=": 9,
"&": 10,
"^": 11,
"|": 12,
"as": 13,
"&&": 14,
"||": 15,
} as { [key: string]: number };

export function GetPrecedence (a: string, b: string) {
const A = precedence[a];
const B = precedence[b];
if (!A) Panic(`Unknown infix operation ${a}`);
if (!B) Panic(`Unknown infix operation ${a}`);
if (A === undefined) Panic(`Unknown infix operation ${a} 0x${a.charCodeAt(0).toString(16)}`);
if (B === undefined) Panic(`Unknown infix operation ${b} 0x${b.charCodeAt(0).toString(16)}`);

return A !== B
? Math.min(1, Math.max(-1, A-B))
Expand All @@ -39,51 +42,69 @@ export type PrecedenceTree = Term_Expr_arg | {
};

export function ApplyPrecedence(syntax: Term_Expr) {
let root: PrecedenceTree = syntax.value[0] as PrecedenceTree;
const rpn = new Array<PrecedenceTree | string>();
const op_stack = new Array<string>();

rpn.push(syntax.value[0]);
for (const action of syntax.value[1].value) {
const op = action.value[0].value;
const arg = action.value[1]

// First action
if (root.type !== "infix") {
root = {
type: "infix",
lhs: root,
op,
rhs: arg,
ref: ReferenceRange.union(root.ref, arg.ref)
};
continue;
const op = action.value[0].value;
while (op_stack.length > 0) {
const prev = op_stack[op_stack.length - 1]!; // peak
if (GetPrecedence(prev, op) <= 0) {
rpn.push(op_stack.pop()!);
} else break;
}
op_stack.push(op);
rpn.push(action.value[1]);
}

// Drain remaining operators
while (op_stack.length > 0) {
rpn.push(op_stack.pop()!);
}

const p = GetPrecedence(root.op, op);
if (p > 0) {
// Transform stealing previous operand
// (1 + 2) * 3 -> (2 * 3) + 1
root = {
type: "infix",
lhs: {
type: "infix",
lhs: root.rhs,
op,
rhs: arg,
ref: ReferenceRange.union(root.ref, arg.ref)
},
op: root.op,
rhs: root.lhs,
ref: ReferenceRange.union(arg.ref, arg.ref)
}
} else {
root = {
type: "infix",
lhs: root,
op: op,
rhs: arg,
ref: ReferenceRange.union(root.ref, arg.ref)
}
// This could probably be optimised in the future to not use a stack, and just manipulate a raw root node
const stack = new Array<PrecedenceTree>();
while (rpn.length > 0) {
const token = rpn.shift()!;

if (typeof token != "string") {
stack.push(token);
continue;
}

const rhs = stack.pop()!;
const lhs = stack.pop()!;

stack.push({
type: "infix",
lhs: lhs,
op: token,
rhs: rhs,
ref: ReferenceRange.union(lhs.ref, rhs.ref)
})
}

const root = stack.pop()!;
assert(typeof root !== "string", "Expression somehow has no arguments during precedence calculation");
assert(stack.length == 0, "Expression somehow has only operators during precedence calculation");

return root;
}


// For debugging assistance when hell breaks loose
function StringifyPrecedence(tree: PrecedenceTree | string): string {
if (typeof tree === "string") return tree;

if (tree.type === "infix") return `(${StringifyPrecedence(tree.lhs)} ${tree.op} ${StringifyPrecedence(tree.rhs)})`;

const arg = tree.value[1].value[0];
if (arg.type == "expr_brackets") return `(...)`;
if (arg.type != "constant") return `type[${arg.type}]`;

if (arg.value[0].type == "boolean") return arg.value[0].value[0].value;
if (arg.value[0].type == "integer") return arg.value[0].value[0].value;
if (arg.value[0].type == "float") return arg.value[0].value[0].value;
return "str";
}
Loading

0 comments on commit 0ee3d57

Please sign in to comment.