/** * Module: API for RSLanguage. */ import { type Tree } from '@lezer/common'; import { cursorNode } from '@/utils/codemirror'; import { PARAMETER } from '@/utils/constants'; import { CstType, type IRSErrorDescription, type RSErrorType } from '../backend/types'; import { type AliasMapping, type IArgumentValue, RSErrorClass, type SyntaxTree } from './rslang'; // cspell:disable const LOCALS_REGEXP = /[_a-zα-ω][a-zα-ω]*\d*/g; const GLOBALS_REGEXP = /[XCSADFPT]\d+/g; const COMPLEX_SYMBOLS_REGEXP = /[∀∃×ℬ;|:]/g; const TYPIFICATION_SET = /^ℬ+\([ℬ\(X\d+\)×]*\)$/g; // cspell:enable /** * Text substitution guided by mapping and regular expression. */ export function applyPattern(text: string, mapping: AliasMapping, pattern: RegExp): string { if (text === '' || pattern === null) { return text; } let posInput = 0; let output = ''; const patternMatches = text.matchAll(pattern); for (const segment of patternMatches) { const entity = segment[0]; const start = segment.index ?? 0; if (entity in mapping) { output += text.substring(posInput, start); output += mapping[entity]; posInput = start + segment[0].length; } } output += text.substring(posInput); return output; } /** * Extracts global variable names from a given expression. */ export function extractGlobals(expression: string): Set { return new Set(expression.match(GLOBALS_REGEXP) ?? []); } /** * Check if expression is simple derivation. */ export function isSimpleExpression(text: string): boolean { return !text.match(COMPLEX_SYMBOLS_REGEXP); } /** * Check if expression is set typification. */ export function isSetTypification(text: string): boolean { return !!text.match(TYPIFICATION_SET); } /** * Infers type of constituent for a given template and arguments. */ export function inferTemplatedType(templateType: CstType, args: IArgumentValue[]): CstType { if (args.length === 0 || args.some(arg => !arg.value)) { return templateType; } else if (templateType === CstType.PREDICATE) { return CstType.AXIOM; } else { return CstType.TERM; } } /** * Splits a string containing a template definition into its head and body parts. * * A template definition is expected to have the following format: `[head] body`. * If the input string does not contain the opening square bracket '[', the entire * string is treated as the body, and an empty string is assigned to the head. * If the opening bracket is present, the function attempts to find the matching * closing bracket ']' to determine the head and body parts. * * @example * const template = '[header] body content'; * const result = splitTemplateDefinition(template); * // result: `{ head: 'header', body: 'body content' }` */ export function splitTemplateDefinition(target: string) { let start = 0; for (; start < target.length && target[start] !== '['; ++start); if (start < target.length) { for (let counter = 0, end = start + 1; end < target.length; ++end) { if (target[end] === '[') { ++counter; } else if (target[end] === ']') { if (counter !== 0) { --counter; } else { return { head: target.substring(start + 1, end).trim(), body: target.substring(end + 1).trim() }; } } } } return { head: '', body: target }; } /** * Substitutes values for template arguments in a given expression. * * This function takes an input mathematical expression and a list of argument values. * It replaces template argument placeholders in the expression with their corresponding values * from the provided arguments. */ export function substituteTemplateArgs(expression: string, args: IArgumentValue[]): string { if (args.every(arg => !arg.value)) { return expression; } const mapping: AliasMapping = {}; args .filter(arg => !!arg.value) .forEach(arg => { mapping[arg.alias] = arg.value!; }); let { head, body } = splitTemplateDefinition(expression); body = applyPattern(body, mapping, LOCALS_REGEXP); const argTexts = head.split(',').map(text => text.trim()); head = argTexts .filter(arg => [...arg.matchAll(LOCALS_REGEXP)].every(local => local.every(match => !(match in mapping)))) .join(', '); if (!head) { return body; } else { return `[${head}] ${body}`; } } const ERROR_LEXER_MASK = 512; const ERROR_PARSER_MASK = 1024; const ERROR_SEMANTIC_MASK = 2048; /** * Infers error class from error type (code). */ export function inferErrorClass(error: RSErrorType): RSErrorClass { if ((error & ERROR_LEXER_MASK) !== 0) { return RSErrorClass.LEXER; } else if ((error & ERROR_PARSER_MASK) !== 0) { return RSErrorClass.PARSER; } else if ((error & ERROR_SEMANTIC_MASK) !== 0) { return RSErrorClass.SEMANTIC; } else { return RSErrorClass.UNKNOWN; } } /** * Generate ErrorID label. */ export function getRSErrorPrefix(error: IRSErrorDescription): string { const id = error.errorType.toString(16); // prettier-ignore switch(inferErrorClass(error.errorType)) { case RSErrorClass.LEXER: return 'L' + id; case RSErrorClass.PARSER: return 'P' + id; case RSErrorClass.SEMANTIC: return 'S' + id; case RSErrorClass.UNKNOWN: return 'U' + id; } } /** * Apply alias mapping. */ export function applyAliasMapping(target: string, mapping: AliasMapping): string { return applyPattern(target, mapping, GLOBALS_REGEXP); } /** * Apply alias typification mapping. */ export function applyTypificationMapping(target: string, mapping: AliasMapping): string { const modified = applyAliasMapping(target, mapping); if (modified === target) { return target; } const deleteBrackets: number[] = []; const positions: number[] = []; const booleans: number[] = []; let boolCount: number = 0; let stackSize: number = 0; for (let i = 0; i < modified.length; i++) { const char = modified[i]; if (char === 'ℬ') { boolCount++; continue; } if (char === '(') { stackSize++; positions.push(i); booleans.push(boolCount); } boolCount = 0; if (char === ')') { if ( i < modified.length - 1 && modified[i + 1] === ')' && stackSize > 1 && positions[stackSize - 2] + booleans[stackSize - 1] + 1 === positions[stackSize - 1] ) { deleteBrackets.push(i); deleteBrackets.push(positions[stackSize - 2]); } if (i === modified.length - 1 && stackSize === 1 && positions[0] === 0) { deleteBrackets.push(i); deleteBrackets.push(positions[0]); } stackSize--; positions.pop(); booleans.pop(); } } let result = ''; for (let i = 0; i < modified.length; i++) { if (!deleteBrackets.includes(i)) { result += modified[i]; } } return result; } /** * Transform Tree to {@link SyntaxTree}. */ export function transformAST(tree: Tree): SyntaxTree { const result: SyntaxTree = []; const parents: number[] = []; const cursor = tree.cursor(); let finished = false; let leave = true; while (!finished) { let node = cursorNode(cursor); node.isLeaf = !cursor.firstChild(); leave = true; result.push({ uid: result.length, parent: parents.length > 0 ? parents[parents.length - 1] : result.length, typeID: node.type.id, start: node.from, finish: node.to, data: { dataType: 'string', value: node.type.name == '⚠' ? PARAMETER.errorNodeLabel : node.type.name } }); parents.push(result.length - 1); if (!node.isLeaf) continue; for (;;) { node = cursorNode(cursor, node.isLeaf); if (leave) { parents.pop(); } leave = cursor.type.isAnonymous; node.isLeaf = false; if (cursor.nextSibling()) { break; } if (!cursor.parent()) { finished = true; break; } leave = true; } } return result; }