Comparing version 0.3.0 to 0.4.0
22
cli.js
@@ -8,9 +8,25 @@ #!/usr/bin/env node | ||
// Skip the first two args, which are just "node" and "cli.js" | ||
var args = process.argv.slice(2); | ||
if (args.indexOf("--help") != -1) { | ||
console.log(process.argv[0] + " " + process.argv[1] + | ||
" [ --help ]" + | ||
" [ --display-mode ]"); | ||
console.log("\n" + | ||
"Options:"); | ||
console.log(" --help Display this help message"); | ||
console.log(" --display-mode Render in display mode (not inline mode)"); | ||
process.exit(); | ||
} | ||
process.stdin.on("data", function(chunk) { | ||
input += chunk.toString(); | ||
input += chunk.toString(); | ||
}); | ||
process.stdin.on("end", function() { | ||
var output = katex.renderToString(input); | ||
console.log(output); | ||
var options = { displayMode: args.indexOf("--display-mode") != -1 }; | ||
var output = katex.renderToString(input, options); | ||
console.log(output); | ||
}); |
14
katex.js
@@ -55,6 +55,20 @@ /** | ||
/** | ||
* Parse an expression and return the parse tree. | ||
*/ | ||
var generateParseTree = function(expression, options) { | ||
var settings = new Settings(options); | ||
return parseTree(expression, settings); | ||
}; | ||
module.exports = { | ||
render: render, | ||
renderToString: renderToString, | ||
/** | ||
* NOTE: This method is not currently recommended for public use. | ||
* The internal tree representation is unstable and is very likely | ||
* to change. Use at your own risk. | ||
*/ | ||
__parse: generateParseTree, | ||
ParseError: ParseError | ||
}; |
{ | ||
"name": "katex", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Fast math typesetting for the web.", | ||
@@ -28,3 +28,6 @@ "main": "katex.js", | ||
"test": "make lint test" | ||
}, | ||
"dependencies": { | ||
"match-at": "^0.1.0" | ||
} | ||
} |
@@ -17,4 +17,4 @@ # [<img src="https://khan.github.io/KaTeX/katex-logo.svg" width="130" alt="KaTeX">](https://khan.github.io/KaTeX/) [![Build Status](https://travis-ci.org/Khan/KaTeX.svg?branch=master)](https://travis-ci.org/Khan/KaTeX) | ||
```html | ||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.2.0/katex.min.css"> | ||
<script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.2.0/katex.min.js"></script> | ||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min.css"> | ||
<script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min.js"></script> | ||
``` | ||
@@ -30,2 +30,4 @@ | ||
If KaTeX can't parse the expression, it throws a `katex.ParseError` error. | ||
#### Server side rendering or rendering to a string | ||
@@ -40,3 +42,3 @@ | ||
Make sure to include the CSS and font files, but there is no need to include the JavaScript. | ||
Make sure to include the CSS and font files, but there is no need to include the JavaScript. Like `render`, `renderToString` throws if it can't parse the expression. | ||
@@ -43,0 +45,0 @@ #### Rendering options |
@@ -46,2 +46,3 @@ /** | ||
genfrac: "minner", | ||
array: "minner", | ||
spacing: "mord", | ||
@@ -502,2 +503,104 @@ punct: "mpunct", | ||
array: function(group, options, prev) { | ||
var r, c; | ||
var nr = group.value.body.length; | ||
var nc = 0; | ||
var body = new Array(nr); | ||
// Horizontal spacing | ||
var pt = 1 / fontMetrics.metrics.ptPerEm; | ||
var arraycolsep = 5 * pt; // \arraycolsep in article.cls | ||
// Vertical spacing | ||
var baselineskip = 12 * pt; // see size10.clo | ||
var arraystretch = 1; // factor, see lttab.dtx | ||
var arrayskip = arraystretch * baselineskip; | ||
var arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and | ||
var arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx | ||
var totalHeight = 0; | ||
for (r = 0; r < group.value.body.length; ++r) { | ||
var inrow = group.value.body[r]; | ||
var height = arstrutHeight; // \@array adds an \@arstrut | ||
var depth = arstrutDepth; // to each tow (via the template) | ||
if (nc < inrow.length) { | ||
nc = inrow.length; | ||
} | ||
var outrow = new Array(inrow.length); | ||
for (c = 0; c < inrow.length; ++c) { | ||
var elt = buildGroup(inrow[c], options); | ||
if (depth < elt.depth) { | ||
depth = elt.depth; | ||
} | ||
if (height < elt.height) { | ||
height = elt.height; | ||
} | ||
outrow[c] = elt; | ||
} | ||
var gap = 0; | ||
if (group.value.rowGaps[r]) { | ||
gap = group.value.rowGaps[r].value; | ||
switch (gap.unit) { | ||
case "em": | ||
gap = gap.number; | ||
break; | ||
case "ex": | ||
gap = gap.number * fontMetrics.metrics.emPerEx; | ||
break; | ||
default: | ||
console.error("Can't handle unit " + gap.unit); | ||
gap = 0; | ||
} | ||
if (gap > 0) { // \@argarraycr | ||
gap += arstrutDepth; | ||
if (depth < gap) { | ||
depth = gap; // \@xargarraycr | ||
} | ||
gap = 0; | ||
} | ||
} | ||
outrow.height = height; | ||
outrow.depth = depth; | ||
totalHeight += height; | ||
outrow.pos = totalHeight; | ||
totalHeight += depth + gap; // \@yargarraycr | ||
body[r] = outrow; | ||
} | ||
var offset = totalHeight / 2 + fontMetrics.metrics.axisHeight; | ||
var colalign = group.value.colalign || []; | ||
var cols = []; | ||
var colsep; | ||
for (c = 0; c < nc; ++c) { | ||
if (c > 0 || group.value.hskipBeforeAndAfter) { | ||
colsep = makeSpan(["arraycolsep"], []); | ||
colsep.style.width = arraycolsep + "em"; | ||
cols.push(colsep); | ||
} | ||
var col = []; | ||
for (r = 0; r < nr; ++r) { | ||
var row = body[r]; | ||
var elem = row[c]; | ||
if (!elem) { | ||
continue; | ||
} | ||
var shift = row.pos - offset; | ||
elem.depth = row.depth; | ||
elem.height = row.height; | ||
col.push({type: "elem", elem: elem, shift: shift}); | ||
} | ||
col = buildCommon.makeVList(col, "individualShift", null, options); | ||
col = makeSpan( | ||
["col-align-" + (colalign[c] || "c")], | ||
[col]); | ||
cols.push(col); | ||
if (c < nc - 1 || group.value.hskipBeforeAndAfter) { | ||
colsep = makeSpan(["arraycolsep"], []); | ||
colsep.style.width = arraycolsep + "em"; | ||
cols.push(colsep); | ||
} | ||
} | ||
body = makeSpan(["mtable"], cols); | ||
return makeSpan(["minner"], [body], options.getColor()); | ||
}, | ||
spacing: function(group, options, prev) { | ||
@@ -817,3 +920,33 @@ if (group.value === "\\ " || group.value === "\\space" || | ||
return makeSpan(["sqrt", "mord"], [delim, body]); | ||
if (!group.value.index) { | ||
return makeSpan(["sqrt", "mord"], [delim, body]); | ||
} else { | ||
// Handle the optional root index | ||
// The index is always in scriptscript style | ||
var root = buildGroup( | ||
group.value.index, | ||
options.withStyle(Style.SCRIPTSCRIPT)); | ||
var rootWrap = makeSpan( | ||
[options.style.reset(), Style.SCRIPTSCRIPT.cls()], | ||
[root]); | ||
// Figure out the height and depth of the inner part | ||
var innerRootHeight = Math.max(delim.height, body.height); | ||
var innerRootDepth = Math.max(delim.depth, body.depth); | ||
// The amount the index is shifted by. This is taken from the TeX | ||
// source, in the definition of `\r@@t`. | ||
var toShift = 0.6 * (innerRootHeight - innerRootDepth); | ||
// Build a VList with the superscript shifted up correctly | ||
var rootVList = buildCommon.makeVList( | ||
[{type: "elem", elem: rootWrap}], | ||
"shift", -toShift, options); | ||
// Add a class surrounding it so we can add on the appropriate | ||
// kerning | ||
var rootVListWrap = makeSpan(["root"], [rootVList]); | ||
return makeSpan(["sqrt", "mord"], [rootVListWrap, delim, body]); | ||
} | ||
}, | ||
@@ -1133,2 +1266,6 @@ | ||
var buildHTML = function(tree, settings) { | ||
// buildExpression is destructive, so we need to make a clone | ||
// of the incoming tree so that it isn't accidentally changed | ||
tree = JSON.parse(JSON.stringify(tree)); | ||
var startStyle = Style.TEXT; | ||
@@ -1135,0 +1272,0 @@ if (settings.displayMode) { |
@@ -189,5 +189,25 @@ /** | ||
array: function(group) { | ||
return new mathMLTree.MathNode( | ||
"mtable", group.value.body.map(function(row) { | ||
return new mathMLTree.MathNode( | ||
"mtr", row.map(function(cell) { | ||
return new mathMLTree.MathNode( | ||
"mtd", [buildGroup(cell)]); | ||
})); | ||
})); | ||
}, | ||
sqrt: function(group) { | ||
var node = new mathMLTree.MathNode( | ||
"msqrt", [buildGroup(group.value.body)]); | ||
var node; | ||
if (group.value.index) { | ||
node = new mathMLTree.MathNode( | ||
"mroot", [ | ||
buildGroup(group.value.body), | ||
buildGroup(group.value.index) | ||
]); | ||
} else { | ||
node = new mathMLTree.MathNode( | ||
"msqrt", [buildGroup(group.value.body)]); | ||
} | ||
@@ -194,0 +214,0 @@ return node; |
@@ -234,25 +234,22 @@ /** | ||
var bottomHeightTotal = bottomMetrics.height + bottomMetrics.depth; | ||
var middleMetrics, middleHeightTotal; | ||
var middleHeightTotal = 0; | ||
var middleFactor = 1; | ||
if (middle !== null) { | ||
middleMetrics = getMetrics(middle, font); | ||
var middleMetrics = getMetrics(middle, font); | ||
middleHeightTotal = middleMetrics.height + middleMetrics.depth; | ||
middleFactor = 2; // repeat symmetrically above and below middle | ||
} | ||
// Calcuate the real height that the delimiter will have. It is at least the | ||
// size of the top, bottom, and optional middle combined. | ||
var realHeightTotal = topHeightTotal + bottomHeightTotal; | ||
if (middle !== null) { | ||
realHeightTotal += middleHeightTotal; | ||
} | ||
// Calcuate the minimal height that the delimiter can have. | ||
// It is at least the size of the top, bottom, and optional middle combined. | ||
var minHeight = topHeightTotal + bottomHeightTotal + middleHeightTotal; | ||
// Then add repeated pieces until we reach the specified height. | ||
while (realHeightTotal < heightTotal) { | ||
realHeightTotal += repeatHeightTotal; | ||
if (middle !== null) { | ||
// If there is a middle section, we need an equal number of pieces | ||
// on the top and bottom. | ||
realHeightTotal += repeatHeightTotal; | ||
} | ||
} | ||
// Compute the number of copies of the repeat symbol we will need | ||
var repeatCount = Math.ceil( | ||
(heightTotal - minHeight) / (middleFactor * repeatHeightTotal)); | ||
// Compute the total height of the delimiter including all the symbols | ||
var realHeightTotal = | ||
minHeight + repeatCount * middleFactor * repeatHeightTotal; | ||
// The center of the delimiter is placed at the center of the axis. Note | ||
@@ -279,8 +276,4 @@ // that in this context, "center" means that the delimiter should be | ||
if (middle === null) { | ||
// Calculate the number of repeated symbols we need | ||
var repeatHeight = realHeightTotal - topHeightTotal - bottomHeightTotal; | ||
var symbolCount = Math.ceil(repeatHeight / repeatHeightTotal); | ||
// Add that many symbols | ||
for (i = 0; i < symbolCount; i++) { | ||
for (i = 0; i < repeatCount; i++) { | ||
inners.push(makeInner(repeat, font, mode)); | ||
@@ -291,24 +284,7 @@ } | ||
// sections | ||
// Calculate the number of symbols needed for the top and bottom | ||
// repeated parts | ||
var topRepeatHeight = | ||
realHeightTotal / 2 - topHeightTotal - middleHeightTotal / 2; | ||
var topSymbolCount = Math.ceil(topRepeatHeight / repeatHeightTotal); | ||
var bottomRepeatHeight = | ||
realHeightTotal / 2 - topHeightTotal - middleHeightTotal / 2; | ||
var bottomSymbolCount = | ||
Math.ceil(bottomRepeatHeight / repeatHeightTotal); | ||
// Add the top repeated part | ||
for (i = 0; i < topSymbolCount; i++) { | ||
for (i = 0; i < repeatCount; i++) { | ||
inners.push(makeInner(repeat, font, mode)); | ||
} | ||
// Add the middle piece | ||
inners.push(makeInner(middle, font, mode)); | ||
// Add the bottom repeated part | ||
for (i = 0; i < bottomSymbolCount; i++) { | ||
for (i = 0; i < repeatCount; i++) { | ||
inners.push(makeInner(repeat, font, mode)); | ||
@@ -315,0 +291,0 @@ } |
@@ -74,12 +74,7 @@ var utils = require("./utils"); | ||
numOptionalArgs: 1, | ||
handler: function(func, optional, body, positions) { | ||
if (optional != null) { | ||
throw new ParseError( | ||
"Optional arguments to \\sqrt aren't supported yet", | ||
this.lexer, positions[1] - 1); | ||
} | ||
handler: function(func, index, body, positions) { | ||
return { | ||
type: "sqrt", | ||
body: body | ||
body: body, | ||
index: index | ||
}; | ||
@@ -235,3 +230,14 @@ } | ||
"\\blue", "\\orange", "\\pink", "\\red", | ||
"\\green", "\\gray", "\\purple" | ||
"\\green", "\\gray", "\\purple", | ||
"\\blueA", "\\blueB", "\\blueC", "\\blueD", "\\blueE", | ||
"\\tealA", "\\tealB", "\\tealC", "\\tealD", "\\tealE", | ||
"\\greenA", "\\greenB", "\\greenC", "\\greenD", "\\greenE", | ||
"\\goldA", "\\goldB", "\\goldC", "\\goldD", "\\goldE", | ||
"\\redA", "\\redB", "\\redC", "\\redD", "\\redE", | ||
"\\maroonA", "\\maroonB", "\\maroonC", "\\maroonD", "\\maroonE", | ||
"\\purpleA", "\\purpleB", "\\purpleC", "\\purpleD", "\\purpleE", | ||
"\\mintA", "\\mintB", "\\mintC", | ||
"\\grayA", "\\grayB", "\\grayC", "\\grayD", "\\grayE", | ||
"\\grayF", "\\grayG", "\\grayH", "\\grayI", | ||
"\\kaBlue", "\\kaGreen" | ||
], | ||
@@ -516,2 +522,43 @@ data: { | ||
} | ||
}, | ||
// Row breaks for aligned data | ||
{ | ||
funcs: ["\\\\", "\\cr"], | ||
data: { | ||
numArgs: 0, | ||
numOptionalArgs: 1, | ||
argTypes: ["size"], | ||
handler: function(func, size) { | ||
return { | ||
type: "cr", | ||
size: size | ||
}; | ||
} | ||
} | ||
}, | ||
// Environment delimiters | ||
{ | ||
funcs: ["\\begin", "\\end"], | ||
data: { | ||
numArgs: 1, | ||
argTypes: ["text"], | ||
handler: function(func, nameGroup, positions) { | ||
if (nameGroup.type !== "ordgroup") { | ||
throw new ParseError( | ||
"Invalid environment name", | ||
this.lexer, positions[1]); | ||
} | ||
var name = ""; | ||
for (var i = 0; i < nameGroup.value.length; ++i) { | ||
name += nameGroup.value[i].value; | ||
} | ||
return { | ||
type: "environment", | ||
name: name, | ||
namepos: positions[1] | ||
}; | ||
} | ||
} | ||
} | ||
@@ -518,0 +565,0 @@ ]; |
@@ -14,2 +14,4 @@ /** | ||
var matchAt = require("match-at"); | ||
var ParseError = require("./ParseError"); | ||
@@ -32,10 +34,12 @@ | ||
var mathNormals = [ | ||
/^[/|@.""`0-9a-zA-Z]/, // ords | ||
/^[*+-]/, // bins | ||
/^[=<>:]/, // rels | ||
/^[,;]/, // punctuation | ||
/^['\^_{}]/, // misc | ||
/^[(\[]/, // opens | ||
/^[)\]?!]/, // closes | ||
/^~/ // spacing | ||
/[/|@.""`0-9a-zA-Z]/, // ords | ||
/[*+-]/, // bins | ||
/[=<>:]/, // rels | ||
/[,;]/, // punctuation | ||
/['\^_{}]/, // misc | ||
/[(\[]/, // opens | ||
/[)\]?!]/, // closes | ||
/~/, // spacing | ||
/&/, // horizontal alignment | ||
/\\\\/ // line break | ||
]; | ||
@@ -46,14 +50,16 @@ | ||
var textNormals = [ | ||
/^[a-zA-Z0-9`!@*()-=+\[\]'";:?\/.,]/, // ords | ||
/^[{}]/, // grouping | ||
/^~/ // spacing | ||
/[a-zA-Z0-9`!@*()-=+\[\]'";:?\/.,]/, // ords | ||
/[{}]/, // grouping | ||
/~/, // spacing | ||
/&/, // horizontal alignment | ||
/\\\\/ // line break | ||
]; | ||
// Regexes for matching whitespace | ||
var whitespaceRegex = /^\s*/; | ||
var whitespaceConcatRegex = /^( +|\\ +)/; | ||
var whitespaceRegex = /\s*/; | ||
var whitespaceConcatRegex = / +|\\ +/; | ||
// This regex matches any other TeX function, which is a backslash followed by a | ||
// word or a single symbol | ||
var anyFunc = /^\\(?:[a-zA-Z]+|.)/; | ||
var anyFunc = /\\(?:[a-zA-Z]+|.)/; | ||
@@ -66,3 +72,3 @@ /** | ||
Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) { | ||
var input = this._input.slice(pos); | ||
var input = this._input; | ||
var whitespace; | ||
@@ -72,8 +78,7 @@ | ||
// Get rid of whitespace. | ||
whitespace = input.match(whitespaceRegex)[0]; | ||
whitespace = matchAt(whitespaceRegex, input, pos)[0]; | ||
pos += whitespace.length; | ||
input = input.slice(whitespace.length); | ||
} else { | ||
// Do the funky concatenation of whitespace that happens in text mode. | ||
whitespace = input.match(whitespaceConcatRegex); | ||
whitespace = matchAt(whitespaceConcatRegex, input, pos); | ||
if (whitespace !== null) { | ||
@@ -85,3 +90,3 @@ return new Token(" ", null, pos + whitespace[0].length); | ||
// If there's no more input to parse, return an EOF token | ||
if (input.length === 0) { | ||
if (pos === input.length) { | ||
return new Token("EOF", null, pos); | ||
@@ -91,3 +96,3 @@ } | ||
var match; | ||
if ((match = input.match(anyFunc))) { | ||
if ((match = matchAt(anyFunc, input, pos))) { | ||
// If we match a function token, return it | ||
@@ -101,3 +106,3 @@ return new Token(match[0], null, pos + match[0].length); | ||
if ((match = input.match(normal))) { | ||
if ((match = matchAt(normal, input, pos))) { | ||
// If it is, return it | ||
@@ -110,8 +115,9 @@ return new Token( | ||
throw new ParseError("Unexpected character: '" + input[0] + | ||
"'", this, pos); | ||
throw new ParseError( | ||
"Unexpected character: '" + input[pos] + "'", | ||
this, pos); | ||
}; | ||
// A regex to match a CSS color (like #ffffff or BlueViolet) | ||
var cssColor = /^(#[a-z0-9]+|[a-z]+)/i; | ||
var cssColor = /#[a-z0-9]+|[a-z]+/i; | ||
@@ -122,11 +128,10 @@ /** | ||
Lexer.prototype._innerLexColor = function(pos) { | ||
var input = this._input.slice(pos); | ||
var input = this._input; | ||
// Ignore whitespace | ||
var whitespace = input.match(whitespaceRegex)[0]; | ||
var whitespace = matchAt(whitespaceRegex, input, pos)[0]; | ||
pos += whitespace.length; | ||
input = input.slice(whitespace.length); | ||
var match; | ||
if ((match = input.match(cssColor))) { | ||
if ((match = matchAt(cssColor, input, pos))) { | ||
// If we look like a color, return a color | ||
@@ -141,3 +146,3 @@ return new Token(match[0], null, pos + match[0].length); | ||
// "1.2em" or ".4pt" or "1 ex" | ||
var sizeRegex = /^(-?)\s*(\d+(?:\.\d*)?|\.\d+)\s*([a-z]{2})/; | ||
var sizeRegex = /(-?)\s*(\d+(?:\.\d*)?|\.\d+)\s*([a-z]{2})/; | ||
@@ -148,11 +153,10 @@ /** | ||
Lexer.prototype._innerLexSize = function(pos) { | ||
var input = this._input.slice(pos); | ||
var input = this._input; | ||
// Ignore whitespace | ||
var whitespace = input.match(whitespaceRegex)[0]; | ||
var whitespace = matchAt(whitespaceRegex, input, pos)[0]; | ||
pos += whitespace.length; | ||
input = input.slice(whitespace.length); | ||
var match; | ||
if ((match = input.match(sizeRegex))) { | ||
if ((match = matchAt(sizeRegex, input, pos))) { | ||
var unit = match[3]; | ||
@@ -176,8 +180,8 @@ // We only currently handle "em" and "ex" units | ||
Lexer.prototype._innerLexWhitespace = function(pos) { | ||
var input = this._input.slice(pos); | ||
var input = this._input; | ||
var whitespace = input.match(whitespaceRegex)[0]; | ||
var whitespace = matchAt(whitespaceRegex, input, pos)[0]; | ||
pos += whitespace.length; | ||
return new Token(whitespace, null, pos); | ||
return new Token(whitespace[0], null, pos); | ||
}; | ||
@@ -184,0 +188,0 @@ |
@@ -114,3 +114,52 @@ /** | ||
"katex-gray": "gray", | ||
"katex-purple": "#9d38bd" | ||
"katex-purple": "#9d38bd", | ||
"katex-blueA": "#c7e9f1", | ||
"katex-blueB": "#9cdceb", | ||
"katex-blueC": "#58c4dd", | ||
"katex-blueD": "#29abca", | ||
"katex-blueE": "#1c758a", | ||
"katex-tealA": "#acead7", | ||
"katex-tealB": "#76ddc0", | ||
"katex-tealC": "#5cd0b3", | ||
"katex-tealD": "#55c1a7", | ||
"katex-tealE": "#49a88f", | ||
"katex-greenA": "#c9e2ae", | ||
"katex-greenB": "#a6cf8c", | ||
"katex-greenC": "#83c167", | ||
"katex-greenD": "#77b05d", | ||
"katex-greenE": "#699c52", | ||
"katex-goldA": "#f7c797", | ||
"katex-goldB": "#f9b775", | ||
"katex-goldC": "#f0ac5f", | ||
"katex-goldD": "#e1a158", | ||
"katex-goldE": "#c78d46", | ||
"katex-redA": "#f7a1a3", | ||
"katex-redB": "#ff8080", | ||
"katex-redC": "#fc6255", | ||
"katex-redD": "#e65a4c", | ||
"katex-redE": "#cf5044", | ||
"katex-maroonA": "#ecabc1", | ||
"katex-maroonB": "#ec92ab", | ||
"katex-maroonC": "#c55f73", | ||
"katex-maroonD": "#a24d61", | ||
"katex-maroonE": "#94424f", | ||
"katex-purpleA": "#caa3e8", | ||
"katex-purpleB": "#b189c6", | ||
"katex-purpleC": "#9a72ac", | ||
"katex-purpleD": "#715582", | ||
"katex-purpleE": "#644172", | ||
"katex-mintA": "#f5f9e8", | ||
"katex-mintB": "#edf2df", | ||
"katex-mintC": "#e0e5cc", | ||
"katex-grayA": "#fdfdfd", | ||
"katex-grayB": "#f7f7f7", | ||
"katex-grayC": "#eeeeee", | ||
"katex-grayD": "#dddddd", | ||
"katex-grayE": "#cccccc", | ||
"katex-grayF": "#aaaaaa", | ||
"katex-grayG": "#999999", | ||
"katex-grayH": "#555555", | ||
"katex-grayI": "#333333", | ||
"katex-kaBlue": "#314453", | ||
"katex-kaGreen": "#639b24" | ||
}; | ||
@@ -117,0 +166,0 @@ |
var functions = require("./functions"); | ||
var environments = require("./environments"); | ||
var Lexer = require("./Lexer"); | ||
@@ -6,2 +7,3 @@ var symbols = require("./symbols"); | ||
var parseData = require("./parseData"); | ||
var ParseError = require("./ParseError"); | ||
@@ -54,20 +56,6 @@ | ||
/** | ||
* The resulting parse tree nodes of the parse tree. | ||
*/ | ||
function ParseNode(type, value, mode) { | ||
this.type = type; | ||
this.value = value; | ||
this.mode = mode; | ||
} | ||
var ParseNode = parseData.ParseNode; | ||
var ParseResult = parseData.ParseResult; | ||
/** | ||
* A result and final position returned by the `.parse...` functions. | ||
*/ | ||
function ParseResult(result, newPosition) { | ||
this.result = result; | ||
this.position = newPosition; | ||
} | ||
/** | ||
* An initial function (without its arguments), or an argument to a function. | ||
@@ -111,9 +99,10 @@ * The `result` argument should be a ParseResult. | ||
// Parse an expression | ||
var expression = this.parseExpression(pos, mode, false, null); | ||
var expression = this.parseExpression(pos, mode, false); | ||
// If we succeeded, make sure there's an EOF at the end | ||
var EOF = this.lexer.lex(expression.position, mode); | ||
this.expect(EOF, "EOF"); | ||
this.expect(expression.peek, "EOF"); | ||
return expression; | ||
}; | ||
var endOfExpression = ["}", "\\end", "\\right", "&", "\\\\", "\\cr"]; | ||
/** | ||
@@ -133,9 +122,13 @@ * Parses an "expression", which is a list of atoms. | ||
var body = []; | ||
var lex = null; | ||
// Keep adding atoms to the body until we can't parse any more atoms (either | ||
// we reached the end, a }, or a \right) | ||
while (true) { | ||
var lex = this.lexer.lex(pos, mode); | ||
if (breakOnToken != null && lex.text === breakOnToken) { | ||
lex = this.lexer.lex(pos, mode); | ||
if (endOfExpression.indexOf(lex.text) !== -1) { | ||
break; | ||
} | ||
if (breakOnToken && lex.text === breakOnToken) { | ||
break; | ||
} | ||
var atom = this.parseAtom(pos, mode); | ||
@@ -151,3 +144,5 @@ if (!atom) { | ||
} | ||
return new ParseResult(this.handleInfixNodes(body, mode), pos); | ||
var res = new ParseResult(this.handleInfixNodes(body, mode), pos); | ||
res.peek = lex; | ||
return res; | ||
}; | ||
@@ -361,27 +356,44 @@ | ||
// Parse out the implicit body | ||
body = this.parseExpression(left.position, mode, false, "}"); | ||
body = this.parseExpression(left.position, mode, false); | ||
// Check the next token | ||
var rightLex = this.parseSymbol(body.position, mode); | ||
if (rightLex && rightLex.result.result === "\\right") { | ||
// If it's a \right, parse the entire right function (including the delimiter) | ||
var right = this.parseFunction(body.position, mode); | ||
return new ParseResult( | ||
new ParseNode("leftright", { | ||
body: body.result, | ||
left: left.result.value.value, | ||
right: right.result.value.value | ||
}, mode), | ||
right.position); | ||
} else { | ||
throw new ParseError("Missing \\right", this.lexer, body.position); | ||
this.expect(body.peek, "\\right"); | ||
var right = this.parseFunction(body.position, mode); | ||
return new ParseResult( | ||
new ParseNode("leftright", { | ||
body: body.result, | ||
left: left.result.value.value, | ||
right: right.result.value.value | ||
}, mode), | ||
right.position); | ||
} else if (func === "\\begin") { | ||
// begin...end is similar to left...right | ||
var begin = this.parseFunction(pos, mode); | ||
var envName = begin.result.value.name; | ||
if (!environments.hasOwnProperty(envName)) { | ||
throw new ParseError( | ||
"No such environment: " + envName, | ||
this.lexer, begin.result.value.namepos); | ||
} | ||
} else if (func === "\\right") { | ||
// If we see a right, explicitly fail the parsing here so the \left | ||
// handling ends the group | ||
return null; | ||
// Build the environment object. Arguments and other information will | ||
// be made available to the begin and end methods using properties. | ||
var env = environments[envName]; | ||
var args = [null, mode, envName]; | ||
var newPos = this.parseArguments( | ||
begin.position, mode, "\\begin{" + envName + "}", env, args); | ||
args[0] = newPos; | ||
var result = env.handler.apply(this, args); | ||
var endLex = this.lexer.lex(result.position, mode); | ||
this.expect(endLex, "\\end"); | ||
var end = this.parseFunction(result.position, mode); | ||
if (end.result.value.name !== envName) { | ||
throw new ParseError( | ||
"Mismatch: \\begin{" + envName + "} matched " + | ||
"by \\end{" + end.result.value.name + "}", | ||
this.lexer, end.namepos); | ||
} | ||
result.position = end.position; | ||
return result; | ||
} else if (utils.contains(sizeFuncs, func)) { | ||
// If we see a sizing function, parse out the implict body | ||
body = this.parseExpression(start.result.position, mode, false, "}"); | ||
body = this.parseExpression(start.result.position, mode, false); | ||
return new ParseResult( | ||
@@ -396,3 +408,3 @@ new ParseNode("sizing", { | ||
// If we see a styling function, parse out the implict body | ||
body = this.parseExpression(start.result.position, mode, true, "}"); | ||
body = this.parseExpression(start.result.position, mode, true); | ||
return new ParseResult( | ||
@@ -430,67 +442,6 @@ new ParseNode("styling", { | ||
var newPos = baseGroup.result.position; | ||
var result; | ||
var totalArgs = funcData.numArgs + funcData.numOptionalArgs; | ||
if (totalArgs > 0) { | ||
var baseGreediness = funcData.greediness; | ||
var args = [func]; | ||
var positions = [newPos]; | ||
for (var i = 0; i < totalArgs; i++) { | ||
var argType = funcData.argTypes && funcData.argTypes[i]; | ||
var arg; | ||
if (i < funcData.numOptionalArgs) { | ||
if (argType) { | ||
arg = this.parseSpecialGroup(newPos, argType, mode, true); | ||
} else { | ||
arg = this.parseOptionalGroup(newPos, mode); | ||
} | ||
if (!arg) { | ||
args.push(null); | ||
positions.push(newPos); | ||
continue; | ||
} | ||
} else { | ||
if (argType) { | ||
arg = this.parseSpecialGroup(newPos, argType, mode); | ||
} else { | ||
arg = this.parseGroup(newPos, mode); | ||
} | ||
if (!arg) { | ||
throw new ParseError( | ||
"Expected group after '" + baseGroup.result.result + | ||
"'", | ||
this.lexer, newPos); | ||
} | ||
} | ||
var argNode; | ||
if (arg.isFunction) { | ||
var argGreediness = | ||
functions.funcs[arg.result.result].greediness; | ||
if (argGreediness > baseGreediness) { | ||
argNode = this.parseFunction(newPos, mode); | ||
} else { | ||
throw new ParseError( | ||
"Got function '" + arg.result.result + "' as " + | ||
"argument to function '" + | ||
baseGroup.result.result + "'", | ||
this.lexer, arg.result.position - 1); | ||
} | ||
} else { | ||
argNode = arg.result; | ||
} | ||
args.push(argNode.result); | ||
positions.push(argNode.position); | ||
newPos = argNode.position; | ||
} | ||
args.push(positions); | ||
result = functions.funcs[func].handler.apply(this, args); | ||
} else { | ||
result = functions.funcs[func].handler.apply(this, [func]); | ||
} | ||
var args = [func]; | ||
var newPos = this.parseArguments( | ||
baseGroup.result.position, mode, func, funcData, args); | ||
var result = functions.funcs[func].handler.apply(this, args); | ||
return new ParseResult( | ||
@@ -507,3 +458,74 @@ new ParseNode(result.type, result, mode), | ||
/** | ||
* Parses the arguments of a function or environment | ||
* | ||
* @param {string} func "\name" or "\begin{name}" | ||
* @param {{numArgs:number,numOptionalArgs:number|undefined}} funcData | ||
* @param {Array} args list of arguments to which new ones will be pushed | ||
* @return the position after all arguments have been parsed | ||
*/ | ||
Parser.prototype.parseArguments = function(pos, mode, func, funcData, args) { | ||
var totalArgs = funcData.numArgs + funcData.numOptionalArgs; | ||
if (totalArgs === 0) { | ||
return pos; | ||
} | ||
var newPos = pos; | ||
var baseGreediness = funcData.greediness; | ||
var positions = [newPos]; | ||
for (var i = 0; i < totalArgs; i++) { | ||
var argType = funcData.argTypes && funcData.argTypes[i]; | ||
var arg; | ||
if (i < funcData.numOptionalArgs) { | ||
if (argType) { | ||
arg = this.parseSpecialGroup(newPos, argType, mode, true); | ||
} else { | ||
arg = this.parseOptionalGroup(newPos, mode); | ||
} | ||
if (!arg) { | ||
args.push(null); | ||
positions.push(newPos); | ||
continue; | ||
} | ||
} else { | ||
if (argType) { | ||
arg = this.parseSpecialGroup(newPos, argType, mode); | ||
} else { | ||
arg = this.parseGroup(newPos, mode); | ||
} | ||
if (!arg) { | ||
throw new ParseError( | ||
"Expected group after '" + func + "'", | ||
this.lexer, newPos); | ||
} | ||
} | ||
var argNode; | ||
if (arg.isFunction) { | ||
var argGreediness = | ||
functions.funcs[arg.result.result].greediness; | ||
if (argGreediness > baseGreediness) { | ||
argNode = this.parseFunction(newPos, mode); | ||
} else { | ||
throw new ParseError( | ||
"Got function '" + arg.result.result + "' as " + | ||
"argument to '" + func + "'", | ||
this.lexer, arg.result.position - 1); | ||
} | ||
} else { | ||
argNode = arg.result; | ||
} | ||
args.push(argNode.result); | ||
positions.push(argNode.position); | ||
newPos = argNode.position; | ||
} | ||
args.push(positions); | ||
return newPos; | ||
}; | ||
/** | ||
* Parses a group when the mode is changing. Takes a position, a new mode, and | ||
@@ -568,3 +590,3 @@ * an outer mode that is used to parse the outside. | ||
// If we get a brace, parse an expression | ||
var expression = this.parseExpression(start.position, mode, false, "}"); | ||
var expression = this.parseExpression(start.position, mode, false); | ||
// Make sure we get a close brace | ||
@@ -638,2 +660,4 @@ var closeBrace = this.lexer.lex(expression.position, mode); | ||
Parser.prototype.ParseNode = ParseNode; | ||
module.exports = Parser; |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
322468
25
7506
65
1
+ Addedmatch-at@^0.1.0
+ Addedmatch-at@0.1.1(transitive)