UNPKG

ifc-expressions

Version:

Parsing and evaluation of IFC expressions

184 lines (183 loc) 8.11 kB
import IfcExpressionListener from "../gen/parser/IfcExpressionListener.js"; import { MethodCallChainEndContext, MethodCallChainInnerContext, SEMethodCallContext, } from "../gen/parser/IfcExpressionParser.js"; import { IfcExpressionFunctions } from "../expression/function/IfcExpressionFunctions.js"; import { NoSuchFunctionException } from "../error/NoSuchFunctionException.js"; import { InvalidSyntaxException } from "../error/InvalidSyntaxException.js"; import { Type, Types } from "../type/Types.js"; import { TypeManager } from "./TypeManager.js"; import { ExpressionTypeError } from "../error/ExpressionTypeError.js"; import { isNullish } from "../util/IfcExpressionUtils.js"; import { ValidationException } from "../error/ValidationException.js"; export class IfcExpressionValidationListener extends IfcExpressionListener { constructor() { super(); this.methodCallTargetStack = []; this.enterFunctionCall = (ctx) => { if (!IfcExpressionFunctions.isBuiltinFunction(ctx.IDENTIFIER().getText())) { throw new NoSuchFunctionException(ctx.IDENTIFIER().getText(), ctx); } }; this.enterSEUnaryMultipleMinus = (ctx) => { throw new InvalidSyntaxException("--", ctx); }; this.exitSEBooleanBinaryOp = (ctx) => { this.typeManager.requireLogicalOrBoolean(ctx._left); this.typeManager.requireLogicalOrBoolean(ctx._right); this.typeManager.setType(ctx, Types.boolean()); }; this.exitSEComparison = (ctx) => { this.typeManager.requireTypesOverlap(ctx._left, ctx._right); this.typeManager.setType(ctx, Types.boolean()); }; this.exitSEParenthesis = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx._sub); }; this.exitSELiteral = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx._sub); }; this.exitLiteral = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx.getChild(0)); }; this.exitNumLiteral = (ctx) => { this.typeManager.setType(ctx, Types.numeric()); }; this.exitStringLiteral = (ctx) => { this.typeManager.setType(ctx, Types.string()); }; this.exitBooleanLiteral = (ctx) => { this.typeManager.setType(ctx, Types.boolean()); }; this.exitLogicalLiteral = (ctx) => { this.typeManager.setType(ctx, Types.logical()); }; this.exitExpr = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx.singleExpr()); }; this.exitSEMulDiv = (ctx) => { this.typeManager.requireNumeric(ctx._left); this.typeManager.requireNumeric(ctx._right); this.typeManager.setType(ctx, Types.numeric()); }; this.exitSEPower = (ctx) => { this.typeManager.requireNumeric(ctx._left); this.typeManager.requireNumeric(ctx._right); this.typeManager.setType(ctx, Types.numeric()); }; this.exitSEFunctionCall = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx._sub); }; this.exitSEArrayExpr = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx._sub); }; this.exitSENot = (ctx) => { this.typeManager.requireLogicalOrBoolean(ctx._sub); this.typeManager.setType(ctx, Types.boolean()); }; this.exitSEVariableRef = (ctx) => { switch (ctx._sub.IDENTIFIER().getText()) { case "property": this.typeManager.setType(ctx, Type.IFC_PROPERTY_REF); return; case "element": this.typeManager.setType(ctx, Type.IFC_ELEMENT_REF); return; } throw new ValidationException(`Encountered Variable ref that was neither $property nor $element`, ctx); }; this.exitSEUnaryMinus = (ctx) => { this.typeManager.requireNumeric(ctx._sub); this.typeManager.setType(ctx, Types.numeric()); }; this.exitSEAddSub = (ctx) => { if (this.typeManager.overlapsWithString(ctx._left, ctx._right)) { this.typeManager.setType(ctx, Types.string()); } else if (this.typeManager.overlapsWithNumeric(ctx._left, ctx._right)) { this.typeManager.setType(ctx, Types.numeric()); } else { throw new ExpressionTypeError(`Operator '+' does not allow provided operand types ${this.typeManager .getType(ctx._left) .getName()}(left operand) and ${this.typeManager .getType(ctx._right) .getName()}(right operand). Operands must be both string or both numeric.`, ctx); } }; this.exitSEMethodCall = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx._call); }; this.enterMethodCallChainInner = (ctx) => { this.pushMethodCallTarget(ctx); }; this.exitMethodCallChainInner = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx._call); }; this.enterMethodCallChainEnd = (ctx) => { this.pushMethodCallTarget(ctx); }; this.exitMethodCallChainEnd = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx.functionCall()); }; this.exitFunctionCall = (ctx) => { const func = IfcExpressionFunctions.getFunction(ctx.IDENTIFIER().getText()); const argumentTypes = this.collectArgumentTypes(ctx.exprList()); const parent = ctx.parentCtx; if (parent instanceof MethodCallChainInnerContext || parent instanceof MethodCallChainEndContext || parent instanceof SEMethodCallContext) { argumentTypes.unshift(this.methodCallTargetStack.pop()); } const returnType = func.checkArgumentsAndGetReturnType(argumentTypes, ctx); this.typeManager.setType(ctx, returnType); }; this.collectArgumentTypes = (ctx, resultSoFar) => { if (isNullish(resultSoFar)) { resultSoFar = []; } if (!isNullish(ctx)) { resultSoFar.push([ ctx.singleExpr(), this.typeManager.getType(ctx.singleExpr()), ]); const rest = ctx.exprList(); if (!isNullish(rest)) { return this.collectArgumentTypes(rest, resultSoFar); } } return resultSoFar; }; this.exitArrayExpr = (ctx) => { this.typeManager.setType(ctx, Types.tuple(...this.collectArrayElementTypes(ctx.arrayElementList()))); }; this.collectArrayElementTypes = (ctx, resultSoFar) => { if (isNullish(resultSoFar)) { resultSoFar = []; } if (!isNullish(ctx)) { resultSoFar.push(this.typeManager.getType(ctx.singleExpr())); const rest = ctx.arrayElementList(); if (!isNullish(rest)) { return this.collectArrayElementTypes(rest, resultSoFar); } } return resultSoFar; }; this.exitVariableRef = (ctx) => { this.typeManager.setType(ctx, Types.or(Type.IFC_PROPERTY_REF, Type.IFC_ELEMENT_REF)); }; this.typeManager = new TypeManager(); } getTypeManager() { return this.typeManager; } pushMethodCallTarget(ctx) { if (ctx.parentCtx["_target"]) { const targetType = this.typeManager.getType(ctx.parentCtx["_target"]); this.methodCallTargetStack.push([ctx, targetType]); } else { throw new ValidationException("Did not find expected context attribute 'target' in parent rule context", ctx); } } } //# sourceMappingURL=IfcExpressionValidationListener.js.map