StackItem.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2009-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Stack items hold information on the TexParser stack.
  19. *
  20. * @author v.sorge@mathjax.org (Volker Sorge)
  21. */
  22. import {MmlNode} from '../../core/MmlTree/MmlNode.js';
  23. import {FactoryNodeClass} from '../../core/Tree/Factory.js';
  24. import TexError from './TexError.js';
  25. import StackItemFactory from './StackItemFactory.js';
  26. // Union types for abbreviation.
  27. export type EnvProp = string | number | boolean;
  28. export type EnvList = {[key: string]: EnvProp};
  29. // This is the type for all fields that used to be set with With.
  30. export type Prop = string | number | boolean | MmlNode | PropList;
  31. export type PropList = {[key: string]: Prop};
  32. export type CheckType = [(MmlNode | StackItem)[], boolean];
  33. export interface NodeStack {
  34. /**
  35. * Get or set the topmost element on the node stack without removing it.
  36. * @return {MmlNode} The topmost node on the stack.
  37. */
  38. First: MmlNode;
  39. /**
  40. * Get or set the last element on the node stack without removing it.
  41. * @return {MmlNode} The last node on the stack.
  42. */
  43. Last: MmlNode;
  44. /**
  45. * @return {MmlNode} The topmost node on the item's node stack.
  46. */
  47. Pop(): MmlNode | void;
  48. /**
  49. * Pushes new nodes onto the items node stack.
  50. * @param {MmlNode[]} ...nodes A list of nodes.
  51. */
  52. Push(...nodes: MmlNode[]): void;
  53. /**
  54. * Get the top n elements on the node stack without removing them.
  55. * @param {number=} n Number of elements that should be returned.
  56. * @return {MmlNode[]} List of nodes on top of stack.
  57. */
  58. Peek(n?: number): MmlNode[];
  59. /**
  60. * @return {number} The size of the stack.
  61. */
  62. Size(): number;
  63. /**
  64. * Clears the stack.
  65. */
  66. Clear(): void;
  67. /**
  68. * Returns nodes on the stack item's node stack as an Mml node. I.e., in case
  69. * the item contains more than one node, it creates an mrow.
  70. * @param {boolean=} inferred If set the mrow will be an inferred mrow.
  71. * @param {boolean=} forceRow If set an mrow will be created, regardless of
  72. * how many nodes the item contains.
  73. * @return {MmlNode} The topmost Mml node.
  74. */
  75. toMml(inferred?: boolean, forceRow?: boolean): MmlNode;
  76. }
  77. export abstract class MmlStack implements NodeStack {
  78. /**
  79. * @constructor
  80. * @extends {NodeStack}
  81. * @param {MmlNode[]} nodes An initial list of nodes to put on the stack.
  82. */
  83. constructor(private _nodes: MmlNode[]) { }
  84. /**
  85. * @return {MmlNode[]} The nodes on the stack.
  86. */
  87. protected get nodes(): MmlNode[] {
  88. return this._nodes;
  89. }
  90. /**
  91. * @override
  92. */
  93. public Push(...nodes: MmlNode[]) {
  94. this._nodes.push(...nodes);
  95. }
  96. /**
  97. * @override
  98. */
  99. public Pop(): MmlNode {
  100. return this._nodes.pop();
  101. }
  102. /**
  103. * @override
  104. */
  105. public get First(): MmlNode {
  106. return this._nodes[this.Size() - 1];
  107. }
  108. /**
  109. * @override
  110. */
  111. public set First(node: MmlNode) {
  112. this._nodes[this.Size() - 1] = node;
  113. }
  114. /**
  115. * @override
  116. */
  117. public get Last(): MmlNode {
  118. return this._nodes[0];
  119. }
  120. /**
  121. * @override
  122. */
  123. public set Last(node: MmlNode) {
  124. this._nodes[0] = node;
  125. }
  126. /**
  127. * @override
  128. */
  129. public Peek(n?: number): MmlNode[] {
  130. if (n == null) {
  131. n = 1;
  132. }
  133. return this._nodes.slice(this.Size() - n);
  134. }
  135. /**
  136. * @override
  137. */
  138. public Size(): number {
  139. return this._nodes.length;
  140. }
  141. /**
  142. * @override
  143. */
  144. public Clear(): void {
  145. this._nodes = [];
  146. }
  147. protected abstract get factory(): StackItemFactory;
  148. /**
  149. * @override
  150. */
  151. public toMml(inferred: boolean = true, forceRow?: boolean) {
  152. if (this._nodes.length === 1 && !forceRow) {
  153. return this.First;
  154. }
  155. // @test Two Identifiers
  156. return this.create(
  157. 'node', inferred ? 'inferredMrow' : 'mrow', this._nodes, {});
  158. }
  159. /**
  160. * Convenience method to create nodes with the node factory on this stack.
  161. * @param {string} kind The kind of node to create.
  162. * @param {any[]} ...rest The remaining arguments for the creation method.
  163. * @return {MmlNode} The newly created node.
  164. */
  165. public create(kind: string, ...rest: any[]): MmlNode {
  166. return this.factory.configuration.nodeFactory.create(kind, ...rest);
  167. }
  168. }
  169. export interface StackItem extends NodeStack {
  170. /**
  171. * Type of stack item.
  172. * @type {string}
  173. */
  174. kind: string;
  175. /**
  176. * Is this a closing item, e.g., end.
  177. * @type {boolean}
  178. */
  179. isClose: boolean;
  180. /**
  181. * Is this an opening item, e.g., begin.
  182. * @type {boolean}
  183. */
  184. isOpen: boolean;
  185. /**
  186. * Is this a finalising item, i.e., one that only collects nodes.
  187. * @type {boolean}
  188. */
  189. isFinal: boolean;
  190. /**
  191. * Global properties of the parser.
  192. * @type {EnvList}
  193. */
  194. global: EnvList;
  195. /**
  196. * Local properties of the stack item.
  197. * @type {EnvList}
  198. */
  199. env: EnvList;
  200. /**
  201. * Copy local properties when pushed to stack?
  202. * @type {boolean}
  203. */
  204. copyEnv: boolean;
  205. /**
  206. * Tests if item is of the given type.
  207. * @param {string} kind The type.
  208. * @return {boolean} True if item is of that type.
  209. */
  210. isKind(kind: string): boolean;
  211. /**
  212. * Get a property of the item.
  213. * @param {string} key Property name.
  214. * @return {Prop} Property value if it exists.
  215. */
  216. getProperty(key: string): Prop;
  217. /**
  218. * Set a property.
  219. * @param {string} key Property name.
  220. * @param {Prop} value Property value.
  221. * @return {StackItem} The item for pipelining.
  222. */
  223. setProperty(key: string, value: Prop): StackItem;
  224. /**
  225. * Sets a list of properties.
  226. * @param {PropList} def The properties to set.
  227. * @return {StackItem} Returns the stack item object for pipelining.
  228. */
  229. setProperties(def: PropList): StackItem;
  230. /**
  231. * Convenience method for returning the string property "name".
  232. * @return {string} The value for the name property.
  233. */
  234. getName(): string;
  235. /**
  236. * TeX parsing in MathJax is essentially implemented via a nested stack
  237. * automaton. That is the tex parser works on a stack, and each item on the
  238. * stack can have a data stack of its own. Data on the stack is either a stack
  239. * item or a node.
  240. *
  241. * The checkItem method effectively implements the recursive checking of
  242. * input data from the parser against data recursively given on the stack.
  243. *
  244. * I.e., new input is parsed resulting in a new item. When pushed on the stack
  245. * it is checked against the top most item on the stack. This either leads to
  246. * the item being pushed onto the stack or combined with the top most
  247. * element(s), pushing a new item, which is recursively checked, unless an
  248. * error is thrown.
  249. *
  250. * A simple example: If \\end{foo} is parsed, an endItem is created, pushed on
  251. * the stack. Nodes on the stack are collapsed into content of the 'foo'
  252. * environment, until a beginItem for 'foo' is found. If a beginItem is not
  253. * for 'foo' or does not exist an error is thrown.
  254. *
  255. * @param {StackItem} item The pushed item.
  256. * @return {CheckType} True/false or an item or node.
  257. */
  258. checkItem(item: StackItem): CheckType;
  259. }
  260. export interface StackItemClass extends FactoryNodeClass<StackItem> {
  261. // new (factory: StackItemFactory, ...args: any[]): StackItem;
  262. }
  263. /**
  264. * Abstract basic item class that implements most of the stack item
  265. * functionality. In particular, it contains the base method for checkItem.
  266. */
  267. export abstract class BaseItem extends MmlStack implements StackItem {
  268. /**
  269. * The fail value.
  270. * @type {CheckType}
  271. */
  272. protected static fail: CheckType = [null, false];
  273. /**
  274. * The success value.
  275. * @type {CheckType}
  276. */
  277. protected static success: CheckType = [null, true];
  278. /**
  279. * A list of basic errors.
  280. * @type {{[key: string]: string[]}}
  281. */
  282. protected static errors: {[key: string]: string[]} = {
  283. // @test ExtraOpenMissingClose
  284. end: ['MissingBeginExtraEnd', 'Missing \\begin{%1} or extra \\end{%1}'],
  285. // @test ExtraCloseMissingOpen
  286. close: ['ExtraCloseMissingOpen', 'Extra close brace or missing open brace'],
  287. // @test MissingLeftExtraRight
  288. right: ['MissingLeftExtraRight', 'Missing \\left or extra \\right'],
  289. middle: ['ExtraMiddle', 'Extra \\middle']
  290. };
  291. /**
  292. * @override
  293. */
  294. public global: EnvList = {};
  295. private _env: EnvList;
  296. private _properties: PropList = {};
  297. /**
  298. * @constructor
  299. * @extends {MmlStack}
  300. */
  301. constructor(protected factory: StackItemFactory, ...nodes: MmlNode[]) {
  302. super(nodes);
  303. if (this.isOpen) {
  304. this._env = {};
  305. }
  306. }
  307. /**
  308. * @return {string} The type of the stack item.
  309. */
  310. public get kind(): string {
  311. return 'base';
  312. }
  313. /**
  314. * @return {EnvList} Get the private environment
  315. */
  316. public get env(): EnvList {
  317. return this._env;
  318. }
  319. /**
  320. * Set the private environment
  321. * @param {EnvList} value New private environemt.
  322. */
  323. public set env(value: EnvList) {
  324. this._env = value;
  325. }
  326. /**
  327. * Default is to copy local environment when pushed on stack
  328. */
  329. public get copyEnv() {
  330. return true;
  331. }
  332. /**
  333. * @override
  334. */
  335. public getProperty(key: string): Prop {
  336. return this._properties[key];
  337. }
  338. /**
  339. * @override
  340. */
  341. public setProperty(key: string, value: Prop) {
  342. this._properties[key] = value;
  343. return this;
  344. }
  345. /**
  346. * @return {boolean} True if item is an opening entity, i.e., it expects a
  347. * closing counterpart on the stack later.
  348. */
  349. get isOpen(): boolean {
  350. return false;
  351. }
  352. /**
  353. * @return {boolean} True if item is an closing entity, i.e., it needs an
  354. * opening counterpart already on the stack.
  355. */
  356. get isClose(): boolean {
  357. return false;
  358. }
  359. /**
  360. * @return {boolean} True if item is final, i.e., it contains one or multiple
  361. * finished parsed nodes.
  362. */
  363. get isFinal(): boolean {
  364. return false;
  365. }
  366. /**
  367. * @override
  368. */
  369. public isKind(kind: string) {
  370. return kind === this.kind;
  371. }
  372. /**
  373. * @override
  374. */
  375. public checkItem(item: StackItem): CheckType {
  376. if (item.isKind('over') && this.isOpen) {
  377. item.setProperty('num', this.toMml(false));
  378. this.Clear();
  379. }
  380. if (item.isKind('cell') && this.isOpen) {
  381. if (item.getProperty('linebreak')) {
  382. return BaseItem.fail;
  383. }
  384. // @test Ampersand-error
  385. throw new TexError('Misplaced', 'Misplaced %1', item.getName());
  386. }
  387. if (item.isClose && this.getErrors(item.kind)) {
  388. // @test ExtraOpenMissingClose, ExtraCloseMissingOpen,
  389. // MissingLeftExtraRight, MissingBeginExtraEnd
  390. const [id, message] = this.getErrors(item.kind);
  391. throw new TexError(id, message, item.getName());
  392. }
  393. if (!item.isFinal) {
  394. return BaseItem.success;
  395. }
  396. this.Push(item.First);
  397. return BaseItem.fail;
  398. }
  399. /**
  400. * Clears the item's environment.
  401. */
  402. public clearEnv() {
  403. for (const id of Object.keys(this.env)) {
  404. delete this.env[id];
  405. }
  406. }
  407. /**
  408. * @override
  409. */
  410. public setProperties(def: PropList) {
  411. Object.assign(this._properties, def);
  412. return this;
  413. }
  414. /**
  415. * @override
  416. */
  417. public getName() {
  418. return this.getProperty('name') as string;
  419. }
  420. /**
  421. * @override
  422. */
  423. public toString() {
  424. return this.kind + '[' + this.nodes.join('; ') + ']';
  425. }
  426. /**
  427. * Get error messages for a particular types of stack items. This reads error
  428. * messages from the static errors object, which can be extended in
  429. * subclasses.
  430. * @param {string} kind The stack item type.
  431. * @return {string[]} The list of arguments for the TeXError.
  432. */
  433. public getErrors(kind: string): string[] {
  434. const CLASS = (this.constructor as typeof BaseItem);
  435. return (CLASS.errors || {})[kind] || BaseItem.errors[kind];
  436. }
  437. }