Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@mathigon/hilbert

Package Overview
Dependencies
Maintainers
1
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mathigon/hilbert - npm Package Compare versions

Comparing version 0.2.1 to 0.2.2

test/string-test.js

108

build/hilbert.js

@@ -334,3 +334,3 @@ 'use strict';

const SIMPLE_SYMBOLS = '|()[]{}÷,!<>=*/+-–−~^_…';
const SIMPLE_SYMBOLS = '|()[]{}÷,!<>=*/+-–−~^_…°';
const COMPLEX_SYMBOLS = Object.values(SPECIAL_OPERATORS);

@@ -449,3 +449,3 @@ const OPERATOR_SYMBOLS = [...SIMPLE_SYMBOLS, ...COMPLEX_SYMBOLS];

get variables() { return unique(join(...this.items.map(i => i.variables))); }
get functions() { return unique(join(...this.items.map(i => i.functions))); }
get functions() { return this.collapse().functions; }
toString() { return this.items.map(i => i.toString()).join(' '); }

@@ -459,3 +459,3 @@ toMathML(custom={}) { return this.items.map(i => i.toMathML(custom)).join(''); }

const PRECEDENCE = words('+ − * × · // ^');
const PRECEDENCE = words('+ − * × · / ÷ // sup sub');
const COMMA = '<mo value="," lspace="0">,</mo>';

@@ -468,6 +468,10 @@

if (!PRECEDENCE.includes(expr.fn)) return false;
return PRECEDENCE.indexOf(parentFn) > PRECEDENCE.indexOf(expr);
return PRECEDENCE.indexOf(parentFn) > PRECEDENCE.indexOf(expr.fn);
}
function addRow(expr, string) {
function addMFence(expr, fn, string) {
return needsBrackets(expr, fn) ? `<mfenced>${string}</mfenced>` : string;
}
function addMRow(expr, string) {
const needsRow = (expr instanceof ExprTerm) || (expr instanceof ExprFunction);

@@ -516,2 +520,3 @@ return needsRow ? `<mrow>${string}</mrow>` : string;

collapse() {
if (this.fn === '(') return this.args[0].collapse();
return new ExprFunction(this.fn, this.args.map(a => a.collapse()));

@@ -540,5 +545,6 @@ }

if (this.fn === '^') return args.join('^');
if (this.fn === 'sup') return args.join('^');
if (this.fn === 'sub') return args.join('_');
if (words('+ * × · / sup = < > ≤ ≥').includes(this.fn))
if (words('+ * × · / = < > ≤ ≥').includes(this.fn))
return args.join(' ' + this.fn + ' ');

@@ -556,19 +562,22 @@

toMathML(custom={}) {
const args = this.args.map(a => needsBrackets(a, this.fn) ?
'<mfenced>' + a.toMathML() + '</mfenced>' : a.toMathML());
const args = this.args.map(a => a.toMathML(custom));
const argsF = this.args.map((a, i) => addMFence(a, this.fn, args[i]));
if (this.fn in custom) return custom[this.fn](...args);
if (this.fn in custom) {
const argsX = args.map((a, i) => ({toString: () => a, val: this.args[i]}));
return custom[this.fn](...argsX);
}
if (this.fn === '−') return args.length > 1 ?
args.join('<mo value="−">−</mo>') : '<mo rspace="0" value="−">−</mo>' + args[0];
if (this.fn === '−') return argsF.length > 1 ?
argsF.join('<mo value="−">−</mo>') : '<mo rspace="0" value="−">−</mo>' + argsF[0];
if (isOneOf(this.fn, '+', '=', '<', '>', '≤', '≥'))
return args.join(`<mo value="${this.fn}">${this.fn}</mo>`);
return argsF.join(`<mo value="${this.fn}">${this.fn}</mo>`);
if (isOneOf(this.fn, '*', '×', '·')) {
let str = args[0];
for (let i = 1; i < args.length - 1; ++i) {
let str = argsF[0];
for (let i = 1; i < argsF.length - 1; ++i) {
// We only show the × symbol between consecutive numbers.
const showTimes = (this.args[0] instanceof ExprNumber && this.args[1] instanceof ExprNumber);
str += (showTimes ? `<mo value="×">×</mo>` : '') + args[1];
str += (showTimes ? `<mo value="×">×</mo>` : '') + argsF[1];
}

@@ -578,18 +587,26 @@ return str;

if (this.fn === 'sqrt') return `<msqrt>${args[0]}</msqrt>`;
if (this.fn === '//') return argsF.join(`<mo value="/">/</mo>`);
if (this.fn === 'sqrt') return `<msqrt>${argsF[0]}</msqrt>`;
if (isOneOf(this.fn, '/', 'sup', 'sub', 'root')) {
const el = {'/': 'mfrac', 'sup': 'msup', 'sub': 'msub', 'root': 'mroot'}[this.fn];
const args1 = args.map((a, i) => addRow(this.args[i], a));
if (isOneOf(this.fn, '/', 'root')) {
// Fractions or square roots don't have brackets around their arguments
const el = (this.fn === '/' ? 'mfrac' : 'mroot');
const args1 = this.args.map((a, i) => addMRow(a, args[i]));
return `<${el}>${args1.join('')}</${el}>`;
}
if (isOneOf(this.fn, 'sup', 'sub')) {
// Sup and sub only have brackets around their first argument.
const args1 = [addMRow(this.args[0], argsF[0]), addMRow(this.args[1], args[1])];
return `<m${this.fn}>${args1.join('')}</m${this.fn}>`;
}
if (isOneOf(this.fn, '(', '[', '{'))
return `<mfenced open="${this.fn}" close="${BRACKETS[this.fn]}">${args.join(COMMA)}</mfenced>`;
return `<mfenced open="${this.fn}" close="${BRACKETS[this.fn]}">${argsF.join(COMMA)}</mfenced>`;
if (isOneOf(this.fn, '!', '%'))
return args[0] + `<mo value="${this.fn}" lspace="0">${this.fn}</mo>`;
return argsF[0] + `<mo value="${this.fn}" lspace="0">${this.fn}</mo>`;
// TODO Implement other functions
return `<mi>${this.fn}</mi><mfenced>${args.join(COMMA)}</mfenced>`;
return `<mi>${this.fn}</mi><mfenced>${argsF.join(COMMA)}</mfenced>`;
}

@@ -610,3 +627,3 @@ }

if (type === 'SPACE' && buffer.length > 1) return new ExprSpace();
if (type === 'STRING') return new ExprString(buffer);
if (type === 'STR') return new ExprString(buffer);

@@ -680,3 +697,5 @@ if (type === 'VAR') {

function makeTerm(items) {
return (items.length === 1) ? items[0] : new ExprTerm(items);
if (items.length > 1) return new ExprTerm(items);
if (items[0] instanceof ExprOperator) return new ExprTerm(items);
return items[0];
}

@@ -749,3 +768,5 @@

// Check if this is a normal bracket, or a function call.
const isFn = (isOperator(t, ')') && last(term) instanceof ExprIdentifier);
// Terms like x(y) are treated as functions, rather than implicit
// multiplication, except for π(y).
const isFn = (isOperator(t, ')') && last(term) instanceof ExprIdentifier && last(term).i !== 'π');
const fnName = isFn ? term.pop().i : isOperator(t, '|') ? 'abs' : closed[0].o;

@@ -779,2 +800,3 @@

function clearBuffer() {
if (lastWasSymbol) throw ExprError.invalidExpression();
if (!buffer.length) return;

@@ -803,3 +825,2 @@ result.push(buffer.length > 1 ? new ExprFunction(symbol[0], buffer) : buffer[0]);

if (lastWasSymbol) throw ExprError.invalidExpression();
clearBuffer();

@@ -824,6 +845,6 @@ return result;

findBinaryFunction(tokens, '= < > ≤ ≥');
findBinaryFunction(tokens, '//', '/');
findBinaryFunction(tokens, '// ÷', '/');
// Match multiplication operators.
tokens = findAssociativeFunction(tokens, '* × ·', true);
tokens = findAssociativeFunction(tokens, '× * ·', true);

@@ -867,16 +888,21 @@ // Match - and ± operators.

function numEquals(expr1, expr2) {
const vars = unique([...expr1.variables, ...expr2.variables]);
const fn1 = expr1.collapse();
const fn2 = expr2.collapse();
try {
const vars = unique([...expr1.variables, ...expr2.variables]);
const fn1 = expr1.collapse();
const fn2 = expr2.collapse();
// We only test positive random numbers, because negative numbers raised
// to non-integer powers return NaN.
for (let i = 0; i < 5; ++i) {
const substitution = {};
for (let v of vars) substitution[v] = CONSTANTS[v] || Math.random() * 5;
const a = fn1.evaluate(substitution);
const b = fn2.evaluate(substitution);
if (!nearlyEquals(a, b)) return false;
// We only test positive random numbers, because negative numbers raised
// to non-integer powers return NaN.
for (let i = 0; i < 5; ++i) {
const substitution = {};
for (let v of vars) substitution[v] = CONSTANTS[v] || Math.random() * 5;
const a = fn1.evaluate(substitution);
const b = fn2.evaluate(substitution);
if (isNaN(a) || isNaN(b)) continue; // This might happen in square roots.
if (!nearlyEquals(a, b)) return false;
}
return true;
} catch(e) {
return false;
}
return true;
}

@@ -883,0 +909,0 @@

{
"name": "@mathigon/hilbert",
"version": "0.2.1",
"version": "0.2.2",
"description": "JavaScript expression parsing, MathML rendering and CAS.",

@@ -28,3 +28,3 @@ "keywords": [

"dependencies": {
"@mathigon/fermat": "^0.2.6",
"@mathigon/fermat": "^0.2.7",
"@mathigon/core": "^0.2.5"

@@ -31,0 +31,0 @@ },

@@ -15,3 +15,2 @@ # Hilbert.js

* [ ] _Remove expressions code from `fermat.js`. Update x-equation._
* [ ] Support for functions with subscripts (e.g. `log_a(b)`).

@@ -18,0 +17,0 @@ * [ ] Support for super+subscripts (e.g. `a_n^2` or `a^2_n`).

@@ -121,3 +121,3 @@ // =============================================================================

get variables() { return unique(join(...this.items.map(i => i.variables))); }
get functions() { return unique(join(...this.items.map(i => i.functions))); }
get functions() { return this.collapse().functions; }
toString() { return this.items.map(i => i.toString()).join(' '); }

@@ -124,0 +124,0 @@ toMathML(custom={}) { return this.items.map(i => i.toMathML(custom)).join(''); }

@@ -34,16 +34,21 @@ // =============================================================================

function numEquals(expr1, expr2) {
const vars = unique([...expr1.variables, ...expr2.variables]);
const fn1 = expr1.collapse();
const fn2 = expr2.collapse();
try {
const vars = unique([...expr1.variables, ...expr2.variables]);
const fn1 = expr1.collapse();
const fn2 = expr2.collapse();
// We only test positive random numbers, because negative numbers raised
// to non-integer powers return NaN.
for (let i = 0; i < 5; ++i) {
const substitution = {};
for (let v of vars) substitution[v] = CONSTANTS[v] || Math.random() * 5;
const a = fn1.evaluate(substitution);
const b = fn2.evaluate(substitution);
if (!nearlyEquals(a, b)) return false;
// We only test positive random numbers, because negative numbers raised
// to non-integer powers return NaN.
for (let i = 0; i < 5; ++i) {
const substitution = {};
for (let v of vars) substitution[v] = CONSTANTS[v] || Math.random() * 5;
const a = fn1.evaluate(substitution);
const b = fn2.evaluate(substitution);
if (isNaN(a) || isNaN(b)) continue; // This might happen in square roots.
if (!nearlyEquals(a, b)) return false;
}
return true;
} catch(e) {
return false;
}
return true;
}

@@ -50,0 +55,0 @@

@@ -14,3 +14,3 @@ // =============================================================================

const PRECEDENCE = words('+ − * × · // ^');
const PRECEDENCE = words('+ − * × · / ÷ // sup sub');
const COMMA = '<mo value="," lspace="0">,</mo>';

@@ -23,6 +23,10 @@

if (!PRECEDENCE.includes(expr.fn)) return false;
return PRECEDENCE.indexOf(parentFn) > PRECEDENCE.indexOf(expr);
return PRECEDENCE.indexOf(parentFn) > PRECEDENCE.indexOf(expr.fn);
}
function addRow(expr, string) {
function addMFence(expr, fn, string) {
return needsBrackets(expr, fn) ? `<mfenced>${string}</mfenced>` : string;
}
function addMRow(expr, string) {
const needsRow = (expr instanceof ExprTerm) || (expr instanceof ExprFunction);

@@ -71,2 +75,3 @@ return needsRow ? `<mrow>${string}</mrow>` : string;

collapse() {
if (this.fn === '(') return this.args[0].collapse();
return new ExprFunction(this.fn, this.args.map(a => a.collapse()));

@@ -95,5 +100,6 @@ }

if (this.fn === '^') return args.join('^');
if (this.fn === 'sup') return args.join('^');
if (this.fn === 'sub') return args.join('_');
if (words('+ * × · / sup = < > ≤ ≥').includes(this.fn))
if (words('+ * × · / = < > ≤ ≥').includes(this.fn))
return args.join(' ' + this.fn + ' ');

@@ -111,19 +117,22 @@

toMathML(custom={}) {
const args = this.args.map(a => needsBrackets(a, this.fn) ?
'<mfenced>' + a.toMathML() + '</mfenced>' : a.toMathML());
const args = this.args.map(a => a.toMathML(custom));
const argsF = this.args.map((a, i) => addMFence(a, this.fn, args[i]));
if (this.fn in custom) return custom[this.fn](...args);
if (this.fn in custom) {
const argsX = args.map((a, i) => ({toString: () => a, val: this.args[i]}));
return custom[this.fn](...argsX);
}
if (this.fn === '−') return args.length > 1 ?
args.join('<mo value="−">−</mo>') : '<mo rspace="0" value="−">−</mo>' + args[0];
if (this.fn === '−') return argsF.length > 1 ?
argsF.join('<mo value="−">−</mo>') : '<mo rspace="0" value="−">−</mo>' + argsF[0];
if (isOneOf(this.fn, '+', '=', '<', '>', '≤', '≥'))
return args.join(`<mo value="${this.fn}">${this.fn}</mo>`);
return argsF.join(`<mo value="${this.fn}">${this.fn}</mo>`);
if (isOneOf(this.fn, '*', '×', '·')) {
let str = args[0];
for (let i = 1; i < args.length - 1; ++i) {
let str = argsF[0];
for (let i = 1; i < argsF.length - 1; ++i) {
// We only show the × symbol between consecutive numbers.
const showTimes = (this.args[0] instanceof ExprNumber && this.args[1] instanceof ExprNumber);
str += (showTimes ? `<mo value="×">×</mo>` : '') + args[1];
str += (showTimes ? `<mo value="×">×</mo>` : '') + argsF[1];
}

@@ -133,19 +142,27 @@ return str;

if (this.fn === 'sqrt') return `<msqrt>${args[0]}</msqrt>`;
if (this.fn === '//') return argsF.join(`<mo value="/">/</mo>`);
if (this.fn === 'sqrt') return `<msqrt>${argsF[0]}</msqrt>`;
if (isOneOf(this.fn, '/', 'sup', 'sub', 'root')) {
const el = {'/': 'mfrac', 'sup': 'msup', 'sub': 'msub', 'root': 'mroot'}[this.fn];
const args1 = args.map((a, i) => addRow(this.args[i], a));
if (isOneOf(this.fn, '/', 'root')) {
// Fractions or square roots don't have brackets around their arguments
const el = (this.fn === '/' ? 'mfrac' : 'mroot');
const args1 = this.args.map((a, i) => addMRow(a, args[i]));
return `<${el}>${args1.join('')}</${el}>`;
}
if (isOneOf(this.fn, 'sup', 'sub')) {
// Sup and sub only have brackets around their first argument.
const args1 = [addMRow(this.args[0], argsF[0]), addMRow(this.args[1], args[1])];
return `<m${this.fn}>${args1.join('')}</m${this.fn}>`;
}
if (isOneOf(this.fn, '(', '[', '{'))
return `<mfenced open="${this.fn}" close="${BRACKETS[this.fn]}">${args.join(COMMA)}</mfenced>`;
return `<mfenced open="${this.fn}" close="${BRACKETS[this.fn]}">${argsF.join(COMMA)}</mfenced>`;
if (isOneOf(this.fn, '!', '%'))
return args[0] + `<mo value="${this.fn}" lspace="0">${this.fn}</mo>`;
return argsF[0] + `<mo value="${this.fn}" lspace="0">${this.fn}</mo>`;
// TODO Implement other functions
return `<mi>${this.fn}</mi><mfenced>${args.join(COMMA)}</mfenced>`;
return `<mi>${this.fn}</mi><mfenced>${argsF.join(COMMA)}</mfenced>`;
}
}

@@ -24,3 +24,3 @@ // =============================================================================

if (type === 'SPACE' && buffer.length > 1) return new ExprSpace();
if (type === 'STRING') return new ExprString(buffer);
if (type === 'STR') return new ExprString(buffer);

@@ -94,3 +94,5 @@ if (type === 'VAR') {

function makeTerm(items) {
return (items.length === 1) ? items[0] : new ExprTerm(items);
if (items.length > 1) return new ExprTerm(items);
if (items[0] instanceof ExprOperator) return new ExprTerm(items);
return items[0];
}

@@ -163,3 +165,5 @@

// Check if this is a normal bracket, or a function call.
const isFn = (isOperator(t, ')') && last(term) instanceof ExprIdentifier);
// Terms like x(y) are treated as functions, rather than implicit
// multiplication, except for π(y).
const isFn = (isOperator(t, ')') && last(term) instanceof ExprIdentifier && last(term).i !== 'π');
const fnName = isFn ? term.pop().i : isOperator(t, '|') ? 'abs' : closed[0].o;

@@ -193,2 +197,3 @@

function clearBuffer() {
if (lastWasSymbol) throw ExprError.invalidExpression();
if (!buffer.length) return;

@@ -217,3 +222,2 @@ result.push(buffer.length > 1 ? new ExprFunction(symbol[0], buffer) : buffer[0]);

if (lastWasSymbol) throw ExprError.invalidExpression();
clearBuffer();

@@ -238,6 +242,6 @@ return result;

findBinaryFunction(tokens, '= < > ≤ ≥');
findBinaryFunction(tokens, '//', '/');
findBinaryFunction(tokens, '// ÷', '/');
// Match multiplication operators.
tokens = findAssociativeFunction(tokens, '* × ·', true);
tokens = findAssociativeFunction(tokens, '× * ·', true);

@@ -244,0 +248,0 @@ // Match - and ± operators.

@@ -104,4 +104,4 @@ // =============================================================================

const SIMPLE_SYMBOLS = '|()[]{}÷,!<>=*/+-–−~^_…';
const SIMPLE_SYMBOLS = '|()[]{}÷,!<>=*/+-–−~^_…°';
const COMPLEX_SYMBOLS = Object.values(SPECIAL_OPERATORS);
export const OPERATOR_SYMBOLS = [...SIMPLE_SYMBOLS, ...COMPLEX_SYMBOLS];

@@ -12,3 +12,3 @@ // =============================================================================

const expr = (src) => hilbert.Expression.parse(src);
const mathML = (src) => expr(src).toMathML();
const mathML = (src, replace = {}) => expr(src).toMathML(replace);

@@ -29,2 +29,17 @@

tape('Custom Functions', function(test) {
const options = {
a: (a) => `<a>${a}</a>`,
b: (...b) => `<b>${b.join(',')}</b>`,
c: (c) => `<c attr="${c.val.s}">${c}</c>`
};
test.equal(mathML('a(1)', options), '<a><mn>1</mn></a>');
test.equal(mathML('b(1,2)', options), '<b><mn>1</mn>,<mn>2</mn></b>');
test.equal(mathML('c("text")', options), '<c attr="text"><mtext>text</mtext></c>');
test.end();
});
tape('Whitespace', function(test) {

@@ -31,0 +46,0 @@ test.equal(mathML('a b'), '<mi>a</mi><mspace/><mi>b</mi>');

@@ -18,6 +18,6 @@ // =============================================================================

test.equal(str('-1'), '− 1');
test.equal(expr('x + y').toString(), 'x + y');
test.equal(expr('aa + bb + cc').toString(), 'aa + bb + cc');
test.equal(expr('5x + 2').toString(), '5 x + 2');
test.equal(expr('|a|').toString(), 'abs(a)');
test.equal(str('x + y'), 'x + y');
test.equal(str('aa + bb + cc'), 'aa + bb + cc');
test.equal(str('5x + 2'), '5 x + 2');
test.equal(str('|a|'), 'abs(a)');
test.end();

@@ -27,6 +27,28 @@ });

tape('special operators', function(test) {
test.equal(expr('x - y').toString(), 'x − y');
test.equal(expr('x – y').toString(), 'x − y');
test.equal(expr('x − y').toString(), 'x − y');
test.equal(str('x - y'), 'x − y');
test.equal(str('x – y'), 'x − y');
test.equal(str('x − y'), 'x − y');
test.end();
});
tape('functions', function(test) {
test.equal(str('A_B'), 'A_B');
test.equal(str('fn(A, B)'), 'fn(A, B)');
test.end();
});
tape('strings', function(test) {
test.equal(str('"A" + "B"'), '"A" + "B"');
test.equal(str('"A"_"B"'), '"A"_"B"');
test.equal(str('fn(A_"B",C)'), 'fn(A_"B", C)');
test.end();
});
tape('errors', function(test) {
test.throws(() => expr('a + + b').collapse());
test.throws(() => expr('a * - b').collapse());
test.throws(() => expr('a + (a +)').collapse());
test.throws(() => expr('a + (*)').collapse());
test.throws(() => expr('(+) - a').collapse());
test.end();
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc