Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8e90a00
Create parsing for texvc.sty using texutil.js
May 24, 2016
1030481
Change texvc.sty parsing and fix failing tests.
notjagan Aug 5, 2016
2a806ab
Remove console.log for testing.
notjagan Aug 5, 2016
349cc73
Remove test output file.
notjagan Aug 5, 2016
b65f235
Restructure parseSty into a separate file and add testing for it.
notjagan Aug 9, 2016
3cc7f8f
Fix BitHound issues.
notjagan Aug 9, 2016
e66c79f
Reduce function complexity.
notjagan Aug 9, 2016
3e129c2
Fix out-of-scope references.
notjagan Aug 9, 2016
0141b06
Unify 'if' spacing.
notjagan Aug 9, 2016
434930f
Further reduce complexity (most likely temporary).
notjagan Aug 9, 2016
4c0768b
Remove csv parsing code.
notjagan Aug 9, 2016
e1b5c43
Restructure parseSty.js.
notjagan Aug 9, 2016
13c86b0
Add test for findBracketSections in parseSty.js.
notjagan Aug 9, 2016
8f51f73
Fix typo in parseSty.js.
notjagan Aug 10, 2016
112fedc
Change undefined checks.
notjagan Aug 11, 2016
17022e7
Add comment in parseSty.js.
notjagan Aug 11, 2016
dcaa0d5
Fix condition in parseSty.js.
notjagan Aug 11, 2016
a59dbb9
Add support for Windows line breaks (\r\n).
notjagan Aug 11, 2016
0a9e13f
Improve comments on parseSty.js.
notjagan Aug 15, 2016
3762226
Add tests and improve comment style for parseSty.js
notjagan Aug 15, 2016
75cff6d
Add more documentation and tests and improve style
notjagan Aug 16, 2016
a37bf45
Add trailing newline to test file for parseSty.js
notjagan Aug 16, 2016
efa645b
Add trailing newline (again?)
notjagan Aug 16, 2016
112053d
Another newline
notjagan Aug 16, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ crashlytics-build.properties

# Semantic Macros
lib/optionalFunctions.csv

# Node log
lib/npm-debug.log

55 changes: 30 additions & 25 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ module.exports.SyntaxError = Parser.SyntaxError;
var astutil = require('./astutil');
var contains_func = module.exports.contains_func = astutil.contains_func;

function handleTexError(e, options) {
Copy link
Member

@physikerwelt physikerwelt Aug 9, 2016

Choose a reason for hiding this comment

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

nice. You should also do a pull requst for this change in the upstream repo.

Copy link
Author

Choose a reason for hiding this comment

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

Should I do the pull request now or wait until this pull request is approved?

Copy link
Member

Choose a reason for hiding this comment

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

Those two tasks are independent of each other, so you can go ahead and make the pull request. However, this is not high priority.

if (options && options.debug) {
throw e;
}
if (e instanceof Parser.SyntaxError) {
if (e.message === 'Illegal TeX function') {
return {
status: 'F', details: e.found,
offset: e.offset, line: e.line, column: e.column
};
}
return {
status: 'S', details: e.toString(),
offset: e.offset, line: e.line, column: e.column
};
}
return { status: '-', details: e.toString() };
}

var check = module.exports.check = function(input, options) {
/* status is one character:
* + : success! result is in 'output'
Expand All @@ -28,15 +47,15 @@ var check = module.exports.check = function(input, options) {
* output file already exist, a problem with an external
* command ...
*/
if (!options) {
options = {};
options.usemathrm = false;
options.usmhchem = false;
options.semanticlatex = false;
}
Copy link
Member

Choose a reason for hiding this comment

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

👍

try {
if( typeof options === "undefined" ){
options = {};
options.usemathrm = false;
options.usmhchem = false;
options.semanticlatex = false;
}
// allow user to pass a parsed AST as input, as well as a string
if (typeof(input)==='string') {
if (typeof(input) === 'string') {
Copy link
Member

Choose a reason for hiding this comment

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

👍

input = Parser.parse(input, {usemathrm:options.usemathrm, semanticlatex:options.semanticlatex});
}
var output = render(input);
Expand All @@ -45,30 +64,16 @@ var check = module.exports.check = function(input, options) {
pkg = pkg + '_required';
result[pkg] = astutil.contains_func(input, tu[pkg]);
});
if (!options.usemhchem){
if (!options.usemhchem) {
if (result.mhchem_required){
return {
status: 'C', details: "mhchem package required."
};
}
}
return result;
} catch (e) {
if (options && options.debug) {
throw e;
}
if (e instanceof Parser.SyntaxError) {
if (e.message === 'Illegal TeX function') {
return {
status: 'F', details: e.found,
offset: e.offset, line: e.line, column: e.column
};
}
return {
status: 'S', details: e.toString(),
offset: e.offset, line: e.line, column: e.column
};
}
return { status: '-', details: e.toString() };
}
catch (e) {
return handleTexError(e, options);
}
};
183 changes: 183 additions & 0 deletions lib/parseSty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
"use strict";

var balanced = require("balanced-match");

var declarationCommands = ["\\newcommand",
"\\renewcommand",
"\\DeclareRobustCommand",
"\\defSpecFun"];
/**
* Returns a two dimensional array, where each element contains the body of a bracket section and the type of bracket
* that surrounds it. Makes use of the npm balanced-match module.
* E.g.: [ [ "in brackets", "[" ], [ "in braces", "{" ] ]
*
* @param {string} string - The string to parse.
* @param {Array[]=[]} sections - The bracket sections that have already been parsed.
* @returns {Array[]} An array of bracket sections, each containing a body and the type of bracket.
*/
var findBracketSections = module.exports.findBracketSections = function(string, sections) {
if (!sections) {
sections = [];
}
var bracket = balanced("[", "]", string);
var brace = balanced("{", "}", string);
if (brace && (!bracket || brace.start < bracket.start)) {
sections.push([brace.body, "{"]);
return findBracketSections(brace.post, sections);
}
else if (bracket) {
sections.push([bracket.body, "["]);
return findBracketSections(bracket.post, sections);
}
return sections;
Copy link
Member

Choose a reason for hiding this comment

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

other than my first coomment this method is OK.

};

/**
* Finds the first occurrence in the provided string of the earliest occurring item in the given array, starting from
* a specified index. Returns both the index of occurrence and the relevant item of subs (returns -1 and the first item
* if none of the provided substrings can be found).
*
* @param {string} search - The string to search.
* @param {string[]} substrings - All items to search for.
* @param {number} start - The index to start searching at.
* @returns {Array} The index of the first matching item and the item itself as an array.
*/
var findFirst = module.exports.findFirst = function(search, substrings, start) {
var min = -1;
var index;
var first = substrings[0];
for (var i in substrings) {
index = search.indexOf(substrings[i], start);
if ((index !== -1) && (index < min || min === -1)) {
min = index;
first = substrings[i];
}
if (min === 0) {
break;
}
}
return [min, first];
};

/**
* Parses a command declaration of form \newcommand (or \renewcommand) followed by a command and its replacement. If the
* declaration is valid, an association is added in cmds between the command string and its replacement. Otherwise, an
* error is thrown stating that the declaration is invalid and also providing the declaration in question.
*
* @param {string} declaration - The replacement declaration.
* @param {string} command - The command used to call the declaration (\newcommand or \renewcommand).
* @param {Object} cmds - An associative array between commands and their respective replacements.
*/
var parseNewCommand = module.exports.parseNewCommand = function(declaration, command, cmds) {
// Creates a variable for the declaration without either \newcommand or \renewcommand.
var definition = declaration.substring(command.length);
var sections = findBracketSections(definition);
switch (sections.length) {
// \newcommand\command\replacement
case 0:
// /\\[a-zA-Z]+/ depends on the alphabetic restrictions on a LaTeX command name.
var commands = definition.match(/\\[a-zA-Z]+/g);
Copy link
Member

Choose a reason for hiding this comment

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

The pattern originates from the restriction of latexmacronames. Can you add a reference to this fact here?

cmds[commands[0]] = commands[1];
break;
case 1:
// Finds a command preceded directly by an end bracket.
var replacement = definition.match(/\}\\[a-zA-Z]+/);
// \newcommand{\command}\replacement: if such a command exists
if (replacement !== null) {
cmds[sections[0][0]] = replacement[0].substring(1);
}
// \newcommand\command{\replacement}: if there is no command preceded by an end bracket
else {
// It instead matches a command followed a start bracket.
var cmd = definition.match(/\\[a-zA-Z]+(?=\{)/);
cmds[cmd[0]] = sections[0][0];
}
break;
case 2:
// \newcommand{\command}{\replacement}
if (sections[0][1] !== "[") {
cmds[sections[0][0]] = sections[1][0];
}
break;
default:
throw new Error("Invalid new command declaration " + declaration + ".");
}
Copy link
Member

Choose a reason for hiding this comment

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

I suggest the program sould error out of sections.length>2.

};

/**
* Finds the net level of brackets, braces, parentheses.
* ([]) -> 0, [[] -> 1, }} -> -2, etc.
*
* @param {string} string - The string to find the balance of.
* @returns {number} The net level of brackets in the string.
*/
var bracketBalance = module.exports.bracketBalance = function(string) {
var balance = 0;
var open = ["(", "[", "{"];
for (var index in open) {
balance += string.split(open[index]).length - 1;
}
var close = [")", "]", "}"];
for (index in close) {
balance -= string.split(close[index]).length - 1;
}
return balance;
};

/**
* Parses the first command declaration of the lines provided. Takes the lines of .sty from the point to start parsing
* forward. This is due to some declarations taking multiple lines. Since the number of lines used by parsing a single
* declaration is indeterminate, the function returns the number of lines used for the parsing the first declaration.
*
* @param {string[]} lines - The lines of the .sty, starting with the first to be parsed.
* @param {Object} cmds - An associative array between commands and their respective replacements.
* @returns {number} The number of lines used in parsing the declaration.
*/
var parseLine = module.exports.parseLine = function(lines, cmds) {
// Removes comments from the line.
var line = lines[0].match(/([^\\%]|\\.|^)+/g)[0].trim();
var offset = 1;
var command = findFirst(line, declarationCommands, 0);
var balance = bracketBalance(line);
while (offset < lines.length && (balance !== 0 || lines[offset].charAt(0) !== "\\")) {
lines[offset] = lines[offset].match(/([^\\%]|\\.|^)+/g)[0].trim();
balance += bracketBalance(lines[offset]);
line += lines[offset];
offset++;
}
line = line.replace(/\s/g, "");
if (command[0] !== -1) {
command = command[1];
if (command === "\\newcommand" || command === "\\renewcommand") {
parseNewCommand(line, command, cmds);
}
else if (command === "\\DeclareRobustCommand") {
var sections = findBracketSections(line);
if (sections[0][0] !== undefined && sections[1][0] !== undefined) {
cmds[sections[0][0]] = sections[1][0];
}
}
}
return offset;
};

/**
* Parses the entirety of a .sty file, returning the associative array of command replacements it finds in declarations.
*
* @param {string} inp - A .sty file's text.
* @param {Object={}} cmds - An associative array between commands and their respective replacements.
* @returns {Object} An associative array between commands and their respective replacements.
*/
module.exports.parseSty = function(inp, cmds) {
if (!cmds) {
cmds = {};
}
// Removes repeating newlines and trailing whitespace.
inp = inp.replace(/(\r?\n)+/g, "\n").trim();
var lines = inp.split("\n");
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible that a single command spans multiple lines?

Copy link
Author

Choose a reason for hiding this comment

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

What do you mean?

var lineno = 0;
while (lineno < lines.length) {
lineno += parseLine(lines.slice(lineno), cmds);
}
return cmds;
};
100 changes: 6 additions & 94 deletions lib/texutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// every time that parse() is called.
"use strict";

var parseSty = require("./parseSty");

// track all known function names, so we can give good errors for unknown
// functions.
var all_functions = module.exports.all_functions = Object.create(null);
Expand Down Expand Up @@ -43,24 +45,10 @@ module.exports.latex_function_names = arr2set([
]);

module.exports.paren = arr2set(["\\sinh"]);
try{ //try catch to make sure program still runs without csv file
var csv = require('csv-parse/lib/sync');
var fs = require('fs');
var path = require('path');
var filePathread = path.join(__dirname, 'optionalFunctions.csv');
var paren = [];
var inp = fs.readFileSync(filePathread, 'utf8');
var cmds = csv(inp, {columns: true});
var c = 0;
while (c < cmds.length) {
if (cmds[c].type === 'paren') {
paren.push(cmds[c].command);
}
c++;
}
module.exports.paren = arr2set(paren);
} catch (e) {}

module.exports.other_literals3 = obj2map({});
var fs = require('fs');
var path = require('path');
module.exports.other_literals3 = parseSty.parseSty(fs.readFileSync(path.join(__dirname, "texvc.sty"), 'utf8'));

module.exports.mediawiki_function_names = arr2set([
"\\arccot", "\\arcsec", "\\arccsc", "\\sgn", "\\sen"
Expand Down Expand Up @@ -518,82 +506,6 @@ module.exports.other_literals2 = arr2set([
"\\varstigma"
]);

module.exports.other_literals3 = obj2map({
"\\C": "\\mathbb{C}",
"\\H": "\\mathbb{H}",
"\\N": "\\mathbb{N}",
"\\Q": "\\mathbb{Q}",
"\\R": "\\mathbb{R}",
"\\Z": "\\mathbb{Z}",
"\\alef": "\\aleph",
"\\alefsym": "\\aleph",
"\\Alpha": "\\mathrm{A}",
"\\and": "\\land",
"\\ang": "\\angle",
"\\Beta": "\\mathrm{B}",
"\\bull": "\\bullet",
"\\Chi": "\\mathrm{X}",
"\\clubs": "\\clubsuit",
"\\cnums": "\\mathbb{C}",
"\\Complex": "\\mathbb{C}",
"\\Dagger": "\\ddagger",
"\\diamonds": "\\diamondsuit",
"\\Doteq": "\\doteqdot",
"\\doublecap": "\\Cap",
"\\doublecup": "\\Cup",
"\\empty": "\\emptyset",
"\\Epsilon": "\\mathrm{E}",
"\\Eta": "\\mathrm{H}",
"\\exist": "\\exists",
"\\ge": "\\geq",
"\\gggtr": "\\ggg",
"\\hAar": "\\Leftrightarrow",
"\\harr": "\\leftrightarrow",
"\\Harr": "\\Leftrightarrow",
"\\hearts": "\\heartsuit",
"\\image": "\\Im",
"\\infin": "\\infty",
"\\Iota": "\\mathrm{I}",
"\\isin": "\\in",
"\\Kappa": "\\mathrm{K}",
"\\larr": "\\leftarrow",
"\\Larr": "\\Leftarrow",
"\\lArr": "\\Leftarrow",
"\\le": "\\leq",
"\\lrarr": "\\leftrightarrow",
"\\Lrarr": "\\Leftrightarrow",
"\\lrArr": "\\Leftrightarrow",
"\\Mu": "\\mathrm{M}",
"\\natnums": "\\mathbb{N}",
"\\ne": "\\neq",
"\\Nu": "\\mathrm{N}",
"\\O": "\\emptyset",
"\\omicron": "\\mathrm{o}",
"\\Omicron": "\\mathrm{O}",
"\\or": "\\lor",
"\\part": "\\partial",
"\\plusmn": "\\pm",
"\\rarr": "\\rightarrow",
"\\Rarr": "\\Rightarrow",
"\\rArr": "\\Rightarrow",
"\\real": "\\Re",
"\\reals": "\\mathbb{R}",
"\\Reals": "\\mathbb{R}",
"\\restriction": "\\upharpoonright",
"\\Rho": "\\mathrm{P}",
"\\sdot": "\\cdot",
"\\sect": "\\S",
"\\spades": "\\spadesuit",
"\\sub": "\\subset",
"\\sube": "\\subseteq",
"\\supe": "\\supseteq",
"\\Tau": "\\mathrm{T}",
"\\thetasym": "\\vartheta",
"\\varcoppa": "\\mbox{\\coppa}",
"\\weierp": "\\wp",
"\\Zeta": "\\mathrm{Z}"
});

module.exports.big_literals = arr2set([
"\\big",
"\\Big",
Expand Down
Loading