Skip to content

Commit f079743

Browse files
committed
Provide information about packages required by TeX string.
The `check` function now returns `<foo>_required` fields to indicate whether certain TeX packages are required to format the given string. The change in commit 36a7aec which changed the return value of the `parse` method was reverted, since the extra information was added to `check` instead of `parse`.
1 parent 5853774 commit f079743

13 files changed

+446
-42
lines changed

CHANGELOG.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# texvcjs x.x.x (not yet released)
22

3-
* Changed return value of low-level `parse` API to allow providing
4-
more detailed information about the TeX functions encountered.
53
* Switch to text mode before emitting \AA and \textvisiblespace.
64
* Require mandatory argument for \overbrace, \overleftarrow,
75
\overleftrightarrow, \overline, \overrightarrow, \underbrace, and
@@ -10,6 +8,8 @@
108
* Be stricter when parsing \color, \definecolor, and \pagecolor.
119
* Add `contains_func` to the API, to test for the presence of specific
1210
TeX functions in the validated/translated input string.
11+
* Return `<foo>_required` fields in the result of `check` to provide
12+
more detailed information about the TeX functions encountered.
1313

1414
# texvcjs 0.2.0 (2014-07-23)
1515

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ in the `line`, `column` and `offset` fields of the result. More information
6363
about the problem can be found in the `details` field of the result, which
6464
is a string.
6565

66+
The fields `ams_required`, `cancel_required`, `color_required`,
67+
`euro_required`, and `teubner_required` are set to `true` iff the input
68+
string requires the use of the corresponding LaTeX packages.
69+
The `ams_required` field requires the use of the `amsmath` and `amssymb`
70+
packages.
71+
6672
### Low-level API
6773

6874
The low level parser, abstract syntax tree (AST), and renderer are also

bin/texvcjs

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env node
22

3+
var PACKAGES = ['ams', 'cancel', 'color', 'euro', 'teubner'];
4+
35
var program = require('commander');
46
var util = require('util');
57

@@ -12,6 +14,16 @@ program
1214
.option('-v, --verbose', 'Show verbose error information')
1315
.option('-D, --debug', 'Show stack trace on failure');
1416

17+
PACKAGES.forEach(function(pkg) {
18+
var msg = 'Fail validation if input requires the ';
19+
if (pkg === 'ams') {
20+
msg += 'ams* packages';
21+
} else {
22+
msg += pkg + ' package';
23+
}
24+
program.option('--no-'+pkg, msg);
25+
});
26+
1527
program.parse(process.argv);
1628

1729
var input = program.args.join(' ');
@@ -22,11 +34,21 @@ if (program.rebuild) {
2234

2335
var texvcjs = require('../');
2436
var result = texvcjs.check(input, { debug: program.debug });
37+
38+
// check required packages
39+
PACKAGES.forEach(function(pkg) {
40+
if (result[pkg+'_required'] && !program[pkg]) {
41+
result.status = 'F';
42+
result.details = result[pkg+'_required'];
43+
};
44+
});
45+
46+
// output result
2547
if (result.status === '+') {
2648
util.print(result.status + (result.output || ''));
2749
} else if (result.status === 'F' || program.verbose) {
2850
util.print(result.status + (result.details || ''));
2951
} else {
3052
util.print(result.status);
3153
}
32-
process.exit(result.status);
54+
process.exit(result.status === '+' ? 0 : 1);

lib/ast.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ var type2str = function(type) {
3131
// vaguely based on:
3232
// https://github.com/rauschma/enums/blob/master/enums.js
3333
var Enum = function(name, fields, proto) {
34-
Object.defineProperty(this, 'name', { value: name }); /* not enumerable */
3534
proto = proto || {};
35+
// Non-enumerable properties 'name' and 'prototype'
36+
Object.defineProperty(this, 'name', { value: name });
37+
Object.defineProperty(this, 'prototype', { value: proto });
3638
Object.keys(fields).forEach(function(fname) {
3739
var args = fields[fname].args || [];
3840
var self = this;

lib/astutil.js

+28-9
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,44 @@
55

66
var ast = require("./ast");
77

8+
// like Array#some, but returns the non-falsy value, rather than the
9+
// boolean constant `true`.
10+
var some = function(array, testfunc) {
11+
var i, b;
12+
for (i=0; i<array.length; i++) {
13+
b = testfunc(array[i], i, array);
14+
if (b) { return b; } // return the non-falsy value
15+
}
16+
return false;
17+
};
18+
19+
// Matches a string against an string, array, or set target.
20+
// Returns the matching value, or `false`.
821
var match = function(target, str) {
922
if (Array.isArray(target)) {
10-
return target.some(function(t) { return match(t, str); });
23+
return some(target, function(t) { return match(t, str); });
1124
}
1225
if (typeof(target)==='string') {
13-
return target === str;
26+
return target === str ? str : false;
1427
}
15-
return !!target[str];
28+
return target[str] ? str : false;
1629
};
1730

31+
// Check if any of the array of AST nodes contains `target`.
1832
var arr_contains_func = function(array, target) {
19-
return array.some(function(t) { return t.contains_func(target); });
33+
return some(array, function(t) { return t.contains_func(target); });
2034
};
2135

2236
/**
2337
* RenderT nodes can contain function references only in a few specific
2438
* forms, which we test for here.
2539
*/
26-
ast.RenderT.TEX_ONLY.prototype.tex_contains_func = function(target) {
40+
ast.RenderT.prototype.tex_contains_func = function(target) {
2741
var t = this.tex_part(), m;
2842
// may have trailing '(', '[', '\\{' or " "
2943
t = t.replace(/(\(|\[|\\{| )$/, '');
3044
// special case #1: \\operatorname {someword}
31-
m = /^\\operatorname \{(.*)\}$/.exec(t);
45+
m = /^\\operatorname \{([^\\]*)\}$/.exec(t);
3246
if (m) {
3347
return match(target, '\\operatorname');
3448
}
@@ -42,6 +56,11 @@ ast.RenderT.TEX_ONLY.prototype.tex_contains_func = function(target) {
4256
if (m) {
4357
return match(target, m[1]);
4458
}
59+
// special case #4: \\mathbb, \\mathrm
60+
m = /^(\\math..) \{[^\\]*\}$/.exec(t);
61+
if (m) {
62+
return match(target, m[1]);
63+
}
4564
// protect against using random strings as keys in target
4665
return t.charAt(0) === '\\' && match(target, t);
4766
};
@@ -153,8 +172,8 @@ ast.Tex.defineVisitor("contains_func", {
153172
// t is the environment name.
154173
// m is a doubly-nested array
155174
var expr_has = function(e) { return arr_contains_func(e, target); };
156-
var line_has = function(l) { return l.some(expr_has); };
157-
var matrix_has = function(m) { return m.some(line_has); };
175+
var line_has = function(l) { return some(l, expr_has); };
176+
var matrix_has = function(m) { return some(m, line_has); };
158177
return match(target, '\\begin{'+t+'}') ||
159178
match(target, '\\end{'+t+'}') ||
160179
matrix_has(m);
@@ -171,7 +190,7 @@ ast.Tex.defineVisitor("contains_func", {
171190
// usually be an array of `ast.Tex`), or a low-level `ast.Tex` node.
172191
module.exports.contains_func = function(t, target) {
173192
if (typeof(t) === 'string') {
174-
t = require('parser').parse(t).result;
193+
t = require('parser').parse(t);
175194
}
176195
if (Array.isArray(t)) {
177196
return arr_contains_func(t, target);

lib/index.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ module.exports = {
99

1010
var Parser = require('./parser');
1111
var render = module.exports.render = require('./render');
12+
var tu = require('./texutil');
1213

1314
module.exports.ast = require('./ast');
1415
module.exports.parse = Parser.parse.bind(Parser);
1516
module.exports.SyntaxError = Parser.SyntaxError;
1617

17-
module.exports.contains_func = require('./astutil').contains_func;
18+
var astutil = require('./astutil');
19+
var contains_func = module.exports.contains_func = astutil.contains_func;
1820

1921
var check = module.exports.check = function(input, options) {
2022
/* status is one character:
@@ -29,10 +31,15 @@ var check = module.exports.check = function(input, options) {
2931
try {
3032
// allow user to pass a parsed AST as input, as well as a string
3133
if (typeof(input)==='string') {
32-
input = Parser.parse(input).result;
34+
input = Parser.parse(input);
3335
}
3436
var output = render(input);
35-
return { status: '+', output: output };
37+
var result = { status: '+', output: output };
38+
['ams', 'cancel', 'color', 'euro', 'teubner'].forEach(function(pkg) {
39+
pkg = pkg + '_required';
40+
result[pkg] = astutil.contains_func(input, tu[pkg]);
41+
});
42+
return result;
3643
} catch (e) {
3744
if (options && options.debug) {
3845
throw e;

lib/parser.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,7 @@ module.exports = (function() {
3636
peg$startRuleFunction = peg$parsestart,
3737

3838
peg$c0 = peg$FAILED,
39-
peg$c1 = function(t) { console.assert(t instanceof ast.LList);
40-
context.result = t.toArray();
41-
return context;
42-
},
39+
peg$c1 = function(t) { console.assert(t instanceof ast.LList); return t.toArray(); },
4340
peg$c2 = [],
4441
peg$c3 = /^[ \t\n\r]/,
4542
peg$c4 = { type: "class", value: "[ \\t\\n\\r]", description: "[ \\t\\n\\r]" },
@@ -6345,8 +6342,6 @@ module.exports = (function() {
63456342
return arr;
63466343
};
63476344

6348-
var context = {};
6349-
63506345

63516346
peg$result = peg$startRuleFunction();
63526347

lib/parser.pegjs

+1-6
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,11 @@
1313
}
1414
return arr;
1515
};
16-
17-
var context = {};
1816
}
1917
// first rule is the start production.
2018
start
2119
= _ t:tex_expr
22-
{ console.assert(t instanceof ast.LList);
23-
context.result = t.toArray();
24-
return context;
25-
}
20+
{ console.assert(t instanceof ast.LList); return t.toArray(); }
2621

2722
// the PEG grammar doesn't automatically ignore whitespace when tokenizing.
2823
// so we add `_` productions in appropriate places to eat whitespace.

0 commit comments

Comments
 (0)