From c566ae68885ae9767c72e883978fdab40236db04 Mon Sep 17 00:00:00 2001 From: Kevin Barabash Date: Sun, 21 Sep 2014 20:50:58 -0600 Subject: [PATCH] added support for \over changed stopType (string) parameter to breakOnInfix (boolean) renamed rewriteInfixNodes to handleInfixNodes added a test for {1 \over 2} \over 3, fixed some grammar, and added code in the parser to squash superfluous ordgroups removed squashOrdGroups and instead don't create an "ordgroup" if one already exists removed unnecessary variable moved variable declarations out of "if" statements removed comment Fixed style issue with where variables are declared and remove unnecessary comment from functions.js --- src/Parser.js | 66 +++++++++++++++++++++++++++++++--- src/functions.js | 10 ++++++ test/katex-spec.js | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 5 deletions(-) diff --git a/src/Parser.js b/src/Parser.js index 1874da515..865fb2b5a 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -120,21 +120,77 @@ Parser.prototype.parseInput = function(pos, mode) { /** * Handles a body of an expression. */ -Parser.prototype.handleExpressionBody = function(pos, mode) { +Parser.prototype.handleExpressionBody = function(pos, mode, breakOnInfix) { var body = []; var atom; // Keep adding atoms to the body until we can't parse any more atoms (either // we reached the end, a }, or a \right) while ((atom = this.parseAtom(pos, mode))) { - body.push(atom.result); - pos = atom.position; + if (breakOnInfix && atom.result.type === "infix") { + break; + } else { + body.push(atom.result); + pos = atom.position; + } } return { - body: body, + body: this.handleInfixNodes(body, mode), position: pos }; }; +/** + * Rewrites infix operators such as \over with corresponding commands such + * as \frac. + * + * There can only be one infix operator per group. If there's more than one + * then the expression is ambiguous. This can be resolved by adding {}. + * + * @returns {Array} + */ +Parser.prototype.handleInfixNodes = function (body, mode) { + var overIndex = -1; + var func; + var funcName; + + for (var i = 0; i < body.length; i++) { + var node = body[i]; + if (node.type === "infix") { + if (overIndex !== -1) { + throw new ParseError("only one infix operator per group", + this.lexer, -1); + } + overIndex = i; + funcName = node.value.replaceWith; + func = functions.funcs[funcName]; + } + } + + if (overIndex !== -1) { + var numerNode, denomNode; + + var numerBody = body.slice(0, overIndex); + var denomBody = body.slice(overIndex + 1); + + if (numerBody.length === 1 && numerBody[0].type === "ordgroup") { + numerNode = numerBody[0]; + } else { + numerNode = new ParseNode("ordgroup", numerBody, mode); + } + + if (denomBody.length === 1 && denomBody[0].type === "ordgroup") { + denomNode = denomBody[0]; + } else { + denomNode = new ParseNode("ordgroup", denomBody, mode); + } + + var value = func.handler(funcName, numerNode, denomNode); + return [new ParseNode(value.type, value, mode)]; + } else { + return body; + } +}; + /** * Parses an "expression", which is a list of atoms. * @@ -332,7 +388,7 @@ Parser.prototype.parseImplicitGroup = function(pos, mode) { body.position); } else if (utils.contains(styleFuncs, func)) { // If we see a styling function, parse out the implict body - var body = this.handleExpressionBody(start.result.position, mode); + var body = this.handleExpressionBody(start.result.position, mode, true); return new ParseResult( new ParseNode("styling", { // Figure out what style to use by pulling out the style from diff --git a/src/functions.js b/src/functions.js index 3b0d69bde..0c28dcda1 100644 --- a/src/functions.js +++ b/src/functions.js @@ -150,6 +150,16 @@ var functions = { type: "katex" }; } + }, + + "\\over": { + numArgs: 0, + handler: function (func) { + return { + type: "infix", + replaceWith: "\\frac" + } + } } }; diff --git a/test/katex-spec.js b/test/katex-spec.js index ef977932b..b2508417e 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -474,6 +474,94 @@ describe("A frac parser", function() { }); }); +describe("An over parser", function() { + var simpleOver = "1 \\over x"; + var complexOver = "1+2i \\over 3+4i"; + + it("should not fail", function () { + expect(simpleOver).toParse(); + expect(complexOver).toParse(); + }); + + it("should produce a frac", function() { + var parse; + + parse = parseTree(simpleOver)[0]; + + expect(parse.type).toMatch("frac"); + expect(parse.value.numer).toBeDefined(); + expect(parse.value.denom).toBeDefined(); + + parse = parseTree(complexOver)[0]; + + expect(parse.type).toMatch("frac"); + expect(parse.value.numer).toBeDefined(); + expect(parse.value.denom).toBeDefined(); + }); + + it("should create a numerator from the atoms before \\over", function () { + var parse = parseTree(complexOver)[0]; + + var numer = parse.value.numer; + expect(numer.value.length).toEqual(4); + }); + + it("should create a demonimator from the atoms after \\over", function () { + var parse = parseTree(complexOver)[0]; + + var denom = parse.value.numer; + expect(denom.value.length).toEqual(4); + }); + + it("should handle empty numerators", function () { + var emptyNumerator = "\\over x"; + expect(emptyNumerator).toParse(); + + var parse = parseTree(emptyNumerator)[0]; + expect(parse.type).toMatch("frac"); + expect(parse.value.numer).toBeDefined(); + expect(parse.value.denom).toBeDefined(); + }); + + it("should handle empty denominators", function () { + var emptyDenominator = "1 \\over"; + expect(emptyDenominator).toParse(); + + var parse = parseTree(emptyDenominator)[0]; + expect(parse.type).toMatch("frac"); + expect(parse.value.numer).toBeDefined(); + expect(parse.value.denom).toBeDefined(); + }); + + it("should handle \\displaystyle correctly", function () { + var displaystyleExpression = "\\displaystyle 1 \\over 2"; + expect(displaystyleExpression).toParse(); + + var parse = parseTree(displaystyleExpression)[0]; + expect(parse.type).toMatch("frac"); + expect(parse.value.numer.value[0].type).toMatch("styling"); + expect(parse.value.denom).toBeDefined(); + }); + + it("should handle nested factions", function () { + var nestedOverExpression = "{1 \\over 2} \\over 3"; + expect(nestedOverExpression).toParse(); + + var parse = parseTree(nestedOverExpression)[0]; + expect(parse.type).toMatch("frac"); + expect(parse.value.numer.value[0].type).toMatch("frac"); + expect(parse.value.numer.value[0].value.numer.value[0].value).toMatch(1); + expect(parse.value.numer.value[0].value.denom.value[0].value).toMatch(2); + expect(parse.value.denom).toBeDefined(); + expect(parse.value.denom.value[0].value).toMatch(3); + }); + + it("should fail with multiple overs in the same group", function () { + var badMultipleOvers = "1 \\over 2 + 3 \\over 4"; + expect(badMultipleOvers).toNotParse(); + }); +}); + describe("A sizing parser", function() { var sizeExpression = "\\Huge{x}\\small{x}"; var nestedSizeExpression = "\\Huge{\\small{x}}";