UNPKG

ifc-expressions

Version:

Parsing and evaluation of IFC expressions

251 lines (250 loc) 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IfcExpressionValidationListener = void 0; const IfcExpressionListener_js_1 = require("../gen/parser/IfcExpressionListener.js"); const IfcExpressionParser_js_1 = require("../gen/parser/IfcExpressionParser.js"); const IfcExpressionFunctions_js_1 = require("../expression/function/IfcExpressionFunctions.js"); const NoSuchFunctionException_js_1 = require("../error/NoSuchFunctionException.js"); const InvalidSyntaxException_js_1 = require("../error/InvalidSyntaxException.js"); const Types_js_1 = require("../type/Types.js"); const TypeManager_js_1 = require("./TypeManager.js"); const ExpressionTypeError_js_1 = require("../error/ExpressionTypeError.js"); const IfcExpressionUtils_js_1 = require("../util/IfcExpressionUtils.js"); const ValidationException_js_1 = require("../error/ValidationException.js"); const BuiltinVariableRegistry_js_1 = require("../builtin/BuiltinVariableRegistry.js"); const ContextObjectType_js_1 = require("../type/ContextObjectType.js"); const NoSuchMemberException_js_1 = require("../error/NoSuchMemberException.js"); const NoSuchMethodException_js_1 = require("../error/NoSuchMethodException.js"); const WrongFunctionArgumentTypeException_js_1 = require("../error/WrongFunctionArgumentTypeException.js"); const MissingFunctionArgumentException_js_1 = require("../error/MissingFunctionArgumentException.js"); const SpuriousFunctionArgumentException_js_1 = require("../error/SpuriousFunctionArgumentException.js"); class IfcExpressionValidationListener extends IfcExpressionListener_js_1.IfcExpressionListener { constructor(builtinVariableRegistry = BuiltinVariableRegistry_js_1.BuiltinVariableRegistry.getDefaultRegistry()) { super(); this.methodCallTargetStack = []; this.enterFunctionCall = (ctx) => { if (!IfcExpressionFunctions_js_1.IfcExpressionFunctions.isBuiltinFunction(ctx.IDENTIFIER().getText())) { const parent = ctx.parent; const grandParent = parent?.parent; const isMethodAccessor = parent instanceof IfcExpressionParser_js_1.MethodFunctionCallContext && (grandParent instanceof IfcExpressionParser_js_1.MethodCallChainInnerContext || grandParent instanceof IfcExpressionParser_js_1.MethodCallChainEndContext); if (!isMethodAccessor) { throw new NoSuchFunctionException_js_1.NoSuchFunctionException(ctx.IDENTIFIER().getText(), ctx); } } }; this.enterSEUnaryMultipleMinus = (ctx) => { throw new InvalidSyntaxException_js_1.InvalidSyntaxException("--", ctx); }; this.exitSEBooleanBinaryOp = (ctx) => { this.typeManager.requireLogicalOrBoolean(ctx._left); this.typeManager.requireLogicalOrBoolean(ctx._right); this.typeManager.setType(ctx, Types_js_1.Types.boolean()); }; this.exitSEComparison = (ctx) => { this.typeManager.requireTypesOverlap(ctx._left, ctx._right); this.typeManager.setType(ctx, Types_js_1.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_js_1.Types.numeric()); }; this.exitStringLiteral = (ctx) => { this.typeManager.setType(ctx, Types_js_1.Types.string()); }; this.exitBooleanLiteral = (ctx) => { this.typeManager.setType(ctx, Types_js_1.Types.boolean()); }; this.exitLogicalLiteral = (ctx) => { this.typeManager.setType(ctx, Types_js_1.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_js_1.Types.numeric()); }; this.exitSEPower = (ctx) => { this.typeManager.requireNumeric(ctx._left); this.typeManager.requireNumeric(ctx._right); this.typeManager.setType(ctx, Types_js_1.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_js_1.Types.boolean()); }; this.exitSEVariableRef = (ctx) => { const builtin = this.builtinVariableRegistry.getDefinition(ctx._sub.IDENTIFIER().getText()); if (builtin) { this.typeManager.setType(ctx, builtin.type); return; } throw new ValidationException_js_1.ValidationException(`Encountered Variable ref that was neither a built-in variable nor a configured client builtin`, ctx); }; this.exitSEUnaryMinus = (ctx) => { this.typeManager.requireNumeric(ctx._sub); this.typeManager.setType(ctx, Types_js_1.Types.numeric()); }; this.exitSEAddSub = (ctx) => { if (this.typeManager.overlapsWithString(ctx._left, ctx._right)) { this.typeManager.setType(ctx, Types_js_1.Types.string()); } else if (this.typeManager.overlapsWithNumeric(ctx._left, ctx._right)) { this.typeManager.setType(ctx, Types_js_1.Types.numeric()); } else { throw new ExpressionTypeError_js_1.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._call); }; this.exitMethodFunctionCall = (ctx) => { this.typeManager.copyTypeFrom(ctx, ctx.functionCall()); }; this.exitMethodPropertyAccess = (ctx) => { const [_, targetType] = this.popMethodCallTarget(ctx); if (!(targetType instanceof ContextObjectType_js_1.ContextObjectType)) { throw new NoSuchMemberException_js_1.NoSuchMemberException(ctx.IDENTIFIER().getText(), targetType.getName(), ctx); } const member = targetType.getMemberDefinition(ctx.IDENTIFIER().getText()); if (!(0, BuiltinVariableRegistry_js_1.isBuiltinPropertyDefinition)(member)) { throw new NoSuchMemberException_js_1.NoSuchMemberException(ctx.IDENTIFIER().getText(), targetType.getName(), ctx); } this.typeManager.setType(ctx, member.valueType); }; this.exitFunctionCall = (ctx) => { const argumentTypes = this.collectArgumentTypes(ctx.exprList()); const parent = ctx.parent; const grandParent = parent?.parent; const isMethodAccessor = parent instanceof IfcExpressionParser_js_1.MethodFunctionCallContext && (grandParent instanceof IfcExpressionParser_js_1.MethodCallChainInnerContext || grandParent instanceof IfcExpressionParser_js_1.MethodCallChainEndContext); if (isMethodAccessor) { const [targetCtx, targetType] = this.popMethodCallTarget(ctx); if (targetType instanceof ContextObjectType_js_1.ContextObjectType) { const member = targetType.getMemberDefinition(ctx.IDENTIFIER().getText()); if (!(0, BuiltinVariableRegistry_js_1.isBuiltinFunctionDefinition)(member)) { throw new NoSuchMethodException_js_1.NoSuchMethodException(ctx.IDENTIFIER().getText(), targetType.getName(), ctx); } this.checkBuiltinFunctionArguments(ctx.IDENTIFIER().getText(), member.argumentTypes, argumentTypes, ctx); this.typeManager.setType(ctx, member.returnType); return; } argumentTypes.unshift([targetCtx, targetType]); } const func = IfcExpressionFunctions_js_1.IfcExpressionFunctions.getFunction(ctx.IDENTIFIER().getText()); if ((0, IfcExpressionUtils_js_1.isNullish)(func)) { throw new NoSuchFunctionException_js_1.NoSuchFunctionException(ctx.IDENTIFIER().getText(), ctx); } const returnType = func.checkArgumentsAndGetReturnType(argumentTypes, ctx); this.typeManager.setType(ctx, returnType); }; this.collectArgumentTypes = (ctx, resultSoFar) => { if ((0, IfcExpressionUtils_js_1.isNullish)(resultSoFar)) { resultSoFar = []; } if (!(0, IfcExpressionUtils_js_1.isNullish)(ctx)) { resultSoFar.push([ ctx.singleExpr(), this.typeManager.getType(ctx.singleExpr()), ]); const rest = ctx.exprList(); if (!(0, IfcExpressionUtils_js_1.isNullish)(rest)) { return this.collectArgumentTypes(rest, resultSoFar); } } return resultSoFar; }; this.exitArrayExpr = (ctx) => { this.typeManager.setType(ctx, Types_js_1.Types.tuple(...this.collectArrayElementTypes(ctx.arrayElementList()))); }; this.collectArrayElementTypes = (ctx, resultSoFar) => { if ((0, IfcExpressionUtils_js_1.isNullish)(resultSoFar)) { resultSoFar = []; } if (!(0, IfcExpressionUtils_js_1.isNullish)(ctx)) { resultSoFar.push(this.typeManager.getType(ctx.singleExpr())); const rest = ctx.arrayElementList(); if (!(0, IfcExpressionUtils_js_1.isNullish)(rest)) { return this.collectArrayElementTypes(rest, resultSoFar); } } return resultSoFar; }; this.exitVariableRef = (ctx) => { const builtinTypes = [Types_js_1.Type.IFC_PROPERTY_REF, Types_js_1.Type.IFC_ELEMENT_REF]; const otherBuiltinDefinitions = ["property", "element"] .map((name) => this.builtinVariableRegistry.getDefinition(name)?.type) .filter((type) => !(0, IfcExpressionUtils_js_1.isNullish)(type)); this.typeManager.setType(ctx, Types_js_1.Types.or(...builtinTypes, ...otherBuiltinDefinitions)); }; this.typeManager = new TypeManager_js_1.TypeManager(); this.builtinVariableRegistry = builtinVariableRegistry; } getTypeManager() { return this.typeManager; } pushMethodCallTarget(ctx) { if (ctx.parent["_target"]) { const targetType = this.typeManager.getType(ctx.parent["_target"]); this.methodCallTargetStack.push([ctx.parent["_target"], targetType]); } else { throw new ValidationException_js_1.ValidationException("Did not find expected context attribute 'target' in parent rule context", ctx); } } checkBuiltinFunctionArguments(functionName, expectedArgumentTypes, providedArgumentTypes, ctx) { if (providedArgumentTypes.length > expectedArgumentTypes.length) { throw new SpuriousFunctionArgumentException_js_1.SpuriousFunctionArgumentException(functionName, "[unexpected argument]", expectedArgumentTypes.length, ctx, `Function expects (at most) ${expectedArgumentTypes.length} arguments`); } for (let i = 0; i < expectedArgumentTypes.length; i++) { if (providedArgumentTypes.length <= i) { throw new MissingFunctionArgumentException_js_1.MissingFunctionArgumentException(functionName, `[arg${i}]`, i, ctx); } Types_js_1.Types.requireWeakIsAssignableFrom(expectedArgumentTypes[i], providedArgumentTypes[i][1], () => new WrongFunctionArgumentTypeException_js_1.WrongFunctionArgumentTypeException(functionName, `[arg${i}]`, expectedArgumentTypes[i], providedArgumentTypes[i][1], i, providedArgumentTypes[i][0])); } } popMethodCallTarget(ctx) { const target = this.methodCallTargetStack.pop(); if ((0, IfcExpressionUtils_js_1.isNullish)(target)) { throw new ValidationException_js_1.ValidationException("Did not find expected method call target on stack", ctx); } return target; } } exports.IfcExpressionValidationListener = IfcExpressionValidationListener; //# sourceMappingURL=IfcExpressionValidationListener.js.map