site/node_modules/mathjax-full/ts/input/tex/SymbolMap.ts
2024-10-14 08:09:33 +02:00

393 lines
9 KiB
TypeScript

/*************************************************************
*
* Copyright (c) 2017-2022 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Symbol map classes.
*
* @author v.sorge@mathjax.org (Volker Sorge)
*/
import {Attributes, Args, ParseMethod, ParseInput, ParseResult} from './Types.js';
import {Symbol, Macro} from './Symbol.js';
import {MapHandler} from './MapHandler.js';
/**
* SymbolMaps are the base components for the input parsers.
*
* They provide a contains method that checks if a map is applicable (contains)
* a particular string. Implementing classes then perform the actual symbol
* parsing, from simple regular expression test, straight forward symbol mapping
* to transformational functionality on the parsed string.
*
* @interface
*/
export interface SymbolMap {
/**
* @return {string} The name of the map.
*/
name: string;
/**
* @return {ParseMethod} The default parsing method.
*/
parser: ParseMethod;
/**
* @param {string} symbol A symbol to parse.
* @return {boolean} True if the symbol map applies to the symbol.
*/
contains(symbol: string): boolean;
/**
* @param {string} symbol A symbol to parse.
* @return {ParseMethod} A parse method for the symbol.
*/
parserFor(symbol: string): ParseMethod;
/**
* @param {TexParser} env The current parser.
* @param {string} symbol A symbol to parse.
* @return {ParseResult} The parsed symbol and the rest of the string.
*/
parse([env, symbol]: ParseInput): ParseResult;
}
/**
* @param {ParseResult} result The result to check
* @return {ParseResult} True if result was void, result otherwise
*/
export function parseResult(result: ParseResult): ParseResult {
return result === void 0 ? true : result;
}
/**
* Abstract implementation of symbol maps.
* @template T
*/
export abstract class AbstractSymbolMap<T> implements SymbolMap {
/**
* @constructor
* @implements {SymbolMap}
* @param {string} name Name of the mapping.
* @param {ParseMethod} parser The parser for the mappiong.
*/
constructor(private _name: string, private _parser: ParseMethod) {
MapHandler.register(this);
}
/**
* @override
*/
public get name(): string {
return this._name;
}
/**
* @override
*/
public abstract contains(symbol: string): boolean;
/**
* @override
*/
public parserFor(symbol: string) {
return this.contains(symbol) ? this.parser : null;
}
/**
* @override
*/
public parse([env, symbol]: ParseInput) {
let parser = this.parserFor(symbol);
let mapped = this.lookup(symbol);
return (parser && mapped) ? parseResult(parser(env, mapped as any)) : null;
}
public set parser(parser: ParseMethod) {
this._parser = parser;
}
public get parser(): ParseMethod {
return this._parser;
}
/**
* @param {string} symbol
* @return {T}
*/
public abstract lookup(symbol: string): T;
}
/**
* Regular expressions used for parsing strings.
*/
export class RegExpMap extends AbstractSymbolMap<string> {
/**
* @constructor
* @extends {AbstractSymbolMap}
* @param {string} name Name of the mapping.
* @param {ParseMethod} parser The parser for the mappiong.
* @param {RegExp} regexp The regular expression.
*/
constructor(name: string, parser: ParseMethod, private _regExp: RegExp) {
super(name, parser);
}
/**
* @override
*/
public contains(symbol: string) {
return this._regExp.test(symbol);
}
/**
* @override
*/
public lookup(symbol: string): string {
return this.contains(symbol) ? symbol : null;
}
}
/**
* Parse maps associate strings with parsing functionality.
* @constructor
* @extends {AbstractSymbolMap}
* @template K
*/
export abstract class AbstractParseMap<K> extends AbstractSymbolMap<K> {
private map: Map<string, K> = new Map<string, K>();
/**
* @override
*/
public lookup(symbol: string): K {
return this.map.get(symbol);
}
/**
* @override
*/
public contains(symbol: string) {
return this.map.has(symbol);
}
/**
* Sets mapping for a symbol.
* @param {string} symbol The symbol to map.
* @param {K} object The symbols value in the mapping's codomain.
*/
public add(symbol: string, object: K) {
this.map.set(symbol, object);
}
/**
* Removes a symbol from the map
* @param {string} symbol The symbol to remove
*/
public remove(symbol: string) {
this.map.delete(symbol);
}
}
/**
* Maps symbols that can all be parsed with the same method.
*
* @constructor
* @extends {AbstractParseMap}
*/
export class CharacterMap extends AbstractParseMap<Symbol> {
/**
* @constructor
* @param {string} name Name of the mapping.
* @param {ParseMethod} parser The parser for the mapping.
* @param {JSON} json The JSON representation of the character mapping.
*/
constructor(name: string, parser: ParseMethod,
json: {[index: string]: string | [string, Attributes]}) {
super(name, parser);
for (const key of Object.keys(json)) {
let value = json[key];
let [char, attrs] = (typeof(value) === 'string') ? [value, null] : value;
let character = new Symbol(key, char, attrs);
this.add(key, character);
}
}
}
/**
* Maps symbols that are delimiters, that are all parsed with the same method.
*
* @constructor
* @extends {CharacterMap}
*/
export class DelimiterMap extends CharacterMap {
/**
* @override
*/
public parse([env, symbol]: ParseInput) {
return super.parse([env, '\\' + symbol]);
}
}
/**
* Maps macros that all bring their own parsing method.
*
* @constructor
* @extends {AbstractParseMap}
*/
export class MacroMap extends AbstractParseMap<Macro> {
/**
* @constructor
* @param {string} name Name of the mapping.
* @param {JSON} json The JSON representation of the macro map.
* @param {Record<string, ParseMethod>} functionMap Collection of parse
* functions for the single macros.
*/
constructor(name: string,
json: {[index: string]: string | Args[]},
functionMap: Record<string, ParseMethod>) {
super(name, null);
for (const key of Object.keys(json)) {
let value = json[key];
let [func, ...attrs] = (typeof(value) === 'string') ? [value] : value;
let character = new Macro(key, functionMap[func as string], attrs);
this.add(key, character);
}
}
/**
* @override
*/
public parserFor(symbol: string) {
let macro = this.lookup(symbol);
return macro ? macro.func : null;
}
/**
* @override
*/
public parse([env, symbol]: ParseInput) {
let macro = this.lookup(symbol);
let parser = this.parserFor(symbol);
if (!macro || !parser) {
return null;
}
return parseResult(parser(env, macro.symbol, ...macro.args));
}
}
/**
* Maps macros that all bring their own parsing method.
*
* @constructor
* @extends {MacroMap}
*/
export class CommandMap extends MacroMap {
/**
* @override
*/
public parse([env, symbol]: ParseInput) {
let macro = this.lookup(symbol);
let parser = this.parserFor(symbol);
if (!macro || !parser) {
return null;
}
let saveCommand = env.currentCS;
env.currentCS = '\\' + symbol;
let result = parser(env, '\\' + macro.symbol, ...macro.args);
env.currentCS = saveCommand;
return parseResult(result);
}
}
/**
* Maps macros for environments. It has a general parsing method for
* environments, i.e., one that deals with begin/end, and each environment has
* its own parsing method returning the content.
*
* @constructor
* @extends {MacroMap}
*/
export class EnvironmentMap extends MacroMap {
/**
* @constructor
* @param {string} name Name of the mapping.
* @param {ParseMethod} parser The parser for the environments.
* @param {JSON} json The JSON representation of the macro map.
* @param {Record<string, ParseMethod>} functionMap Collection of parse
* functions for the single macros.
*/
constructor(name: string,
parser: ParseMethod,
json: {[index: string]: string | Args[]},
functionMap: Record<string, ParseMethod>) {
super(name, json, functionMap);
this.parser = parser;
}
/**
* @override
*/
public parse([env, symbol]: ParseInput) {
let macro = this.lookup(symbol);
let envParser = this.parserFor(symbol);
if (!macro || !envParser) {
return null;
}
return parseResult(this.parser(env, macro.symbol, envParser, macro.args));
}
}