UNPKG

ifc-expressions

Version:

Parsing and evaluation of IFC expressions

268 lines (267 loc) 13.9 kB
import { isNullish } from "../../util/IfcExpressionUtils.js"; import { MAP } from "./impl/MAP.js"; import { ROUND } from "./impl/ROUND.js"; import { AttributeAccessorFunction } from "./impl/AttributeAccessorFunction.js"; import { PROPERTYSET } from "./impl/PROPERTYSET.js"; import { PROPERTY } from "./impl/PROPERTY.js"; import { TYPE } from "./impl/TYPE.js"; import { FuncBooleanBinary } from "./impl/FuncBooleanBinary.js"; import { NOT } from "./impl/NOT.js"; import { CHOOSE } from "./impl/CHOOSE.js"; import { MatchesPattern } from "./impl/MatchesPattern.js"; import { Type, Types } from "../../type/Types.js"; import { TOSTRING } from "./impl/TOSTRING.js"; import { EXISTS } from "./impl/EXISTS.js"; import { EQUALS } from "./impl/EQUALS.js"; import { CompareMagnitudes } from "./impl/CompareMagnitudes.js"; import { ReplacePattern } from "./impl/ReplacePattern.js"; import { IF } from "./impl/IF.js"; import { TONUMERIC } from "./impl/TONUMERIC.js"; import { IfcExpressionFunctionConfigException } from "../../error/IfcExpressionFunctionConfigException.js"; import { TOBOOLEAN } from "./impl/TOBOOLEAN.js"; import { TOLOGICAL } from "./impl/TOLOGICAL.js"; import { TOLOWERCASE } from "./impl/TOLOWERCASE.js"; import { TOUPPERCASE } from "./impl/TOUPPERCASE.js"; import { SUBSTRING } from "./impl/SUBSTRING.js"; import { SPLIT } from "./impl/SPLIT.js"; import { AT } from "./impl/AT.js"; import { TOIFCDATE } from "./impl/TOIFCDATE.js"; import { TOIFCDATETIME } from "./impl/TOIFCDATETIME.js"; import { TOIFCTIME } from "./impl/TOIFCTIME.js"; import { TOIFCDURATION } from "./impl/TOIFCDURATION.js"; import { TOIFCTIMESTAMP } from "./impl/TOIFCTIMESTAMP.js"; import { ADDDURATION } from "./impl/ADDDURATION.js"; const builtinFunctions = new Map(); function registerOrDie(fnKey, func) { if (builtinFunctions.has(fnKey)) { throw new IfcExpressionFunctionConfigException(`cannot register function with name '${fnKey}': name already in use`); } builtinFunctions.set(fnKey, func); } function toDocKey(label) { return label.replace(/[^a-zA-Z0-9]+/g, "_"); } function withDocumentation(func, summary, args = []) { func.withDocumentation({ key: `function.${func.getName()}.summary`, fallback: summary, }); args.forEach((argument, index) => { const formalArgument = func.getFormalArguments()[index]; if (!formalArgument) { return; } const docKey = toDocKey(argument.label); formalArgument.withDocumentation({ key: `function.${func.getName()}.arg.${docKey}.label`, fallback: argument.label, }, { key: `function.${func.getName()}.arg.${docKey}.summary`, fallback: argument.documentation, }); }); return func; } function registerFunc(func, ...aliases) { const fnKey = func.getName(); const keys = [...aliases]; keys.unshift(fnKey); keys.forEach((key) => registerOrDie(IfcExpressionFunctions.normalizeName(key), func)); } function documentAttributeAccessor(name, summary) { return withDocumentation(new AttributeAccessorFunction(name, Type.STRING), summary, [ { label: "object", documentation: "The IFC object to read from", }, ]); } export class IfcExpressionFunctions { static normalizeName(name) { if (isNullish(name)) { return undefined; } return name.toUpperCase(); } static isBuiltinFunction(name) { if (isNullish(name)) { return false; } return builtinFunctions.has(this.normalizeName(name)); } static getFunction(name) { if (isNullish(name)) { return undefined; } return builtinFunctions.get(this.normalizeName(name)); } static getBuiltinFunctionNames() { return [...builtinFunctions.keys()].sort((left, right) => left.localeCompare(right)); } } registerFunc(withDocumentation(new MAP(), "map an input value through a list of key/value mappings", [ { label: "input", documentation: "The value to look up" }, { label: "mappings", documentation: "The mapping table as [key, value] pairs", }, { label: "defaultValue", documentation: "The fallback value to return when no mapping matches", }, ])); registerFunc(withDocumentation(new CHOOSE(), "return the value from the first matching boolean case", [ { label: "cases", documentation: "The candidate [condition, value] pairs to evaluate in order", }, { label: "defaultValue", documentation: "The fallback value when no case evaluates to true", }, ])); registerFunc(withDocumentation(new AT(), "return the item at the specified index from an array or tuple", [ { label: "input", documentation: "The array or tuple to read from" }, { label: "index", documentation: "The zero-based index to read" }, ])); registerFunc(withDocumentation(new IF(), "choose one of two or three values based on a boolean or logical condition", [ { label: "condition", documentation: "The condition to evaluate" }, { label: "thenValue", documentation: "The value returned when the condition is true", }, { label: "elseValue", documentation: "The value returned when the condition is false", }, { label: "unknownValue", documentation: "The value returned when a logical condition is unknown", }, ])); registerFunc(withDocumentation(new ROUND(), "round a numeric value to the requested number of decimal places", [ { label: "input", documentation: "The numeric value to round" }, { label: "decimals", documentation: "The number of decimal places to keep", }, ])); registerFunc(documentAttributeAccessor("name", "read the name attribute from an IFC object")); registerFunc(documentAttributeAccessor("guid", "read the global identifier from an IFC object")); registerFunc(documentAttributeAccessor("ifcClass", "read the IFC class name from an IFC object")); registerFunc(documentAttributeAccessor("description", "read the description attribute from an IFC object")); registerFunc(withDocumentation(new AttributeAccessorFunction("value", Types.or(Type.STRING, Type.NUMERIC, Type.BOOLEAN, Type.ARRAY)), "read the value of an IFC property object", [{ label: "object", documentation: "The IFC property object to read from" }])); registerFunc(withDocumentation(new PROPERTYSET(), "resolve a property set on an IFC object", [ { label: "object", documentation: "The IFC object to inspect" }, { label: "name", documentation: "The name of the property set to resolve", }, ])); registerFunc(withDocumentation(new PROPERTY(), "resolve a property on an IFC element, type object, or property set", [ { label: "object", documentation: "The IFC object to inspect" }, { label: "name", documentation: "The name of the property to resolve" }, ])); registerFunc(withDocumentation(new TYPE(), "resolve the IFC type object of an element", [ { label: "object", documentation: "The IFC element to inspect" }, ])); registerFunc(withDocumentation(new NOT(), "negate a boolean or logical value", [ { label: "value", documentation: "The value to negate" }, ])); registerFunc(withDocumentation(new TOSTRING(), "convert a value to its string representation", [{ label: "input", documentation: "The value to convert" }])); registerFunc(withDocumentation(new TONUMERIC(), "convert a value to a numeric representation", [{ label: "input", documentation: "The value to convert" }]), "TONUMBER"); registerFunc(withDocumentation(new TOBOOLEAN(), "convert a value to a boolean representation", [{ label: "input", documentation: "The value to convert" }])); registerFunc(withDocumentation(new TOLOGICAL(), "convert a value to a logical representation that can be true, false, or unknown", [{ label: "input", documentation: "The value to convert" }]), "NOTFOUNDASUNKNOWN"); registerFunc(withDocumentation(new TOIFCDATE(), "convert a value to an IFC date", [ { label: "input", documentation: "The value to convert" }, ])); registerFunc(withDocumentation(new TOIFCTIME(), "convert a value to an IFC time", [ { label: "input", documentation: "The value to convert" }, ])); registerFunc(withDocumentation(new TOIFCDATETIME(), "convert a value to an IFC date-time", [{ label: "input", documentation: "The value to convert" }])); registerFunc(withDocumentation(new TOIFCDURATION(), "convert a value to an IFC duration", [ { label: "input", documentation: "The value to convert" }, ])); registerFunc(withDocumentation(new TOIFCTIMESTAMP(), "convert a value to an IFC timestamp", [{ label: "input", documentation: "The value to convert" }])); registerFunc(withDocumentation(new ADDDURATION(), "add a duration to an IFC date-time or timestamp", [ { label: "pointInTime", documentation: "The IFC date-time or timestamp to shift", }, { label: "duration", documentation: "The duration to add" }, ])); registerFunc(withDocumentation(new TOLOWERCASE(), "convert a string to lowercase", [ { label: "input", documentation: "The string to transform" }, ])); registerFunc(withDocumentation(new TOUPPERCASE(), "convert a string to uppercase", [ { label: "input", documentation: "The string to transform" }, ])); registerFunc(withDocumentation(new SUBSTRING(), "extract a substring from a string", [ { label: "input", documentation: "The source string" }, { label: "from", documentation: "The inclusive start index" }, { label: "to", documentation: "The exclusive end index" }, ])); registerFunc(withDocumentation(new SPLIT(), "split a string into an array of substrings", [ { label: "input", documentation: "The source string" }, { label: "separator", documentation: "The separator string to split on" }, { label: "limit", documentation: "The maximum number of resulting items" }, ])); registerFunc(withDocumentation(new EXISTS(), "check whether an IFC reference can be resolved", [{ label: "object", documentation: "The IFC reference to test" }])); registerFunc(withDocumentation(new FuncBooleanBinary("AND", "and"), "return true only when both inputs are true", [ { label: "left", documentation: "The left logical operand" }, { label: "right", documentation: "The right logical operand" }, ])); registerFunc(withDocumentation(new FuncBooleanBinary("OR", "or"), "return true when at least one input is true", [ { label: "left", documentation: "The left logical operand" }, { label: "right", documentation: "The right logical operand" }, ])); registerFunc(withDocumentation(new FuncBooleanBinary("XOR", "xor"), "return true when exactly one input is true", [ { label: "left", documentation: "The left logical operand" }, { label: "right", documentation: "The right logical operand" }, ])); registerFunc(withDocumentation(new FuncBooleanBinary("IMPLIES", "implies"), "evaluate logical implication from left to right", [ { label: "left", documentation: "The premise" }, { label: "right", documentation: "The consequence" }, ])); registerFunc(withDocumentation(new EQUALS(), "compare two values for equality", [ { label: "left", documentation: "The left value to compare" }, { label: "right", documentation: "The right value to compare" }, ])); registerFunc(withDocumentation(new CompareMagnitudes("GREATERTHAN", (cmp) => cmp > 0), "return true when the left value is greater than the right value", [ { label: "left", documentation: "The left value to compare" }, { label: "right", documentation: "The right value to compare" }, ])); registerFunc(withDocumentation(new CompareMagnitudes("GREATERTHANOREQUAL", (cmp) => cmp >= 0), "return true when the left value is greater than or equal to the right value", [ { label: "left", documentation: "The left value to compare" }, { label: "right", documentation: "The right value to compare" }, ])); registerFunc(withDocumentation(new CompareMagnitudes("LESSTHAN", (cmp) => cmp < 0), "return true when the left value is less than the right value", [ { label: "left", documentation: "The left value to compare" }, { label: "right", documentation: "The right value to compare" }, ])); registerFunc(withDocumentation(new CompareMagnitudes("LESSTHANOREQUAL", (cmp) => cmp <= 0), "return true when the left value is less than or equal to the right value", [ { label: "left", documentation: "The left value to compare" }, { label: "right", documentation: "The right value to compare" }, ])); registerFunc(withDocumentation(new MatchesPattern("CONTAINS", true, false), "check whether the input contains a simple wildcard pattern", [ { label: "input", documentation: "The source string" }, { label: "pattern", documentation: "The wildcard pattern to search for" }, ])); registerFunc(withDocumentation(new MatchesPattern("MATCHES", true, true), "check whether the input fully matches a simple wildcard pattern", [ { label: "input", documentation: "The source string" }, { label: "pattern", documentation: "The wildcard pattern to match" }, ])); registerFunc(withDocumentation(new MatchesPattern("REGEXCONTAINS", false, false), "check whether the input contains a regular-expression match", [ { label: "input", documentation: "The source string" }, { label: "pattern", documentation: "The regular expression to search for", }, ])); registerFunc(withDocumentation(new MatchesPattern("REGEXMATCHES", false, true), "check whether the input fully matches a regular expression", [ { label: "input", documentation: "The source string" }, { label: "pattern", documentation: "The regular expression to match" }, ])); registerFunc(new ReplacePattern("REPLACE", true)); registerFunc(new ReplacePattern("REGEXREPLACE", false)); //# sourceMappingURL=IfcExpressionFunctions.js.map