ifc-expressions
Version:
Parsing and evaluation of IFC expressions
247 lines (246 loc) • 12.4 kB
JavaScript
import { IfcExpressionListener } from "../gen/parser/IfcExpressionListener.js";
import { MethodCallChainEndContext, MethodCallChainInnerContext, MethodFunctionCallContext, } 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";
import { BuiltinVariableRegistry, isBuiltinFunctionDefinition, isBuiltinPropertyDefinition, } from "../builtin/BuiltinVariableRegistry.js";
import { ContextObjectType } from "../type/ContextObjectType.js";
import { NoSuchMemberException } from "../error/NoSuchMemberException.js";
import { NoSuchMethodException } from "../error/NoSuchMethodException.js";
import { WrongFunctionArgumentTypeException } from "../error/WrongFunctionArgumentTypeException.js";
import { MissingFunctionArgumentException } from "../error/MissingFunctionArgumentException.js";
import { SpuriousFunctionArgumentException } from "../error/SpuriousFunctionArgumentException.js";
export class IfcExpressionValidationListener extends IfcExpressionListener {
constructor(builtinVariableRegistry = BuiltinVariableRegistry.getDefaultRegistry()) {
super();
this.methodCallTargetStack = [];
this.enterFunctionCall = (ctx) => {
if (!IfcExpressionFunctions.isBuiltinFunction(ctx.IDENTIFIER().getText())) {
const parent = ctx.parent;
const grandParent = parent?.parent;
const isMethodAccessor = parent instanceof MethodFunctionCallContext &&
(grandParent instanceof MethodCallChainInnerContext ||
grandParent instanceof MethodCallChainEndContext);
if (!isMethodAccessor) {
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) => {
const builtin = this.builtinVariableRegistry.getDefinition(ctx._sub.IDENTIFIER().getText());
if (builtin) {
this.typeManager.setType(ctx, builtin.type);
return;
}
throw new 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.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._call);
};
this.exitMethodFunctionCall = (ctx) => {
this.typeManager.copyTypeFrom(ctx, ctx.functionCall());
};
this.exitMethodPropertyAccess = (ctx) => {
const [_, targetType] = this.popMethodCallTarget(ctx);
if (!(targetType instanceof ContextObjectType)) {
throw new NoSuchMemberException(ctx.IDENTIFIER().getText(), targetType.getName(), ctx);
}
const member = targetType.getMemberDefinition(ctx.IDENTIFIER().getText());
if (!isBuiltinPropertyDefinition(member)) {
throw new 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 MethodFunctionCallContext &&
(grandParent instanceof MethodCallChainInnerContext ||
grandParent instanceof MethodCallChainEndContext);
if (isMethodAccessor) {
const [targetCtx, targetType] = this.popMethodCallTarget(ctx);
if (targetType instanceof ContextObjectType) {
const member = targetType.getMemberDefinition(ctx.IDENTIFIER().getText());
if (!isBuiltinFunctionDefinition(member)) {
throw new 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.getFunction(ctx.IDENTIFIER().getText());
if (isNullish(func)) {
throw new NoSuchFunctionException(ctx.IDENTIFIER().getText(), ctx);
}
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) => {
const builtinTypes = [Type.IFC_PROPERTY_REF, Type.IFC_ELEMENT_REF];
const otherBuiltinDefinitions = ["property", "element"]
.map((name) => this.builtinVariableRegistry.getDefinition(name)?.type)
.filter((type) => !isNullish(type));
this.typeManager.setType(ctx, Types.or(...builtinTypes, ...otherBuiltinDefinitions));
};
this.typeManager = new 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("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(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(functionName, `[arg${i}]`, i, ctx);
}
Types.requireWeakIsAssignableFrom(expectedArgumentTypes[i], providedArgumentTypes[i][1], () => new WrongFunctionArgumentTypeException(functionName, `[arg${i}]`, expectedArgumentTypes[i], providedArgumentTypes[i][1], i, providedArgumentTypes[i][0]));
}
}
popMethodCallTarget(ctx) {
const target = this.methodCallTargetStack.pop();
if (isNullish(target)) {
throw new ValidationException("Did not find expected method call target on stack", ctx);
}
return target;
}
}
//# sourceMappingURL=IfcExpressionValidationListener.js.map