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 ce0c12c49..0f804f707 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}}";