Skip to content
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

Implement \TextOrMath, \@secondoftwo #1024

Merged
merged 5 commits into from
Dec 22, 2017
Merged

Conversation

edemaine
Copy link
Member

@edemaine edemaine commented Dec 16, 2017

Partially addressing #1003.

  • Parser now tells MacroExpander about mode switching. (This seems preferable to a circular reference between Parser and MacroExpander.)
  • Implement \TextOrMath
  • Improve when we switch modes so that this actually works, in all cases except single-symbol arguments.
  • Add \@secondoftwo to match \@firstoftwo.
  • Add comments documenting all the conditional macros

* Parser now tells MacroExpander about mode switching.  (This seems preferable
  to a circular reference between Parser and MacroExpander.)
* Implement \TextOrMath
* Improve when we switch modes so that this actually works,
  in all cases except single-symbol arguments.
* Add \@secondoftwo to match \@FirstOfTwo.
* Add comments documenting all the conditional macros
Copy link
Member

@kevinbarabash kevinbarabash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! I left a few comments and questions, but no major concerns.

/**
* Switches between "text" and "math" modes.
*/
switchMode(newMode) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newMode: Mode

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

const outerMode = this.mode;
this.switchMode("math");
// Expand next symbol now that we're in math mode.
this.consume();
Copy link
Member

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!

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent comment!

@@ -746,29 +752,25 @@ export default class Parser {
*
* @return {?ParsedFuncOrArgOrDollar}
*/
parseGroupOfType(innerMode, optional) {
const outerMode = this.mode;
parseGroupOfType(type, optional) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice change

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll be nice when we can guarantee this with flow.

// Switch mode back before consuming symbol after close brace
if (mode) {
this.switchMode(outerMode);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is why we have to pass in the mode instead of calling switchMode outside of parseGroup.

src/Parser.js Outdated
@@ -976,6 +989,7 @@ export default class Parser {
firstToken.range(lastToken, firstToken.text));
} else {
// Otherwise, just return a nucleus, or nothing for an optional group
// TODO: Not yet switching modes for single-symbol group.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How much work is involved in this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not actually sure how to do it -- it's pretty challenging, because by the time parseGroup gets called, nextToken already has the single-symbol group, which means that it already got expanded in the wrong mode. In the old way of doing things, we could "unget" the symbol, change modes, and reget it, but that seems challenging/impossible to do if there are side-effects/errors caused by the first execution in the wrong mode. So I think we'd need to call parseGroup before actually reading the first symbol of the group, which might cause a rather big change across all of Parser.js.

So in summary, I'm not sure how much work it'll be. It's surely possible, but hard. I thought I'd commit what I have so far, and open another issue for fixing this further. On the plus side, it took me a lot of playing around with tests before I could even get this situation to arise, and in some sense #924 is even more important than this missing case. So maybe this PR is good to merge for now? Up to you -- I can look at this more when I have time.

@@ -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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

compareParseTree("\\mode\\text{\\mode$\\mode$\\mode}\\mode",
"math\\text{text$math$text}math",
{"\\mode": "\\TextOrMath{text}{math}"});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great test suite!

// 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}"});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this currently result in?

Copy link
Member Author

@edemaine edemaine Dec 19, 2017

Choose a reason for hiding this comment

The 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 MacroExpander and Parser, essentially #924 which I'll update.

@kevinbarabash
Copy link
Member

Code changes look good to me, but there's something causing the Colorbox and DashesAndQuotes screenshot tests to fail.

ColorBox:
screen shot 2017-12-19 at 8 39 02 pm
The x should be in typewriter.

DashesAndQuotes:
screen shot 2017-12-19 at 8 38 30 pm
Three dashes in a row should be an em dash.

@edemaine
Copy link
Member Author

Wow, the power of tests. DashesAndQuotes was a nice easy fix pointing out incorrect mode detection for the ligatures (obvious in hindsight). Unfortunately, the Colorbox issue is exactly the missing part of this PR, switching to text mode for a single-symbol argument (here, x). So maybe I should try to do that before this PR gets merged; otherwise, this PR would regress behavior a bit.

@kevinbarabash
Copy link
Member

@edemaine the following seems to be sufficient to fix the ColorBox screenshot test:

        } else {
            // Otherwise, just return a nucleus, or nothing for an optional group
            if (mode) {
                this.switchMode(mode);
            }
            const result = optional ? null : this.parseSymbol();
            if (mode) {
                this.switchMode(outerMode);
            }
            return result;
        }

This fixes the Colorbox screenshot test.
Copy link
Member

@kevinbarabash kevinbarabash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@kevinbarabash
Copy link
Member

There may still be other places where we need to switch modes before calling parseSymbol, but we can deal with them in a separate PR as necessary.

@kevinbarabash kevinbarabash merged commit c30edaa into KaTeX:master Dec 22, 2017
@edemaine
Copy link
Member Author

@kevinbarabash Thanks for investigating! This partially fixes the single-symbol support, basically whenever the single symbol is not a macro (not expanded by the MacroExpander). I'm glad this makes it mergable (backwards-compatible); now I'll open an issue for the remaining challenge of single-symbol macro.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants