UNPKG

@cortex-js/compute-engine

Version:

Symbolic computing and numeric evaluations for JavaScript and Node.js

1,829 lines (1,819 loc) 486 kB
/** Compute Engine 0.58.0 */ // src/math-json/utils.ts var MISSING = ["Error", "'missing'"]; function isNumberExpression(expr) { if (typeof expr === "number" || isNumberObject(expr)) return true; if (typeof expr === "string" && matchesNumber(expr)) return true; return false; } function isNumberObject(expr) { return expr !== null && typeof expr === "object" && "num" in expr; } function isSymbolObject(expr) { return expr !== null && typeof expr === "object" && "sym" in expr; } function isStringObject(expr) { return expr !== null && typeof expr === "object" && "str" in expr; } function isDictionaryObject(expr) { return expr !== null && typeof expr === "object" && "dict" in expr && typeof expr.dict === "object" && !Array.isArray(expr.dict) && expr.dict !== null; } function isFunctionObject(expr) { return expr !== null && typeof expr === "object" && "fn" in expr && Array.isArray(expr.fn) && expr.fn.length > 0 && typeof expr.fn[0] === "string"; } function stringValue(expr) { if (expr === null || expr === void 0) return null; if (typeof expr === "object" && "str" in expr) return expr.str; if (typeof expr !== "string") return null; if (expr.length >= 2 && expr.at(0) === "'" && expr.at(-1) === "'") return expr.substring(1, expr.length - 1); if (matchesNumber(expr) || matchesSymbol(expr)) return null; return expr; } function stripText(expr) { if (expr === null || expr === void 0 || stringValue(expr) !== null) return null; const h = operator(expr); if (!h) return expr; return [ h, ...operands(expr).map((x) => stripText(x)).filter((x) => x !== null) ]; } function operator(expr) { if (Array.isArray(expr)) return expr[0]; if (expr === null || expr === void 0) return ""; if (isFunctionObject(expr)) return expr.fn[0]; return ""; } function operands(expr) { if (Array.isArray(expr)) return expr.slice(1); if (expr !== void 0 && isFunctionObject(expr)) return expr.fn.slice(1); return []; } function operand(expr, n) { if (Array.isArray(expr)) return expr[n] ?? null; if (expr === null || !isFunctionObject(expr)) return null; return expr.fn[n] ?? null; } function nops(expr) { if (expr === null || expr === void 0) return 0; if (Array.isArray(expr)) return Math.max(0, expr.length - 1); if (isFunctionObject(expr)) return Math.max(0, expr.fn.length - 1); return 0; } function unhold(expr) { if (expr === null || expr === void 0) return null; if (operator(expr) === "Hold") return operand(expr, 1); return expr; } function symbol(expr) { if (typeof expr === "string" && matchesSymbol(expr)) { if (expr.length >= 2 && expr.at(0) === "`" && expr.at(-1) === "`") return expr.slice(1, -1); return expr; } if (expr === null || expr === void 0) return null; if (isSymbolObject(expr)) return expr.sym; return null; } function keyValuePair(expr) { const h = operator(expr); if (h === "KeyValuePair" || h === "Tuple" || h === "Pair") { const [k, v] = operands(expr); const key = stringValue(k); if (!key) return null; return [key, v ?? "Nothing"]; } return null; } function dictionaryFromExpression(expr) { if (expr === null) return null; if (isDictionaryObject(expr)) return expr; const kv = keyValuePair(expr); if (kv) return { [kv[0]]: kv[1] }; if (operator(expr) === "Dictionary") { const dict = {}; const ops = operands(expr); for (let i = 1; i < nops(expr); i++) { const kv2 = keyValuePair(ops[i]); if (kv2) { dict[kv2[0]] = expressionToDictionaryValue(kv2[1]) ?? "Nothing"; } } return { dict }; } return null; } function dictionaryFromEntries(dict) { const entries = Object.fromEntries( Object.entries(dict).map(([k, v]) => [ k, jsValueToExpression(v) ?? "Nothing" ]) ); return { dict: entries }; } function machineValueOfString(s) { s = s.toLowerCase().replace(/[nd]$/, "").replace(/[\u0009-\u000d\u0020\u00a0]/g, ""); if (s === "nan") return NaN; if (/^(infinity|\+infinity|oo|\+oo)$/i.test(s)) return Infinity; if (/^(-infinity|-oo)$/.test(s)) return -Infinity; if (/\([0-9]+\)/.test(s)) { const [_, body, repeat, trail] = s.match(/(.+)\(([0-9]+)\)(.*)$/) ?? []; s = body + repeat.repeat(Math.ceil(16 / repeat.length)) + (trail ?? ""); } return parseFloat(s); } function machineValue(expr) { if (typeof expr === "number") return expr; if (typeof expr === "string" && matchesNumber(expr)) return machineValueOfString(expr); if (expr !== void 0 && isNumberObject(expr)) return machineValue(expr.num); return null; } function rationalValue(expr) { if (expr === void 0 || expr === null) return null; if (symbol(expr) === "Half") return [1, 2]; const h = operator(expr); if (!h) return null; let numer = null; let denom = null; if (h === "Negate") { const r = rationalValue(operands(expr)[0]); if (r) return [-r[0], r[1]]; } if (h === "Rational" || h === "Divide") { const [n, d] = operands(expr); numer = machineValue(n) ?? NaN; denom = machineValue(d) ?? NaN; } if (h === "Power") { const [base, exp] = operands(expr); const exponent = machineValue(exp); if (exponent === 1) { numer = machineValue(base); denom = 1; } else if (exponent === -1) { numer = 1; denom = machineValue(base); } } if (h === "Multiply") { const [op1, op2] = operands(expr); if (operator(op2) === "Power") { const [op21, op22] = operands(op2); if (machineValue(op22) === -1) { numer = machineValue(op1); denom = machineValue(op21); } } } if (numer === null || denom === null) return null; if (Number.isInteger(numer) && Number.isInteger(denom)) return [numer, denom]; return null; } function mapArgs(expr, fn) { let args = null; if (Array.isArray(expr)) args = expr; if (isFunctionObject(expr)) args = expr.fn; if (args === null) return []; let i = 1; const result = []; while (i < args.length) { result.push(fn(args[i])); i += 1; } return result; } function foldAssociativeOperator(op, lhs, rhs) { const lhsName = operator(lhs); const rhsName = operator(rhs); if (lhsName === op && rhsName === op) return [op, ...operands(lhs), ...operands(rhs)]; if (lhsName === op) return [op, ...operands(lhs), rhs]; if (rhsName === op) return [op, lhs, ...operands(rhs)]; return [op, lhs, rhs]; } function getSequence(expr) { if (expr === null || expr === void 0) return null; let h = operator(expr); if (h === "Delimiter") { expr = operand(expr, 1); if (expr === null) return []; h = operator(expr); if (h !== "Sequence") return [expr]; } if (h !== "Sequence") return null; return operands(expr); } function isEmptySequence(expr) { if (expr === null || expr === void 0) return true; if (expr === "Nothing") return true; return operator(expr) === "Sequence" && nops(expr) === 0; } function missingIfEmpty(expr) { return isEmptySequence(expr) ? MISSING : expr; } function countFunctionLeaves(xs) { if (xs[0] === "Square") { return countFunctionLeaves(xs.slice(1)) + 2; } return xs.reduce((acc, x) => acc + countLeaves(x), 0); } function countLeaves(expr) { if (expr === null) return 0; if (typeof expr === "number" || typeof expr === "string") return 1; if (isNumberExpression(expr) || isSymbolObject(expr) || isStringObject(expr)) return 1; if (Array.isArray(expr)) return countFunctionLeaves(expr); if ("fn" in expr) return countFunctionLeaves(expr.fn); const dict = dictionaryFromExpression(expr); if (dict) { const keys = Object.keys(dict); return 1 + keys.length + keys.reduce((acc, x) => acc + countLeaves(dict[x]), 0); } return 0; } function matchesNumber(s) { return /^(nan|oo|\+oo|-oo|infinity|\+infinity|-infinity)$/i.test(s) || /^[+-]?(0|[1-9][0-9]*)(\.[0-9]+)?(\([0-9]+\))?([eE][+-]?[0-9]+)?$/.test(s); } function matchesSymbol(s) { return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(s) || s.length >= 2 && s[0] === "`" && s[s.length - 1] === "`"; } function matchesString(s) { if (s.length >= 2 && s[0] === "'" && s[s.length - 1] === "'") { return true; } return !matchesNumber(s) && !matchesSymbol(s); } function jsValueToExpression(v) { if (typeof v === "string") { return { str: v }; } else if (typeof v === "number") { return { num: v.toString() }; } else if (typeof v === "boolean") { return v ? "True" : "False"; } else if (Array.isArray(v)) { return ["List", ...v.map((x) => jsValueToExpression(x) ?? "Nothing")]; } else if (v === null) { return null; } else if (typeof v === "object") { const dict = {}; for (const key in v) { dict[key] = jsValueToExpression(v[key]) ?? "Nothing"; } return { dict }; } if (isFunctionObject(v) || isSymbolObject(v) || isNumberObject(v) || isStringObject(v) || isDictionaryObject(v)) { return v; } return null; } function expressionToDictionaryValue(expr) { if (expr === null || expr === void 0) return null; if (isStringObject(expr)) return expr.str; if (isNumberObject(expr)) return parseFloat(expr.num); if (isSymbolObject(expr)) return expr.sym; if (typeof expr === "string" || typeof expr === "number") return expr; if (Array.isArray(expr)) return { fn: expr }; return expr; } // src/compute-engine/latex-syntax/types.ts var COMPARISON_PRECEDENCE = 245; var ASSIGNMENT_PRECEDENCE = 260; var ARROW_PRECEDENCE = 270; var ADDITION_PRECEDENCE = 275; var MULTIPLICATION_PRECEDENCE = 390; var DIVISION_PRECEDENCE = 600; var INVISIBLE_OP_PRECEDENCE = 650; var EXPONENTIATION_PRECEDENCE = 700; var POSTFIX_PRECEDENCE = 810; function isExpressionEntry(entry) { return !("kind" in entry) || entry.kind === "expression"; } function isSymbolEntry(entry) { return "kind" in entry && entry.kind === "symbol"; } function isMatchfixEntry(entry) { return "kind" in entry && entry.kind === "matchfix"; } function isInfixEntry(entry) { return "kind" in entry && entry.kind === "infix"; } function isPrefixEntry(entry) { return "kind" in entry && entry.kind === "prefix"; } function isPostfixEntry(entry) { return "kind" in entry && entry.kind === "postfix"; } function isEnvironmentEntry(entry) { return "kind" in entry && entry.kind === "environment"; } // src/common/grapheme-splitter.ts function stringToCodepoints(string) { const result = []; for (let i = 0; i < string.length; i++) { let code = string.charCodeAt(i); if (code >= 55296 && code <= 56319) { const nextCode = string.charCodeAt(i + 1); if (nextCode >= 56320 && nextCode <= 57343) { const lead = code - 55296; const trail = nextCode - 56320; code = 2 ** 16 + lead * 2 ** 10 + trail; i++; } } result.push(code); } return result; } var ZWJ = 8205; var REGIONAL_INDICATOR = [127462, 127487]; function isEmojiCombinator(code) { if (code === ZWJ) return true; if (code === 65038 || code === 65039) return true; if (code >= 127995 && code <= 127995 + 5) return true; if (code >= 129456 && code <= 129456 + 4) return true; if (code >= 917536 && code <= 917536 + 96) return true; return false; } function isRegionalIndicator(code) { return code >= REGIONAL_INDICATOR[0] && code <= REGIONAL_INDICATOR[1]; } function splitGraphemes(string) { if (/^[\u0020-\u00FF]*$/.test(string)) return string; const result = []; const codePoints = stringToCodepoints(string); let index = 0; while (index < codePoints.length) { const code = codePoints[index++]; const next = codePoints[index]; if (next === ZWJ) { const baseIndex = index - 1; index += 2; while (codePoints[index] === ZWJ) { index += 2; } result.push( String.fromCodePoint( ...codePoints.slice(baseIndex, 2 * index - baseIndex + 1) ) ); } else if (isEmojiCombinator(next)) { const baseIndex = index - 1; while (isEmojiCombinator(codePoints[index])) { index += codePoints[index] === ZWJ ? 2 : 1; } result.push( String.fromCodePoint( ...codePoints.slice(baseIndex, 2 * index - baseIndex - 1) ) ); } else if (isRegionalIndicator(code)) { index += 1; result.push(String.fromCodePoint(...codePoints.slice(index - 2, 2))); } else { result.push(String.fromCodePoint(code)); } } return result; } // src/compute-engine/latex-syntax/tokenizer.ts var UNICODE_SUPERSCRIPT_MAP = { "\u2070": "0", // ⁰ "\xB9": "1", // ¹ "\xB2": "2", // ² "\xB3": "3", // ³ "\u2074": "4", // ⁴ "\u2075": "5", // ⁵ "\u2076": "6", // ⁶ "\u2077": "7", // ⁷ "\u2078": "8", // ⁸ "\u2079": "9", // ⁹ "\u207B": "-", // ⁻ "\u2071": "i", // ⁱ "\u207F": "n" // ⁿ }; var UNICODE_SUBSCRIPT_MAP = { "\u2080": "0", // ₀ "\u2081": "1", // ₁ "\u2082": "2", // ₂ "\u2083": "3", // ₃ "\u2084": "4", // ₄ "\u2085": "5", // ₅ "\u2086": "6", // ₆ "\u2087": "7", // ₇ "\u2088": "8", // ₈ "\u2089": "9", // ₉ "\u208B": "-" // ₋ }; var Tokenizer = class { s; pos; obeyspaces = false; constructor(s) { s = s.replace(/[\u200E\u200F\u2066-\u2069\u202A-\u202E]/g, ""); s = s.replace(/\u2212/g, "-"); s = s.replace(/[⁰¹²³⁴⁵⁶⁷⁸⁹⁻ⁱⁿ]+/g, (m) => { const digits = Array.from(m).map((c) => UNICODE_SUPERSCRIPT_MAP[c]).join(""); return `^{${digits}}`; }); s = s.replace(/[₀₁₂₃₄₅₆₇₈₉₋]+/g, (m) => { const digits = Array.from(m).map((c) => UNICODE_SUBSCRIPT_MAP[c]).join(""); return `_{${digits}}`; }); this.s = splitGraphemes(s); this.pos = 0; } /** * @return True if we reached the end of the stream */ end() { return this.pos >= this.s.length; } /** * Return the next char and advance */ get() { return this.pos < this.s.length ? this.s[this.pos++] : ""; } /** * Return the next char, but do not advance */ peek() { return this.s[this.pos]; } /** * Return the next substring matching regEx and advance. */ match(regEx) { let execResult; if (typeof this.s === "string") { execResult = regEx.exec(this.s.slice(this.pos)); } else { execResult = regEx.exec(this.s.slice(this.pos).join("")); } if (execResult?.[0]) { this.pos += execResult[0].length; return execResult[0]; } return null; } /** * Return the next token, or null. */ next() { if (this.end()) return null; if (!this.obeyspaces && this.match(/^[ \f\n\r\t\v\xA0\u2028\u2029]+/)) { return "<space>"; } else if (this.obeyspaces && this.match(/^[ \f\n\r\t\v\xA0\u2028\u2029]/)) { return "<space>"; } const next = this.get(); if (next === "\\") { if (!this.end()) { let command = this.match(/^[a-zA-Z]+/); if (command) { this.match(/^[ \f\n\r\t\v\xA0\u2028\u2029]*/); } else { command = this.get(); if (command === " ") { return "<space>"; } } return "\\" + command; } } else if (next === "{") { return "<{>"; } else if (next === "}") { return "<}>"; } else if (next === "^") { if (this.peek() === "^") { this.get(); const hex = this.match( /^(\^(\^(\^(\^[0-9a-f])?[0-9a-f])?[0-9a-f])?[0-9a-f])?[0-9a-f][0-9a-f]/ ); if (hex) { return String.fromCodePoint( parseInt(hex.slice(hex.lastIndexOf("^") + 1), 16) ); } } return next; } else if (next === "#") { if (!this.end()) { let isParam = false; if (/[0-9?]/.test(this.peek())) { isParam = true; if (this.pos + 1 < this.s.length) { const after = this.s[this.pos + 1]; isParam = /[^0-9A-Za-z]/.test(after); } } if (isParam) { return "#" + this.get(); } return "#"; } } else if (next === "$") { if (this.peek() === "$") { this.get(); return "<$$>"; } return "<$>"; } return next; } }; function expand(lex, args) { let token = lex.next(); if (!token) return []; let result = []; if (token === "\\relax") { } else if (token === "\\noexpand") { token = lex.next(); if (token) { result.push(token); } } else if (token === "\\obeyspaces") { lex.obeyspaces = true; } else if (token === "\\space" || token === "~") { result.push("<space>"); } else if (token === "\\bgroup") { result.push("<{>"); } else if (token === "\\egroup") { result.push("<}>"); } else if (token === "\\string") { token = lex.next(); if (token) { if (token[0] === "\\") { Array.from(token).forEach( (x) => result.push(x === "\\" ? "\\backslash" : x) ); } else if (token === "<{>") { result.push("\\{"); } else if (token === "<space>") { result.push("~"); } else if (token === "<}>") { result.push("\\}"); } } } else if (token === "\\csname") { while (lex.peek() === "<space>") { lex.next(); } let command = ""; let done = false; let tokens = []; do { if (tokens.length === 0) { if (/^#[0-9?]$/.test(lex.peek())) { const param = lex.get().slice(1); tokens = tokenize( args?.[param] ?? args?.["?"] ?? "\\placeholder{}", args ); token = tokens[0]; } else { token = lex.next(); tokens = token ? [token] : []; } } done = tokens.length === 0; if (!done && token === "\\endcsname") { done = true; tokens.shift(); } if (!done) { done = token === "<$>" || token === "<$$>" || token === "<{>" || token === "<}>" || !!token && token.length > 1 && token[0] === "\\"; } if (!done) { command += tokens.shift(); } } while (!done); if (command) { result.push("\\" + command); } result = result.concat(tokens); } else if (token === "\\endcsname") { } else if (token.length > 1 && token[0] === "#") { const param = token.slice(1); result = result.concat( tokenize(args?.[param] ?? args?.["?"] ?? "\\placeholder{}", args) ); } else { result.push(token); } return result; } function tokenize(s, args = []) { const lines = s.toString().split(/\r?\n/); let stream = ""; let sep = ""; for (const line of lines) { stream += sep; sep = " "; const m = line.match(/((?:\\%)|[^%])*/); if (m !== null) stream += m[0]; } const tokenizer = new Tokenizer(stream); const result = []; do result.push(...expand(tokenizer, args)); while (!tokenizer.end()); return result; } function countTokens(s) { return tokenize(s).length; } function joinLatex(segments) { let sep = ""; let result = ""; for (const segment of segments) { if (segment === void 0 || segment === null) continue; if (typeof segment === "string") { if (/[a-zA-Z]/.test(segment[0])) result += sep; if (/\\[a-zA-Z]+\*?$/.test(segment)) sep = " "; else sep = ""; } result += segment.toString(); } return result; } function supsub(c, body, x) { if (body.includes(c)) body = `{${body}}`; if (/^[0-9]$/.test(x)) return `${body}${c}${x}`; return `${body}${c}{${x}}`; } function tokensToString(tokens) { let flat = []; if (Array.isArray(tokens)) { for (const item of tokens) { if (Array.isArray(item)) { flat = [...flat, ...item]; } else { flat.push(item); } } } else { flat = [tokens]; } const result = joinLatex( flat.map((token) => { return { "<space>": " ", "<$$>": "$$", "<$>": "$", "<{>": "{", "<}>": "}" }[token] ?? token; }) ); return result; } // src/compute-engine/latex-syntax/dictionary/definitions-relational-operators.ts var DEFINITIONS_INEQUALITIES = [ { latexTrigger: ["\\not", "<"], kind: "infix", associativity: "any", precedence: 246, parse: "NotLess" }, { name: "NotLess", latexTrigger: ["\\nless"], kind: "infix", associativity: "any", precedence: 246 }, { latexTrigger: ["<"], kind: "infix", associativity: "any", precedence: 245, parse: "Less" }, { name: "Less", latexTrigger: ["\\lt"], kind: "infix", associativity: "any", precedence: 245 }, { latexTrigger: ["<", "="], kind: "infix", associativity: "any", precedence: 241, parse: "LessEqual" }, { name: "LessEqual", latexTrigger: ["\\le"], kind: "infix", associativity: "any", precedence: 241 }, { latexTrigger: ["\\leq"], kind: "infix", associativity: "any", precedence: 241, parse: "LessEqual" }, { latexTrigger: ["\\leqslant"], kind: "infix", associativity: "any", precedence: COMPARISON_PRECEDENCE + 5, // Note different precedence than `<=` as per MathML parse: "LessEqual" }, { name: "LessNotEqual", latexTrigger: ["\\lneqq"], kind: "infix", associativity: "any", precedence: COMPARISON_PRECEDENCE }, { name: "NotLessNotEqual", latexTrigger: ["\\nleqq"], kind: "infix", associativity: "any", precedence: COMPARISON_PRECEDENCE }, { name: "LessOverEqual", latexTrigger: ["\\leqq"], kind: "infix", associativity: "any", precedence: COMPARISON_PRECEDENCE + 5 }, { name: "GreaterOverEqual", latexTrigger: ["\\geqq"], kind: "infix", associativity: "any", precedence: COMPARISON_PRECEDENCE + 5, parse: "GreaterEqual" }, { name: "Equal", latexTrigger: ["="], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { latexTrigger: ["*", "="], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE, parse: "StarEqual" }, { name: "StarEqual", latexTrigger: ["\\star", "="], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "PlusEqual", latexTrigger: ["+", "="], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "MinusEqual", latexTrigger: ["-", "="], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "SlashEqual", latexTrigger: ["/", "="], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "EqualEqual", latexTrigger: ["=", "="], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "EqualEqualEqual", latexTrigger: ["=", "=", "="], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE + 5 }, { name: "TildeFullEqual", // MathML: approximately equal to latexTrigger: ["\\cong"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "NotTildeFullEqual", // MathML: approximately but not actually equal to latexTrigger: ["\\ncong"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "Approx", // Note: Mathematica TildeTilde latexTrigger: ["\\approx"], kind: "infix", associativity: "right", precedence: 247 }, { name: "NotApprox", // Note: Mathematica TildeTilde latexTrigger: ["\\not", "\\approx"], kind: "infix", associativity: "right", precedence: 247 }, { name: "ApproxEqual", // Note: Mathematica TildeEqual, MathML: `asymptotically equal to` latexTrigger: ["\\approxeq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "NotApproxEqual", // Note: Mathematica NotTildeEqual latexTrigger: ["\\not", "\\approxeq"], kind: "infix", // Note: no LaTeX symbol for char U+2249 associativity: "right", precedence: 250 }, { name: "NotEqual", latexTrigger: ["\\ne"], kind: "infix", associativity: "right", precedence: 255 }, { name: "Unequal", latexTrigger: ["!", "="], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE // Note different precedence than \\ne per MathML }, { name: "GreaterEqual", latexTrigger: ["\\ge"], kind: "infix", associativity: "right", precedence: 242 // Note: different precedence than `>=` as per MathML }, { latexTrigger: ["\\geq"], kind: "infix", associativity: "right", precedence: 242, // Note: different precedence than `>=` as per MathML parse: "GreaterEqual" }, { latexTrigger: [">", "="], kind: "infix", associativity: "right", precedence: 243, parse: "GreaterEqual" }, { latexTrigger: ["\\geqslant"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE + 5, // Note: different precedence than `>=` as per MathML parse: "GreaterEqual" }, { name: "GreaterNotEqual", latexTrigger: ["\\gneqq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "NotGreaterNotEqual", latexTrigger: ["\\ngeqq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { latexTrigger: [">"], kind: "infix", associativity: "right", precedence: 245, parse: "Greater" }, { name: "Greater", latexTrigger: ["\\gt"], kind: "infix", associativity: "right", precedence: 245 }, { name: "NotGreater", latexTrigger: ["\\ngtr"], kind: "infix", associativity: "right", precedence: 244 }, { latexTrigger: ["\\not", ">"], kind: "infix", associativity: "right", precedence: 244, parse: "NotGreater" }, { name: "RingEqual", latexTrigger: ["\\circeq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "TriangleEqual", // MathML: delta equal to latexTrigger: ["\\triangleq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "DotEqual", // MathML: approaches the limit latexTrigger: ["\\doteq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE + 5 }, { name: "DotEqualDot", // MathML: Geometrically equal latexTrigger: ["\\doteqdot"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE + 5 }, { name: "FallingDotEqual", // MathML: approximately equal to or the image of latexTrigger: ["\\fallingdotseq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE + 5 }, { name: "RisingDotEqual", // MathML: image of or approximately equal to latexTrigger: ["\\fallingdotseq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE + 5 }, { name: "QuestionEqual", latexTrigger: ["\\questeq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "MuchLess", latexTrigger: ["\\ll"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "MuchGreater", latexTrigger: ["\\gg"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "Precedes", latexTrigger: ["\\prec"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "Succeeds", latexTrigger: ["\\succ"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "PrecedesEqual", latexTrigger: ["\\preccurlyeq"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "SucceedsEqual", latexTrigger: ["\\curlyeqprec"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "NotPrecedes", latexTrigger: ["\\nprec"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "NotSucceeds", latexTrigger: ["\\nsucc"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE }, { name: "Between", latexTrigger: ["\\between"], kind: "infix", associativity: "right", precedence: COMPARISON_PRECEDENCE + 5 } ]; // src/compute-engine/latex-syntax/utils.ts function isInequalityOperator(operator2) { if (typeof operator2 !== "string") return false; return ["Less", "LessEqual", "Greater", "GreaterEqual"].includes(operator2); } function isEquationOperator(operator2) { if (typeof operator2 !== "string") return false; return ["Equal", "NotEqual"].includes(operator2); } // src/common/type/primitive.ts var NUMERIC_TYPES = [ "number", "finite_number", "complex", "finite_complex", "imaginary", "real", "finite_real", "rational", "finite_rational", "integer", "finite_integer", "non_finite_number" ]; var INDEXED_COLLECTION_TYPES = [ "indexed_collection", "list", "tuple" ]; var COLLECTION_TYPES = [ ...INDEXED_COLLECTION_TYPES, "collection", "set", "record", "dictionary" ]; var SCALAR_TYPES = [ "scalar", ...NUMERIC_TYPES, "boolean", "string" ]; var VALUE_TYPES = [ "value", "color", ...COLLECTION_TYPES, ...SCALAR_TYPES ]; var EXPRESSION_TYPES = [ "expression", "symbol", "function", ...VALUE_TYPES ]; var PRIMITIVE_TYPES = [ "any", "unknown", "nothing", "never", "error", ...EXPRESSION_TYPES ]; function isValidType(t) { if (typeof t === "string") return PRIMITIVE_TYPES.includes(t); if (typeof t !== "object") return false; if (!("kind" in t)) return false; return t.kind === "signature" || t.kind === "union" || t.kind === "intersection" || t.kind === "negation" || t.kind === "tuple" || t.kind === "list" || t.kind === "record" || t.kind === "dictionary" || t.kind === "set" || t.kind === "function" || t.kind === "collection" || t.kind === "indexed_collection" || t.kind === "reference"; } // src/common/type/serialize.ts var NEGATION_PRECEDENCE = 3; var UNION_PRECEDENCE = 1; var INTERSECTION_PRECEDENCE = 2; var LIST_PRECEDENCE = 4; var RECORD_PRECEDENCE = 5; var DICTIONARY_PRECEDENCE = 6; var SET_PRECEDENCE = 7; var COLLECTION_PRECEDENCE = 8; var TUPLE_PRECEDENCE = 9; var SIGNATURE_PRECEDENCE = 10; var VALUE_PRECEDENCE = 11; function typeToString(type, precedence = 0) { if (typeof type === "string") return type; let result = ""; switch (type.kind) { case "value": if (typeof type.value === "string") result = `"${type.value}"`; else if (typeof type.value === "boolean") result = type.value ? "true" : "false"; else result = type.value.toString(); break; case "reference": result = type.name; break; case "negation": result = `!${typeToString(type.type, NEGATION_PRECEDENCE)}`; break; case "union": result = type.types.map((t) => typeToString(t, UNION_PRECEDENCE)).join(" | "); break; case "intersection": result = type.types.map((t) => typeToString(t, INTERSECTION_PRECEDENCE)).join(" & "); break; case "expression": result = `expression<${symbolName(type.operator)}>`; break; case "symbol": result = `symbol<${symbolName(type.name)}>`; break; case "numeric": if (Number.isFinite(type.lower) && Number.isFinite(type.upper)) { result = `${type.type}<${type.lower}..${type.upper}>`; } else if (Number.isFinite(type.lower)) { result = `${type.type}<${type.lower}..>`; } else if (Number.isFinite(type.upper)) { result = `${type.type}<..${type.upper}>`; } else { result = `${type.type}`; } break; case "list": if (type.dimensions && typeof type.elements === "string" && NUMERIC_TYPES.includes(type.elements)) { if (type.dimensions === void 0) { if (type.elements === "number") result = "tensor"; } else if (type.dimensions.length === 1) { if (type.elements === "number") { if (type.dimensions[0] < 0) result = "vector"; else result = `vector<${type.dimensions[0]}>`; } else { if (type.dimensions[0] < 0) result = `vector<${typeToString(type.elements)}>`; else result = `vector<${typeToString(type.elements)}^${type.dimensions[0]}>`; } } else if (type.dimensions.length === 2) { const dims = type.dimensions; if (type.elements === "number") { if (dims[0] < 0 && dims[1] < 0) result = "matrix"; else result = `matrix<${dims[0]}x${dims[1]}>`; } else { if (dims[0] < 0 && dims[1] < 0) result = `matrix<${typeToString(type.elements)}>`; else result = `matrix<${typeToString(type.elements)}^(${dims[0]}x${dims[1]})>`; } } } if (!result) { const dimensions = type.dimensions ? type.dimensions.length === 1 ? `^${type.dimensions[0].toString()}` : `^(${type.dimensions.join("x")})` : ""; result = `list<${typeToString(type.elements)}${dimensions}>`; } break; case "record": const elements = Object.entries(type.elements).map(([key, value]) => `${key}: ${typeToString(value)}`).join(", "); result = `record<${elements}>`; break; case "dictionary": result = `dictionary<${typeToString(type.values)}>`; break; case "set": result = `set<${typeToString(type.elements)}>`; break; case "collection": result = `collection<${typeToString(type.elements)}>`; break; case "indexed_collection": result = `indexed_collection<${typeToString(type.elements)}>`; break; case "tuple": if (type.elements.length === 0) result = "tuple"; else if (type.elements.length === 1) { const [el] = type.elements; result = `tuple<${namedElement(el)}>`; } else { result = "tuple<" + type.elements.map((el) => namedElement(el)).join(", ") + ">"; } break; case "signature": const args = type.args ? type.args.map((arg) => namedElement(arg)).join(", ") : ""; const optArgs = type.optArgs ? type.optArgs.map((arg) => namedElement(arg) + "?").join(", ") : ""; const varArg = type.variadicArg ? type.variadicMin === 0 ? `${namedElement(type.variadicArg)}*` : `${namedElement(type.variadicArg)}+` : ""; const argsList = [args, optArgs, varArg].filter((s) => s).join(", "); result = `(${argsList}) -> ${typeToString(type.result)}`; break; default: result = "error"; } if (precedence > 0 && precedence > getPrecedence(type.kind)) return `(${result})`; return result; } function namedElement(el) { if (el.name) return `${el.name}: ${typeToString(el.type)}`; return typeToString(el.type); } function symbolName(name) { if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) return name; return `\`${name}\``; } function getPrecedence(kind) { switch (kind) { case "negation": return NEGATION_PRECEDENCE; case "union": return UNION_PRECEDENCE; case "intersection": return INTERSECTION_PRECEDENCE; case "list": return LIST_PRECEDENCE; case "record": return RECORD_PRECEDENCE; case "dictionary": return DICTIONARY_PRECEDENCE; case "set": return SET_PRECEDENCE; case "collection": case "indexed_collection": return COLLECTION_PRECEDENCE; case "tuple": return TUPLE_PRECEDENCE; case "signature": return SIGNATURE_PRECEDENCE; case "value": return VALUE_PRECEDENCE; default: return 0; } } // src/common/type/lexer.ts var Lexer = class { input; pos = 0; line = 1; column = 1; tokens = []; constructor(input) { this.input = input; } // Save current lexer state for backtracking saveState() { return { pos: this.pos, line: this.line, column: this.column, tokens: [...this.tokens] }; } // Restore lexer state for backtracking restoreState(state) { this.pos = state.pos; this.line = state.line; this.column = state.column; this.tokens = state.tokens; } error(message) { throw new Error( `Lexer error at line ${this.line}, column ${this.column}: ${message}` ); } peek(offset = 0) { const index = this.pos + offset; return index < this.input.length ? this.input[index] : ""; } advance() { const char = this.input[this.pos++]; if (char === "\n") { this.line++; this.column = 1; } else { this.column++; } return char; } match(str) { if (this.input.slice(this.pos, this.pos + str.length) === str) { for (let i = 0; i < str.length; i++) { this.advance(); } return true; } return false; } isEOF() { return this.pos >= this.input.length; } skipWhitespace() { while (!this.isEOF() && /\s/.test(this.peek())) { this.advance(); } } readIdentifier() { let value = ""; while (!this.isEOF() && /[a-zA-Z0-9_]/.test(this.peek())) { value += this.advance(); } return value; } readVerbatimString() { if (!this.match("`")) return ""; let value = ""; while (!this.isEOF() && this.peek() !== "`") { if (this.match("\\`")) { value += "`"; } else if (this.match("\\\\")) { value += "\\"; } else { value += this.advance(); } } if (this.isEOF()) { this.error("Unterminated verbatim string"); } this.advance(); return value; } readStringLiteral() { const quote = this.advance(); let value = ""; while (!this.isEOF() && this.peek() !== quote) { if (this.match("\\" + quote)) { value += quote; } else if (this.match("\\\\")) { value += "\\"; } else { value += this.advance(); } } if (this.isEOF()) { this.error("Unterminated string literal"); } this.advance(); return value; } readNumber() { let value = ""; if (this.peek() === "-" || this.peek() === "+") { value += this.advance(); } if (this.match("0x") || this.match("0X")) { value += "x"; while (!this.isEOF() && /[0-9a-fA-F]/.test(this.peek())) { value += this.advance(); } return "0" + value; } if (this.match("0b") || this.match("0B")) { value += "b"; while (!this.isEOF() && /[01]/.test(this.peek())) { value += this.advance(); } return "0" + value; } while (!this.isEOF() && /[0-9]/.test(this.peek())) { value += this.advance(); } if (this.peek() === "." && /[0-9]/.test(this.peek(1))) { value += this.advance(); while (!this.isEOF() && /[0-9]/.test(this.peek())) { value += this.advance(); } } if (this.peek() === "e" || this.peek() === "E") { value += this.advance(); if (this.peek() === "+" || this.peek() === "-") { value += this.advance(); } while (!this.isEOF() && /[0-9]/.test(this.peek())) { value += this.advance(); } } return value; } createToken(type, value) { return { type, value, position: this.pos - value.length, line: this.line, column: this.column - value.length }; } nextToken() { this.skipWhitespace(); if (this.isEOF()) { return this.createToken("EOF", ""); } const start = this.pos; const char = this.peek(); if (this.match("->")) { return this.createToken("->", "->"); } if (this.match("..")) { return this.createToken("..", ".."); } if (this.match("+\u221E") || this.match("+oo")) { return this.createToken( "PLUS_INFINITY", this.input.slice(start, this.pos) ); } if (this.match("-\u221E") || this.match("-oo")) { return this.createToken( "MINUS_INFINITY", this.input.slice(start, this.pos) ); } if (this.match("+infinity")) { return this.createToken("PLUS_INFINITY", "+infinity"); } if (this.match("-infinity")) { return this.createToken("MINUS_INFINITY", "-infinity"); } if (/[a-zA-Z_]/.test(char)) { const value = this.readIdentifier(); switch (value) { case "true": return this.createToken("TRUE", value); case "false": return this.createToken("FALSE", value); case "nan": return this.createToken("NAN", value); case "infinity": return this.createToken("INFINITY", value); case "oo": return this.createToken("INFINITY", value); default: return this.createToken("IDENTIFIER", value); } } switch (char) { case "|": this.advance(); return this.createToken("|", "|"); case "&": this.advance(); return this.createToken("&", "&"); case "!": this.advance(); return this.createToken("!", "!"); case "^": this.advance(); return this.createToken("^", "^"); case "(": this.advance(); return this.createToken("(", "("); case ")": this.advance(); return this.createToken(")", ")"); case "<": this.advance(); return this.createToken("<", "<"); case ">": this.advance(); return this.createToken(">", ">"); case "[": this.advance(); return this.createToken("[", "["); case "]": this.advance(); return this.createToken("]", "]"); case ",": this.advance(); return this.createToken(",", ","); case ":": this.advance(); return this.createToken(":", ":"); case "?": this.advance(); return this.createToken("?", "?"); case "*": this.advance(); return this.createToken("*", "*"); case "+": if (/[0-9]/.test(this.peek(1))) { return this.createToken("NUMBER_LITERAL", this.readNumber()); } this.advance(); return this.createToken("+", "+"); case "x": if (/[0-9]/.test(this.peek(1))) { this.advance(); return this.createToken("x", "x"); } this.advance(); return this.createToken("x", "x"); } if (char === '"' || char === "'") { return this.createToken("STRING_LITERAL", this.readStringLiteral()); } if (char === "`") { return this.createToken("VERBATIM_STRING", this.readVerbatimString()); } if (/[0-9]/.test(char) || char === "-" && /[0-9]/.test(this.peek(1))) { const number = this.readNumber(); if (this.peek() === "x" && /[0-9]/.test(this.peek(1))) { } return this.createToken("NUMBER_LITERAL", number); } if (char === "\u221E") { this.advance(); return this.createToken("INFINITY", "\u221E"); } this.error(`Unexpected character: ${char}`); } tokenize() { const tokens = []; while (!this.isEOF()) { const token = this.nextToken(); if (token) { tokens.push(token); if (token.type === "EOF") break; } } return tokens; } peekToken() { if (this.tokens.length === 0) { const token = this.nextToken(); if (token) this.tokens.push(token); } return this.tokens[0] || this.createToken("EOF", ""); } consumeToken() { if (this.tokens.length === 0) { const token = this.nextToken(); if (token) return token; } return this.tokens.shift() || this.createToken("EOF", ""); } matchToken(type) { const token = this.peekToken(); if (token.type === type) { this.consumeToken(); return true; } return false; } expectToken(type) { const token = this.consumeToken(); if (token.type !== type) { this.error(`Expected ${type}, got ${token.type}`); } return token; } }; // src/common/type/parser.ts var Parser = class { lexer; typeResolver; current; constructor(input, options) { this.lexer = new Lexer(input); this.typeResolver = options?.typeResolver ?? { forward: () => void 0, resolve: () => void 0, get names() { return []; } }; this.current = this.lexer.consumeToken(); } error(message, suggestion) { this.errorAtToken(this.current, message, suggestion); } errorAtToken(token, message, suggestion) { const input = this.lexer.input; const lines = input.split("\n"); const currentLine = lines[token.line - 1] || input; const column = token.column; const pointer = " ".repeat(Math.max(0, column - 1)) + "^"; const formattedMessage = [ "", "Invalid type", `| ${currentLine}`, `| ${pointer}`, "|", `| ${message}` ]; if (suggestion) formattedMessage.push(`| ${suggestion}`); formattedMessage.push(""); throw new Error(formattedMessage.join("\n")); } advance() { const prev = this.current; this.current = this.lexer.consumeToken(); return prev; } match(type) { if (this.current.type === type) { this.advance(); return true; } return false; } expect(type) { if (this.current.type !== type) { this.error(`Expected ${type}, got ${this.current.type}`); } return this.advance(); } createNode(kind, additional = {}) { return { kind, position: this.current.position, line: this.current.line, column: this.current.column, ...additional }; } parseType() { this.checkForNakedFunctionSignature(); const type = this.parseUnionType(); if (!type) { this.error("Expected a type"); } if (this.current.type !== "EOF") { if (this.current.type === "->" || this.current.type === "+" || this.current.type === "*" || this.current.type === "?") { this.error( "Function signatures must be enclosed in parentheses", "For example `(x: number) -> number`" ); } else if (this.current.type === "(") { const input = this.lexer.input; if (input.includes("set(") || input.includes("collection(") || input.includes("list(") || input.includes("tuple(")) { if (input.includes("set(")) { this.error("Use `set<integer>` instead of `set(integer)`."); } else if (input.includes("collection(")) { this.error( "Use `collection<type>` instead of `collection(type)`.", "For example `collection<number>`" ); } else if (input.includes("list(")) { this.error( "Use `list<type>` instead of `list(type)`.", "For example `list<number>`" ); } else if (input.includes("tuple(")) { this.error( "Use `tuple<type1, type2>` instead of `tuple(type1, type2)`.", "For example `tuple<string, number>`" ); } } else { this.error("Unexpected token after type"); } } else { this.error("Unexpected token after type"); } } return type; } checkForNakedFunctionSignature() { if (this.current.type === "IDENTIFIER") { const savedState = this.lexer.saveState(); const savedCurrent = this.current; try { const identifierToken = this.current; this.advance(); if (this.current.type === ":") { this.advance(); let foundSignatureTokens = false; let tokenCount = 0; const maxLookahead = 10; while (this.current.type !== "EOF" && tokenCount < maxLookahead) { if (this.current.type === "->") { foundSignatureTokens = true; break; } if (this.current.type === "+" || this.current.type === "*" || this.current.type === "?") { this.advance(); if (this.current.type === "->") { foundSignatureTokens = true; break; } tokenCount++; } this.advance(); tokenCount++; } if (foundSignatureTokens) { this.lexer.restoreState(savedState); this.current = savedCurrent; this.errorAtToken( identifierToken, "Function signatures must be enclosed in parentheses", "For example `(z: string*) -> boolean`" ); } } this.lexer.restoreState(savedState); this.current = savedCurrent; } catch (error) { this.lexer.restoreState(savedState); this.current = savedCurrent; if (error instanceof Error && error.message.includes("Function signatures must be enclosed")) { throw error; } } } } parseUnionType() { const firstType = this.parseIntersectionType(); if (!firstType) return void 0; const types = [firstType]; while (this.match("|")) { const type = this.parseIntersectionType(); if (!type) { this.error("Expected type after |"); } types.push(type); } if (types.length === 1) return types[0]; return this.createNode("union", { types }); } parseIntersectionType() { const firstType = this.parsePrimaryType(); if (!firstType) return void 0; const types = [firstType]; while (this.match("&")) { const type = this.parsePrimaryType(); if (!type) { thi