Skip to content
Snippets Groups Projects
Commit 8ea56a9b authored by Ariya Hidayat's avatar Ariya Hidayat
Browse files

Custom expression parser: formalize the tree visitor (#14219)

* Custom expression parser: formalize the tree visitor

Also included, a pretty-printer to debug the syntax tree.

* Simplify the tree's pretty printer
parent a9bbb494
Branches
Tags
No related merge requests found
export class ExpressionVisitor {
visit(node) {
if (!node) {
return;
}
if (Array.isArray(node)) {
node = node[0];
}
if (!this[node.name]) {
console.error(node);
throw new Error(`ExpressionVisitor: missing ${node.name}`);
}
return this[node.name](node.children, node);
}
any(ctx) {
return this.visit(ctx.expression);
}
expression(ctx) {
return this.visit(ctx.expression);
}
aggregation(ctx) {
return this.visit(ctx.expression);
}
number(ctx) {
return this.visit(ctx.expression);
}
string(ctx) {
return this.visit(ctx.expression);
}
boolean(ctx) {
return this.visit(ctx.expression);
}
additionExpression(ctx) {
return (ctx.operands || []).map(operand => this.visit(operand));
}
multiplicationExpression(ctx) {
return (ctx.operands || []).map(operand => this.visit(operand));
}
functionExpression(ctx) {
return (ctx.arguments || []).map(argument => this.visit(argument));
}
caseExpression(ctx) {
return (ctx.arguments || []).map(argument => this.visit(argument));
}
metricExpression(ctx) {
return this.visit(ctx.metricName);
}
segmentExpression(ctx) {
return this.visit(ctx.segmentName);
}
dimensionExpression(ctx) {
return this.visit(ctx.dimensionName);
}
identifier(ctx) {
return (ctx.Identifier || []).map(id => id.image);
}
identifierString(ctx) {
return (ctx.IdentifierString || []).map(id => id.image);
}
stringLiteral(ctx) {
return (ctx.StringLiteral || []).map(id => id.image);
}
numberLiteral(ctx) {
return (ctx.NumberLiteral || []).map(id => id.image);
}
atomicExpression(ctx) {
return this.visit(ctx.expression);
}
parenthesisExpression(ctx) {
return this.visit(ctx.expression);
}
booleanExpression(ctx) {
return (ctx.operands || []).map(operand => this.visit(operand));
}
comparisonExpression(ctx) {
return (ctx.operands || []).map(operand => this.visit(operand));
}
booleanUnaryExpression(ctx) {
return (ctx.operands || []).map(operand => this.visit(operand));
}
}
// only for troubleshooting or debugging
export function prettyPrint(cst) {
class Formatter extends ExpressionVisitor {
constructor() {
super();
this.indent = 0;
}
visit(node) {
console.log(
" ".repeat(this.indent),
Array.isArray(node) ? node[0].name : node.name,
);
++this.indent;
const result = super.visit(node);
--this.indent;
return result;
}
}
new Formatter().visit(cst);
}
import { parse } from "metabase/lib/expressions/parser";
import { ExpressionVisitor } from "metabase/lib/expressions/visitor";
describe("ExpressionVisitor", () => {
function parseSource(source, startRule) {
let cst = null;
try {
cst = parse({ source, tokenVector: null, startRule }).cst;
} catch (e) {
let err = e;
if (err.length && err.length > 0) {
err = err[0];
if (typeof err.message === "string") {
err = err.message;
}
}
throw err;
}
return cst;
}
function collect(source) {
class LiteralCollector extends ExpressionVisitor {
constructor() {
super();
this.literals = [];
}
stringLiteral(ctx) {
this.literals.push(ctx.StringLiteral[0].image);
}
numberLiteral(ctx) {
this.literals.push(ctx.NumberLiteral[0].image);
}
}
const tree = parseSource(source, "boolean");
const collector = new LiteralCollector();
collector.visit(tree);
return collector.literals;
}
it("should collect string literals", () => {
expect(collect("contains([Vendor],'Super')")).toEqual(["'Super'"]);
});
it("should collect number literals", () => {
expect(collect("between([Rating],3,5)")).toEqual(["3", "5"]);
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment