Skip to content

Commit 795d826

Browse files
committed
begin support for integrals
1 parent 8c8c31e commit 795d826

7 files changed

+190
-10
lines changed

lib/converters/ast-to-latex.js

+45-6
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ const operators = {
172172

173173
// allowed multicharacter latex symbols
174174
// in addition to the below applied function symbols
175-
const allowedLatexSymbolsDefault = ['alpha', 'beta', 'gamma', 'Gamma', 'delta', 'Delta', 'epsilon', 'zeta', 'eta', 'theta', 'Theta', 'iota', 'kappa', 'lambda', 'Lambda', 'mu', 'nu', 'xi', 'Xi', 'pi', 'Pi', 'rho', 'sigma', 'Sigma', 'tau', 'Tau', 'upsilon', 'Upsilon', 'phi', 'Phi', 'chi', 'psi', 'Psi', 'omega', 'Omega', 'partial', "abs", "exp", "log", "ln", "log10", "sign", "sqrt", "erf", "cos", "cosh", "cot", "coth", "csc", "csch", "sec", "sech", "sin", "sinh", "tan", "tanh", 'arcsin', 'arccos', 'arctan', 'arccsc', 'arcsec', 'arccot', 'arg', 'Re', 'Im', 'det', 'angle', 'perp', 'circ'];
175+
const allowedLatexSymbolsDefault = ['alpha', 'beta', 'gamma', 'Gamma', 'delta', 'Delta', 'epsilon', 'zeta', 'eta', 'theta', 'Theta', 'iota', 'kappa', 'lambda', 'Lambda', 'mu', 'nu', 'xi', 'Xi', 'pi', 'Pi', 'rho', 'sigma', 'Sigma', 'tau', 'Tau', 'upsilon', 'Upsilon', 'phi', 'Phi', 'chi', 'psi', 'Psi', 'omega', 'Omega', 'partial', "abs", "exp", "log", "ln", "log10", "sign", "sqrt", "erf", "cos", "cosh", "cot", "coth", "csc", "csch", "sec", "sech", "sin", "sinh", "tan", "tanh", 'arcsin', 'arccos', 'arctan', 'arccsc', 'arcsec', 'arccot', 'arg', 'Re', 'Im', 'det', 'angle', 'perp', 'circ', 'int'];
176176

177177

178178
const convertLatexSymbolsDefault = {
@@ -656,20 +656,59 @@ class astToLatex {
656656
return '\\left\\lfloor ' + this.statement(operands[1]) + ' \\right\\rfloor';
657657
} else if (operands[0] === 'ceil') {
658658
return '\\left\\lceil ' + this.statement(operands[1]) + ' \\right\\rceil';
659-
}
660-
661-
if (operands[0] === "factorial") {
659+
} else if (operands[0] === "factorial") {
662660
let result = this.factor(operands[1]);
663661
if (this.simple_factor_or_function_or_parens(operands[1]) ||
664662
(operands[1][0] === '_' && (typeof operands[1][1] === 'string'))
665663
)
666664
return result + "!";
667665
else
668666
return '\\left(' + result + '\\right)!';
667+
} else if (operands[0] === 'sqrt') {
668+
return '\\sqrt{' + this.statement(operands[1]) + '}';
669669
}
670670

671-
if (operands[0] === 'sqrt') {
672-
return '\\sqrt{' + this.statement(operands[1]) + '}';
671+
672+
// check if have integral
673+
let fun = operands[0];
674+
if (fun[0] === "^") {
675+
fun = fun[1];
676+
}
677+
if (fun[0] === "_") {
678+
fun = fun[1]
679+
}
680+
if (fun === "int") {
681+
let integral = this.factor(operands[0]);
682+
let integrand_ast = operands[1];
683+
let integrand;
684+
if (Array.isArray(integrand_ast) && integrand_ast[0] === "*") {
685+
let ds = [];
686+
let integrand_ast2 = ["*"];
687+
for (let i = 1; i < integrand_ast.length; i++) {
688+
let factor = integrand_ast[i];
689+
if (Array.isArray(factor) && factor[0] === "d") {
690+
ds.push(factor);
691+
} else {
692+
integrand_ast2.push(factor);
693+
}
694+
}
695+
696+
integrand = this.term(integrand_ast2);
697+
698+
if (ds.length > 0) {
699+
700+
if (integrand[integrand.length - 1].match(/[a-zA-Z]/)) {
701+
integrand += "\\,"
702+
}
703+
integrand += ds.map(x => "d" + this.factor(x[1])).join("\\,")
704+
}
705+
}
706+
707+
if (!integrand) {
708+
integrand = this.term(operands[1]);
709+
}
710+
711+
return integral + ' ' + integrand;
673712
}
674713

675714
let f;

lib/converters/ast-to-text.js

+41-3
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ class astToText {
380380
'Omega': 'Ω',
381381
'omega': 'ω',
382382
'perp': '⟂',
383+
'int': '∫',
383384
}
384385
if (this.output_unicode && (symbol in symbolConversions)) {
385386
return symbolConversions[symbol];
@@ -664,9 +665,7 @@ class astToText {
664665

665666
if (operands[0] === 'abs') {
666667
return '|' + this.statement(operands[1]) + '|';
667-
}
668-
669-
if (operands[0] === "factorial") {
668+
} else if (operands[0] === "factorial") {
670669
let result = this.factor(operands[1]);
671670
if (this.simple_factor_or_function_or_parens(operands[1]) ||
672671
(operands[1][0] === '_' && (typeof operands[1][1] === 'string'))
@@ -677,6 +676,45 @@ class astToText {
677676

678677
}
679678

679+
680+
// check if have integral
681+
let fun = operands[0];
682+
if (fun[0] === "^") {
683+
fun = fun[1];
684+
}
685+
if (fun[0] === "_") {
686+
fun = fun[1]
687+
}
688+
if (fun === "int") {
689+
let integral = this.factor(operands[0]);
690+
let integrand_ast = operands[1];
691+
let integrand;
692+
if (Array.isArray(integrand_ast) && integrand_ast[0] === "*") {
693+
let ds = [];
694+
let integrand_ast2 = ["*"];
695+
for (let i = 1; i < integrand_ast.length; i++) {
696+
let factor = integrand_ast[i];
697+
if (Array.isArray(factor) && factor[0] === "d") {
698+
ds.push(factor);
699+
} else {
700+
integrand_ast2.push(factor);
701+
}
702+
}
703+
704+
integrand = this.term(integrand_ast2);
705+
706+
if (ds.length > 0) {
707+
integrand += " " + ds.map(x => "d" + this.factor(x[1])).join(" ")
708+
}
709+
}
710+
711+
if (!integrand) {
712+
integrand = this.term(operands[1]);
713+
}
714+
715+
return integral + ' ' + integrand;
716+
}
717+
680718
let f = this.factor(operands[0]);
681719
let f_args = this.statement(operands[1]);
682720

lib/converters/latex-to-ast.js

+42
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,8 @@ const base_latex_rules = [
325325

326326
['\\\\angle(?![a-zA-Z])', 'ANGLE'],
327327

328+
['\\\\int(?![a-zA-Z])', 'INT'],
329+
328330
['!', '!'],
329331
['\'', '\''],
330332
['_', '_'],
@@ -1485,6 +1487,46 @@ class latexToAst {
14851487
result = ["angle", ...args];
14861488
}
14871489
}
1490+
} else if (this.token.token_type === 'INT') {
1491+
1492+
this.advance();
1493+
1494+
let result = "int";
1495+
1496+
1497+
if (this.token.token_type === "_") {
1498+
this.advance();
1499+
let subscript = this.get_subsuperscript({ parse_absolute_value });
1500+
result = ['_', result, subscript];
1501+
}
1502+
1503+
if (this.token.token_type === '^') {
1504+
this.advance();
1505+
let superscript = this.get_subsuperscript({ parse_absolute_value });
1506+
result = ["^", result, superscript];
1507+
}
1508+
1509+
let integrand = flatten(this.term({ parse_absolute_value }));
1510+
1511+
if (Array.isArray(integrand) && integrand[0] === "*") {
1512+
// look for consecutive factors of "d" followed by a string
1513+
1514+
let ds = [];
1515+
for (let i = 0; i < integrand.length - 1; i++) {
1516+
let factor1 = integrand[i];
1517+
if (factor1 === "d") {
1518+
let factor2 = integrand[i + 1];
1519+
integrand.splice(i, 2);
1520+
ds.push(["d", factor2]);
1521+
i--;
1522+
}
1523+
}
1524+
integrand.push(...ds);
1525+
1526+
}
1527+
1528+
return ["apply", result, integrand];
1529+
14881530
}
14891531

14901532
if (this.token.token_type === '_') {

lib/converters/text-to-ast.js

+43-1
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ const base_text_rules = [
334334
['angle\\b', 'ANGLE'],
335335
['\u2220', 'ANGLE'], // '∠'
336336

337+
['int(?![a-zA-Z])', 'INT'],
338+
['\u222b', 'INT'], // '∫'
339+
337340
['!', '!'],
338341
['\'', '\''],
339342
['_', '_'],
@@ -1264,8 +1267,47 @@ class textToAst {
12641267
result = ["angle", ...args];
12651268
}
12661269
}
1267-
}
1270+
} else if (this.token.token_type === 'INT') {
1271+
1272+
this.advance();
1273+
1274+
let result = "int";
1275+
1276+
1277+
if (this.token.token_type === "_") {
1278+
this.advance();
1279+
let subscript = this.get_subsuperscript({ parse_absolute_value });
1280+
result = ['_', result, subscript];
1281+
}
1282+
1283+
if (this.token.token_type === '^') {
1284+
this.advance();
1285+
let superscript = this.get_subsuperscript({ parse_absolute_value });
1286+
result = ["^", result, superscript];
1287+
}
1288+
1289+
let integrand = flatten(this.term({ parse_absolute_value }));
12681290

1291+
if (Array.isArray(integrand) && integrand[0] === "*") {
1292+
// look for consecutive factors of "d" followed by a string
1293+
1294+
let ds = [];
1295+
for (let i = 0; i < integrand.length - 1; i++) {
1296+
let factor1 = integrand[i];
1297+
if (factor1 === "d") {
1298+
let factor2 = integrand[i + 1];
1299+
integrand.splice(i, 2);
1300+
ds.push(["d", factor2]);
1301+
i--;
1302+
}
1303+
}
1304+
integrand.push(...ds);
1305+
1306+
}
1307+
1308+
return ["apply", result, integrand];
1309+
1310+
}
12691311

12701312

12711313
if (this.token.token_type === '_') {

spec/quick_latex-to-ast-to-latex.spec.js

+5
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,11 @@ var inputs = [
319319
['x 50 \\circ y', '(x \\cdot 50 \\circ) y'],
320320
['x$8y', 'x($8y)'],
321321
['x 75%y', '(x \\cdot 75%)y'],
322+
['\\int_a^bf(x)dx', '\\int_{a}^{b}f(x)dx'],
323+
'\\int f(x) dx',
324+
['\\int x^2 dx', '\\int x^{2} dx'],
325+
['\\int (x^2+y^2)dxdy', '\\int (x^{2}+y^{2})dxdy'],
326+
['\\int dx f(x)', '\\int f(x) dx'],
322327
];
323328

324329
function clean(text) {

spec/quick_text-to-ast-to-text.spec.js

+5
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,11 @@ var inputs = [
320320
['x 50deg y', '(x*50 deg) y'],
321321
['x$8y', 'x($8y)'],
322322
['x 75%y', '(x*75%)y'],
323+
['int_a^bf(x)dx', '∫_a^bf(x)dx'],
324+
['int f(x)dx', '∫f(x)dx'],
325+
['int x^2 dx', '∫x^2 dx'],
326+
['int (x^2+y^2)dxdy', '∫(x^2+y^2)dxdy'],
327+
['int dx f(x)', '∫f(x) dx'],
323328
];
324329

325330

spec/slow_math-expressions.spec.js

+9
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,10 @@ describe("expression", function () {
382382
['50% + 1', '1.5'],
383383
['x%', 'x/100'],
384384
['180 deg', 'pi'],
385+
['∫_(a+a)^(5b-b)xx dx', '∫_(2a)^(4b)x^2dx'],
386+
['∫_(a+a)^(5b-b)dx xx', '∫_(2a)^(4b)x^2dx'],
387+
['∫ f(x)f(x)dx', '∫ f(x)^2dx'],
388+
385389
];
386390

387391
_.each(equivalences, function (equiv) {
@@ -477,6 +481,11 @@ describe("expression", function () {
477481
['∠ABC', '∠ACB'],
478482
['$5', '5'],
479483
['$x', 'x'],
484+
['∫_a^b xdx', '∫_(2a)^b xdx'],
485+
['∫_a^b xdx', '∫_a^(2b) xdx'],
486+
['∫_a^b xdx', '∫_a^b 2xdx'],
487+
['∫ xdx', '∫ 2xdx'],
488+
['∫ f(x)dx', '∫ g(x)dx'],
480489
];
481490

482491
_.each(nonequivalences, function (nonequiv) {

0 commit comments

Comments
 (0)