UNPKG

@atomic-ehr/fhirpath

Version:

A TypeScript implementation of FHIRPath

1,502 lines (1,486 loc) 309 kB
import { ucum } from '@atomic-ehr/ucum'; import { CanonicalManager } from '@atomic-ehr/fhir-canonical-manager'; import { translate } from '@atomic-ehr/fhirschema'; var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/lexer.ts var TokenType = /* @__PURE__ */ ((TokenType2) => { TokenType2[TokenType2["EOF"] = 0] = "EOF"; TokenType2[TokenType2["IDENTIFIER"] = 1] = "IDENTIFIER"; TokenType2[TokenType2["NUMBER"] = 2] = "NUMBER"; TokenType2[TokenType2["STRING"] = 3] = "STRING"; TokenType2[TokenType2["DATETIME"] = 4] = "DATETIME"; TokenType2[TokenType2["TIME"] = 5] = "TIME"; TokenType2[TokenType2["QUANTITY"] = 6] = "QUANTITY"; TokenType2[TokenType2["OPERATOR"] = 10] = "OPERATOR"; TokenType2[TokenType2["DOT"] = 50] = "DOT"; TokenType2[TokenType2["COMMA"] = 51] = "COMMA"; TokenType2[TokenType2["LPAREN"] = 52] = "LPAREN"; TokenType2[TokenType2["RPAREN"] = 53] = "RPAREN"; TokenType2[TokenType2["LBRACKET"] = 54] = "LBRACKET"; TokenType2[TokenType2["RBRACKET"] = 55] = "RBRACKET"; TokenType2[TokenType2["LBRACE"] = 56] = "LBRACE"; TokenType2[TokenType2["RBRACE"] = 57] = "RBRACE"; TokenType2[TokenType2["SPECIAL_IDENTIFIER"] = 60] = "SPECIAL_IDENTIFIER"; TokenType2[TokenType2["ENVIRONMENT_VARIABLE"] = 70] = "ENVIRONMENT_VARIABLE"; TokenType2[TokenType2["CURSOR"] = 71] = "CURSOR"; TokenType2[TokenType2["WHITESPACE"] = 80] = "WHITESPACE"; TokenType2[TokenType2["LINE_COMMENT"] = 81] = "LINE_COMMENT"; TokenType2[TokenType2["BLOCK_COMMENT"] = 82] = "BLOCK_COMMENT"; return TokenType2; })(TokenType || {}); var Lexer = class { // Start positions of each line constructor(input, options = {}) { this.position = 0; this.line = 1; // Legacy: 1-based for backward compatibility this.column = 1; // Legacy: 1-based for backward compatibility this.lspLine = 0; // LSP: zero-based this.lspCharacter = 0; this.lineOffsets = [0]; this.input = input; this.options = { trackPosition: options.trackPosition ?? true, preserveTrivia: options.preserveTrivia ?? false }; if (this.options.trackPosition) { this.buildLineOffsets(); } } /** * Build line offset map for efficient position conversions */ buildLineOffsets() { this.lineOffsets = [0]; for (let i = 0; i < this.input.length; i++) { const char = this.input[i]; if (char === "\n") { this.lineOffsets.push(i + 1); } else if (char === "\r") { if (i + 1 < this.input.length && this.input[i + 1] === "\n") { i++; } this.lineOffsets.push(i + 1); } } } tokenize() { const tokens = []; while (this.position < this.input.length) { const token = this.nextToken(); if (token) { tokens.push(token); } } tokens.push(this.createToken(0 /* EOF */, "", this.position, this.position)); return tokens; } nextToken() { if (this.options.preserveTrivia && this.position < this.input.length) { const wsStart = this.position; const wsStartLine = this.line; const wsStartColumn = this.column; if (this.isWhitespace(this.current())) { this.skipWhitespace(); const wsToken = this.createToken( 80 /* WHITESPACE */, this.input.substring(wsStart, this.position), wsStart, this.position, wsStartLine, wsStartColumn ); wsToken.channel = 1 /* HIDDEN */; return wsToken; } } else { this.skipWhitespace(); } if (this.position >= this.input.length) { return null; } const start = this.position; const startLine = this.line; const startColumn = this.column; const char = this.input[this.position]; const charCode = this.input.charCodeAt(this.position); switch (char) { case "+": this.advance(); return this.createToken(10 /* OPERATOR */, "+", start, this.position, startLine, startColumn); case "-": this.advance(); return this.createToken(10 /* OPERATOR */, "-", start, this.position, startLine, startColumn); case "*": this.advance(); return this.createToken(10 /* OPERATOR */, "*", start, this.position, startLine, startColumn); case "/": if (this.peek() === "/") { if (this.options.preserveTrivia) { const commentStart = this.position; this.skipLineComment(); const token = this.createToken( 81 /* LINE_COMMENT */, this.input.substring(commentStart, this.position), commentStart, this.position, startLine, startColumn ); token.channel = 1 /* HIDDEN */; return token; } else { this.skipLineComment(); return null; } } if (this.peek() === "*") { if (this.options.preserveTrivia) { const commentStart = this.position; this.skipBlockComment(); const token = this.createToken( 82 /* BLOCK_COMMENT */, this.input.substring(commentStart, this.position), commentStart, this.position, startLine, startColumn ); token.channel = 1 /* HIDDEN */; return token; } else { this.skipBlockComment(); return null; } } this.advance(); return this.createToken(10 /* OPERATOR */, "/", start, this.position, startLine, startColumn); case "<": this.advance(); if (this.current() === "=") { this.advance(); return this.createToken(10 /* OPERATOR */, "<=", start, this.position, startLine, startColumn); } return this.createToken(10 /* OPERATOR */, "<", start, this.position, startLine, startColumn); case ">": this.advance(); if (this.current() === "=") { this.advance(); return this.createToken(10 /* OPERATOR */, ">=", start, this.position, startLine, startColumn); } return this.createToken(10 /* OPERATOR */, ">", start, this.position, startLine, startColumn); case "=": this.advance(); return this.createToken(10 /* OPERATOR */, "=", start, this.position, startLine, startColumn); case "!": this.advance(); if (this.current() === "=") { this.advance(); return this.createToken(10 /* OPERATOR */, "!=", start, this.position, startLine, startColumn); } else if (this.current() === "~") { this.advance(); return this.createToken(10 /* OPERATOR */, "!~", start, this.position, startLine, startColumn); } throw this.error(`Unexpected character '!' at position ${start}`); case "~": this.advance(); return this.createToken(10 /* OPERATOR */, "~", start, this.position, startLine, startColumn); case "|": this.advance(); return this.createToken(10 /* OPERATOR */, "|", start, this.position, startLine, startColumn); case "&": this.advance(); return this.createToken(10 /* OPERATOR */, "&", start, this.position, startLine, startColumn); case ".": this.advance(); return this.createToken(50 /* DOT */, ".", start, this.position, startLine, startColumn); case ",": this.advance(); return this.createToken(51 /* COMMA */, ",", start, this.position, startLine, startColumn); case "(": this.advance(); return this.createToken(52 /* LPAREN */, "(", start, this.position, startLine, startColumn); case ")": this.advance(); return this.createToken(53 /* RPAREN */, ")", start, this.position, startLine, startColumn); case "[": this.advance(); return this.createToken(54 /* LBRACKET */, "[", start, this.position, startLine, startColumn); case "]": this.advance(); return this.createToken(55 /* RBRACKET */, "]", start, this.position, startLine, startColumn); case "{": this.advance(); return this.createToken(56 /* LBRACE */, "{", start, this.position, startLine, startColumn); case "}": this.advance(); return this.createToken(57 /* RBRACE */, "}", start, this.position, startLine, startColumn); case "%": return this.readEnvironmentVariable(); case "'": return this.readString("'"); case '"': return this.readString('"'); case "`": return this.readDelimitedIdentifier(); case "@": return this.readDateTimeOrTime(); case "$": return this.readSpecialIdentifier(); } if (charCode >= 48 && charCode <= 57) { return this.readNumber(); } if (charCode >= 65 && charCode <= 90 || // A-Z charCode >= 97 && charCode <= 122 || // a-z charCode === 95) { return this.readIdentifier(); } throw this.error(`Unexpected character '${char}' at position ${this.position}`); } readIdentifier() { const start = this.position; const startLine = this.line; const startColumn = this.column; this.advance(); while (this.position < this.input.length) { const charCode = this.input.charCodeAt(this.position); if (charCode >= 65 && charCode <= 90 || // A-Z charCode >= 97 && charCode <= 122 || // a-z charCode >= 48 && charCode <= 57 || // 0-9 charCode === 95) { this.advance(); } else { break; } } const value = this.input.substring(start, this.position); return this.createToken(1 /* IDENTIFIER */, value, start, this.position, startLine, startColumn); } readDelimitedIdentifier() { const start = this.position; const startLine = this.line; const startColumn = this.column; this.advance(); while (this.position < this.input.length) { const char = this.current(); if (char === "`") { this.advance(); const value = this.input.substring(start, this.position); return this.createToken(1 /* IDENTIFIER */, value, start, this.position, startLine, startColumn); } if (char === "\\") { this.advance(); if (this.position >= this.input.length) { throw this.error("Unterminated delimited identifier"); } } this.advance(); } throw this.error("Unterminated delimited identifier"); } readSpecialIdentifier() { const start = this.position; const startLine = this.line; const startColumn = this.column; this.advance(); while (this.position < this.input.length) { const charCode = this.input.charCodeAt(this.position); if (charCode >= 65 && charCode <= 90 || // A-Z charCode >= 97 && charCode <= 122 || // a-z charCode >= 48 && charCode <= 57 || // 0-9 charCode === 95) { this.advance(); } else { break; } } const value = this.input.substring(start, this.position); return this.createToken(60 /* SPECIAL_IDENTIFIER */, value, start, this.position, startLine, startColumn); } readEnvironmentVariable() { const start = this.position; const startLine = this.line; const startColumn = this.column; this.advance(); const char = this.current(); if (char === "`") { this.advance(); while (this.position < this.input.length) { const ch = this.current(); if (ch === "`") { this.advance(); const value = this.input.substring(start, this.position); return this.createToken(70 /* ENVIRONMENT_VARIABLE */, value, start, this.position, startLine, startColumn); } if (ch === "\\") { this.advance(); if (this.position >= this.input.length) { throw this.error("Unterminated environment variable"); } } this.advance(); } throw this.error("Unterminated environment variable"); } else if (char === "'") { this.advance(); while (this.position < this.input.length) { const ch = this.current(); if (ch === "'") { this.advance(); const value = this.input.substring(start, this.position); return this.createToken(70 /* ENVIRONMENT_VARIABLE */, value, start, this.position, startLine, startColumn); } if (ch === "\\") { this.advance(); if (this.position >= this.input.length) { throw this.error("Unterminated environment variable"); } } this.advance(); } throw this.error("Unterminated environment variable"); } else { const charCode = this.input.charCodeAt(this.position); if (!(charCode >= 65 && charCode <= 90 || // A-Z charCode >= 97 && charCode <= 122 || // a-z charCode === 95)) { throw this.error("Invalid environment variable name"); } while (this.position < this.input.length) { const charCode2 = this.input.charCodeAt(this.position); if (charCode2 >= 65 && charCode2 <= 90 || // A-Z charCode2 >= 97 && charCode2 <= 122 || // a-z charCode2 >= 48 && charCode2 <= 57 || // 0-9 charCode2 === 95) { this.advance(); } else { break; } } const value = this.input.substring(start, this.position); return this.createToken(70 /* ENVIRONMENT_VARIABLE */, value, start, this.position, startLine, startColumn); } } readString(quote) { const start = this.position; const startLine = this.line; const startColumn = this.column; this.advance(); while (this.position < this.input.length) { const char = this.current(); if (char === quote) { this.advance(); const value = this.input.substring(start, this.position); return this.createToken(3 /* STRING */, value, start, this.position, startLine, startColumn); } if (char === "\\") { this.advance(); if (this.position >= this.input.length) { throw this.error("Unterminated string"); } this.advance(); } else { this.advance(); } } throw this.error("Unterminated string"); } readNumber() { const start = this.position; const startLine = this.line; const startColumn = this.column; while (this.position < this.input.length && this.isDigit(this.current())) { this.advance(); } if (this.current() === "." && this.position + 1 < this.input.length && this.input[this.position + 1] && this.isDigit(this.input[this.position + 1])) { this.advance(); while (this.position < this.input.length && this.isDigit(this.current())) { this.advance(); } } const value = this.input.substring(start, this.position); return this.createToken(2 /* NUMBER */, value, start, this.position, startLine, startColumn); } readDateTimeOrTime() { const start = this.position; const startLine = this.line; const startColumn = this.column; this.advance(); if (this.current() === "T") { return this.readTime(start, startLine, startColumn); } return this.readDateTime(start, startLine, startColumn); } readDateTime(start, startLine, startColumn) { for (let i = 0; i < 4; i++) { if (!this.isDigit(this.current())) { throw this.error("Invalid datetime format"); } this.advance(); } if (this.current() === "-") { this.advance(); for (let i = 0; i < 2; i++) { if (!this.isDigit(this.current())) { throw this.error("Invalid datetime format"); } this.advance(); } if (this.current() === "-") { this.advance(); for (let i = 0; i < 2; i++) { if (!this.isDigit(this.current())) { throw this.error("Invalid datetime format"); } this.advance(); } } } if (this.current() === "T") { this.advance(); this.readTimeFormat(); } this.readTimezone(); const value = this.input.substring(start, this.position); return this.createToken(4 /* DATETIME */, value, start, this.position, startLine, startColumn); } readTime(start, startLine, startColumn) { this.advance(); this.readTimeFormat(); const value = this.input.substring(start, this.position); return this.createToken(5 /* TIME */, value, start, this.position, startLine, startColumn); } readTimeFormat() { for (let i = 0; i < 2; i++) { if (!this.isDigit(this.current())) { return; } this.advance(); } if (this.current() === ":") { this.advance(); for (let i = 0; i < 2; i++) { if (!this.isDigit(this.current())) { throw this.error("Invalid time format"); } this.advance(); } if (this.current() === ":") { this.advance(); for (let i = 0; i < 2; i++) { if (!this.isDigit(this.current())) { throw this.error("Invalid time format"); } this.advance(); } if (this.current() === ".") { this.advance(); if (!this.isDigit(this.current())) { throw this.error("Invalid time format"); } while (this.isDigit(this.current())) { this.advance(); } } } } } readTimezone() { const char = this.current(); if (char === "Z") { this.advance(); } else if (char === "+" || char === "-") { this.advance(); for (let i = 0; i < 2; i++) { if (!this.isDigit(this.current())) { return; } this.advance(); } if (this.current() === ":") { this.advance(); for (let i = 0; i < 2; i++) { if (!this.isDigit(this.current())) { throw this.error("Invalid timezone format"); } this.advance(); } } } } skipWhitespace() { while (this.position < this.input.length) { const char = this.current(); if (char === " " || char === " " || char === "\r" || char === "\n") { this.advance(); } else { break; } } } skipLineComment() { this.advance(); this.advance(); while (this.position < this.input.length && this.current() !== "\n") { this.advance(); } } skipBlockComment() { this.advance(); this.advance(); while (this.position < this.input.length) { if (this.current() === "*" && this.peek() === "/") { this.advance(); this.advance(); break; } this.advance(); } } advance() { if (this.position < this.input.length) { const char = this.input[this.position]; if (this.options.trackPosition) { if (char === "\n") { this.line++; this.column = 1; this.lspLine++; this.lspCharacter = 0; } else if (char === "\r") { if (this.position + 1 < this.input.length && this.input[this.position + 1] === "\n") ; else { this.line++; this.column = 1; this.lspLine++; this.lspCharacter = 0; } } else { this.column++; this.lspCharacter++; } } this.position++; } } current() { return this.position < this.input.length ? this.input[this.position] : ""; } peek() { return this.position + 1 < this.input.length ? this.input[this.position + 1] : ""; } isDigit(char) { if (!char) return false; const code = char.charCodeAt(0); return code >= 48 && code <= 57; } isWhitespace(char) { if (!char) return false; return char === " " || char === " " || char === "\n" || char === "\r"; } /** * Convert absolute offset to LSP Position */ offsetToPosition(offset) { if (!this.options.trackPosition) { return { line: 0, character: 0, offset }; } let low = 0; let high = this.lineOffsets.length - 1; while (low < high) { const mid = Math.floor((low + high + 1) / 2); if (this.lineOffsets[mid] <= offset) { low = mid; } else { high = mid - 1; } } const line = low; const lineStart = this.lineOffsets[line]; const character = offset - lineStart; return { line, character, offset }; } createToken(type, value, start, end, line = this.line, column = this.column) { const token = { type, value, start, end, line: this.options.trackPosition ? line : 0, column: this.options.trackPosition ? column : 0 }; if (this.options.trackPosition) { const startPos = this.offsetToPosition(start); const endPos = this.offsetToPosition(end); token.range = { start: startPos, end: endPos }; } return token; } error(message) { return new Error(`Lexer error: ${message}`); } // Public methods for parser use /** * Get the text value for a token */ getTokenText(token) { return token.value; } /** * Check if a token is an identifier (including keyword operators) */ static isIdentifier(token) { return token.type === 1 /* IDENTIFIER */; } /** * Check if a token could be a keyword operator (parser decides) */ static couldBeKeywordOperator(token) { return token.type === 1 /* IDENTIFIER */; } }; // src/types.ts var DiagnosticSeverity = /* @__PURE__ */ ((DiagnosticSeverity2) => { DiagnosticSeverity2[DiagnosticSeverity2["Error"] = 1] = "Error"; DiagnosticSeverity2[DiagnosticSeverity2["Warning"] = 2] = "Warning"; DiagnosticSeverity2[DiagnosticSeverity2["Information"] = 3] = "Information"; DiagnosticSeverity2[DiagnosticSeverity2["Hint"] = 4] = "Hint"; return DiagnosticSeverity2; })(DiagnosticSeverity || {}); function isIdentifierNode(node) { return node.type === "Identifier" /* Identifier */; } function isFunctionNode(node) { return node.type === "Function" /* Function */; } // src/operations/index.ts var operations_exports = {}; __export(operations_exports, { absFunction: () => absFunction, aggregateFunction: () => aggregateFunction, allFalseFunction: () => allFalseFunction, allFunction: () => allFunction, allTrueFunction: () => allTrueFunction, andOperator: () => andOperator, anyFalseFunction: () => anyFalseFunction, anyTrueFunction: () => anyTrueFunction, asOperator: () => asOperator, ceilingFunction: () => ceilingFunction, childrenFunction: () => childrenFunction, combineFunction: () => combineFunction, combineOperator: () => combineOperator, containsFunction: () => containsFunction, containsOperator: () => containsOperator, countFunction: () => countFunction, defineVariableFunction: () => defineVariableFunction, descendantsFunction: () => descendantsFunction, distinctFunction: () => distinctFunction, divOperator: () => divOperator, divideOperator: () => divideOperator, dotOperator: () => dotOperator, emptyFunction: () => emptyFunction, endsWithFunction: () => endsWithFunction, equalOperator: () => equalOperator, equivalentOperator: () => equivalentOperator, excludeFunction: () => excludeFunction, existsFunction: () => existsFunction, firstFunction: () => firstFunction, floorFunction: () => floorFunction, greaterOperator: () => greaterOperator, greaterOrEqualOperator: () => greaterOrEqualOperator, iifFunction: () => iifFunction, impliesOperator: () => impliesOperator, inOperator: () => inOperator, indexOfFunction: () => indexOfFunction, intersectFunction: () => intersectFunction, isDistinctFunction: () => isDistinctFunction, isOperator: () => isOperator, joinFunction: () => joinFunction, lastFunction: () => lastFunction, lengthFunction: () => lengthFunction, lessOperator: () => lessOperator, lessOrEqualOperator: () => lessOrEqualOperator, lowerFunction: () => lowerFunction, minusOperator: () => minusOperator, modOperator: () => modOperator, multiplyOperator: () => multiplyOperator, notEqualOperator: () => notEqualOperator, notEquivalentOperator: () => notEquivalentOperator, notFunction: () => notFunction, ofTypeFunction: () => ofTypeFunction, orOperator: () => orOperator, plusOperator: () => plusOperator, powerFunction: () => powerFunction, replaceFunction: () => replaceFunction, roundFunction: () => roundFunction, selectFunction: () => selectFunction, singleFunction: () => singleFunction, skipFunction: () => skipFunction, splitFunction: () => splitFunction, sqrtFunction: () => sqrtFunction, startsWithFunction: () => startsWithFunction, subsetOfFunction: () => subsetOfFunction, substringFunction: () => substringFunction, supersetOfFunction: () => supersetOfFunction, tailFunction: () => tailFunction, takeFunction: () => takeFunction, toBooleanFunction: () => toBooleanFunction, toDecimalFunction: () => toDecimalFunction, toIntegerFunction: () => toIntegerFunction, toStringFunction: () => toStringFunction, traceFunction: () => traceFunction, trimFunction: () => trimFunction, truncateFunction: () => truncateFunction, unaryMinusOperator: () => unaryMinusOperator, unaryPlusOperator: () => unaryPlusOperator, unionFunction: () => unionFunction, unionOperator: () => unionOperator, upperFunction: () => upperFunction, whereFunction: () => whereFunction, xorOperator: () => xorOperator }); var CALENDAR_TO_UCUM = { "year": "a", // annum "years": "a", "month": "mo", // month "months": "mo", "week": "wk", // week "weeks": "wk", "day": "d", // day "days": "d", "hour": "h", // hour "hours": "h", "minute": "min", // minute "minutes": "min", "second": "s", // second "seconds": "s", "millisecond": "ms", // millisecond "milliseconds": "ms" }; function createQuantity(value, unit, isCalendarUnit = false) { const actualUnit = isCalendarUnit && CALENDAR_TO_UCUM[unit] ? CALENDAR_TO_UCUM[unit] : unit; return { value, unit: actualUnit }; } function getUcumQuantity(quantity) { if (!quantity._ucumQuantity) { try { quantity._ucumQuantity = ucum.quantity(quantity.value, quantity.unit); } catch (e) { return null; } } return quantity._ucumQuantity || null; } function addQuantities(left, right) { const leftUcum = getUcumQuantity(left); const rightUcum = getUcumQuantity(right); if (!leftUcum || !rightUcum) { return null; } try { const result = ucum.add(leftUcum, rightUcum); return { value: result.value, unit: result.unit }; } catch (e) { return null; } } function subtractQuantities(left, right) { const leftUcum = getUcumQuantity(left); const rightUcum = getUcumQuantity(right); if (!leftUcum || !rightUcum) { return null; } try { const result = ucum.subtract(leftUcum, rightUcum); return { value: result.value, unit: result.unit }; } catch (e) { return null; } } function multiplyQuantities(left, right) { const leftUcum = getUcumQuantity(left); const rightUcum = getUcumQuantity(right); if (!leftUcum || !rightUcum) { return null; } try { const result = ucum.multiply(leftUcum, rightUcum); return { value: result.value, unit: result.unit }; } catch (e) { return null; } } function divideQuantities(left, right) { const leftUcum = getUcumQuantity(left); const rightUcum = getUcumQuantity(right); if (!leftUcum || !rightUcum) { return null; } try { const result = ucum.divide(leftUcum, rightUcum); return { value: result.value, unit: result.unit }; } catch (e) { return null; } } function compareQuantities(left, right) { const leftUcum = getUcumQuantity(left); const rightUcum = getUcumQuantity(right); if (!leftUcum || !rightUcum) { return null; } try { const rightValue = ucum.convert(rightUcum.value, rightUcum.unit, leftUcum.unit); if (leftUcum.value < rightValue) { return -1; } else if (leftUcum.value > rightValue) { return 1; } else { return 0; } } catch (e) { return null; } } // src/boxing.ts var BOXED_SYMBOL = Symbol("FHIRPathBoxedValue"); function box(value, typeInfo, primitiveElement) { return { value, typeInfo, primitiveElement, [BOXED_SYMBOL]: true }; } function unbox(boxedValue) { return boxedValue.value; } function isBoxed(value) { return value !== null && value !== void 0 && typeof value === "object" && BOXED_SYMBOL in value && value[BOXED_SYMBOL] === true; } function ensureBoxed(value, typeInfo) { if (isBoxed(value)) { return value; } return box(value, typeInfo); } // src/operations/plus-operator.ts var evaluate = async (input, context, left, right) => { if (left.length === 0 || right.length === 0) { return { value: [], context }; } const boxedL = left[0]; const boxedR = right[0]; if (!boxedL || !boxedR) { return { value: [], context }; } const l = unbox(boxedL); const r = unbox(boxedR); if (l && typeof l === "object" && "unit" in l && r && typeof r === "object" && "unit" in r) { const result = addQuantities(l, r); return { value: result ? [box(result, { type: "Quantity", singleton: true })] : [], context }; } if (typeof l === "string" || typeof r === "string") { return { value: [box(String(l) + String(r), { type: "String", singleton: true })], context }; } if (typeof l === "number" && typeof r === "number") { const result = l + r; const typeInfo = Number.isInteger(result) ? { type: "Integer", singleton: true } : { type: "Decimal", singleton: true }; return { value: [box(result, typeInfo)], context }; } return { value: [box(String(l) + String(r), { type: "String", singleton: true })], context }; }; var plusOperator = { symbol: "+", name: "plus", category: ["arithmetic"], precedence: 90 /* ADDITIVE */, associativity: "left", description: "For Integer, Decimal, and Quantity, adds the operands. For strings, concatenates the right operand to the left operand. For Date/DateTime/Time, increments by time-valued quantity.", examples: ["2 + 3", '"Hello" + " " + "World"', "@2018-03-01 + 1 day", "3 'm' + 3 'cm'"], signatures: [ { name: "integer-plus", left: { type: "Integer", singleton: true }, right: { type: "Integer", singleton: true }, result: { type: "Integer", singleton: true } }, { name: "decimal-plus", left: { type: "Decimal", singleton: true }, right: { type: "Decimal", singleton: true }, result: { type: "Decimal", singleton: true } }, { name: "string-plus", left: { type: "String", singleton: true }, right: { type: "String", singleton: true }, result: { type: "String", singleton: true } }, { name: "quantity-plus", left: { type: "Quantity", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Quantity", singleton: true } }, { name: "date-plus", left: { type: "Date", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Date", singleton: true } }, { name: "datetime-plus", left: { type: "DateTime", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "DateTime", singleton: true } }, { name: "time-plus", left: { type: "Time", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Time", singleton: true } } ], evaluate }; // src/operations/unary-plus-operator.ts var evaluate2 = async (input, context, operand) => { return { value: operand, context }; }; var unaryPlusOperator = { symbol: "+", name: "unaryPlus", category: ["arithmetic"], precedence: 110 /* UNARY */, associativity: "right", description: "Unary plus operator", examples: ["+5"], signatures: [], evaluate: evaluate2 }; // src/operations/minus-operator.ts var evaluate3 = async (input, context, left, right) => { if (left.length === 0 || right.length === 0) { return { value: [], context }; } const boxedl = left[0]; if (!boxedl) return { value: [], context }; const l = unbox(boxedl); const boxedr = right[0]; if (!boxedr) return { value: [], context }; const r = unbox(boxedr); if (l && typeof l === "object" && "unit" in l && r && typeof r === "object" && "unit" in r) { const result = subtractQuantities(l, r); return { value: result ? [box(result, { type: "Quantity", singleton: true })] : [], context }; } if (typeof l === "number" && typeof r === "number") { return { value: [box(l - r, { type: "Any", singleton: true })], context }; } return { value: [], context }; }; var minusOperator = { symbol: "-", name: "minus", category: ["arithmetic"], precedence: 90 /* ADDITIVE */, associativity: "left", description: "Subtracts the right operand from the left operand (supported for Integer, Decimal, and Quantity). For Date/DateTime/Time, decrements by time-valued quantity.", examples: ["5 - 3", "10.5 - 2.5", "3 'm' - 3 'cm'", "@2019-03-01 - 24 months"], signatures: [ { name: "integer-minus", left: { type: "Integer", singleton: true }, right: { type: "Integer", singleton: true }, result: { type: "Integer", singleton: true } }, { name: "decimal-minus", left: { type: "Decimal", singleton: true }, right: { type: "Decimal", singleton: true }, result: { type: "Decimal", singleton: true } }, { name: "quantity-minus", left: { type: "Quantity", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Quantity", singleton: true } }, { name: "date-minus", left: { type: "Date", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Date", singleton: true } }, { name: "datetime-minus", left: { type: "DateTime", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "DateTime", singleton: true } }, { name: "time-minus", left: { type: "Time", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Time", singleton: true } } ], evaluate: evaluate3 }; // src/operations/unary-minus-operator.ts var evaluate4 = async (input, context, operand) => { const results = []; for (const boxedValue of operand) { const v = unbox(boxedValue); if (v && typeof v === "object" && "unit" in v) { const q = v; results.push(box({ value: -q.value, unit: q.unit }, { type: "Quantity", singleton: true })); } else if (typeof v === "number") { const result = -v; const typeInfo = Number.isInteger(result) ? { type: "Integer", singleton: true } : { type: "Decimal", singleton: true }; results.push(box(result, typeInfo)); } } return { value: results, context }; }; var unaryMinusOperator = { symbol: "-", name: "unaryMinus", category: ["arithmetic"], precedence: 110 /* UNARY */, associativity: "right", description: "Unary minus operator", examples: ["-5"], signatures: [], evaluate: evaluate4 }; // src/operations/multiply-operator.ts var evaluate5 = async (input, context, left, right) => { if (left.length === 0 || right.length === 0) { return { value: [], context }; } const boxedl = left[0]; if (!boxedl) return { value: [], context }; const l = unbox(boxedl); const boxedr = right[0]; if (!boxedr) return { value: [], context }; const r = unbox(boxedr); if (l && typeof l === "object" && "unit" in l && r && typeof r === "object" && "unit" in r) { const result = multiplyQuantities(l, r); return { value: result ? [box(result, { type: "Quantity", singleton: true })] : [], context }; } if (l && typeof l === "object" && "unit" in l && typeof r === "number") { const q = l; return { value: [box({ value: q.value * r, unit: q.unit }, { type: "Quantity", singleton: true })], context }; } if (typeof l === "number" && r && typeof r === "object" && "unit" in r) { const q = r; return { value: [box({ value: l * q.value, unit: q.unit }, { type: "Quantity", singleton: true })], context }; } if (typeof l === "number" && typeof r === "number") { return { value: [box(l * r, { type: "Any", singleton: true })], context }; } return { value: [], context }; }; var multiplyOperator = { symbol: "*", name: "multiply", category: ["arithmetic"], precedence: 100 /* MULTIPLICATIVE */, associativity: "left", description: "Multiplies both arguments (supported for Integer, Decimal, and Quantity). For multiplication involving quantities, the resulting quantity will have the appropriate unit", examples: ["2 * 3", "5.5 * 2", "12 'cm' * 3 'cm'", "3 'cm' * 12 'cm2'"], signatures: [ { name: "integer-multiply", left: { type: "Integer", singleton: true }, right: { type: "Integer", singleton: true }, result: { type: "Integer", singleton: true } }, { name: "decimal-multiply", left: { type: "Decimal", singleton: true }, right: { type: "Decimal", singleton: true }, result: { type: "Decimal", singleton: true } }, { name: "quantity-multiply", left: { type: "Quantity", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Quantity", singleton: true } } ], evaluate: evaluate5 }; // src/operations/divide-operator.ts var evaluate6 = async (input, context, left, right) => { if (left.length === 0 || right.length === 0) { return { value: [], context }; } const boxedl = left[0]; if (!boxedl) return { value: [], context }; const l = unbox(boxedl); const boxedr = right[0]; if (!boxedr) return { value: [], context }; const r = unbox(boxedr); if (l && typeof l === "object" && "unit" in l && r && typeof r === "object" && "unit" in r) { const result = divideQuantities(l, r); return { value: result ? [box(result, { type: "Quantity", singleton: true })] : [], context }; } if (l && typeof l === "object" && "unit" in l && typeof r === "number") { if (r === 0) { return { value: [], context }; } const q = l; return { value: [box({ value: q.value / r, unit: q.unit }, { type: "Quantity", singleton: true })], context }; } if (typeof l === "number" && typeof r === "number") { if (r === 0) { return { value: [], context }; } return { value: [box(l / r, { type: "Any", singleton: true })], context }; } return { value: [], context }; }; var divideOperator = { symbol: "/", name: "divide", category: ["arithmetic"], precedence: 100 /* MULTIPLICATIVE */, associativity: "left", description: "Divides the left operand by the right operand (supported for Integer, Decimal, and Quantity). The result is always Decimal, even if inputs are both Integer. Division by zero returns empty.", examples: ["10 / 2", "7.5 / 1.5", "12 'cm2' / 3 'cm'", "12 / 0"], signatures: [ { name: "integer-divide", left: { type: "Integer", singleton: true }, right: { type: "Integer", singleton: true }, result: { type: "Decimal", singleton: true } }, { name: "decimal-divide", left: { type: "Decimal", singleton: true }, right: { type: "Decimal", singleton: true }, result: { type: "Decimal", singleton: true } }, { name: "quantity-divide", left: { type: "Quantity", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Quantity", singleton: true } } ], evaluate: evaluate6 }; // src/operations/div-operator.ts var evaluate7 = async (input, context, left, right) => { if (left.length === 0 || right.length === 0) { return { value: [], context }; } const boxedLeft = left[0]; const boxedRight = right[0]; if (!boxedLeft || !boxedRight) { return { value: [], context }; } const leftValue = unbox(boxedLeft); const rightValue = unbox(boxedRight); if (rightValue === 0) { return { value: [], context }; } const result = Math.floor(leftValue / rightValue); return { value: [box(result, { type: "Integer", singleton: true })], context }; }; var divOperator = { symbol: "div", name: "div", category: ["arithmetic"], precedence: 100 /* MULTIPLICATIVE */, associativity: "left", description: "Performs truncated division (integer division) of the left operand by the right operand, ignoring any remainder. Always returns an Integer, even if operands are Decimal. Division by zero returns empty.", examples: ["5 div 2", "5.5 div 0.7", "10 div 3", "5 div 0"], signatures: [ { name: "integer-div", left: { type: "Integer", singleton: true }, right: { type: "Integer", singleton: true }, result: { type: "Integer", singleton: true } }, { name: "decimal-div", left: { type: "Decimal", singleton: true }, right: { type: "Decimal", singleton: true }, result: { type: "Integer", singleton: true } } ], evaluate: evaluate7 }; // src/operations/mod-operator.ts var evaluate8 = async (input, context, left, right) => { if (left.length === 0 || right.length === 0) { return { value: [], context }; } const boxedLeft = left[0]; const boxedRight = right[0]; if (!boxedLeft || !boxedRight) { return { value: [], context }; } const leftValue = unbox(boxedLeft); const rightValue = unbox(boxedRight); if (rightValue === 0) { return { value: [], context }; } const result = leftValue % rightValue; const resultType = Number.isInteger(leftValue) && Number.isInteger(rightValue) ? "Integer" : "Decimal"; return { value: [box(result, { type: resultType, singleton: true })], context }; }; var modOperator = { symbol: "mod", name: "mod", category: ["arithmetic"], precedence: 100 /* MULTIPLICATIVE */, associativity: "left", description: "Computes the remainder of the truncated division of its arguments. Supported for Integer and Decimal types. Division by zero returns empty.", examples: ["5 mod 2", "5.5 mod 0.7", "5 mod 0"], signatures: [ { name: "integer-mod", left: { type: "Integer", singleton: true }, right: { type: "Integer", singleton: true }, result: { type: "Integer", singleton: true } }, { name: "decimal-mod", left: { type: "Decimal", singleton: true }, right: { type: "Decimal", singleton: true }, result: { type: "Decimal", singleton: true } } ], evaluate: evaluate8 }; // src/operations/less-operator.ts var evaluate9 = async (input, context, left, right) => { if (left.length === 0 || right.length === 0) { return { value: [], context }; } const boxedl = left[0]; if (!boxedl) return { value: [], context }; const l = unbox(boxedl); const boxedr = right[0]; if (!boxedr) return { value: [], context }; const r = unbox(boxedr); if (l && typeof l === "object" && "unit" in l && r && typeof r === "object" && "unit" in r) { const result = compareQuantities(l, r); return { value: result !== null ? [box(result < 0, { type: "Boolean", singleton: true })] : [], context }; } return { value: [box(l < r, { type: "Boolean", singleton: true })], context }; }; var lessOperator = { symbol: "<", name: "less", category: ["comparison"], precedence: 70 /* COMPARISON */, associativity: "left", description: "Returns true if the first operand is strictly less than the second. The operands must be of the same type, or convertible to the same type using implicit conversion.", examples: ["age < 18", "10 < 5", "@2018-03-01 < @2018-01-01", '"abc" < "ABC"'], signatures: [ { name: "string-less", left: { type: "String", singleton: true }, right: { type: "String", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "integer-less", left: { type: "Integer", singleton: true }, right: { type: "Integer", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "decimal-less", left: { type: "Decimal", singleton: true }, right: { type: "Decimal", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "quantity-less", left: { type: "Quantity", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "date-less", left: { type: "Date", singleton: true }, right: { type: "Date", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "datetime-less", left: { type: "DateTime", singleton: true }, right: { type: "DateTime", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "time-less", left: { type: "Time", singleton: true }, right: { type: "Time", singleton: true }, result: { type: "Boolean", singleton: true } } ], evaluate: evaluate9 }; // src/operations/greater-operator.ts var evaluate10 = async (input, context, left, right) => { if (left.length === 0 || right.length === 0) { return { value: [], context }; } const boxedl = left[0]; if (!boxedl) return { value: [], context }; const l = unbox(boxedl); const boxedr = right[0]; if (!boxedr) return { value: [], context }; const r = unbox(boxedr); if (l && typeof l === "object" && "unit" in l && r && typeof r === "object" && "unit" in r) { const result = compareQuantities(l, r); return { value: result !== null ? [box(result > 0, { type: "Boolean", singleton: true })] : [], context }; } return { value: [box(l > r, { type: "Boolean", singleton: true })], context }; }; var greaterOperator = { symbol: ">", name: "greater", category: ["comparison"], precedence: 70 /* COMPARISON */, associativity: "left", description: "Returns true if the first operand is strictly greater than the second. The operands must be of the same type, or convertible to the same type using implicit conversion", examples: ["age > 18", "10 > 5", '"abc" > "ABC"', "@2018-03-01 > @2018-01-01"], signatures: [ { name: "string-greater", left: { type: "String", singleton: true }, right: { type: "String", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "integer-greater", left: { type: "Integer", singleton: true }, right: { type: "Integer", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "decimal-greater", left: { type: "Decimal", singleton: true }, right: { type: "Decimal", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "quantity-greater", left: { type: "Quantity", singleton: true }, right: { type: "Quantity", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "date-greater", left: { type: "Date", singleton: true }, right: { type: "Date", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "datetime-greater", left: { type: "DateTime", singleton: true }, right: { type: "DateTime", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "time-greater", left: { type: "Time", singleton: true }, right: { type: "Time", singleton: true }, result: { type: "Boolean", singleton: true } } ], evaluate: evaluate10 }; // src/operations/less-or-equal-operator.ts var evaluate11 = async (input, context, left, right) => { if (left.length === 0 || right.length === 0) { return { value: [], context }; } const boxedl = left[0]; if (!boxedl) return { value: [], context }; const l = unbox(boxedl); const boxedr = right[0]; if (!boxedr) return { value: [], context }; const r = unbox(boxedr); if (l && typeof l === "object" && "unit" in l && r && typeof r === "object" && "unit" in r) { const result = compareQuantities(l, r); return { value: result !== null ? [box(result <= 0, { type: "Boolean", singleton: true })] : [], context }; } return { value: [box(l <= r, { type: "Boolean", singleton: true })], context }; }; var lessOrEqualOperator = { symbol: "<=", name: "lessOrEqual", category: ["comparison"], precedence: 70 /* COMPARISON */, associativity: "left", description: "Returns true if the first operand is less than or equal to the second. The operands must be of the same type, or convertible to the same type using implicit conversion. For partial precision dates/times, returns empty if precision differs.", examples: [ "10 <= 5", "'abc' <= 'ABC'", "4 <= 4.0", "@2018-03-01 <= @2018-01-01", "@T10:30:00 <= @T10:00:00" ], signatures: [ { name: "integer-lessOrEqual", left: { type: "Integer", singleton: true }, right: { type: "Integer", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "decimal-lessOrEqual", left: { type: "Decimal", singleton: true }, right: { type: "Decimal", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "string-lessOrEqual", left: { type: "String", singleton: true }, right: { type: "String", singleton: true }, result: { type: "Boolean", singleton: true } }, { name: "date-lessOrEqual", left: { type: "Date", singleton: true }, ri