\newcommand, \renewcommand, \providecommand (#1382)

* \newcommand, \renewcommand, \providecommand

* Tests

* Add comment

* Add symbols to the set of already defined things

* Add implicitCommands, catch \hline outside array

* Add \relax

* Move isDefined to be a method of MacroExpander

* Namespace.has

* Reword error messages

* Add \hdashline given #1407
This commit is contained in:
Erik Demaine
2018-06-07 13:39:39 +02:00
committed by GitHub
parent a5ef29fab1
commit 65569249be
9 changed files with 212 additions and 46 deletions

View File

@@ -5,15 +5,10 @@ import ParseError from "../src/ParseError";
import parseTree from "../src/parseTree";
import Settings from "../src/Settings";
export const defaultSettings = new Settings({
strict: false, // deal with warnings only when desired
});
export const nonstrictSettings = new Settings({strict: false});
export const strictSettings = new Settings({strict: true});
export const _getBuilt = function(expr, settings = defaultSettings) {
if (settings === defaultSettings) {
settings.macros = {};
}
export const _getBuilt = function(expr, settings = new Settings()) {
let rootNode = katex.__renderToDomTree(expr, settings);
if (rootNode.classes.indexOf('katex-error') >= 0) {
@@ -43,7 +38,7 @@ export const _getBuilt = function(expr, settings = defaultSettings) {
* @param settings
* @returns {Object}
*/
export const getBuilt = function(expr, settings = defaultSettings) {
export const getBuilt = function(expr, settings = new Settings()) {
expect(expr).toBuild(settings);
return _getBuilt(expr, settings);
};
@@ -54,7 +49,7 @@ export const getBuilt = function(expr, settings = defaultSettings) {
* @param settings
* @returns {Object}
*/
export const getParsed = function(expr, settings = defaultSettings) {
export const getParsed = function(expr, settings = new Settings()) {
expect(expr).toParse(settings);
return parseTree(expr, settings);
};
@@ -73,7 +68,7 @@ export const stripPositions = function(expr) {
};
export const parseAndSetResult = function(expr, result,
settings = defaultSettings) {
settings = new Settings()) {
try {
return parseTree(expr, settings);
} catch (e) {
@@ -89,7 +84,7 @@ export const parseAndSetResult = function(expr, result,
};
export const buildAndSetResult = function(expr, result,
settings = defaultSettings) {
settings = new Settings()) {
try {
return _getBuilt(expr, settings);
} catch (e) {

View File

@@ -11,7 +11,7 @@ import Options from "../src/Options";
import Settings from "../src/Settings";
import Style from "../src/Style";
import {
defaultSettings, strictSettings,
strictSettings, nonstrictSettings,
_getBuilt, getBuilt, getParsed, stripPositions,
} from "./helpers";
@@ -1140,6 +1140,10 @@ describe("A begin/end parser", function() {
expect("\\begin{matrix}\\hdashline a&b\\\\ \\hdashline c&d\\end{matrix}").toParse();
});
it("should forbid hlines outside array environment", () => {
expect("\\hline").toNotParse();
});
it("should error when name is mismatched", function() {
expect("\\begin{matrix}a&b\\\\c&d\\end{pmatrix}").toNotParse();
});
@@ -2282,7 +2286,7 @@ describe("A smash builder", function() {
describe("A parser error", function() {
it("should report the position of an error", function() {
try {
parseTree("\\sqrt}", defaultSettings);
parseTree("\\sqrt}", new Settings());
} catch (e) {
expect(e.position).toEqual(5);
}
@@ -2490,7 +2494,7 @@ describe("A macro expander", function() {
const compareParseTree = function(actual, expected, macros) {
const settings = new Settings({macros: macros});
actual = stripPositions(parseTree(actual, settings));
expected = stripPositions(parseTree(expected, defaultSettings));
expected = stripPositions(parseTree(expected, new Settings()));
expect(actual).toEqual(expected);
};
@@ -2775,6 +2779,57 @@ describe("A macro expander", function() {
expect(macros["\\foo"]).toBeFalsy();
});
it("\\newcommand defines new macros", () => {
compareParseTree("\\newcommand\\foo{x^2}\\foo+\\foo", "x^2+x^2");
compareParseTree("\\newcommand{\\foo}{x^2}\\foo+\\foo", "x^2+x^2");
// Function detection
expect("\\newcommand\\bar{x^2}\\bar+\\bar").toNotParse();
expect("\\newcommand{\\bar}{x^2}\\bar+\\bar").toNotParse();
// Symbol detection
expect("\\newcommand\\lambda{x^2}\\lambda").toNotParse();
expect("\\newcommand\\textdollar{x^2}\\textdollar").toNotParse();
// Macro detection
expect("\\newcommand{\\foo}{1}\\foo\\newcommand{\\foo}{2}\\foo")
.toNotParse();
// Implicit detection
expect("\\newcommand\\limits{}").toNotParse();
});
it("\\renewcommand redefines macros", () => {
expect("\\renewcommand\\foo{x^2}\\foo+\\foo").toNotParse();
expect("\\renewcommand{\\foo}{x^2}\\foo+\\foo").toNotParse();
compareParseTree("\\renewcommand\\bar{x^2}\\bar+\\bar", "x^2+x^2");
compareParseTree("\\renewcommand{\\bar}{x^2}\\bar+\\bar", "x^2+x^2");
expect("\\newcommand{\\foo}{1}\\foo\\renewcommand{\\foo}{2}\\foo")
.toParseLike("12");
});
it("\\providecommand (re)defines macros", () => {
compareParseTree("\\providecommand\\foo{x^2}\\foo+\\foo", "x^2+x^2");
compareParseTree("\\providecommand{\\foo}{x^2}\\foo+\\foo", "x^2+x^2");
compareParseTree("\\providecommand\\bar{x^2}\\bar+\\bar", "x^2+x^2");
compareParseTree("\\providecommand{\\bar}{x^2}\\bar+\\bar", "x^2+x^2");
expect("\\newcommand{\\foo}{1}\\foo\\providecommand{\\foo}{2}\\foo")
.toParseLike("12");
expect("\\providecommand{\\foo}{1}\\foo\\renewcommand{\\foo}{2}\\foo")
.toParseLike("12");
expect("\\providecommand{\\foo}{1}\\foo\\providecommand{\\foo}{2}\\foo")
.toParseLike("12");
});
it("\\newcommand is local", () => {
expect("\\newcommand\\foo{1}\\foo{\\renewcommand\\foo{2}\\foo}\\foo")
.toParseLike("1{2}1");
});
it("\\newcommand accepts number of arguments", () => {
compareParseTree("\\newcommand\\foo[1]{#1^2}\\foo x+\\foo{y}",
"x^2+y^2");
compareParseTree("\\newcommand\\foo[10]{#1^2}\\foo 0123456789", "0^2");
expect("\\newcommand\\foo[x]{}").toNotParse();
expect("\\newcommand\\foo[1.5]{}").toNotParse();
});
// This may change in the future, if we support the extra features of
// \hspace.
it("should treat \\hspace, \\hskip like \\kern", function() {
@@ -2847,7 +2902,7 @@ describe("Unicode accents", function() {
"\\tilde n" +
"\\grave o\\acute o\\hat o\\tilde o\\ddot o" +
"\\grave u\\acute u\\hat u\\ddot u" +
"\\acute y\\ddot y");
"\\acute y\\ddot y", nonstrictSettings);
});
it("should parse Latin-1 letters in text mode", function() {
@@ -2877,19 +2932,19 @@ describe("Unicode accents", function() {
});
it("should parse combining characters", function() {
expect("A\u0301C\u0301").toParseLike("Á\\acute C");
expect("A\u0301C\u0301").toParseLike("Á\\acute C", nonstrictSettings);
expect("\\text{A\u0301C\u0301}").toParseLike("\\text{Á\\'C}", strictSettings);
});
it("should parse multi-accented characters", function() {
expect("ấā́ắ\\text{ấā́ắ}").toParse();
expect("ấā́ắ\\text{ấā́ắ}").toParse(nonstrictSettings);
// Doesn't parse quite the same as
// "\\text{\\'{\\^a}\\'{\\=a}\\'{\\u a}}" because of the ordgroups.
});
it("should parse accented i's and j's", function() {
expect("íȷ́").toParseLike("\\acute ı\\acute ȷ");
expect("ấā́ắ\\text{ấā́ắ}").toParse();
expect("íȷ́").toParseLike("\\acute ı\\acute ȷ", nonstrictSettings);
expect("ấā́ắ\\text{ấā́ắ}").toParse(nonstrictSettings);
});
});
@@ -3076,8 +3131,8 @@ describe("Symbols", function() {
describe("strict setting", function() {
it("should allow unicode text when not strict", () => {
expect("é").toParse(new Settings({strict: false}));
expect("試").toParse(new Settings({strict: false}));
expect("é").toParse(new Settings(nonstrictSettings));
expect("試").toParse(new Settings(nonstrictSettings));
expect("é").toParse(new Settings({strict: "ignore"}));
expect("試").toParse(new Settings({strict: "ignore"}));
expect("é").toParse(new Settings({strict: () => false}));
@@ -3103,7 +3158,7 @@ describe("strict setting", function() {
});
it("should always allow unicode text in text mode", () => {
expect("\\text{é試}").toParse(new Settings({strict: false}));
expect("\\text{é試}").toParse(nonstrictSettings);
expect("\\text{é試}").toParse(strictSettings);
expect("\\text{é試}").toParse();
});

View File

@@ -8,13 +8,9 @@ import Options from "../src/Options";
import Settings from "../src/Settings";
import Style from "../src/Style";
const defaultSettings = new Settings({});
const getMathML = function(expr, settings) {
const usedSettings = settings ? settings : defaultSettings;
const getMathML = function(expr, settings = new Settings()) {
let startStyle = Style.TEXT;
if (usedSettings.displayMode) {
if (settings.displayMode) {
startStyle = Style.DISPLAY;
}
@@ -24,7 +20,7 @@ const getMathML = function(expr, settings) {
maxSize: Infinity,
});
const built = buildMathML(parseTree(expr, usedSettings), expr, options);
const built = buildMathML(parseTree(expr, settings), expr, options);
// Strip off the surrounding <span>
return built.children[0].toMarkup();

View File

@@ -4,10 +4,10 @@
import katex from "../katex";
import ParseError from "../src/ParseError";
import parseTree from "../src/parseTree";
import Settings from "../src/Settings";
import Warning from "./Warning";
import stringify from 'json-stable-stringify';
import {
defaultSettings,
_getBuilt, buildAndSetResult, parseAndSetResult, stripPositions,
} from "./helpers";
@@ -44,7 +44,7 @@ global.console.warn = jest.fn((warning) => {
// Expect extensions
expect.extend({
toParse: function(actual, settings = defaultSettings) {
toParse: function(actual, settings = new Settings()) {
const result = {
pass: true,
message: () => `'${actual}' succeeded parsing`,
@@ -53,7 +53,7 @@ expect.extend({
return result;
},
toNotParse: function(actual, settings = defaultSettings) {
toNotParse: function(actual, settings = new Settings()) {
const result = {
pass: false,
message: () =>
@@ -79,7 +79,7 @@ expect.extend({
toFailWithParseError: function(actual, expected) {
const prefix = "KaTeX parse error: ";
try {
parseTree(actual, defaultSettings);
parseTree(actual, new Settings());
return {
pass: false,
message: () => `'${actual}' parsed without error`,
@@ -115,7 +115,7 @@ expect.extend({
}
},
toBuild: function(actual, settings = defaultSettings) {
toBuild: function(actual, settings = new Settings()) {
const result = {
pass: true,
message: () => `'${actual}' succeeded in building`,
@@ -124,7 +124,7 @@ expect.extend({
return result;
},
toNotBuild: function(actual, settings = defaultSettings) {
toNotBuild: function(actual, settings = new Settings()) {
const result = {
pass: false,
message: () =>
@@ -147,7 +147,7 @@ expect.extend({
return result;
},
toParseLike: function(actual, expected, settings = defaultSettings) {
toParseLike: function(actual, expected, settings = new Settings()) {
const result = {
pass: true,
message: () =>
@@ -174,7 +174,7 @@ expect.extend({
return result;
},
toBuildLike: function(actual, expected, settings = defaultSettings) {
toBuildLike: function(actual, expected, settings = new Settings()) {
const result = {
pass: true,
message: () =>
@@ -201,7 +201,7 @@ expect.extend({
return result;
},
toWarn: function(actual, settings = defaultSettings) {
toWarn: function(actual, settings = new Settings()) {
const result = {
pass: false,
message: () =>

View File

@@ -4,7 +4,7 @@
/* global describe: false */
import Settings from "../src/Settings";
import {scriptFromCodepoint, supportedCodepoint} from "../src/unicodeScripts";
import {strictSettings} from "./helpers";
import {strictSettings, nonstrictSettings} from "./helpers";
describe("unicode", function() {
it("should parse Latin-1 inside \\text{}", function() {
@@ -21,7 +21,7 @@ describe("unicode", function() {
it("should parse Latin-1 outside \\text{}", function() {
expect('ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ' +
'ÇÐÞçðþ').toParse();
'ÇÐÞçðþ').toParse(nonstrictSettings);
});
it("should parse all lower case Greek letters", function() {