/** * Module: API for RSLanguage. */ import { applyPattern } from '@/utils/utils'; import { CstType } from './rsform'; import { IArgumentValue, IRSErrorDescription, RSErrorClass, RSErrorType } from './rslang'; const LOCALS_REGEXP = /[_a-zα-ω][a-zα-ω]*\d*/g; /** * Extracts global variable names from a given expression. */ export function extractGlobals(expression: string): Set { // cspell:disable-next-line return new Set(expression.match(/[XCSADFPT]\d+/g) ?? []); } /** * 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: { [key: string]: string } = {}; 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; } }