-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Implement \TextOrMath, \@secondoftwo #1024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
59d40f0
3ebdd30
cecd839
259a8cd
7bd4f59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,9 +102,11 @@ function assertFuncOrArg(parsed) { | |
|
|
||
| export default class Parser { | ||
| constructor(input, settings) { | ||
| // Start in math mode | ||
| this.mode = "math"; | ||
| // Create a new macro expander (gullet) and (indirectly via that) also a | ||
| // new lexer (mouth) for this parser (stomach, in the language of TeX) | ||
| this.gullet = new MacroExpander(input, settings.macros); | ||
| this.gullet = new MacroExpander(input, settings.macros, this.mode); | ||
| // Use old \color behavior (same as LaTeX's \textcolor) if requested. | ||
| // We do this after the macros object has been copied by MacroExpander. | ||
| if (settings.colorIsTextColor) { | ||
|
|
@@ -148,6 +150,7 @@ export default class Parser { | |
| */ | ||
| switchMode(newMode) { | ||
| this.mode = newMode; | ||
| this.gullet.switchMode(newMode); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -157,7 +160,6 @@ export default class Parser { | |
| */ | ||
| parse() { | ||
| // Try to parse the input | ||
| this.mode = "math"; | ||
| this.consume(); | ||
| const parse = this.parseInput(); | ||
| return parse; | ||
|
|
@@ -586,12 +588,16 @@ export default class Parser { | |
| if (this.mode === "math") { | ||
| throw new ParseError("$ within math mode"); | ||
| } | ||
| this.consume(); | ||
| const outerMode = this.mode; | ||
| this.switchMode("math"); | ||
| // Expand next symbol now that we're in math mode. | ||
| this.consume(); | ||
| const body = this.parseExpression(false, "$"); | ||
| this.expect("$", true); | ||
| // We can't expand the next symbol after the $ until after | ||
| // switching modes back. So don't consume within expect. | ||
| this.expect("$", false); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent comment! |
||
| this.switchMode(outerMode); | ||
| this.consume(); | ||
| return new ParseNode("styling", { | ||
| style: "text", | ||
| value: body, | ||
|
|
@@ -746,29 +752,25 @@ export default class Parser { | |
| * | ||
| * @return {?ParsedFuncOrArgOrDollar} | ||
| */ | ||
| parseGroupOfType(innerMode, optional) { | ||
| const outerMode = this.mode; | ||
| parseGroupOfType(type, optional) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice change |
||
| // Handle `original` argTypes | ||
| if (innerMode === "original") { | ||
| innerMode = outerMode; | ||
| if (type === "original") { | ||
| type = this.mode; | ||
| } | ||
|
|
||
| if (innerMode === "color") { | ||
| if (type === "color") { | ||
| return this.parseColorGroup(optional); | ||
| } | ||
| if (innerMode === "size") { | ||
| if (type === "size") { | ||
| return this.parseSizeGroup(optional); | ||
| } | ||
| if (innerMode === "url") { | ||
| if (type === "url") { | ||
| return this.parseUrlGroup(optional); | ||
| } | ||
|
|
||
| // By the time we get here, innerMode is one of "text" or "math". | ||
| // We switch the mode of the parser, recurse, then restore the old mode. | ||
| this.switchMode(innerMode); | ||
| const res = this.parseGroup(optional); | ||
| this.switchMode(outerMode); | ||
| return res; | ||
| // By the time we get here, type is one of "text" or "math". | ||
| // Specify this as mode to parseGroup. | ||
| return this.parseGroup(optional, type); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'll be nice when we can guarantee this with |
||
| } | ||
|
|
||
| consumeSpaces() { | ||
|
|
@@ -947,27 +949,38 @@ export default class Parser { | |
| } | ||
|
|
||
| /** | ||
| * If the argument is false or absent, this parses an ordinary group, | ||
| * If `optional` is false or absent, this parses an ordinary group, | ||
| * which is either a single nucleus (like "x") or an expression | ||
| * in braces (like "{x+y}"). | ||
| * If the argument is true, it parses either a bracket-delimited expression | ||
| * If `optional` is true, it parses either a bracket-delimited expression | ||
| * (like "[x+y]") or returns null to indicate the absence of a | ||
| * bracket-enclosed group. | ||
| * If `mode` is present, switches to that mode while parsing the group, | ||
| * and switches back after. | ||
| * | ||
| * @param {boolean=} optional Whether the group is optional or required | ||
| * @return {?ParsedFuncOrArgOrDollar} | ||
| */ | ||
| parseGroup(optional) { | ||
| parseGroup(optional, mode) { | ||
| const outerMode = this.mode; | ||
| const firstToken = this.nextToken; | ||
| // Try to parse an open brace | ||
| if (this.nextToken.text === (optional ? "[" : "{")) { | ||
| // Switch to specified mode before we expand symbol after brace | ||
| if (mode) { | ||
| this.switchMode(mode); | ||
| } | ||
| // If we get a brace, parse an expression | ||
| this.consume(); | ||
| const expression = this.parseExpression(false, optional ? "]" : "}"); | ||
| const lastToken = this.nextToken; | ||
| // Switch mode back before consuming symbol after close brace | ||
| if (mode) { | ||
| this.switchMode(outerMode); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this is why we have to pass in the |
||
| // Make sure we get a close brace | ||
| this.expect(optional ? "]" : "}"); | ||
| if (this.mode === "text") { | ||
| if (mode === "text") { | ||
| this.formLigatures(expression); | ||
| } | ||
| return newArgument( | ||
|
|
@@ -976,7 +989,14 @@ export default class Parser { | |
| firstToken.range(lastToken, firstToken.text)); | ||
| } else { | ||
| // Otherwise, just return a nucleus, or nothing for an optional group | ||
| return optional ? null : this.parseSymbol(); | ||
| if (mode) { | ||
| this.switchMode(mode); | ||
| } | ||
| const result = optional ? null : this.parseSymbol(); | ||
| if (mode) { | ||
| this.switchMode(outerMode); | ||
| } | ||
| return result; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2672,11 +2672,61 @@ describe("A macro expander", function() { | |
| expect("\\@ifnextchar!{yes}{no}?!").toParseLike("no?!"); | ||
| }); | ||
|
|
||
| it("\\@firstoftwwo should consume star but nothing else", function() { | ||
| it("\\@ifstar should consume star but nothing else", function() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch |
||
| expect("\\@ifstar{yes}{no}*!").toParseLike("yes!"); | ||
| expect("\\@ifstar{yes}{no}?!").toParseLike("no?!"); | ||
| }); | ||
|
|
||
| it("\\TextOrMath should work immediately", function() { | ||
| expect("\\TextOrMath{text}{math}").toParseLike("math"); | ||
| }); | ||
|
|
||
| it("\\TextOrMath should work after other math", function() { | ||
| expect("x+\\TextOrMath{text}{math}").toParseLike("x+math"); | ||
| }); | ||
|
|
||
| it("\\TextOrMath should work immediately after \\text", function() { | ||
| expect("\\text{\\TextOrMath{text}{math}}").toParseLike("\\text{text}"); | ||
| }); | ||
|
|
||
| it("\\TextOrMath should work later after \\text", function() { | ||
| expect("\\text{hello \\TextOrMath{text}{math}}") | ||
| .toParseLike("\\text{hello text}"); | ||
| }); | ||
|
|
||
| it("\\TextOrMath should work immediately after \\text ends", function() { | ||
| expect("\\text{\\TextOrMath{text}{math}}\\TextOrMath{text}{math}") | ||
| .toParseLike("\\text{text}math"); | ||
| }); | ||
|
|
||
| it("\\TextOrMath should work immediately after $", function() { | ||
| expect("\\text{$\\TextOrMath{text}{math}$}") | ||
| .toParseLike("\\text{$math$}"); | ||
| }); | ||
|
|
||
| it("\\TextOrMath should work later after $", function() { | ||
| expect("\\text{$x+\\TextOrMath{text}{math}$}") | ||
| .toParseLike("\\text{$x+math$}"); | ||
| }); | ||
|
|
||
| it("\\TextOrMath should work immediately after $ ends", function() { | ||
| expect("\\text{$\\TextOrMath{text}{math}$\\TextOrMath{text}{math}}") | ||
| .toParseLike("\\text{$math$text}"); | ||
| }); | ||
|
|
||
| it("\\TextOrMath should work in a macro", function() { | ||
| compareParseTree("\\mode\\text{\\mode$\\mode$\\mode}\\mode", | ||
| "math\\text{text$math$text}math", | ||
| {"\\mode": "\\TextOrMath{text}{math}"}); | ||
| }); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great test suite! |
||
|
|
||
| // TODO(edemaine): This doesn't work yet. Parses like `\text math`, | ||
| // which doesn't even treat all four letters as an argument. | ||
| //it("\\TextOrMath should work in a macro passed to \\text", function() { | ||
| // compareParseTree("\\text\\mode", "\\text{text}", | ||
| // {"\\mode": "\\TextOrMath{text}{math}"}); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this currently result in?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It results in "m" formatted in text mode followed by "ath" formatted in math mode. So it's wrong in two ways: it thinks it's in math mode when it's actually in text mode, and the argument is just the first character instead of all four. This is a bug with interaction between |
||
| //}); | ||
|
|
||
| // This may change in the future, if we support the extra features of | ||
| // \hspace. | ||
| it("should treat \\hspace, \\hspace*, \\hskip like \\kern", function() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This had to be moved so that the gullet is in the right mode before expansion, cool!