2023-11-17 20:51:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* Module: API for RSLanguage.
|
|
|
|
|
*/
|
2023-11-06 20:21:30 +03:00
|
|
|
|
|
2023-12-13 14:32:57 +03:00
|
|
|
|
import { applyPattern } from '@/utils/utils';
|
|
|
|
|
|
2023-11-06 20:21:30 +03:00
|
|
|
|
import { CstType } from './rsform';
|
2024-08-26 22:53:27 +03:00
|
|
|
|
import { AliasMapping, IArgumentValue, IRSErrorDescription, RSErrorClass, RSErrorType } from './rslang';
|
2023-11-06 20:21:30 +03:00
|
|
|
|
|
2024-04-05 15:53:05 +03:00
|
|
|
|
// cspell:disable
|
2023-11-06 22:21:36 +03:00
|
|
|
|
const LOCALS_REGEXP = /[_a-zα-ω][a-zα-ω]*\d*/g;
|
2024-04-05 15:53:05 +03:00
|
|
|
|
const GLOBALS_REGEXP = /[XCSADFPT]\d+/g;
|
|
|
|
|
const COMPLEX_SYMBOLS_REGEXP = /[∀∃×ℬ;|:]/g;
|
2024-08-26 22:53:27 +03:00
|
|
|
|
const TYPIFICATION_SET = /^ℬ+\([ℬ\(X\d+\)×]*\)$/g;
|
2024-04-05 15:53:05 +03:00
|
|
|
|
// cspell:enable
|
2023-11-06 22:21:36 +03:00
|
|
|
|
|
2023-11-17 20:51:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* Extracts global variable names from a given expression.
|
|
|
|
|
*/
|
2023-11-06 20:21:30 +03:00
|
|
|
|
export function extractGlobals(expression: string): Set<string> {
|
2024-04-05 15:53:05 +03:00
|
|
|
|
return new Set(expression.match(GLOBALS_REGEXP) ?? []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if expression is simple derivation.
|
|
|
|
|
*/
|
|
|
|
|
export function isSimpleExpression(text: string): boolean {
|
2024-04-05 20:04:12 +03:00
|
|
|
|
return !text.match(COMPLEX_SYMBOLS_REGEXP);
|
2023-11-06 20:21:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-26 22:53:27 +03:00
|
|
|
|
/**
|
|
|
|
|
* Check if expression is set typification.
|
|
|
|
|
*/
|
|
|
|
|
export function isSetTypification(text: string): boolean {
|
|
|
|
|
return !!text.match(TYPIFICATION_SET);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-17 20:51:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* Infers type of constituent for a given template and arguments.
|
|
|
|
|
*/
|
2023-11-06 20:21:30 +03:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-17 20:51:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* 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
|
2023-12-17 20:19:28 +03:00
|
|
|
|
* const template = '[header] body content';
|
2023-11-17 20:51:13 +03:00
|
|
|
|
* const result = splitTemplateDefinition(template);
|
|
|
|
|
* // result: `{ head: 'header', body: 'body content' }`
|
|
|
|
|
*/
|
2023-11-06 20:21:30 +03:00
|
|
|
|
export function splitTemplateDefinition(target: string) {
|
|
|
|
|
let start = 0;
|
2023-12-28 14:04:44 +03:00
|
|
|
|
for (; start < target.length && target[start] !== '['; ++start);
|
2023-11-06 20:21:30 +03:00
|
|
|
|
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 {
|
2023-11-06 22:21:36 +03:00
|
|
|
|
return {
|
|
|
|
|
head: target.substring(start + 1, end).trim(),
|
|
|
|
|
body: target.substring(end + 1).trim()
|
2023-12-28 14:04:44 +03:00
|
|
|
|
};
|
2023-11-06 20:21:30 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
head: '',
|
|
|
|
|
body: target
|
2023-12-28 14:04:44 +03:00
|
|
|
|
};
|
2023-11-06 20:21:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-17 20:51:13 +03:00
|
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2023-11-06 22:21:36 +03:00
|
|
|
|
export function substituteTemplateArgs(expression: string, args: IArgumentValue[]): string {
|
|
|
|
|
if (args.every(arg => !arg.value)) {
|
|
|
|
|
return expression;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-26 22:53:27 +03:00
|
|
|
|
const mapping: AliasMapping = {};
|
2023-12-28 14:04:44 +03:00
|
|
|
|
args
|
|
|
|
|
.filter(arg => !!arg.value)
|
|
|
|
|
.forEach(arg => {
|
|
|
|
|
mapping[arg.alias] = arg.value!;
|
|
|
|
|
});
|
2023-11-06 22:21:36 +03:00
|
|
|
|
|
|
|
|
|
let { head, body } = splitTemplateDefinition(expression);
|
|
|
|
|
body = applyPattern(body, mapping, LOCALS_REGEXP);
|
|
|
|
|
const argTexts = head.split(',').map(text => text.trim());
|
|
|
|
|
head = argTexts
|
2023-12-28 14:04:44 +03:00
|
|
|
|
.filter(arg => [...arg.matchAll(LOCALS_REGEXP)].every(local => local.every(match => !(match in mapping))))
|
|
|
|
|
.join(', ');
|
2023-11-06 22:21:36 +03:00
|
|
|
|
|
|
|
|
|
if (!head) {
|
|
|
|
|
return body;
|
|
|
|
|
} else {
|
2023-12-28 14:04:44 +03:00
|
|
|
|
return `[${head}] ${body}`;
|
2023-11-06 22:21:36 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-17 20:51:13 +03:00
|
|
|
|
|
2023-12-27 16:45:18 +03:00
|
|
|
|
const ERROR_LEXER_MASK = 512;
|
|
|
|
|
const ERROR_PARSER_MASK = 1024;
|
|
|
|
|
const ERROR_SEMANTIC_MASK = 2048;
|
2023-11-17 20:51:13 +03:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Infers error class from error type (code).
|
|
|
|
|
*/
|
|
|
|
|
export function inferErrorClass(error: RSErrorType): RSErrorClass {
|
2023-12-27 16:45:18 +03:00
|
|
|
|
if ((error & ERROR_LEXER_MASK) !== 0) {
|
2023-11-17 20:51:13 +03:00
|
|
|
|
return RSErrorClass.LEXER;
|
2023-12-27 16:45:18 +03:00
|
|
|
|
} else if ((error & ERROR_PARSER_MASK) !== 0) {
|
2023-11-17 20:51:13 +03:00
|
|
|
|
return RSErrorClass.PARSER;
|
2023-12-27 16:45:18 +03:00
|
|
|
|
} else if ((error & ERROR_SEMANTIC_MASK) !== 0) {
|
2023-11-17 20:51:13 +03:00
|
|
|
|
return RSErrorClass.SEMANTIC;
|
|
|
|
|
} else {
|
|
|
|
|
return RSErrorClass.UNKNOWN;
|
|
|
|
|
}
|
2023-12-28 14:04:44 +03:00
|
|
|
|
}
|
2024-01-04 14:35:46 +03:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-26 22:53:27 +03:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 result = applyAliasMapping(target, mapping);
|
|
|
|
|
if (result === target) {
|
|
|
|
|
return target;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// remove double parentheses
|
|
|
|
|
// deal with ℬ(ℬ)
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|