-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix operator precedence not applying correctly (#12)
- Loading branch information
1 parent
1b62e92
commit 0ee3d57
Showing
3 changed files
with
257 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
|
@@ -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"; | ||
} |
Oops, something went wrong.