BussproofsMethods.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2018-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 Mappings for TeX parsing for the bussproofs package.
  19. *
  20. * @author v.sorge@mathjax.org (Volker Sorge)
  21. */
  22. import {ParseMethod} from '../Types.js';
  23. import TexError from '../TexError.js';
  24. import TexParser from '../TexParser.js';
  25. import ParseUtil from '../ParseUtil.js';
  26. import {StackItem} from '../StackItem.js';
  27. import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
  28. import * as BussproofsUtil from './BussproofsUtil.js';
  29. // Namespace
  30. let BussproofsMethods: Record<string, ParseMethod> = {};
  31. /**
  32. * Implements the proof tree environment.
  33. * @param {TexParser} parser The current parser.
  34. * @param {StackItem} begin The opening element of the environment.
  35. * @return {StackItem} The proof tree stackitem.
  36. */
  37. // TODO: Error handling if we have leftover elements or elements are not in the
  38. // required order.
  39. BussproofsMethods.Prooftree = function(parser: TexParser, begin: StackItem): StackItem {
  40. parser.Push(begin);
  41. // TODO: Check if opening a proof tree is legal.
  42. let newItem = parser.itemFactory.create('proofTree').
  43. setProperties({name: begin.getName(),
  44. line: 'solid', currentLine: 'solid', rootAtTop: false});
  45. // parser.Push(item);
  46. return newItem;
  47. };
  48. /**
  49. * Implements the Axiom command.
  50. * @param {TexParser} parser The current parser.
  51. * @param {string} name The name of the command.
  52. */
  53. BussproofsMethods.Axiom = function(parser: TexParser, name: string) {
  54. let top = parser.stack.Top();
  55. // TODO: Label error
  56. if (top.kind !== 'proofTree') {
  57. throw new TexError('IllegalProofCommand',
  58. 'Proof commands only allowed in prooftree environment.');
  59. }
  60. let content = paddedContent(parser, parser.GetArgument(name));
  61. BussproofsUtil.setProperty(content, 'axiom', true);
  62. top.Push(content);
  63. };
  64. /**
  65. * Pads content of an inference rule.
  66. * @param {TexParser} parser The calling parser.
  67. * @param {string} content The content to be padded.
  68. * @return {MmlNode} The mrow element with padded content.
  69. */
  70. const paddedContent = function(parser: TexParser, content: string): MmlNode {
  71. // Add padding on either site.
  72. let nodes = ParseUtil.internalMath(parser, ParseUtil.trimSpaces(content), 0);
  73. if (!nodes[0].childNodes[0].childNodes.length) {
  74. return parser.create('node', 'mrow', []);
  75. }
  76. let lpad = parser.create('node', 'mspace', [], {width: '.5ex'});
  77. let rpad = parser.create('node', 'mspace', [], {width: '.5ex'});
  78. return parser.create('node', 'mrow', [lpad, ...nodes, rpad]);
  79. };
  80. /**
  81. * Implements the Inference rule commands.
  82. * @param {TexParser} parser The current parser.
  83. * @param {string} name The name of the command.
  84. * @param {number} n Number of premises for this inference rule.
  85. */
  86. BussproofsMethods.Inference = function(parser: TexParser, name: string, n: number) {
  87. let top = parser.stack.Top();
  88. if (top.kind !== 'proofTree') {
  89. throw new TexError('IllegalProofCommand',
  90. 'Proof commands only allowed in prooftree environment.');
  91. }
  92. if (top.Size() < n) {
  93. throw new TexError('BadProofTree', 'Proof tree badly specified.');
  94. }
  95. const rootAtTop = top.getProperty('rootAtTop') as boolean;
  96. const childCount = (n === 1 && !top.Peek()[0].childNodes.length) ? 0 : n;
  97. let children: MmlNode[] = [];
  98. do {
  99. if (children.length) {
  100. children.unshift(parser.create('node', 'mtd', [], {}));
  101. }
  102. children.unshift(
  103. parser.create('node', 'mtd', [top.Pop()],
  104. {'rowalign': (rootAtTop ? 'top' : 'bottom')}));
  105. n--;
  106. } while (n > 0);
  107. let row = parser.create('node', 'mtr', children, {});
  108. let table = parser.create('node', 'mtable', [row], {framespacing: '0 0'});
  109. let conclusion = paddedContent(parser, parser.GetArgument(name));
  110. let style = top.getProperty('currentLine') as string;
  111. if (style !== top.getProperty('line')) {
  112. top.setProperty('currentLine', top.getProperty('line'));
  113. }
  114. let rule = createRule(
  115. parser, table, [conclusion], top.getProperty('left') as MmlNode,
  116. top.getProperty('right') as MmlNode, style, rootAtTop);
  117. top.setProperty('left', null);
  118. top.setProperty('right', null);
  119. BussproofsUtil.setProperty(rule, 'inference', childCount);
  120. parser.configuration.addNode('inference', rule);
  121. top.Push(rule);
  122. };
  123. /**
  124. * Creates a ND style inference rule.
  125. * @param {TexParser} parser The calling parser.
  126. * @param {MmlNode} premise The premise (a single table).
  127. * @param {MmlNode[]} conclusions Elements that are combined into the conclusion.
  128. * @param {MmlNode|null} left The left label if it exists.
  129. * @param {MmlNode|null} right The right label if it exists.
  130. * @param {string} style Style of inference rule line.
  131. * @param {boolean} rootAtTop Direction of inference rule: true for root at top.
  132. */
  133. function createRule(parser: TexParser, premise: MmlNode,
  134. conclusions: MmlNode[], left: MmlNode | null,
  135. right: MmlNode | null, style: string,
  136. rootAtTop: boolean) {
  137. const upper = parser.create(
  138. 'node', 'mtr', [parser.create('node', 'mtd', [premise], {})], {});
  139. const lower = parser.create(
  140. 'node', 'mtr', [parser.create('node', 'mtd', conclusions, {})], {});
  141. let rule = parser.create('node', 'mtable', rootAtTop ? [lower, upper] : [upper, lower],
  142. {align: 'top 2', rowlines: style, framespacing: '0 0'});
  143. BussproofsUtil.setProperty(rule, 'inferenceRule', rootAtTop ? 'up' : 'down');
  144. let leftLabel, rightLabel;
  145. if (left) {
  146. leftLabel = parser.create(
  147. 'node', 'mpadded', [left],
  148. {height: '+.5em', width: '+.5em', voffset: '-.15em'});
  149. BussproofsUtil.setProperty(leftLabel, 'prooflabel', 'left');
  150. }
  151. if (right) {
  152. rightLabel = parser.create(
  153. 'node', 'mpadded', [right],
  154. {height: '+.5em', width: '+.5em', voffset: '-.15em'});
  155. BussproofsUtil.setProperty(rightLabel, 'prooflabel', 'right');
  156. }
  157. let children, label;
  158. if (left && right) {
  159. children = [leftLabel, rule, rightLabel];
  160. label = 'both';
  161. } else if (left) {
  162. children = [leftLabel, rule];
  163. label = 'left';
  164. } else if (right) {
  165. children = [rule, rightLabel];
  166. label = 'right';
  167. } else {
  168. return rule;
  169. }
  170. rule = parser.create('node', 'mrow', children);
  171. BussproofsUtil.setProperty(rule, 'labelledRule', label);
  172. return rule;
  173. }
  174. /**
  175. * Implements the label command.
  176. * @param {TexParser} parser The current parser.
  177. * @param {string} name The name of the command.
  178. * @param {string} side The side of the label.
  179. */
  180. BussproofsMethods.Label = function(parser: TexParser, name: string, side: string) {
  181. let top = parser.stack.Top();
  182. // Label error
  183. if (top.kind !== 'proofTree') {
  184. throw new TexError('IllegalProofCommand',
  185. 'Proof commands only allowed in prooftree environment.');
  186. }
  187. let content = ParseUtil.internalMath(parser, parser.GetArgument(name), 0);
  188. let label = (content.length > 1) ?
  189. parser.create('node', 'mrow', content, {}) : content[0];
  190. top.setProperty(side, label);
  191. };
  192. /**
  193. * Sets line style for inference rules.
  194. * @param {TexParser} parser The current parser.
  195. * @param {string} name The name of the command.
  196. * @param {string} style The line style to set.
  197. * @param {boolean} always Set as permanent style.
  198. */
  199. BussproofsMethods.SetLine = function(parser: TexParser, _name: string, style: string, always: boolean) {
  200. let top = parser.stack.Top();
  201. // Label error
  202. if (top.kind !== 'proofTree') {
  203. throw new TexError('IllegalProofCommand',
  204. 'Proof commands only allowed in prooftree environment.');
  205. }
  206. top.setProperty('currentLine', style);
  207. if (always) {
  208. top.setProperty('line', style);
  209. }
  210. };
  211. /**
  212. * Implements commands indicating where the root of the proof tree is.
  213. * @param {TexParser} parser The current parser.
  214. * @param {string} name The name of the command.
  215. * @param {string} where If true root is at top, otherwise at bottom.
  216. */
  217. BussproofsMethods.RootAtTop = function(parser: TexParser, _name: string, where: boolean) {
  218. let top = parser.stack.Top();
  219. if (top.kind !== 'proofTree') {
  220. throw new TexError('IllegalProofCommand',
  221. 'Proof commands only allowed in prooftree environment.');
  222. }
  223. top.setProperty('rootAtTop', where);
  224. };
  225. /**
  226. * Implements Axiom command for sequents.
  227. * @param {TexParser} parser The current parser.
  228. * @param {string} name The name of the command.
  229. */
  230. BussproofsMethods.AxiomF = function(parser: TexParser, name: string) {
  231. let top = parser.stack.Top();
  232. if (top.kind !== 'proofTree') {
  233. throw new TexError('IllegalProofCommand',
  234. 'Proof commands only allowed in prooftree environment.');
  235. }
  236. let line = parseFCenterLine(parser, name);
  237. BussproofsUtil.setProperty(line, 'axiom', true);
  238. top.Push(line);
  239. };
  240. /**
  241. * Parses a line with a sequent (i.e., one containing \\fcenter).
  242. * @param {TexParser} parser The current parser.
  243. * @param {string} name The name of the calling command.
  244. * @return {MmlNode} The parsed line.
  245. */
  246. function parseFCenterLine(parser: TexParser, name: string): MmlNode {
  247. let dollar = parser.GetNext();
  248. if (dollar !== '$') {
  249. throw new TexError('IllegalUseOfCommand',
  250. 'Use of %1 does not match it\'s definition.', name);
  251. }
  252. parser.i++;
  253. let axiom = parser.GetUpTo(name, '$');
  254. if (axiom.indexOf('\\fCenter') === -1) {
  255. throw new TexError('IllegalUseOfCommand',
  256. 'Missing \\fCenter in %1.', name);
  257. }
  258. // Check for fCenter and throw error?
  259. let [prem, conc] = axiom.split('\\fCenter');
  260. let premise = (new TexParser(prem, parser.stack.env, parser.configuration)).mml();
  261. let conclusion = (new TexParser(conc, parser.stack.env, parser.configuration)).mml();
  262. let fcenter = (new TexParser('\\fCenter', parser.stack.env, parser.configuration)).mml();
  263. const left = parser.create('node', 'mtd', [premise], {});
  264. const middle = parser.create('node', 'mtd', [fcenter], {});
  265. const right = parser.create('node', 'mtd', [conclusion], {});
  266. const row = parser.create('node', 'mtr', [left, middle, right], {});
  267. const table = parser.create('node', 'mtable', [row], {columnspacing: '.5ex', columnalign: 'center 2'});
  268. BussproofsUtil.setProperty(table, 'sequent', true);
  269. parser.configuration.addNode('sequent', row);
  270. return table;
  271. }
  272. /**
  273. * Placeholder for Fcenter macro that can be overwritten with renewcommand.
  274. * @param {TexParser} parser The current parser.
  275. * @param {string} name The name of the command.
  276. */
  277. BussproofsMethods.FCenter = function(_parser: TexParser, _name: string) { };
  278. /**
  279. * Implements inference rules for sequents.
  280. * @param {TexParser} parser The current parser.
  281. * @param {string} name The name of the command.
  282. * @param {number} n Number of premises for this inference rule.
  283. */
  284. BussproofsMethods.InferenceF = function(parser: TexParser, name: string, n: number) {
  285. let top = parser.stack.Top();
  286. if (top.kind !== 'proofTree') {
  287. throw new TexError('IllegalProofCommand',
  288. 'Proof commands only allowed in prooftree environment.');
  289. }
  290. if (top.Size() < n) {
  291. throw new TexError('BadProofTree', 'Proof tree badly specified.');
  292. }
  293. const rootAtTop = top.getProperty('rootAtTop') as boolean;
  294. const childCount = (n === 1 && !top.Peek()[0].childNodes.length) ? 0 : n;
  295. let children: MmlNode[] = [];
  296. do {
  297. if (children.length) {
  298. children.unshift(parser.create('node', 'mtd', [], {}));
  299. }
  300. children.unshift(
  301. parser.create('node', 'mtd', [top.Pop()],
  302. {'rowalign': (rootAtTop ? 'top' : 'bottom')}));
  303. n--;
  304. } while (n > 0);
  305. let row = parser.create('node', 'mtr', children, {});
  306. let table = parser.create('node', 'mtable', [row], {framespacing: '0 0'});
  307. let conclusion = parseFCenterLine(parser, name); // TODO: Padding
  308. let style = top.getProperty('currentLine') as string;
  309. if (style !== top.getProperty('line')) {
  310. top.setProperty('currentLine', top.getProperty('line'));
  311. }
  312. let rule = createRule(
  313. parser, table, [conclusion], top.getProperty('left') as MmlNode,
  314. top.getProperty('right') as MmlNode, style, rootAtTop);
  315. top.setProperty('left', null);
  316. top.setProperty('right', null);
  317. BussproofsUtil.setProperty(rule, 'inference', childCount);
  318. parser.configuration.addNode('inference', rule);
  319. top.Push(rule);
  320. };
  321. export default BussproofsMethods;