ifc-expressions
Version:
Parsing and evaluation of IFC expressions
258 lines (257 loc) • 13.1 kB
JavaScript
import IfcExpressionVisitor from "../gen/parser/IfcExpressionVisitor.js";
import { NumericLiteralExpr } from "../expression/numeric/NumericLiteralExpr.js";
import { isNullish, isPresent } from "../util/IfcExpressionUtils.js";
import Decimal from "decimal.js";
import { PlusExpr } from "../expression/numeric/PlusExpr.js";
import { MinusExpr } from "../expression/numeric/MinusExpr.js";
import { MultiplyExpr } from "../expression/numeric/MultiplyExpr.js";
import { DivideExpr } from "../expression/numeric/DivideExpr.js";
import { PropObjectReferenceExpr } from "../expression/reference/PropObjectReferenceExpr.js";
import { ElemObjectReferenceExpr } from "../expression/reference/ElemObjectReferenceExpr.js";
import { StringLiteralExpr } from "../expression/string/StringLiteralExpr.js";
import { ArrayExpr } from "../expression/structure/ArrayExpr.js";
import { FunctionExpr } from "../expression/function/FunctionExpr.js";
import { PowerExpr } from "../expression/numeric/PowerExpr.js";
import { UnaryMinusExpr } from "../expression/numeric/UnaryMinusExpr.js";
import { AndExpr } from "../expression/boolean/AndExpr.js";
import { BooleanLiteralExpr } from "../expression/boolean/BooleanLiteralExpr.js";
import { OrExpr } from "../expression/boolean/OrExpr.js";
import { XorExpr } from "../expression/boolean/XorExpr.js";
import { SyntaxErrorException } from "../error/SyntaxErrorException.js";
import { NotExpr } from "../expression/boolean/NotExpr.js";
import { ParenthesisExpr } from "../expression/structure/ParenthesisExpr.js";
import { EqualsExpr } from "../expression/comparison/EqualsExpr.js";
import { NotEqualsExpr } from "../expression/comparison/NotEqualsExpr.js";
import { StringConcatExpr } from "../expression/string/StringConcatExpr.js";
import { PlusOrConcatExpr } from "../expression/poly/PlusOrConcatExpr.js";
import { ExpressionTypeError } from "../error/ExpressionTypeError.js";
import { GreaterThan } from "../expression/comparison/GreaterThan.js";
import { GreaterThanOrEqual } from "../expression/comparison/GreaterThanOrEqual.js";
import { LessThan } from "../expression/comparison/LessThan.js";
import { LessThanOrEqual } from "../expression/comparison/LessThanOrEqual.js";
import { ExprManager } from "./ExprManager.js";
import { LogicalValue } from "../value/LogicalValue.js";
import { LogicalLiteralExpr } from "../expression/boolean/LogicalLiteralExpr.js";
export class ExprCompiler extends IfcExpressionVisitor {
constructor(typeManager) {
super();
this.methodCallTargetStack = [];
this.visitExpr = (ctx) => {
return this.visit(ctx.singleExpr());
};
this.visitVariableRef = (ctx) => {
if (ctx.IDENTIFIER().getText().toUpperCase() === "PROPERTY") {
return this.associateContextAndReturn(new PropObjectReferenceExpr(), ctx);
}
else if (ctx.IDENTIFIER().getText().toUpperCase() === "ELEMENT") {
return this.associateContextAndReturn(new ElemObjectReferenceExpr(), ctx);
}
throw new Error(`Parsing error: No variable '${ctx.getText()}' found `);
};
this.visitSEComparison = (ctx) => {
const left = this.visit(ctx.singleExpr(0));
const right = this.visit(ctx.singleExpr(1));
switch (ctx.CMP_OP().getText()) {
case "==":
return this.associateContextAndReturn(new EqualsExpr(left, right), ctx);
case "!=":
return this.associateContextAndReturn(new NotEqualsExpr(left, right), ctx);
case ">":
return this.associateContextAndReturn(new GreaterThan(left, right), ctx);
case ">=":
return this.associateContextAndReturn(new GreaterThanOrEqual(left, right), ctx);
case "<":
return this.associateContextAndReturn(new LessThan(left, right), ctx);
case "<=":
return this.associateContextAndReturn(new LessThanOrEqual(left, right), ctx);
}
throw new Error(`Parsing error: comparison operator '${ctx
.CMP_OP()
.getText()}' not supported`);
};
this.visitSEMethodCall = (ctx) => {
this.methodCallTargetStack.push(this.visit(ctx.singleExpr()));
return this.visit(ctx.methodCallChain());
};
this.visitSELiteral = (ctx) => {
return this.visit(ctx.literal());
};
this.visitSEVariableRef = (ctx) => {
return this.visit(ctx.variableRef());
};
this.visitSEArrayExpr = (ctx) => {
return this.visit(ctx.arrayExpr());
};
this.visitSEFunctionCall = (ctx) => {
return this.visit(ctx.functionCall());
};
this.visitSEParenthesis = (ctx) => {
return this.associateContextAndReturn(new ParenthesisExpr(this.visit(ctx._sub)), ctx);
};
/*================================================
* MethodCallChain
*==============================================*/
this.visitMethodCallChainInner = (ctx) => {
const functionExpr = new FunctionExpr(ctx.functionCall().IDENTIFIER().getText(), this.collectFunctionArguments(ctx.functionCall().exprList(), [
this.methodCallTargetStack.pop(),
]));
this.associateContextAndReturn(functionExpr, ctx.functionCall());
this.methodCallTargetStack.push(functionExpr);
return this.visit(ctx.methodCallChain());
};
this.visitMethodCallChainEnd = (ctx) => {
return this.associateContextAndReturn(new FunctionExpr(ctx.functionCall().IDENTIFIER().getText(), this.collectFunctionArguments(ctx.functionCall().exprList(), [
this.methodCallTargetStack.pop(),
])), ctx.functionCall());
};
this.visitSEAddSub = (ctx) => {
this.typeManager.requireTypesOverlap(ctx._left, ctx._right);
switch (ctx._op.text) {
case "+":
return this.makePlusExpr(ctx);
case "-":
return this.associateContextAndReturn(new MinusExpr(this.visit(ctx._left), this.visit(ctx._right)), ctx);
default:
ExprCompiler.failNode(ctx);
}
};
this.visitSEMulDiv = (ctx) => {
switch (ctx._op.text) {
case "*":
return this.associateContextAndReturn(new MultiplyExpr(this.visit(ctx.getChild(0)), this.visit(ctx.getChild(2))), ctx);
case "/":
return this.associateContextAndReturn(new DivideExpr(this.visit(ctx.getChild(0)), this.visit(ctx.getChild(2))), ctx);
default:
ExprCompiler.failNode(ctx);
}
};
/*================================================
* StringExpr
*===============================================*/
this.visitStringLiteral = (ctx) => {
const quotedString = ctx.QUOTED_STRING().getText();
const text = quotedString.substring(1, quotedString.length - 1);
return this.associateContextAndReturn(new StringLiteralExpr(text), ctx);
};
/*================================================
* NumExpr
*===============================================*/
this.visitNumLiteral = (ctx) => {
let val = ctx.INT();
if (isPresent(val)) {
return this.associateContextAndReturn(new NumericLiteralExpr(new Decimal(ctx.getText())), ctx);
}
val = ctx.FLOAT();
if (isPresent(val)) {
return this.associateContextAndReturn(new NumericLiteralExpr(new Decimal(ctx.getText())), ctx);
}
ExprCompiler.failNode(ctx);
};
this.visitSEUnaryMultipleMinus = (ctx) => {
return this.visit(ctx.singleExpr());
};
this.visitSEPower = (ctx) => {
return this.associateContextAndReturn(new PowerExpr(this.visit(ctx._left), this.visit(ctx._right)), ctx);
};
this.visitSEUnaryMinus = (ctx) => {
return this.associateContextAndReturn(new UnaryMinusExpr(this.visit(ctx.singleExpr())), ctx);
};
/*================================================
* BooleanExpr
*===============================================*/
this.visitSEBooleanBinaryOp = (ctx) => {
switch (ctx._op.text) {
case "&&":
return this.associateContextAndReturn(new AndExpr(this.visit(ctx.singleExpr(0)), this.visit(ctx.singleExpr(1))), ctx);
case "||":
return this.associateContextAndReturn(new OrExpr(this.visit(ctx.singleExpr(0)), this.visit(ctx.singleExpr(1))), ctx);
case "><":
return this.associateContextAndReturn(new XorExpr(this.visit(ctx.singleExpr(0)), this.visit(ctx.singleExpr(1))), ctx);
}
throw new SyntaxErrorException(ctx._op.text, ctx._op.line, ctx._op.column, `Unknown boolean operator ${ctx._op.text}`);
};
this.visitBooleanLiteral = (ctx) => {
return this.associateContextAndReturn(new BooleanLiteralExpr(ctx.BOOLEAN().getText().toUpperCase() === "TRUE"), ctx);
};
this.visitLogicalLiteral = (ctx) => {
// the only literal we recognize is 'UNKNOWN' (true and false are boolean literals)
return this.associateContextAndReturn(new LogicalLiteralExpr(LogicalValue.UNKNOWN_VALUE), ctx);
};
this.visitSENot = (ctx) => {
return this.associateContextAndReturn(new NotExpr(this.visit(ctx.singleExpr())), ctx);
};
/*================================================
* ArrayExpr
*===============================================*/
this.visitArrayExpr = (ctx) => {
return this.associateContextAndReturn(new ArrayExpr(this.collectArrayElements(ctx.arrayElementList())), ctx);
};
this.collectArrayElements = (ctx) => {
if (!isNullish(ctx)) {
const first = this.visit(ctx.singleExpr());
const rest = ctx.arrayElementList();
if (!isNullish(rest)) {
const arr = this.collectArrayElements(rest);
arr.unshift(first);
return arr;
}
return [first];
}
return [];
};
/*================================================
* FuncExpr
*===============================================*/
this.visitFunctionCall = (ctx) => {
const args = isNullish(ctx.exprList())
? []
: this.collectFunctionArguments(ctx.exprList());
return this.associateContextAndReturn(new FunctionExpr(ctx.IDENTIFIER().getText(), args), ctx);
};
this.collectFunctionArguments = (ctx, resultSoFar) => {
if (isNullish(resultSoFar)) {
resultSoFar = [];
}
if (!isNullish(ctx)) {
resultSoFar.push(this.visit(ctx.singleExpr()));
const rest = ctx.exprList();
if (!isNullish(rest)) {
return this.collectFunctionArguments(rest, resultSoFar);
}
}
return resultSoFar;
};
this.visitLiteral = (ctx) => {
return this.visit(ctx.getChild(0));
};
this.typeManager = typeManager;
this.exprManager = new ExprManager();
}
getExprManager() {
return this.exprManager;
}
/*================================================
* VariableRef
*==============================================*/
associateContextAndReturn(expr, ctx) {
this.exprManager.registerExprWithContext(expr, ctx);
return expr;
}
makePlusExpr(ctx) {
if (this.typeManager.isNumeric(ctx._left, ctx._right)) {
return this.associateContextAndReturn(new PlusExpr(this.visit(ctx._left), this.visit(ctx._right)), ctx);
}
if (this.typeManager.isString(ctx._left, ctx._right)) {
return this.associateContextAndReturn(new StringConcatExpr(this.visit(ctx._left), this.visit(ctx._right)), ctx);
}
if (this.typeManager.overlapsWithString(ctx._left, ctx._right) ||
this.typeManager.overlapsWithNumeric(ctx._left, ctx._right)) {
return this.associateContextAndReturn(new PlusOrConcatExpr(this.visit(ctx._left), this.visit(ctx._right)), ctx);
}
throw new ExpressionTypeError("Operator '+' requires the operands to be either both of type numeric or both of type string, but they are not.", ctx);
}
static failNode(ctx) {
throw new Error(`Cannot parse (sub)expression ${ctx.getText()}`);
}
}
//# sourceMappingURL=ExprCompiler.js.map