@mathigon/hilbert
Advanced tools
Comparing version 0.1.0 to 0.1.1
@@ -6,3 +6,59 @@ 'use strict'; | ||
// ============================================================================= | ||
// Hilbert.js | Expression Errors | ||
// (c) Mathigon | ||
// ============================================================================= | ||
/** | ||
* Expression Error Class | ||
*/ | ||
class ExprError extends Error { | ||
constructor(name, message) { | ||
super(message); | ||
this.name = name; | ||
} | ||
// --------------------------------------------------------------------------- | ||
// Eval Errors | ||
static undefinedVariable(x) { | ||
return new ExprError('EvalError', `Undefined variable “${x}”.`); | ||
} | ||
static undefinedFunction(x) { | ||
return new ExprError('EvalError', `Undefined function “${x}”.`); | ||
} | ||
// --------------------------------------------------------------------------- | ||
// Syntax Errors | ||
static invalidCharacter(x) { | ||
return new ExprError('SyntaxError', `Unknown symbol “${x}”.`); | ||
} | ||
static conflictingBrackets(x) { | ||
return new ExprError('SyntaxError', `Conflicting brackets “${x}”.`); | ||
} | ||
static unclosedBracket(x) { | ||
return new ExprError('SyntaxError', `Unclosed bracket “${x}”.`); | ||
} | ||
static startingOperator(x) { | ||
return new ExprError('SyntaxError', `A term cannot start or end with a “${x}”.`); | ||
} | ||
static consecutiveOperators(x, y) { | ||
return new ExprError('SyntaxError', `A “${x}” cannot be followed by a “${y}”.`); | ||
} | ||
static invalidExpression() { | ||
return new ExprError('SyntaxError', `This expression is invalid.`); | ||
} | ||
} | ||
// ============================================================================= | ||
@@ -12,2 +68,18 @@ | ||
/** | ||
* Checks if x is strictly equal to any one of the following arguments | ||
* @param {*} x | ||
* @param {...*} values | ||
* @returns {boolean} | ||
*/ | ||
function isOneOf(x, ...values) { | ||
for (let v of values) { | ||
if (x === v) return true; | ||
} | ||
return false; | ||
} | ||
// ============================================================================= | ||
/** | ||
* Returns the last item in an array, or the ith item from the end. | ||
@@ -47,21 +119,14 @@ * @param {Array} array | ||
// ============================================================================= | ||
/* | ||
* Checks if an object is a string. | ||
* @param {*} x | ||
* @returns {boolean} | ||
/** | ||
* Splits a string into space separated words. | ||
* @param {string} str | ||
* @returns {Array<string>} | ||
*/ | ||
function isString(x) { | ||
return (x instanceof String) || (typeof x === 'string'); | ||
function words(str) { | ||
if (!str) return []; | ||
return str.trim().split(/\s+/); | ||
} | ||
/* | ||
* Checks if an object is a number. | ||
* @param {*} x | ||
* @returns {boolean} | ||
*/ | ||
function isNumber(x) { | ||
return (x instanceof Number) || (typeof x === 'number'); | ||
} | ||
// ============================================================================= | ||
@@ -77,6 +142,8 @@ // ============================================================================= | ||
const BRACKETS = {'(': ')', '[': ']', '{': '}', '|': '|'}; | ||
const SPECIAL_OPERATORS = { | ||
'*': '·', | ||
'**': '∗', | ||
'//': '/', | ||
'//': '//', | ||
'+-': '±', | ||
@@ -164,3 +231,3 @@ xx: '×', | ||
const SIMPLE_SYMBOLS = '|()[]{}÷,!<>=*/+-–~'; | ||
const SIMPLE_SYMBOLS = '|()[]{}÷,!<>=*/+-–~^_…'; | ||
const COMPLEX_SYMBOLS = Object.values(SPECIAL_OPERATORS); | ||
@@ -172,9 +239,19 @@ const OPERATOR_SYMBOLS = [...SIMPLE_SYMBOLS, ...COMPLEX_SYMBOLS]; | ||
/* const PRECEDENCE = ['+', '-', '*', '/', 'sqrt', '^']; | ||
const PRECEDENCE = words('+ - * × · // ^'); | ||
const COMMA = '<mo value="," lspace="0">,</mo>'; | ||
function needsBrackets(expr, parentOperator) { | ||
if (isNumber(expr) || isString(expr)) return false; | ||
return PRECEDENCE.indexOf(parentOperator) < PRECEDENCE.indexOf(expr[0]); | ||
} */ | ||
function needsBrackets(expr, parentFn) { | ||
if (!PRECEDENCE.includes(parentFn)) return false; | ||
if (expr instanceof ExprTerm) return true; | ||
if (!(expr instanceof ExprFunction)) return false; | ||
if (!PRECEDENCE.includes(expr.fn)) return false; | ||
return PRECEDENCE.indexOf(parentFn) > PRECEDENCE.indexOf(expr); | ||
} | ||
function addRow(expr, string) { | ||
const needsRow = (expr instanceof ExprTerm) || (expr instanceof ExprFunction); | ||
return needsRow ? `<mrow>${string}</mrow>` : string; | ||
} | ||
class ExprFunction { | ||
@@ -187,4 +264,3 @@ | ||
evaluate(vars) { | ||
// TODO Implement for all functions | ||
evaluate(vars={}) { | ||
const args = this.args.map(a => a.evaluate(vars)); | ||
@@ -195,4 +271,6 @@ if (this.fn in vars) return vars[this.fn](...args); | ||
case '+': return args.reduce((a, b) => a + b, 0); | ||
// TODO case '-': return (a, b) => (b === undefined) ? -a : a - b; | ||
case '*': return args.reduce((a, b) => a * b, 1); | ||
case '-': return (args.length > 1) ? args[1] - args[0] : -args[0]; | ||
case '*': | ||
case '·': | ||
case '×': return args.reduce((a, b) => a * b, 1); | ||
case '/': return args[0] / args[1]; | ||
@@ -204,8 +282,11 @@ case 'sin': return Math.sin(args[0]); | ||
case 'sup': return Math.pow(args[0], args[1]); | ||
case 'sqrt': return Math.sqrt(args[0]); | ||
case 'root': return Math.pow(args[0], 1 / args[1]); | ||
// TODO Implement for all functions | ||
} | ||
throw new ExprError('EvalError', `Unable to evaluate function "${this.fn}".`); | ||
throw ExprError.undefinedFunction(this.fn); | ||
} | ||
substitute(vars) { | ||
substitute(vars={}) { | ||
return new ExprFunction(this.fn, this.args.map(a => a.substitute(vars))); | ||
@@ -228,43 +309,52 @@ } | ||
toString() { | ||
// TODO Implement for all functions | ||
const args = this.args.map(a => a.toString()); | ||
const args = this.args.map(a => needsBrackets(a, this.fn) ? | ||
'(' + a.toString() + ')' : a.toString()); | ||
switch(this.fn) { | ||
case '+': return args.join(' + '); | ||
// '-': (a, b) => (b === undefined) ? '-' + a : a + ' - ' + b, | ||
// '*': (...args) => args.join('*'), | ||
// '/': (a, b) => a + '/' + b, | ||
// '!': x => x + '!', | ||
// '%': x => x + '%', | ||
// 'abs': x => '|' + x + '|', | ||
// '^': (a, b) => a + '^' + b, | ||
// '=': (a, b) => a + ' = ' + b, | ||
// '<': (a, b) => a + ' < ' + b, | ||
// '>': (a, b) => a + ' > ' + b, | ||
// '≤': (a, b) => a + ' ≤ ' + b, | ||
// '≥': (a, b) => a + ' ≥ ' + b | ||
} | ||
if (this.fn === '-') | ||
return args.length > 1 ? args.join(' – ') : '-' + args[0]; | ||
if (words('+ * × · / sup = < > ≤ ≥').includes(this.fn)) | ||
return args.join(' ' + this.fn + ' '); | ||
if (isOneOf(this.fn, '(', '[', '{')) | ||
return this.fn + this.args.join(', ') + BRACKETS[this.fn]; | ||
if (isOneOf(this.fn, '!', '%')) return args[0] + this.fn; | ||
// TODO Implement other functions | ||
return `${this.fn}(${args.join(', ')})`; | ||
} | ||
toMathML() { | ||
// TODO Implement for all functions | ||
// TODO Distinguish between fractions and '÷'. | ||
const args = this.args.map(a => a.toMathML()); | ||
toMathML(custom={}) { | ||
const args = this.args.map(a => needsBrackets(a, this.fn) ? | ||
'<mfenced>' + a.toMathML() + '</mfenced>' : a.toMathML()); | ||
// const argsStr = this.args.map(a => needsBrackets(a, this.fn) ? | ||
// `<mfenced>${a.toMathML()}</mfenced>` : a.toMathML()); | ||
if (this.fn in custom) return custom[this.fn](...args); | ||
switch(this.fn) { | ||
case 'sqrt': return `<msqrt>${args[0]}</msqrt>`; | ||
case '/': return `<mfrac><mrow>${args[0]}</mrow><mrow>${args[1]}</mrow></mfrac>`; | ||
case 'sup': return `<msup>${args[0]}<mrow>${args[1]}</mrow></msup>`; | ||
case '*': | ||
if (isNumber(this.args[0]) && isString(this.args[1])) return args.join(''); | ||
return args.join('<mo value="×">×</mo>'); | ||
case '+': return args.join('<mo value="+">+</mo>'); | ||
case '-': return args.join('<mo value="–">–</mo>'); | ||
default: return `<mi>TODO</mi>`; | ||
if (this.fn === '-') return args.length > 1 ? | ||
args.join('<mo value="-">–</mo>') : '<mo rspace="0" value="-">–</mo>' + args[0]; | ||
if (isOneOf(this.fn, '+', '=', '<', '>', '≤', '≥')) | ||
return args.join(`<mo value="${this.fn}">${this.fn}</mo>`); | ||
if (isOneOf(this.fn, '*', '×', '·')) { | ||
return args.join(''); | ||
} | ||
if (this.fn === 'sqrt') return `<msqrt>${args[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)); | ||
return `<${el}>${args1.join('')}</${el}>`; | ||
} | ||
if (isOneOf(this.fn, '(', '[', '{')) | ||
return `<mfenced open="${this.fn}" close="${BRACKETS[this.fn]}">${args.join(COMMA)}</mfenced>`; | ||
if (isOneOf(this.fn, '!', '%')) | ||
return args[0] + `<mo value="${this.fn}" lspace="0">${this.fn}</mo>`; | ||
// TODO Implement other functions | ||
return `<mi>${this.fn}</mi><mfenced>${args.join(COMMA)}</mfenced>`; | ||
} | ||
@@ -275,5 +365,6 @@ } | ||
const BRACKETS = {'(': ')', '[': ']', '{': '}', '|': '|'}; | ||
// ----------------------------------------------------------------------------- | ||
// Tokenizer | ||
@@ -284,3 +375,3 @@ function createToken(buffer, type) { | ||
if (type === 'NUM') return new ExprNumber(+buffer); | ||
if (type === 'SPACE' && buffer.length > 1) return [new ExprSpace()]; | ||
if (type === 'SPACE' && buffer.length > 1) return new ExprSpace(); | ||
if (type === 'STRING') return new ExprString(buffer); | ||
@@ -329,3 +420,3 @@ | ||
OPERATOR_SYMBOLS.includes(s) ? 'OP' : s.match(/\s/) ? 'SPACE' : ''; | ||
if (!sType) throw new ExprError('Syntax Error', `Unknown symbol "${s}".`); | ||
if (!sType) throw ExprError.invalidCharacter(s); | ||
@@ -351,3 +442,5 @@ if (!type || (type === 'NUM' && sType !== 'NUM') || | ||
// ----------------------------------------------------------------------------- | ||
// Utility Functions | ||
@@ -361,3 +454,3 @@ function makeTerm(items) { | ||
for (let i of items) { | ||
if (check[i]) { | ||
if (check(i)) { | ||
result.push([]); | ||
@@ -371,3 +464,41 @@ } else { | ||
function isOperator(expr, fns) { | ||
return expr instanceof ExprOperator && words(fns).includes(expr.o); | ||
} | ||
function removeBrackets(expr) { | ||
return (expr instanceof ExprFunction && expr.fn === '(') ? expr.args[0] : expr; | ||
} | ||
function findBinaryFunction(tokens, fn, toFn) { | ||
if (isOperator(tokens[0], fn) || isOperator(tokens[tokens.length - 1], fn)) | ||
throw ExprError.startingOperator(fn); | ||
for (let i = 1; i < tokens.length - 1; ++i) { | ||
if (!isOperator(tokens[i], fn)) continue; | ||
const a = tokens[i - 1]; | ||
const b = tokens[i + 1]; | ||
if (a instanceof ExprOperator) throw ExprError.consecutiveOperators(a.o, tokens[i].o); | ||
if (b instanceof ExprOperator) throw ExprError.consecutiveOperators(tokens[i].o, b.o); | ||
const args = [removeBrackets(a), removeBrackets(b)]; | ||
tokens.splice(i - 1, 3, new ExprFunction(toFn || tokens[i].o, args)); | ||
i -= 2; | ||
} | ||
} | ||
// ----------------------------------------------------------------------------- | ||
// Match Brackets | ||
function prepareTerm(tokens) { | ||
// TODO Combine sup and sub calls into a single supsub function. | ||
findBinaryFunction(tokens, '^', 'sup'); | ||
findBinaryFunction(tokens, '/'); | ||
return makeTerm(tokens); | ||
} | ||
function matchBrackets(tokens) { | ||
findBinaryFunction(tokens, '_', 'sub'); | ||
const stack = [[]]; | ||
@@ -378,6 +509,6 @@ | ||
if (')]}'.includes(t.o) || (t.o === '|' && lastOpen === '|')) { | ||
if (isOperator(t, ') ] }') || (isOperator(t, '|') && lastOpen === '|')) { | ||
if (t.o !== BRACKETS[lastOpen]) | ||
throw new ExprError('SyntaxError', `Unmatched bracket “${t.o}”.`); | ||
if (!isOperator(t, BRACKETS[lastOpen])) | ||
throw ExprError.conflictingBrackets(t.o); | ||
@@ -388,10 +519,10 @@ const closed = stack.pop(); | ||
// Check if this is a normal bracket, or a function call. | ||
const isFn = (t.o === ')' && last(term) instanceof ExprIdentifier); | ||
const fnName = isFn ? term.pop().i : t.o === '|' ? 'abs' : closed[0]; | ||
const isFn = (isOperator(t, ')') && last(term) instanceof ExprIdentifier); | ||
const fnName = isFn ? term.pop().i : isOperator(t, '|') ? 'abs' : closed[0].o; | ||
// Support multiple arguments for function calls. | ||
const args = splitArray(closed.slice(1), a => a.o === ','); | ||
term.push(new ExprFunction(fnName, args.map(makeTerm))); | ||
const args = splitArray(closed.slice(1), a => isOperator(a, ',')); | ||
term.push(new ExprFunction(fnName, args.map(prepareTerm))); | ||
} else if('([{|'.includes(t.o)) { | ||
} else if (isOperator(t, '( [ { |')) { | ||
stack.push([t]); | ||
@@ -404,51 +535,25 @@ | ||
if (stack.length > 1) | ||
throw new ExprError('SyntaxError', `Unclosed bracket “${last(stack)[0]}”.`); | ||
return makeTerm(stack[0]); | ||
if (stack.length > 1) throw ExprError.unclosedBracket(last(stack)[0].o); | ||
return prepareTerm(stack[0]); | ||
} | ||
// ----------------------------------------------------------------------------- | ||
// Collapse term items | ||
/* function findBinaryFunction(tokens, chars) { | ||
for (let i=0; i<tokens.length; ++i) { | ||
if (chars.includes(tokens[i])) { | ||
let a = tokens[i-1]; | ||
let b = tokens[i+1]; | ||
if (b == null) throw new ExprError('SyntaxError', `An expression cannot end with a “${tokens[i]}”.`); | ||
if ('+-* / ^% ! ' .includes(a)) throw new ExprError('SyntaxError', `A “${a}” cannot be followed by a “${tokens[i]}”.`); | ||
if ('+-* /^%!'.includes(b)) throw new ExprError('SyntaxError', `A “${tokens[i]}” cannot be followed by a “${b}”.`); | ||
tokens.splice(i - 1, 3, [tokens[i], a, b]); | ||
i -= 2; | ||
} | ||
} | ||
}*/ | ||
function collapseTerm(tokens) { | ||
findBinaryFunction(tokens, '= < > ≤ ≥'); | ||
findBinaryFunction(tokens, '//', '/'); | ||
function collapseTerm() { | ||
// TODO Operators to functions, implicit multiplication, equals sign, ... | ||
// TODO Support >2 arguments for + and * | ||
// TODO Match multiplication and implicit multiplication | ||
/* | ||
findBinaryFunction(tokens, '^'); // Powers | ||
findBinaryFunction(tokens, '* /'); // Multiplication and division. | ||
// TODO Match starting - or ± | ||
// Implicit multiplication (consecutive expressions) | ||
for (let i=0; i<tokens.length-1; ++i) { | ||
if (!'+-* /^%!'.includes(tokens[i]) && !'+-* /^%!'.includes(tokens[i+1])) { | ||
tokens.splice(i, 2, ['*', tokens[i], tokens[i+1]]); | ||
i -= 1; | ||
} | ||
} | ||
findBinaryFunction(tokens, '-', '-'); | ||
findBinaryFunction(tokens, '±', '±'); | ||
// Leading (plus)minus. | ||
if ('-±'.includes(tokens[0])) { | ||
if (tokens.length <= 1) throw new ExprError('SyntaxError', `This expression is invalid.`); | ||
tokens.splice(0, 2, [tokens[0], tokens[1]]); | ||
} | ||
// TODO Match addition | ||
findBinaryFunction(tokens, '+-±'); // Addition and subtraction. | ||
findBinaryFunction(tokens, '=<>≤≥'); // Equalities and inequalities. | ||
if (tokens.length > 1) throw new ExprError('SyntaxError', `This expression is invalid.`); | ||
return tokens[0]; */ | ||
if (tokens.length > 1) throw ExprError.invalidExpression(); | ||
return tokens[0]; | ||
} | ||
@@ -464,20 +569,62 @@ | ||
/** | ||
* Maths Expression | ||
*/ | ||
class Expression { | ||
/** | ||
* Parses a string to an expression. | ||
* @param {string} str | ||
* @returns {Expression} | ||
*/ | ||
static parse(str) { return matchBrackets(tokenize(str)) } | ||
evaluate() { return null; } | ||
substitute() { return this; } | ||
/** | ||
* Evaluates an expression using a given map of variables and functions. | ||
* @param {Object.<String, Expression>=} _vars | ||
* @returns {number|null} | ||
*/ | ||
evaluate(_vars={}) { return null; } | ||
/** | ||
* Substitutes a new expression for a variable. | ||
* @param {Object.<String, Expression>=} _vars | ||
* @returns {Expression} | ||
*/ | ||
substitute(_vars={}) { return this; } | ||
/** | ||
* Returns the simplest mathematically equivalent expression. | ||
* @returns {Expression} | ||
*/ | ||
get simplified() { return this; } | ||
/** | ||
* Returns a list of all variables used in the expression. | ||
* @returns {String[]} | ||
*/ | ||
get variables() { return []; } | ||
/** | ||
* Returns a list of all functions called by the expression. | ||
* @returns {String[]} | ||
*/ | ||
get functions() { return []; } | ||
/** | ||
* Converts the expression to a plain text string. | ||
* @returns {string} | ||
*/ | ||
toString() { return ''; } | ||
toMathML() { return ''; } | ||
} | ||
class ExprError extends Error { | ||
constructor(name, message) { | ||
super(message); | ||
this.name = name; | ||
} | ||
/** | ||
* Converts the expression to a MathML string. | ||
* @param {Object.<String, Function>=} _custom | ||
* @returns {string} | ||
*/ | ||
toMathML(_custom={}) { return ''; } | ||
} | ||
// ----------------------------------------------------------------------------- | ||
class ExprNumber extends Expression { | ||
@@ -493,11 +640,11 @@ constructor(n) { super(); this.n = n; } | ||
evaluate(vars) { | ||
evaluate(vars={}) { | ||
if (this.i in vars) return vars[this.i]; | ||
if (this.i in CONSTANTS) return CONSTANTS[this.i]; | ||
throw new ExprError('EvalError', `Unknown identifier "${this.i}".`); | ||
throw ExprError.undefinedVariable(this.i); | ||
} | ||
substitute(vars) { return vars[this.i] || this; } | ||
substitute(vars={}) { return vars[this.i] || this; } | ||
get variables() { return [this.i]; } | ||
toString() { return '' + this.i; } | ||
toString() { return this.i; } | ||
toMathML() { return `<mi>${this.i}</mi>`; } | ||
@@ -508,3 +655,3 @@ } | ||
constructor(s) { super(); this.s = s; } | ||
evaluate() { throw new ExprError('EvalError', 'Expressions contains a string.'); } | ||
evaluate() { throw ExprError.undefinedVariable(this.s); } | ||
toString() { return '"' + this.s + '"'; } | ||
@@ -516,3 +663,3 @@ toMathML() { return `<mtext>${this.s}</mtext>`; } | ||
toString() { return ' '; } | ||
toMathML() { return `<mspace></mspace>`; } | ||
toMathML() { return `<mspace/>`; } | ||
} | ||
@@ -522,4 +669,4 @@ | ||
constructor(o) { this.o = o; } | ||
toString() { return '' + this.o; } | ||
toMathML() { return `<mo value="${this.o}">${this.o}</mo>`; } | ||
toString() { return this.o.replace('//', '/'); } | ||
toMathML() { return `<mo value="${this.toString()}">${this.toString()}</mo>`; } | ||
} | ||
@@ -529,4 +676,4 @@ | ||
constructor(items) { super(); this.items = items; } | ||
evaluate(vars) { return this.toFunction().evaluate(vars); } | ||
substitute(vars) { return this.toFunction().substitute(vars); } | ||
evaluate(vars={}) { return this.toFunction().evaluate(vars); } | ||
substitute(vars={}) { return this.toFunction().substitute(vars); } | ||
get simplified() { return this.toFunction().variables; } | ||
@@ -536,3 +683,3 @@ get variables() { return this.toFunction().variables; } | ||
toString() { return this.items.map(i => i.toString()).join(' '); } | ||
toMathML() { return this.items.map(i => i.toMathML()).join(''); } | ||
toMathML(custom={}) { return this.items.map(i => i.toMathML(custom)).join(''); } | ||
toFunction() { return collapseTerm(this.items); } | ||
@@ -539,0 +686,0 @@ } |
@@ -8,2 +8,3 @@ // ============================================================================= | ||
export {ExprError, Expression} from './src/expression' | ||
export { ExprError } from './src/errors' | ||
export { Expression } from './src/expression' |
{ | ||
"name": "@mathigon/hilbert", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "JavaScript expression parsing, MathML rendering and CAS.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -15,8 +15,15 @@ # Hilbert.js | ||
* [ ] Write `collapseTerm()` function | ||
* [ ] Write Function `evaluate()`, `toString()` and `toMathML()` methods | ||
* [ ] Lots of testing | ||
* [ ] Write Expression simplification algorithms, `equals()`, `numEquals()` and | ||
`same()` methods | ||
* [ ] Remove expressions code from `fermat.js` | ||
* [ ] __Finish collapseTerms: match non-binary addition and multiplication, | ||
match implicit multiplication, match starting - or ±.__ | ||
* [ ] Decide when to show the times symbol between consecutive factors. | ||
* [ ] Support for functions with subscripts (e.g. `log_a(b)`). | ||
* [ ] Support for super+subscripts (e.g. `a_n^2` or `a^2_n`). | ||
* [ ] Support for large operators (sum, product and integral). | ||
* [ ] Parse ^ operator from right to left (e.g. `2^2^2 == 2^(2^2)`). | ||
* [ ] Add `evaluate()`, `toString()` and `toMathML()` methods for many more | ||
special functions. | ||
* [ ] Write CAS Expression simplification algorithms and `equals()`, | ||
`numEquals()` and `same()` methods. | ||
* [ ] Remove expressions code from `fermat.js`. | ||
* [ ] Write many more tests. | ||
@@ -23,0 +30,0 @@ |
@@ -9,2 +9,3 @@ // ============================================================================= | ||
import { tokenize, matchBrackets, collapseTerm } from './parser' | ||
import { ExprError } from './errors' | ||
@@ -17,20 +18,62 @@ | ||
/** | ||
* Maths Expression | ||
*/ | ||
export class Expression { | ||
/** | ||
* Parses a string to an expression. | ||
* @param {string} str | ||
* @returns {Expression} | ||
*/ | ||
static parse(str) { return matchBrackets(tokenize(str)) } | ||
evaluate() { return null; } | ||
substitute() { return this; } | ||
/** | ||
* Evaluates an expression using a given map of variables and functions. | ||
* @param {Object.<String, Expression>=} _vars | ||
* @returns {number|null} | ||
*/ | ||
evaluate(_vars={}) { return null; } | ||
/** | ||
* Substitutes a new expression for a variable. | ||
* @param {Object.<String, Expression>=} _vars | ||
* @returns {Expression} | ||
*/ | ||
substitute(_vars={}) { return this; } | ||
/** | ||
* Returns the simplest mathematically equivalent expression. | ||
* @returns {Expression} | ||
*/ | ||
get simplified() { return this; } | ||
/** | ||
* Returns a list of all variables used in the expression. | ||
* @returns {String[]} | ||
*/ | ||
get variables() { return []; } | ||
/** | ||
* Returns a list of all functions called by the expression. | ||
* @returns {String[]} | ||
*/ | ||
get functions() { return []; } | ||
/** | ||
* Converts the expression to a plain text string. | ||
* @returns {string} | ||
*/ | ||
toString() { return ''; } | ||
toMathML() { return ''; } | ||
} | ||
export class ExprError extends Error { | ||
constructor(name, message) { | ||
super(message); | ||
this.name = name; | ||
} | ||
/** | ||
* Converts the expression to a MathML string. | ||
* @param {Object.<String, Function>=} _custom | ||
* @returns {string} | ||
*/ | ||
toMathML(_custom={}) { return ''; } | ||
} | ||
// ----------------------------------------------------------------------------- | ||
export class ExprNumber extends Expression { | ||
@@ -46,11 +89,11 @@ constructor(n) { super(); this.n = n; } | ||
evaluate(vars) { | ||
evaluate(vars={}) { | ||
if (this.i in vars) return vars[this.i]; | ||
if (this.i in CONSTANTS) return CONSTANTS[this.i]; | ||
throw new ExprError('EvalError', `Unknown identifier "${this.i}".`); | ||
throw ExprError.undefinedVariable(this.i); | ||
} | ||
substitute(vars) { return vars[this.i] || this; } | ||
substitute(vars={}) { return vars[this.i] || this; } | ||
get variables() { return [this.i]; } | ||
toString() { return '' + this.i; } | ||
toString() { return this.i; } | ||
toMathML() { return `<mi>${this.i}</mi>`; } | ||
@@ -61,3 +104,3 @@ } | ||
constructor(s) { super(); this.s = s; } | ||
evaluate() { throw new ExprError('EvalError', 'Expressions contains a string.'); } | ||
evaluate() { throw ExprError.undefinedVariable(this.s); } | ||
toString() { return '"' + this.s + '"'; } | ||
@@ -69,3 +112,3 @@ toMathML() { return `<mtext>${this.s}</mtext>`; } | ||
toString() { return ' '; } | ||
toMathML() { return `<mspace></mspace>`; } | ||
toMathML() { return `<mspace/>`; } | ||
} | ||
@@ -75,4 +118,4 @@ | ||
constructor(o) { this.o = o; } | ||
toString() { return '' + this.o; } | ||
toMathML() { return `<mo value="${this.o}">${this.o}</mo>`; } | ||
toString() { return this.o.replace('//', '/'); } | ||
toMathML() { return `<mo value="${this.toString()}">${this.toString()}</mo>`; } | ||
} | ||
@@ -82,4 +125,4 @@ | ||
constructor(items) { super(); this.items = items; } | ||
evaluate(vars) { return this.toFunction().evaluate(vars); } | ||
substitute(vars) { return this.toFunction().substitute(vars); } | ||
evaluate(vars={}) { return this.toFunction().evaluate(vars); } | ||
substitute(vars={}) { return this.toFunction().substitute(vars); } | ||
get simplified() { return this.toFunction().variables; } | ||
@@ -89,4 +132,4 @@ get variables() { return this.toFunction().variables; } | ||
toString() { return this.items.map(i => i.toString()).join(' '); } | ||
toMathML() { return this.items.map(i => i.toMathML()).join(''); } | ||
toMathML(custom={}) { return this.items.map(i => i.toMathML(custom)).join(''); } | ||
toFunction() { return collapseTerm(this.items); } | ||
} |
// ============================================================================= | ||
// Hilbert.js | Fnctions | ||
// Hilbert.js | Functions | ||
// (c) Mathigon | ||
@@ -8,13 +8,25 @@ // ============================================================================= | ||
import { unique, flatten, isNumber, isString } from '@mathigon/core' | ||
import { ExprError } from './expression' | ||
import { unique, flatten, words, isOneOf } from '@mathigon/core' | ||
import { BRACKETS } from './symbols' | ||
import { ExprTerm } from './expression' | ||
import { ExprError } from './errors' | ||
/* const PRECEDENCE = ['+', '-', '*', '/', 'sqrt', '^']; | ||
const PRECEDENCE = words('+ - * × · // ^'); | ||
const COMMA = '<mo value="," lspace="0">,</mo>'; | ||
function needsBrackets(expr, parentOperator) { | ||
if (isNumber(expr) || isString(expr)) return false; | ||
return PRECEDENCE.indexOf(parentOperator) < PRECEDENCE.indexOf(expr[0]); | ||
} */ | ||
function needsBrackets(expr, parentFn) { | ||
if (!PRECEDENCE.includes(parentFn)) return false; | ||
if (expr instanceof ExprTerm) return true; | ||
if (!(expr instanceof ExprFunction)) return false; | ||
if (!PRECEDENCE.includes(expr.fn)) return false; | ||
return PRECEDENCE.indexOf(parentFn) > PRECEDENCE.indexOf(expr); | ||
} | ||
function addRow(expr, string) { | ||
const needsRow = (expr instanceof ExprTerm) || (expr instanceof ExprFunction); | ||
return needsRow ? `<mrow>${string}</mrow>` : string; | ||
} | ||
export class ExprFunction { | ||
@@ -27,4 +39,3 @@ | ||
evaluate(vars) { | ||
// TODO Implement for all functions | ||
evaluate(vars={}) { | ||
const args = this.args.map(a => a.evaluate(vars)); | ||
@@ -35,4 +46,6 @@ if (this.fn in vars) return vars[this.fn](...args); | ||
case '+': return args.reduce((a, b) => a + b, 0); | ||
// TODO case '-': return (a, b) => (b === undefined) ? -a : a - b; | ||
case '*': return args.reduce((a, b) => a * b, 1); | ||
case '-': return (args.length > 1) ? args[1] - args[0] : -args[0]; | ||
case '*': | ||
case '·': | ||
case '×': return args.reduce((a, b) => a * b, 1); | ||
case '/': return args[0] / args[1]; | ||
@@ -44,8 +57,11 @@ case 'sin': return Math.sin(args[0]); | ||
case 'sup': return Math.pow(args[0], args[1]); | ||
case 'sqrt': return Math.sqrt(args[0]); | ||
case 'root': return Math.pow(args[0], 1 / args[1]); | ||
// TODO Implement for all functions | ||
} | ||
throw new ExprError('EvalError', `Unable to evaluate function "${this.fn}".`); | ||
throw ExprError.undefinedFunction(this.fn); | ||
} | ||
substitute(vars) { | ||
substitute(vars={}) { | ||
return new ExprFunction(this.fn, this.args.map(a => a.substitute(vars))); | ||
@@ -68,44 +84,54 @@ } | ||
toString() { | ||
// TODO Implement for all functions | ||
const args = this.args.map(a => a.toString()); | ||
const args = this.args.map(a => needsBrackets(a, this.fn) ? | ||
'(' + a.toString() + ')' : a.toString()); | ||
switch(this.fn) { | ||
case '+': return args.join(' + '); | ||
// '-': (a, b) => (b === undefined) ? '-' + a : a + ' - ' + b, | ||
// '*': (...args) => args.join('*'), | ||
// '/': (a, b) => a + '/' + b, | ||
// '!': x => x + '!', | ||
// '%': x => x + '%', | ||
// 'abs': x => '|' + x + '|', | ||
// '^': (a, b) => a + '^' + b, | ||
// '=': (a, b) => a + ' = ' + b, | ||
// '<': (a, b) => a + ' < ' + b, | ||
// '>': (a, b) => a + ' > ' + b, | ||
// '≤': (a, b) => a + ' ≤ ' + b, | ||
// '≥': (a, b) => a + ' ≥ ' + b | ||
} | ||
if (this.fn === '-') | ||
return args.length > 1 ? args.join(' – ') : '-' + args[0]; | ||
if (words('+ * × · / sup = < > ≤ ≥').includes(this.fn)) | ||
return args.join(' ' + this.fn + ' '); | ||
if (isOneOf(this.fn, '(', '[', '{')) | ||
return this.fn + this.args.join(', ') + BRACKETS[this.fn]; | ||
if (isOneOf(this.fn, '!', '%')) return args[0] + this.fn; | ||
// TODO Implement other functions | ||
return `${this.fn}(${args.join(', ')})`; | ||
} | ||
toMathML() { | ||
// TODO Implement for all functions | ||
// TODO Distinguish between fractions and '÷'. | ||
const args = this.args.map(a => a.toMathML()); | ||
toMathML(custom={}) { | ||
const args = this.args.map(a => needsBrackets(a, this.fn) ? | ||
'<mfenced>' + a.toMathML() + '</mfenced>' : a.toMathML()); | ||
// const argsStr = this.args.map(a => needsBrackets(a, this.fn) ? | ||
// `<mfenced>${a.toMathML()}</mfenced>` : a.toMathML()); | ||
if (this.fn in custom) return custom[this.fn](...args); | ||
switch(this.fn) { | ||
case 'sqrt': return `<msqrt>${args[0]}</msqrt>`; | ||
case '/': return `<mfrac><mrow>${args[0]}</mrow><mrow>${args[1]}</mrow></mfrac>`; | ||
case 'sup': return `<msup>${args[0]}<mrow>${args[1]}</mrow></msup>`; | ||
case '*': | ||
if (isNumber(this.args[0]) && isString(this.args[1])) return args.join(''); | ||
return args.join('<mo value="×">×</mo>'); | ||
case '+': return args.join('<mo value="+">+</mo>'); | ||
case '-': return args.join('<mo value="–">–</mo>'); | ||
default: return `<mi>TODO</mi>`; | ||
if (this.fn === '-') return args.length > 1 ? | ||
args.join('<mo value="-">–</mo>') : '<mo rspace="0" value="-">–</mo>' + args[0]; | ||
if (isOneOf(this.fn, '+', '=', '<', '>', '≤', '≥')) | ||
return args.join(`<mo value="${this.fn}">${this.fn}</mo>`); | ||
if (isOneOf(this.fn, '*', '×', '·')) { | ||
const showTimes = false; // TODO Decide when to show times symbol. | ||
return args.join(showTimes ? `<mo value="×">×</mo>` : ''); | ||
} | ||
if (this.fn === 'sqrt') return `<msqrt>${args[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)); | ||
return `<${el}>${args1.join('')}</${el}>`; | ||
} | ||
if (isOneOf(this.fn, '(', '[', '{')) | ||
return `<mfenced open="${this.fn}" close="${BRACKETS[this.fn]}">${args.join(COMMA)}</mfenced>`; | ||
if (isOneOf(this.fn, '!', '%')) | ||
return args[0] + `<mo value="${this.fn}" lspace="0">${this.fn}</mo>`; | ||
// TODO Implement other functions | ||
return `<mi>${this.fn}</mi><mfenced>${args.join(COMMA)}</mfenced>`; | ||
} | ||
} |
@@ -8,10 +8,12 @@ // ============================================================================= | ||
import { last } from '@mathigon/core' | ||
import { SPECIAL_OPERATORS, SPECIAL_IDENTIFIERS, IDENTIFIER_SYMBOLS, OPERATOR_SYMBOLS } from './symbols' | ||
import { ExprError, ExprNumber, ExprIdentifier, ExprOperator, ExprSpace, ExprString, ExprTerm } from "./expression"; | ||
import { last, words } from '@mathigon/core' | ||
import { SPECIAL_OPERATORS, SPECIAL_IDENTIFIERS, IDENTIFIER_SYMBOLS, OPERATOR_SYMBOLS, BRACKETS } from './symbols' | ||
import { ExprNumber, ExprIdentifier, ExprOperator, ExprSpace, ExprString, ExprTerm } from "./expression"; | ||
import { ExprFunction } from "./functions"; | ||
import { ExprError } from "./errors"; | ||
const BRACKETS = {'(': ')', '[': ']', '{': '}', '|': '|'}; | ||
// ----------------------------------------------------------------------------- | ||
// Tokenizer | ||
@@ -22,3 +24,3 @@ function createToken(buffer, type) { | ||
if (type === 'NUM') return new ExprNumber(+buffer); | ||
if (type === 'SPACE' && buffer.length > 1) return [new ExprSpace()]; | ||
if (type === 'SPACE' && buffer.length > 1) return new ExprSpace(); | ||
if (type === 'STRING') return new ExprString(buffer); | ||
@@ -67,3 +69,3 @@ | ||
OPERATOR_SYMBOLS.includes(s) ? 'OP' : s.match(/\s/) ? 'SPACE' : ''; | ||
if (!sType) throw new ExprError('Syntax Error', `Unknown symbol "${s}".`); | ||
if (!sType) throw ExprError.invalidCharacter(s); | ||
@@ -89,3 +91,5 @@ if (!type || (type === 'NUM' && sType !== 'NUM') || | ||
// ----------------------------------------------------------------------------- | ||
// Utility Functions | ||
@@ -99,3 +103,3 @@ function makeTerm(items) { | ||
for (let i of items) { | ||
if (check[i]) { | ||
if (check(i)) { | ||
result.push([]); | ||
@@ -109,3 +113,41 @@ } else { | ||
function isOperator(expr, fns) { | ||
return expr instanceof ExprOperator && words(fns).includes(expr.o); | ||
} | ||
function removeBrackets(expr) { | ||
return (expr instanceof ExprFunction && expr.fn === '(') ? expr.args[0] : expr; | ||
} | ||
function findBinaryFunction(tokens, fn, toFn) { | ||
if (isOperator(tokens[0], fn) || isOperator(tokens[tokens.length - 1], fn)) | ||
throw ExprError.startingOperator(fn); | ||
for (let i = 1; i < tokens.length - 1; ++i) { | ||
if (!isOperator(tokens[i], fn)) continue; | ||
const a = tokens[i - 1]; | ||
const b = tokens[i + 1]; | ||
if (a instanceof ExprOperator) throw ExprError.consecutiveOperators(a.o, tokens[i].o); | ||
if (b instanceof ExprOperator) throw ExprError.consecutiveOperators(tokens[i].o, b.o); | ||
const args = [removeBrackets(a), removeBrackets(b)]; | ||
tokens.splice(i - 1, 3, new ExprFunction(toFn || tokens[i].o, args)); | ||
i -= 2; | ||
} | ||
} | ||
// ----------------------------------------------------------------------------- | ||
// Match Brackets | ||
function prepareTerm(tokens) { | ||
// TODO Combine sup and sub calls into a single supsub function. | ||
findBinaryFunction(tokens, '^', 'sup'); | ||
findBinaryFunction(tokens, '/'); | ||
return makeTerm(tokens); | ||
} | ||
export function matchBrackets(tokens) { | ||
findBinaryFunction(tokens, '_', 'sub'); | ||
const stack = [[]]; | ||
@@ -116,6 +158,6 @@ | ||
if (')]}'.includes(t.o) || (t.o === '|' && lastOpen === '|')) { | ||
if (isOperator(t, ') ] }') || (isOperator(t, '|') && lastOpen === '|')) { | ||
if (t.o !== BRACKETS[lastOpen]) | ||
throw new ExprError('SyntaxError', `Unmatched bracket “${t.o}”.`); | ||
if (!isOperator(t, BRACKETS[lastOpen])) | ||
throw ExprError.conflictingBrackets(t.o); | ||
@@ -126,10 +168,10 @@ const closed = stack.pop(); | ||
// Check if this is a normal bracket, or a function call. | ||
const isFn = (t.o === ')' && last(term) instanceof ExprIdentifier); | ||
const fnName = isFn ? term.pop().i : t.o === '|' ? 'abs' : closed[0]; | ||
const isFn = (isOperator(t, ')') && last(term) instanceof ExprIdentifier); | ||
const fnName = isFn ? term.pop().i : isOperator(t, '|') ? 'abs' : closed[0].o; | ||
// Support multiple arguments for function calls. | ||
const args = splitArray(closed.slice(1), a => a.o === ','); | ||
term.push(new ExprFunction(fnName, args.map(makeTerm))); | ||
const args = splitArray(closed.slice(1), a => isOperator(a, ',')); | ||
term.push(new ExprFunction(fnName, args.map(prepareTerm))); | ||
} else if('([{|'.includes(t.o)) { | ||
} else if (isOperator(t, '( [ { |')) { | ||
stack.push([t]); | ||
@@ -142,51 +184,25 @@ | ||
if (stack.length > 1) | ||
throw new ExprError('SyntaxError', `Unclosed bracket “${last(stack)[0]}”.`); | ||
return makeTerm(stack[0]); | ||
if (stack.length > 1) throw ExprError.unclosedBracket(last(stack)[0].o); | ||
return prepareTerm(stack[0]); | ||
} | ||
// ----------------------------------------------------------------------------- | ||
// Collapse term items | ||
/* function findBinaryFunction(tokens, chars) { | ||
for (let i=0; i<tokens.length; ++i) { | ||
if (chars.includes(tokens[i])) { | ||
let a = tokens[i-1]; | ||
let b = tokens[i+1]; | ||
if (b == null) throw new ExprError('SyntaxError', `An expression cannot end with a “${tokens[i]}”.`); | ||
if ('+-* / ^% ! ' .includes(a)) throw new ExprError('SyntaxError', `A “${a}” cannot be followed by a “${tokens[i]}”.`); | ||
if ('+-* /^%!'.includes(b)) throw new ExprError('SyntaxError', `A “${tokens[i]}” cannot be followed by a “${b}”.`); | ||
tokens.splice(i - 1, 3, [tokens[i], a, b]); | ||
i -= 2; | ||
} | ||
} | ||
}*/ | ||
export function collapseTerm(tokens) { | ||
findBinaryFunction(tokens, '= < > ≤ ≥'); | ||
findBinaryFunction(tokens, '//', '/'); | ||
export function collapseTerm() { | ||
// TODO Operators to functions, implicit multiplication, equals sign, ... | ||
// TODO Support >2 arguments for + and * | ||
// TODO Match multiplication and implicit multiplication | ||
/* | ||
findBinaryFunction(tokens, '^'); // Powers | ||
findBinaryFunction(tokens, '* /'); // Multiplication and division. | ||
// TODO Match starting - or ± | ||
// Implicit multiplication (consecutive expressions) | ||
for (let i=0; i<tokens.length-1; ++i) { | ||
if (!'+-* /^%!'.includes(tokens[i]) && !'+-* /^%!'.includes(tokens[i+1])) { | ||
tokens.splice(i, 2, ['*', tokens[i], tokens[i+1]]); | ||
i -= 1; | ||
} | ||
} | ||
findBinaryFunction(tokens, '-', '-'); | ||
findBinaryFunction(tokens, '±', '±'); | ||
// Leading (plus)minus. | ||
if ('-±'.includes(tokens[0])) { | ||
if (tokens.length <= 1) throw new ExprError('SyntaxError', `This expression is invalid.`); | ||
tokens.splice(0, 2, [tokens[0], tokens[1]]); | ||
} | ||
// TODO Match addition | ||
findBinaryFunction(tokens, '+-±'); // Addition and subtraction. | ||
findBinaryFunction(tokens, '=<>≤≥'); // Equalities and inequalities. | ||
if (tokens.length > 1) throw new ExprError('SyntaxError', `This expression is invalid.`); | ||
return tokens[0]; */ | ||
if (tokens.length > 1) throw ExprError.invalidExpression(); | ||
return tokens[0]; | ||
} |
@@ -8,6 +8,8 @@ // ============================================================================= | ||
export const BRACKETS = {'(': ')', '[': ']', '{': '}', '|': '|'}; | ||
export const SPECIAL_OPERATORS = { | ||
'*': '·', | ||
'**': '∗', | ||
'//': '/', | ||
'//': '//', | ||
'+-': '±', | ||
@@ -95,4 +97,4 @@ xx: '×', | ||
const SIMPLE_SYMBOLS = '|()[]{}÷,!<>=*/+-–~'; | ||
const SIMPLE_SYMBOLS = '|()[]{}÷,!<>=*/+-–~^_…'; | ||
const COMPLEX_SYMBOLS = Object.values(SPECIAL_OPERATORS); | ||
export const OPERATOR_SYMBOLS = [...SIMPLE_SYMBOLS, ...COMPLEX_SYMBOLS]; |
@@ -15,3 +15,3 @@ // ============================================================================= | ||
tape('basic', function(test) { | ||
tape('Basic', function(test) { | ||
test.equal(mathML('42'), '<mn>42</mn>'); | ||
@@ -29,528 +29,55 @@ test.equal(mathML('3.141592654'), '<mn>3.141592654</mn>'); | ||
/* describe('Operators', function() { | ||
it('Eulers number', function() { | ||
test("e = sum_(n=0)^oo 1 / n!", '<math><mi>e</mi><mo>=</mo><munderover><mo>∑</mo><mrow><mi>n</mi><mo>=</mo><mn>0</mn></mrow><mi mathvariant="normal">∞</mi></munderover><mfrac><mn>1</mn><mrow><mi>n</mi><mo>!</mo></mrow></mfrac></math>'); | ||
}); | ||
it('Should remove space around derivatives', function() { | ||
test("f'(x)", '<math><mi>f</mi><mo lspace="0" rspace="0">′</mo><mfenced open="(" close=")"><mi>x</mi></mfenced></math>'); | ||
test("f''(x)", '<math><mi>f</mi><mo lspace="0" rspace="0">″</mo><mfenced open="(" close=")"><mi>x</mi></mfenced></math>'); | ||
test("f'''(x)", '<math><mi>f</mi><mo lspace="0" rspace="0">‴</mo><mfenced open="(" close=")"><mi>x</mi></mfenced></math>'); | ||
test("f''''(x)", '<math><mi>f</mi><mo lspace="0" rspace="0">⁗</mo><mfenced open="(" close=")"><mi>x</mi></mfenced></math>'); | ||
}); | ||
it('Bayes theorem', function() { | ||
test('p(a | b) = (p(b | a)p(a)) / p(b)', '<math><mi>p</mi><mfenced open="(" close=")"><mrow><mi>a</mi><mo stretchy="true" lspace="veryverythickmathspace" rspace="veryverythickmathspace">|</mo><mi>b</mi></mrow></mfenced><mo>=</mo><mfrac><mrow><mi>p</mi><mfenced open="(" close=")"><mrow><mi>b</mi><mo stretchy="true" lspace="veryverythickmathspace" rspace="veryverythickmathspace">|</mo><mi>a</mi></mrow></mfenced><mi>p</mi><mfenced open="(" close=")"><mi>a</mi></mfenced></mrow><mrow><mi>p</mi><mfenced open="(" close=")"><mi>b</mi></mfenced></mrow></mfrac></math>'); | ||
}); | ||
it('Gradient', function() { | ||
test("grad f(x,y) = ((del f)/(del x) (x, y), (del f)/(del y) (x,y))", | ||
'<math><mo rspace="0">∇</mo><mi>f</mi><mfenced open="(" close=")"><mi>x</mi><mi>y</mi></mfenced><mo>=</mo><mfenced open="(" close=")"><mrow><mfrac><mrow><mo rspace="0">∂</mo><mi>f</mi></mrow><mrow><mo rspace="0">∂</mo><mi>x</mi></mrow></mfrac><mfenced open="(" close=")"><mi>x</mi><mi>y</mi></mfenced></mrow><mrow><mfrac><mrow><mo rspace="0">∂</mo><mi>f</mi></mrow><mrow><mo rspace="0">∂</mo><mi>y</mi></mrow></mfrac><mfenced open="(" close=")"><mi>x</mi><mi>y</mi></mfenced></mrow></mfenced></math>'); | ||
}); | ||
it('Taylor polynomial', function() { | ||
test("P_k(x) = f(a) + f'(a)(x-a) + (f''(a))/2! (x-a)^2 + cdots + (f^((k))(a))/k! (x-a)^k", | ||
'<math><msub><mi>P</mi><mi>k</mi></msub><mfenced open="(" close=")"><mi>x</mi></mfenced><mo>=</mo><mi>f</mi><mfenced open="(" close=")"><mi>a</mi></mfenced><mo>+</mo><mi>f</mi><mo lspace="0" rspace="0">′</mo><mfenced open="(" close=")"><mi>a</mi></mfenced><mfenced open="(" close=")"><mrow><mi>x</mi><mo>-</mo><mi>a</mi></mrow></mfenced><mo>+</mo><mfrac><mrow><mi>f</mi><mo lspace="0" rspace="0">″</mo><mfenced open="(" close=")"><mi>a</mi></mfenced></mrow><mrow><mn>2</mn><mo>!</mo></mrow></mfrac><msup><mfenced open="(" close=")"><mrow><mi>x</mi><mo>-</mo><mi>a</mi></mrow></mfenced><mn>2</mn></msup><mo>+</mo><mo>⋯</mo><mo>+</mo><mfrac><mrow><msup><mi>f</mi><mfenced open="(" close=")"><mi>k</mi></mfenced></msup><mfenced open="(" close=")"><mi>a</mi></mfenced></mrow><mrow><mi>k</mi><mo>!</mo></mrow></mfrac><msup><mfenced open="(" close=")"><mrow><mi>x</mi><mo>-</mo><mi>a</mi></mrow></mfenced><mi>k</mi></msup></math>'); | ||
}); | ||
it('Strokes theorem', function() { | ||
test("oint_(del S) bf`F` * dbf`s` = dint_S grad xx bf`F` * dbf`s`", | ||
'<math><msub><mo>∮</mo><mrow><mo rspace="0">∂</mo><mi>S</mi></mrow></msub><mi mathvariant="bold">F</mi><mo>·</mo><mi>d</mi><mi mathvariant="bold">s</mi><mo>=</mo><msub><mo>∬</mo><mi>S</mi></msub><mo rspace="0">∇</mo><mo>×</mo><mi mathvariant="bold">F</mi><mo>·</mo><mi>d</mi><mi mathvariant="bold">s</mi></math>'); | ||
}); | ||
tape('Whitespace', function(test) { | ||
test.equal(mathML('a b'), '<mi>a</mi><mspace/><mi>b</mi>'); | ||
test.equal(mathML('a b'), '<mi>a</mi><mi>b</mi>'); | ||
test.end(); | ||
}); | ||
describe('Whitespace', function() { | ||
it('Should keep whitespace if more then one space', function() { | ||
test("a b", '<math><mi>a</mi><mspace width="1ex" /><mi>b</mi></math>'); | ||
test("a b", "<math><mi>a</mi><mi>b</mi></math>"); | ||
}); | ||
it('Should have width following the equation `n-1`ex', function() { | ||
for (var n=2; n < 20; n++) { | ||
test("a" + new Array(n+1).join(' ') + "b", | ||
'<math><mi>a</mi><mspace width="' + (n-1) + 'ex" /><mi>b</mi></math>'); | ||
} | ||
}); | ||
it('Should group adjacent symbols on either side of whitespace in an <mrow>', function() { | ||
test('ab cd', "<math><mrow><mi>a</mi><mi>b</mi></mrow><mi>c</mi><mi>d</mi></math>"); | ||
}); | ||
it("But not straight after a function", function() { | ||
test("sin (a + b)", '<math><mi>sin</mi><mfenced open="(" close=")"><mrow><mi>a</mi><mo>+</mo><mi>b</mi></mrow></mfenced></math>'); | ||
}); | ||
tape('Functions', function(test) { | ||
test.equal(mathML('sin (a + b)'), '<mi>sin</mi><mfenced><mi>a</mi><mo value="+">+</mo><mi>b</mi></mfenced>'); | ||
test.equal(mathML('tan = sin/cos'), '<mi>tan</mi><mo value="=">=</mo><mfrac><mi>sin</mi><mi>cos</mi></mfrac>'); | ||
test.equal(mathML('sinh(x) = (e^x - e^(-x))/2'), '<mi>sinh</mi><mfenced><mi>x</mi></mfenced><mo value="=">=</mo><mfrac><mrow><msup><mi>e</mi><mi>x</mi></msup><mo value="-">-</mo><msup><mi>e</mi><mrow><mo value="-">-</mo><mi>x</mi></mrow></msup></mrow><mn>2</mn></mfrac>'); | ||
test.equal(mathML('ln(x^2) = 2 ln(x)'), '<mi>ln</mi><mfenced><msup><mi>x</mi><mn>2</mn></msup></mfenced><mo value="=">=</mo><mn>2</mn><mi>ln</mi><mfenced><mi>x</mi></mfenced>'); | ||
test.equal(mathML('ln(x/y) = ln(x) - ln(y)'), '<mi>ln</mi><mfenced><mfrac><mi>x</mi><mi>y</mi></mfrac></mfenced><mo value="=">=</mo><mi>ln</mi><mfenced><mi>x</mi></mfenced><mo value="-">-</mo><mi>ln</mi><mfenced><mi>y</mi></mfenced>'); | ||
test.equal(mathML('a^(p-1) == 1'), '<msup><mi>a</mi><mrow><mi>p</mi><mo value="-">-</mo><mn>1</mn></mrow></msup><mo value="≡">≡</mo><mn>1</mn>'); | ||
// test.equal(mathML('log_b(x) = log_k(x)/log_k(b)'), 'xxx'); // TODO Support functions with subscripts | ||
test.end(); | ||
}); | ||
describe("Identifier", function() { | ||
it("Greeks (uppercase in normal variant, lowercase in italic)", function() { | ||
test("Gamma Delta Theta Lambda Xi Pi Sigma Phi Psi Omega", | ||
'<math><mi mathvariant="normal">Γ</mi><mi mathvariant="normal">Δ</mi><mi mathvariant="normal">Θ</mi><mi mathvariant="normal">Λ</mi><mi mathvariant="normal">Ξ</mi><mi mathvariant="normal">Π</mi><mi mathvariant="normal">Σ</mi><mi mathvariant="normal">Φ</mi><mi mathvariant="normal">Ψ</mi><mi mathvariant="normal">Ω</mi></math>'); | ||
test("alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi pi rho sigma tau upsilon phi chi psi omega", | ||
"<math><mi>α</mi><mi>β</mi><mi>γ</mi><mi>δ</mi><mi>ɛ</mi><mi>ζ</mi><mi>η</mi><mi>θ</mi><mi>ι</mi><mi>κ</mi><mi>λ</mi><mi>μ</mi><mi>ν</mi><mi>ξ</mi><mi>π</mi><mi>ρ</mi><mi>σ</mi><mi>τ</mi><mi>υ</mi><mi>φ</mi><mi>χ</mi><mi>ψ</mi><mi>ω</mi></math>"); | ||
}); | ||
it('Special', function() { | ||
test("oo O/", '<math><mi mathvariant="normal">∞</mi><mi mathvariant="normal">∅</mi></math>'); | ||
}); | ||
it('Blackboard', function() { | ||
test("NN ZZ QQ RR CC", '<math><mi mathvariant="normal">ℕ</mi><mi mathvariant="normal">ℤ</mi><mi mathvariant="normal">ℚ</mi><mi mathvariant="normal">ℝ</mi><mi mathvariant="normal">ℂ</mi></math>'); | ||
}); | ||
it('Should force identifiers with (`)', function() { | ||
test("`int`", "<math><mi>int</mi></math>"); | ||
}); | ||
}); | ||
describe('Standard functions', function() { | ||
it("Tangent = Sinus over Cosinus", function() { | ||
test("tan = sin/cos", | ||
"<math><mrow><mi>tan</mi><mo>=</mo></mrow><mfrac><mi>sin</mi><mi>cos</mi></mfrac></math>"); | ||
}); | ||
it("The hyperbolic functions", function() { | ||
test("sinh x = (e^x - e^(-x))/2, cosh x = (e^x + e^(-x))/2, tanh x = (sinh x)/(cosh x)", | ||
'<math><mrow><mi>sinh</mi><mi>x</mi></mrow><mo>=</mo><mfrac><mrow><msup><mi>e</mi><mi>x</mi></msup><mo>-</mo><msup><mi>e</mi><mrow><mo>-</mo><mi>x</mi></mrow></msup></mrow><mn>2</mn></mfrac><mo>,</mo><mrow><mi>cosh</mi><mi>x</mi></mrow><mo>=</mo><mfrac><mrow><msup><mi>e</mi><mi>x</mi></msup><mo>+</mo><msup><mi>e</mi><mrow><mo>-</mo><mi>x</mi></mrow></msup></mrow><mn>2</mn></mfrac><mo>,</mo><mrow><mi>tanh</mi><mi>x</mi></mrow><mo>=</mo><mfrac><mrow><mi>sinh</mi><mi>x</mi></mrow><mrow><mi>cosh</mi><mi>x</mi></mrow></mfrac></math>'); | ||
}); | ||
it("Logarithm change of base", function() { | ||
test("log_b x = (log_k x)/(log_k b)", "<math><msub><mi>log</mi><mi>b</mi></msub><mi>x</mi><mo>=</mo><mfrac><mrow><msub><mi>log</mi><mi>k</mi></msub><mi>x</mi></mrow><mrow><msub><mi>log</mi><mi>k</mi></msub><mi>b</mi></mrow></mfrac></math>"); | ||
}); | ||
it("Logarithm powers", function() { | ||
test("ln x^2 = 2 ln x", "<math><mrow><mi>ln</mi><msup><mi>x</mi><mn>2</mn></msup></mrow><mo>=</mo><mn>2</mn><mrow><mi>ln</mi><mi>x</mi></mrow></math>"); | ||
}); | ||
it("Logarithm division", function() { | ||
test("ln(x/y) = ln x - ln y", | ||
'<math><mi>ln</mi><mfenced open="(" close=")"><mfrac><mi>x</mi><mi>y</mi></mfrac></mfenced><mo>=</mo><mrow><mi>ln</mi><mi>x</mi></mrow><mo>-</mo><mrow><mi>ln</mi><mi>y</mi></mrow></math>'); | ||
}); | ||
it("2×2 determinants", function() { | ||
test("det(A) = |(a, b), (c, d)| = ad - cd", '<math><mi>det</mi><mfenced open="(" close=")"><mi>A</mi></mfenced><mo>=</mo><mfenced open="|" close="|"><mtable><mtr><mtd><mi>a</mi></mtd><mtd><mi>b</mi></mtd></mtr><mtr><mtd><mi>c</mi></mtd><mtd><mi>d</mi></mtd></mtr></mtable></mfenced><mo>=</mo><mrow><mi>a</mi><mi>d</mi></mrow><mo>-</mo><mi>c</mi><mi>d</mi></math>'); | ||
}); | ||
it("Fermats little theorem", function() { | ||
test("a^(p-1) -= 1 (mod p)", | ||
'<math><msup><mi>a</mi><mrow><mi>p</mi><mo>-</mo><mn>1</mn></mrow></msup><mo>≡</mo><mn>1</mn><mspace width="2ex" /><mfenced open="(" close=")"><mrow><mi>mod</mi><mi>p</mi></mrow></mfenced></math>'); | ||
}); | ||
}); | ||
describe('Fractions', function() { | ||
it("Should display fractions", function() { | ||
test("a/b", "<math><mfrac><mi>a</mi><mi>b</mi></mfrac></math>"); | ||
}); | ||
it('Should have bevelled fractions', function() { | ||
test("a./b", '<math><mfrac bevelled="true"><mi>a</mi><mi>b</mi></mfrac></math>'); | ||
}); | ||
it('Should not parse `//` as fraction', function() { | ||
test('a//b', '<math><mi>a</mi><mo>/</mo><mi>b</mi></math>'); | ||
}); | ||
it("Should not display brackets around numerator or denominator", function() { | ||
test("(a+b)/(c+d)", "<math><mfrac><mrow><mi>a</mi><mo>+</mo><mi>b</mi></mrow><mrow><mi>c</mi><mo>+</mo><mi>d</mi></mrow></mfrac></math>"); | ||
}); | ||
it("Should not fail on trailing fractions", function() { | ||
test("a/", "<math><mfrac><mi>a</mi><mrow></mrow></mfrac></math>"); | ||
test("b ./ ", '<math><mfrac bevelled="true"><mi>b</mi><mrow></mrow></mfrac></math>'); | ||
}); | ||
it('Should have whitespace delimited fractions', function() { | ||
test("1+3 / 2+2", "<math><mfrac><mrow><mn>1</mn><mo>+</mo><mn>3</mn></mrow><mrow><mn>2</mn><mo>+</mo><mn>2</mn></mrow></mfrac></math>"); | ||
test("1 + 3/2 + 2", "<math><mn>1</mn><mo>+</mo><mfrac><mn>3</mn><mn>2</mn></mfrac><mo>+</mo><mn>2</mn></math>"); | ||
test("a./b / c./d", '<math><mfrac><mfrac bevelled="true"><mi>a</mi><mi>b</mi></mfrac><mfrac bevelled="true"><mi>c</mi><mi>d</mi></mfrac></mfrac></math>'); | ||
}); | ||
it("Golden ratio (continued fraction)", function() { | ||
test("phi = 1 + 1/(1 + 1/(1 + 1/(1 + 1/(1 + ddots))))", | ||
"<math><mi>φ</mi><mo>=</mo><mn>1</mn><mo>+</mo><mfrac><mn>1</mn><mrow><mn>1</mn><mo>+</mo><mfrac><mn>1</mn><mrow><mn>1</mn><mo>+</mo><mfrac><mn>1</mn><mrow><mn>1</mn><mo>+</mo><mfrac><mn>1</mn><mrow><mn>1</mn><mo>+</mo><mo>⋱</mo></mrow></mfrac></mrow></mfrac></mrow></mfrac></mrow></mfrac></math>"); | ||
}); | ||
it("Normal distribution", function() { | ||
test('cc`N`(x | mu, sigma^2) = 1/(sqrt(2pi sigma^2)) e^(-((x-mu)^2) / 2sigma^2)', | ||
'<math><mi mathvariant="script">N</mi><mfenced open="(" close=")"><mrow><mi>x</mi><mo stretchy="true" lspace="veryverythickmathspace" rspace="veryverythickmathspace">|</mo><mi>μ</mi></mrow><msup><mi>σ</mi><mn>2</mn></msup></mfenced><mo>=</mo><mfrac><mn>1</mn><msqrt><mrow><mn>2</mn><mi>π</mi><msup><mi>σ</mi><mn>2</mn></msup></mrow></msqrt></mfrac><msup><mi>e</mi><mrow><mo>-</mo><mfrac><msup><mfenced open="(" close=")"><mrow><mi>x</mi><mo>-</mo><mi>μ</mi></mrow></mfenced><mn>2</mn></msup><mrow><mn>2</mn><msup><mi>σ</mi><mn>2</mn></msup></mrow></mfrac></mrow></msup></math>'); | ||
}); | ||
}); | ||
describe('Roots', function() { | ||
it("Should display square roots", function() { | ||
test("sqrt x", "<math><msqrt><mi>x</mi></msqrt></math>"); | ||
}); | ||
it("Should display n-roots", function() { | ||
test("root n x", "<math><mroot><mi>x</mi><mi>n</mi></mroot></math>"); | ||
}); | ||
it("Should not display brackets in roots", function() { | ||
test("sqrt(2)", "<math><msqrt><mn>2</mn></msqrt></math>"); | ||
test("root(3)(2)", "<math><mroot><mn>2</mn><mn>3</mn></mroot></math>"); | ||
}); | ||
it("Should not fail in empty roots", function() { | ||
test("sqrt", "<math><msqrt><mrow></mrow></msqrt></math>"); | ||
test("root ", "<math><mroot><mrow></mrow><mrow></mrow></mroot></math>"); | ||
test("root 3 ", "<math><mroot><mrow></mrow><mn>3</mn></mroot></math>"); | ||
}); | ||
it("sqrt(2) ≈ 1.414", function() { | ||
test("sqrt 2 ~~ 1.414213562", "<math><msqrt><mn>2</mn></msqrt><mo>≈</mo><mn>1.414213562</mn></math>"); | ||
}); | ||
it("Quadradic formula", function() { | ||
test("x = (-b +- sqrt(b^2 - 4ac)) / 2a", | ||
"<math><mi>x</mi><mo>=</mo><mfrac><mrow><mo>-</mo><mi>b</mi><mo>±</mo><msqrt><mrow><msup><mi>b</mi><mn>2</mn></msup><mo>-</mo><mn>4</mn><mi>a</mi><mi>c</mi></mrow></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac></math>"); | ||
}); | ||
it("Golden ratio (algebraic form)", function() { | ||
test("phi = (1 + sqrt 5)/2", "<math><mi>φ</mi><mo>=</mo><mfrac><mrow><mn>1</mn><mo>+</mo><msqrt><mn>5</mn></msqrt></mrow><mn>2</mn></mfrac></math>"); | ||
}); | ||
it("Plastic number", function() { | ||
test("rho = (root3(108 + 12 sqrt 69) + root3(108 - 12 sqrt 69)) / 6", | ||
"<math><mi>ρ</mi><mo>=</mo><mfrac><mrow><mroot><mrow><mn>108</mn><mo>+</mo><mn>12</mn><msqrt><mn>69</mn></msqrt></mrow><mn>3</mn></mroot><mo>+</mo><mroot><mrow><mn>108</mn><mo>-</mo><mn>12</mn><msqrt><mn>69</mn></msqrt></mrow><mn>3</mn></mroot></mrow><mn>6</mn></mfrac></math>"); | ||
}); | ||
it("Continued square root", function() { | ||
test("sqrt(1 + sqrt(1 + sqrt(1 + sqrt(1 + sqrt(1 + sqrt(1 + sqrt(1 + cdots)))))))", | ||
"<math><msqrt><mrow><mn>1</mn><mo>+</mo><msqrt><mrow><mn>1</mn><mo>+</mo><msqrt><mrow><mn>1</mn><mo>+</mo><msqrt><mrow><mn>1</mn><mo>+</mo><msqrt><mrow><mn>1</mn><mo>+</mo><msqrt><mrow><mn>1</mn><mo>+</mo><msqrt><mrow><mn>1</mn><mo>+</mo><mo>⋯</mo></mrow></msqrt></mrow></msqrt></mrow></msqrt></mrow></msqrt></mrow></msqrt></mrow></msqrt></mrow></msqrt></math>"); | ||
}); | ||
}); | ||
describe('Groupings', function() { | ||
it('Should group brackets together', function() { | ||
test('(a+b)', '<math><mfenced open="(" close=")"><mrow><mi>a</mi><mo>+</mo><mi>b</mi></mrow></mfenced></math>'); | ||
}); | ||
it('Should handle comma seperated lists', function() { | ||
test('a,b,c', '<math><mi>a</mi><mo>,</mo><mi>b</mi><mo>,</mo><mi>c</mi></math>'); | ||
}); | ||
it('Should add parentesis around parentesized comma seperated lists', function() { | ||
test('(a,b,c)', '<math><mfenced open="(" close=")"><mi>a</mi><mi>b</mi><mi>c</mi></mfenced></math>'); | ||
}); | ||
it("Should not fail on unclosed fences", function() { | ||
test("(a", '<math><mfenced open="(" close=""><mi>a</mi></mfenced></math>'); | ||
test("((a)", '<math><mfenced open="(" close=""><mfenced open="(" close=")"><mi>a</mi></mfenced></mfenced></math>'); | ||
test("[(", '<math><mfenced open="[" close=""><mfenced open="(" close=""></mfenced></mfenced></math>'); | ||
}); | ||
it('Simplify polynomials', function() { | ||
test("(x+y)(x-y) = x^2-y^2", '<math><mfenced open="(" close=")"><mrow><mi>x</mi><mo>+</mo><mi>y</mi></mrow></mfenced><mfenced open="(" close=")"><mrow><mi>x</mi><mo>-</mo><mi>y</mi></mrow></mfenced><mo>=</mo><msup><mi>x</mi><mn>2</mn></msup><mo>-</mo><msup><mi>y</mi><mn>2</mn></msup></math>'); | ||
}); | ||
it('Exponential decay', function() { | ||
test("e^(-x)", "<math><msup><mi>e</mi><mrow><mo>-</mo><mi>x</mi></mrow></msup></math>"); | ||
}); | ||
it('Eulers identity', function() { | ||
test("e^(i tau) = 1", "<math><msup><mi>e</mi><mrow><mi>i</mi><mi>τ</mi></mrow></msup><mo>=</mo><mn>1</mn></math>"); | ||
}); | ||
it("The natural numbers", function() { | ||
test('NN = {1, 2, 3, ...}', '<math><mi mathvariant="normal">ℕ</mi><mo>=</mo><mfenced open="{" close="}"><mn>1</mn><mn>2</mn><mn>3</mn><mo>…</mo></mfenced></math>'); | ||
}); | ||
it("Average over time", function() { | ||
test('(: V(t)^2 :) = lim_(T->oo) 1/T int_(-T./2)^(T./2) V(t)^2 dt', | ||
'<math><mfenced open="⟨" close="⟩"><mrow><mi>V</mi><msup><mfenced open="(" close=")"><mi>t</mi></mfenced><mn>2</mn></msup></mrow></mfenced><mo>=</mo><munder><mi>lim</mi><mrow><mi>T</mi><mo>→</mo><mi mathvariant="normal">∞</mi></mrow></munder><mfrac><mn>1</mn><mi>T</mi></mfrac><msubsup><mo>∫</mo><mrow><mo>-</mo><mfrac bevelled="true"><mi>T</mi><mn>2</mn></mfrac></mrow><mfrac bevelled="true"><mi>T</mi><mn>2</mn></mfrac></msubsup><mi>V</mi><msup><mfenced open="(" close=")"><mi>t</mi></mfenced><mn>2</mn></msup><mi>d</mi><mi>t</mi></math>'); | ||
}); | ||
it('Complex groupings', function() { | ||
test('abs(x)', '<math><mfenced open="|" close="|"><mi>x</mi></mfenced></math>'); | ||
test('floor(x)', '<math><mfenced open="⌊" close="⌋"><mi>x</mi></mfenced></math>'); | ||
test('ceil(x)', '<math><mfenced open="⌈" close="⌉"><mi>x</mi></mfenced></math>'); | ||
test('norm(x)', '<math><mfenced open="∥" close="∥"><mi>x</mi></mfenced></math>'); | ||
test('abs x', '<math><mfenced open="|" close="|"><mi>x</mi></mfenced></math>'); | ||
test('floor x', '<math><mfenced open="⌊" close="⌋"><mi>x</mi></mfenced></math>'); | ||
test('ceil x', '<math><mfenced open="⌈" close="⌉"><mi>x</mi></mfenced></math>'); | ||
test('norm x', '<math><mfenced open="∥" close="∥"><mi>x</mi></mfenced></math>'); | ||
}); | ||
}); | ||
describe('Super and subscripts', function() { | ||
it('Should display subscripts', function() { | ||
test("a_i", "<math><msub><mi>a</mi><mi>i</mi></msub></math>"); | ||
}); | ||
it('Should display superscripts', function() { | ||
test("a^2", "<math><msup><mi>a</mi><mn>2</mn></msup></math>"); | ||
}); | ||
it('Should display sub-superscripts', function() { | ||
test("a_i^2", "<math><msubsup><mi>a</mi><mi>i</mi><mn>2</mn></msubsup></math>"); | ||
}); | ||
it('Should render sub-superscripts in either direction', function() { | ||
test("a^2_i", "<math><msubsup><mi>a</mi><mi>i</mi><mn>2</mn></msubsup></math>"); | ||
}); | ||
it("Should not throw on trailing sub or superscripts", function() { | ||
test("a^", "<math><msup><mi>a</mi><mrow></mrow></msup></math>"); | ||
test("b_ ", "<math><msub><mi>b</mi><mrow></mrow></msub></math>"); | ||
}); | ||
it("Should not throw on trailing under or overscripts", function() { | ||
test("a.^", "<math><mover><mi>a</mi><mrow></mrow></mover></math>"); | ||
test("b._ ", "<math><munder><mi>b</mi><mrow></mrow></munder></math>"); | ||
}); | ||
it("Should not throw on trailing sub-supscript", function() { | ||
test("a_i^", "<math><msubsup><mi>a</mi><mi>i</mi><mrow></mrow></msubsup></math>"); | ||
test("a^2_", "<math><msubsup><mi>a</mi><mrow></mrow><mn>2</mn></msubsup></math>"); | ||
}); | ||
it("Should not throw on trailing under-overscript", function() { | ||
expect(ascii2mathml).withArgs("a._i .^").not.to.throwException(); | ||
expect(ascii2mathml).withArgs("a._i ^").not.to.throwException(); | ||
expect(ascii2mathml).withArgs("a.^2._ ").not.to.throwException(); | ||
expect(ascii2mathml).withArgs("a.^ 2_ ").not.to.throwException(); | ||
}); | ||
it('Should not treat `^^` as superscript', function() { | ||
test('a^^2', '<math><mi>a</mi><mo>∧</mo><mn>2</mn></math>'); | ||
}); | ||
it('Should not treat `__` or `_|` as subscript', function() { | ||
test('a_|_i', '<math><mi>a</mi><mo>⊥</mo><mi>i</mi></math>'); | ||
test('|__a__|i', '<math><mo>⌊</mo><mi>a</mi><mo>⌋</mo><mi>i</mi></math>'); | ||
}); | ||
it('Pythagorean theorem', function() { | ||
test("a^2 + b^2 = c^2", "<math><msup><mi>a</mi><mn>2</mn></msup><mo>+</mo><msup><mi>b</mi><mn>2</mn></msup><mo>=</mo><msup><mi>c</mi><mn>2</mn></msup></math>"); | ||
}); | ||
it('Matrix transpose', function() { | ||
test("(X^T)_(ij) = X_(ji)", | ||
'<math><msub><mfenced open="(" close=")"><msup><mi>X</mi><mi>T</mi></msup></mfenced><mrow><mi>i</mi><mi>j</mi></mrow></msub><mo>=</mo><msub><mi>X</mi><mrow><mi>j</mi><mi>i</mi></mrow></msub></math>'); | ||
}); | ||
it('The natural logarithm', function() { | ||
test("ln x = int_1^x 1/t dt", "<math><mrow><mi>ln</mi><mi>x</mi></mrow><mo>=</mo><msubsup><mo>∫</mo><mn>1</mn><mi>x</mi></msubsup><mfrac><mn>1</mn><mi>t</mi></mfrac><mi>d</mi><mi>t</mi></math>"); | ||
}); | ||
it('Powers of powers of two', function() { | ||
test("2^2^2^2", "<math><msup><mn>2</mn><msup><mn>2</mn><msup><mn>2</mn><mn>2</mn></msup></msup></msup></math>"); | ||
}); | ||
}); | ||
describe('Under and overscripts', function() { | ||
it('Should display underscripts', function() { | ||
test('X._a', "<math><munder><mi>X</mi><mi>a</mi></munder></math>"); | ||
}); | ||
it('Should display overscripts', function() { | ||
test('X.^a', "<math><mover><mi>X</mi><mi>a</mi></mover></math>"); | ||
}); | ||
it('Should display overscripts', function() { | ||
test('X.^a._b', "<math><munderover><mi>X</mi><mi>b</mi><mi>a</mi></munderover></math>"); | ||
}); | ||
it('Should go under limits', function() { | ||
test('lim_(a -> b)', "<math><munder><mi>lim</mi><mrow><mi>a</mi><mo>→</mo><mi>b</mi></mrow></munder></math>"); | ||
test('lim_(a->b)', "<math><munder><mi>lim</mi><mrow><mi>a</mi><mo>→</mo><mi>b</mi></mrow></munder></math>"); | ||
}); | ||
it('Should go over and under sums', function() { | ||
test('sum_(i=0)^n', "<math><munderover><mo>∑</mo><mrow><mi>i</mi><mo>=</mo><mn>0</mn></mrow><mi>n</mi></munderover></math>"); | ||
test('sum^n_(i=0)', "<math><munderover><mo>∑</mo><mrow><mi>i</mi><mo>=</mo><mn>0</mn></mrow><mi>n</mi></munderover></math>"); | ||
}); | ||
it("Should go over and under products", function() { | ||
test('prod_(i=1)^n', "<math><munderover><mo>∏</mo><mrow><mi>i</mi><mo>=</mo><mn>1</mn></mrow><mi>n</mi></munderover></math>"); | ||
test('prod^n_(i=1)', "<math><munderover><mo>∏</mo><mrow><mi>i</mi><mo>=</mo><mn>1</mn></mrow><mi>n</mi></munderover></math>"); | ||
}); | ||
it("Golden ratio (defenition)", function() { | ||
test('phi =.^"def" a/b = (a+b)/a', "<math><mi>φ</mi><mover><mo>=</mo><mtext>def</mtext></mover><mfrac><mi>a</mi><mi>b</mi></mfrac><mo>=</mo><mfrac><mrow><mi>a</mi><mo>+</mo><mi>b</mi></mrow><mi>a</mi></mfrac></math>"); | ||
}); | ||
it("Matrix dimentions", function() { | ||
test('X._(n xx m)', "<math><munder><mi>X</mi><mrow><mi>n</mi><mo>×</mo><mi>m</mi></mrow></munder></math>"); | ||
}); | ||
it("k times x", function() { | ||
test('{: x + ... + x :}.^⏞.^(k "times")', '<math><mover><mfenced open="" close=""><mrow><mi>x</mi><mo>+</mo><mo>…</mo><mo>+</mo><mi>x</mi></mrow></mfenced><mover><mo>⏞</mo><mrow><mi>k</mi><mspace width="1ex" /><mtext>times</mtext></mrow></mover></mover></math>'); | ||
}); | ||
}); | ||
describe('Matrices', function() { | ||
it('Should display column vectors', function() { | ||
test("(1;2;3)", '<math><mfenced open="(" close=")"><mtable><mtr><mtd><mn>1</mn></mtd></mtr><mtr><mtd><mn>2</mn></mtd></mtr><mtr><mtd><mn>3</mn></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('Should display matrices', function() { | ||
test('[[a, b], [c, d]]', '<math><mfenced open="[" close="]"><mtable><mtr><mtd><mi>a</mi></mtd><mtd><mi>b</mi></mtd></mtr><mtr><mtd><mi>c</mi></mtd><mtd><mi>d</mi></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('Should display matrices with semicolon as a column separator', function() { | ||
expect(ascii2mathml('[[a; b]; [c; d]]', {decimalMark: ','})) | ||
.to.be(ascii2mathml('[[a, b], [c, d]]')); | ||
}); | ||
it('Should accept newlines instead of commas', function() { | ||
expect(ascii2mathml('[[a, b]\n [c, d]]')) | ||
.to.be(ascii2mathml('[[a, b], [c, d]]')); | ||
expect(ascii2mathml('[[a, b],\n [c, d]]')) | ||
.to.be(ascii2mathml('[[a, b], [c, d]]')); | ||
}); | ||
it('Should accept newlines instead of semicolons (w. `;` as colSep)', function() { | ||
expect(ascii2mathml('[[a; b]\n [c; d]]', {colSep: ';'})) | ||
.to.be(ascii2mathml('[[a, b], [c, d]]')); | ||
expect(ascii2mathml('[[a; b];\n [c; d]]', {colSep: ';'})) | ||
.to.be(ascii2mathml('[[a, b], [c, d]]')); | ||
}); | ||
it('Should display matrices using comma, semicolon syntax', function() { | ||
test('[1, 2, 3; 4, 5, 6]', '<math><mfenced open="[" close="]"><mtable><mtr><mtd><mn>1</mn></mtd><mtd><mn>2</mn></mtd><mtd><mn>3</mn></mtd></mtr><mtr><mtd><mn>4</mn></mtd><mtd><mn>5</mn></mtd><mtd><mn>6</mn></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('Should allow trailing row brakes', function() { | ||
test('[1, 2, 3;]', '<math><mfenced open="[" close="]"><mtable><mtr><mtd><mn>1</mn></mtd><mtd><mn>2</mn></mtd><mtd><mn>3</mn></mtd></mtr></mtable></mfenced></math>'); | ||
test('[(1, 2, 3),]', '<math><mfenced open="[" close="]"><mtable><mtr><mtd><mn>1</mn></mtd><mtd><mn>2</mn></mtd><mtd><mn>3</mn></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('Should allow newlines as row separators', function() { | ||
expect(ascii2mathml('[1, 2, 3\n 4, 5, 6]')) | ||
.to.be(ascii2mathml('[1, 2, 3; 4, 5, 6]')); | ||
expect(ascii2mathml('[a\n b\n c]')) | ||
.to.be(ascii2mathml('[a; b; c]')); | ||
expect(ascii2mathml('(4\n 6)')) | ||
.to.be(ascii2mathml('(4; 6)')); | ||
}); | ||
it('Should allow comma/semicolon to take precedence over bracket delimited matrices', function() { | ||
test('[(a), (b); (c), (d)]', '<math><mfenced open="[" close="]"><mtable><mtr><mtd><mfenced open="(" close=")"><mi>a</mi></mfenced></mtd><mtd><mfenced open="(" close=")"><mi>b</mi></mfenced></mtd></mtr><mtr><mtd><mfenced open="(" close=")"><mi>c</mi></mfenced></mtd><mtd><mfenced open="(" close=")"><mi>d</mi></mfenced></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('Should allow newlines to take precedence over bracket delimited matrices', function() { | ||
test('[(a), (b)\n (c), (d)]', '<math><mfenced open="[" close="]"><mtable><mtr><mtd><mfenced open="(" close=")"><mi>a</mi></mfenced></mtd><mtd><mfenced open="(" close=")"><mi>b</mi></mfenced></mtd></mtr><mtr><mtd><mfenced open="(" close=")"><mi>c</mi></mfenced></mtd><mtd><mfenced open="(" close=")"><mi>d</mi></mfenced></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('Should display vertical bar delimited matrices', function() { | ||
test('|(a,b,c), (d,e,f), (h,i,j)|', '<math><mfenced open="|" close="|"><mtable><mtr><mtd><mi>a</mi></mtd><mtd><mi>b</mi></mtd><mtd><mi>c</mi></mtd></mtr><mtr><mtd><mi>d</mi></mtd><mtd><mi>e</mi></mtd><mtd><mi>f</mi></mtd></mtr><mtr><mtd><mi>h</mi></mtd><mtd><mi>i</mi></mtd><mtd><mi>j</mi></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('Should display double vertical bar delimited matrices', function() { | ||
test('|| a ; b ; c ||', '<math><mfenced open="‖" close="‖"><mtable><mtr><mtd><mi>a</mi></mtd></mtr><mtr><mtd><mi>b</mi></mtd></mtr><mtr><mtd><mi>c</mi></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('The general n×m matrix', function() { | ||
test('A = [[a_(1 1), a_(1 2), cdots, a_(1 n)], [a_(2 1), a_(2 2), cdots, a_(2 n)], [vdots, vdots, ddots, vdots], [a_(m 1), a_(m 2), cdots, a_(m n)]]', | ||
'<math><mi>A</mi><mo>=</mo><mfenced open="[" close="]"><mtable><mtr><mtd><msub><mi>a</mi><mrow><mn>1</mn><mn>1</mn></mrow></msub></mtd><mtd><msub><mi>a</mi><mrow><mn>1</mn><mn>2</mn></mrow></msub></mtd><mtd><mo>⋯</mo></mtd><mtd><msub><mi>a</mi><mrow><mn>1</mn><mi>n</mi></mrow></msub></mtd></mtr><mtr><mtd><msub><mi>a</mi><mrow><mn>2</mn><mn>1</mn></mrow></msub></mtd><mtd><msub><mi>a</mi><mrow><mn>2</mn><mn>2</mn></mrow></msub></mtd><mtd><mo>⋯</mo></mtd><mtd><msub><mi>a</mi><mrow><mn>2</mn><mi>n</mi></mrow></msub></mtd></mtr><mtr><mtd><mo>⋮</mo></mtd><mtd><mo>⋮</mo></mtd><mtd><mo>⋱</mo></mtd><mtd><mo>⋮</mo></mtd></mtr><mtr><mtd><msub><mi>a</mi><mrow><mi>m</mi><mn>1</mn></mrow></msub></mtd><mtd><msub><mi>a</mi><mrow><mi>m</mi><mn>2</mn></mrow></msub></mtd><mtd><mo>⋯</mo></mtd><mtd><msub><mi>a</mi><mrow><mi>m</mi><mi>n</mi></mrow></msub></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('Nested matrices', function() { | ||
test("[[((a, b), (d, e)), -1], [1, ((f, g), (h, i))]]", | ||
'<math><mfenced open="[" close="]"><mtable><mtr><mtd><mfenced open="(" close=")"><mtable><mtr><mtd><mi>a</mi></mtd><mtd><mi>b</mi></mtd></mtr><mtr><mtd><mi>d</mi></mtd><mtd><mi>e</mi></mtd></mtr></mtable></mfenced></mtd><mtd><mo>-</mo><mn>1</mn></mtd></mtr><mtr><mtd><mn>1</mn></mtd><mtd><mfenced open="(" close=")"><mtable><mtr><mtd><mi>f</mi></mtd><mtd><mi>g</mi></mtd></mtr><mtr><mtd><mi>h</mi></mtd><mtd><mi>i</mi></mtd></mtr></mtable></mfenced></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('The binomial coefficient', function() { | ||
test("(n; k) = n! / (n-k)!k!", | ||
'<math><mfenced open="(" close=")"><mfrac linethickness="0"><mi>n</mi><mi>k</mi></mfrac></mfenced><mo>=</mo><mfrac><mrow><mi>n</mi><mo>!</mo></mrow><mrow><mfenced open="(" close=")"><mrow><mi>n</mi><mo>-</mo><mi>k</mi></mrow></mfenced><mo>!</mo><mi>k</mi><mo>!</mo></mrow></mfrac></math>'); | ||
}); | ||
it("The absolute value", function() { | ||
test('|x| = { (x\\,, if x >= 0), (-x\\,, if x < 0) :}', | ||
'<math><mfenced open="|" close="|"><mi>x</mi></mfenced><mo>=</mo><mfenced open="{" close=""><mtable columnalign="center left"><mtr><mtd><mi>x</mi><mo>,</mo></mtd><mtd><mo>if</mo><mi>x</mi><mo>≥</mo><mn>0</mn></mtd></mtr><mtr><mtd><mo>-</mo><mi>x</mi><mo>,</mo></mtd><mtd><mo>if</mo><mi>x</mi><mo><</mo><mn>0</mn></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
it('Factorial', function() { | ||
test('n! = { [1, if n=0 or n=1], [n(n-1)!, if n > 1] :}', | ||
'<math><mrow><mi>n</mi><mo>!</mo></mrow><mo>=</mo><mfenced open="{" close=""><mtable columnalign="center left"><mtr><mtd><mn>1</mn></mtd><mtd><mo>if</mo><mi>n</mi><mo>=</mo><mn>0</mn><mo>or</mo><mi>n</mi><mo>=</mo><mn>1</mn></mtd></mtr><mtr><mtd><mi>n</mi><mfenced open="(" close=")"><mrow><mi>n</mi><mo>-</mo><mn>1</mn></mrow></mfenced><mo>!</mo></mtd><mtd><mo>if</mo><mi>n</mi><mo>></mo><mn>1</mn></mtd></mtr></mtable></mfenced></math>'); | ||
}); | ||
}); | ||
describe('Accents', function() { | ||
it('Should display accents', function() { | ||
test('hat x', '<math><mover><mi>x</mi><mo accent="true">^</mo></mover></math>'); | ||
test('bar x', '<math><mover><mi>x</mi><mo accent="true">‾</mo></mover></math>'); | ||
test('ul x', '<math><munder><mi>x</mi><mo>_</mo></munder></math>'); | ||
test('vec x', '<math><mover><mi>x</mi><mo accent="true">→</mo></mover></math>'); | ||
test('dot x', '<math><mover><mi>x</mi><mo accent="true">⋅</mo></mover></math>'); | ||
test('ddot x', '<math><mover><mi>x</mi><mo accent="true">⋅⋅</mo></mover></math>'); | ||
test('tilde x', '<math><mover><mi>x</mi><mo accent="true">˜</mo></mover></math>'); | ||
test('cancel x', '<math><menclose notation="updiagonalstrike"><mi>x</mi></menclose></math>'); | ||
}); | ||
it('Should display dottless i and dottless j under overscript accents', function() { | ||
test('bar i', '<math><mover><mi>ı</mi><mo accent="true">‾</mo></mover></math>'); | ||
test('vec j', '<math><mover><mi>ȷ</mi><mo accent="true">→</mo></mover></math>'); | ||
test('ul i', '<math><munder><mi>i</mi><mo>_</mo></munder></math>'); | ||
}); | ||
it('Should put accents over all the following parenthesis', function() { | ||
test("3hat(xyz)", '<math><mn>3</mn><mover><mrow><mi>x</mi><mi>y</mi><mi>z</mi></mrow><mo accent="true">^</mo></mover></math>'); | ||
}); | ||
it('Physics vector notation', function() { | ||
test('vec x = ahat i + bhat j + chat k', | ||
'<math><mover><mi>x</mi><mo accent="true">→</mo></mover><mo>=</mo><mrow><mi>a</mi><mover><mi>ı</mi><mo accent="true">^</mo></mover></mrow><mo>+</mo><mrow><mi>b</mi><mover><mi>ȷ</mi><mo accent="true">^</mo></mover></mrow><mo>+</mo><mrow><mi>c</mi><mover><mi>k</mi><mo accent="true">^</mo></mover></mrow></math>'); | ||
}); | ||
/*tape('Implicit evaluation', function(test) { | ||
test.equal(expr('1 + 2').toString(), '3'); | ||
test.equal(expr('1 - 2').toString(), '-1'); | ||
test.equal(expr('2*3').toString(), '6'); | ||
test.equal(expr('5/2').toString(), '2.5'); | ||
test.equal(expr('2^3').toString(), '8'); | ||
test.equal(expr('3+2/4+2*8').toString(), '19.5'); | ||
test.equal(expr('sqrt(9)').toString(), '3'); | ||
test.equal(expr('3+sin(pi)').toString(), '3'); | ||
test.equal(expr('5%').toString(), '0.05'); | ||
test.equal(expr('log(e)').toString(), '1'); | ||
tape('Fractions', function(test) { | ||
test.equal(mathML('a/b'), '<mfrac><mi>a</mi><mi>b</mi></mfrac>'); | ||
test.equal(mathML('a//b'), '<mi>a</mi><mo value="/">/</mo><mi>b</mi>'); | ||
test.equal(mathML('(a+b)/(c+d)'), '<mfrac><mrow><mi>a</mi><mo value="+">+</mo><mi>b</mi></mrow><mrow><mi>c</mi><mo value="+">+</mo><mi>d</mi></mrow></mfrac>'); | ||
test.equal(mathML('phi = 1 + 1/(1 + 1/(1 + 1/(1 + 1/(1 + …))))'), '<mi>φ</mi><mo value="=">=</mo><mn>1</mn><mo value="+">+</mo><mfrac><mn>1</mn><mrow><mn>1</mn><mo value="+">+</mo><mfrac><mn>1</mn><mrow><mn>1</mn><mo value="+">+</mo><mfrac><mn>1</mn><mrow><mn>1</mn><mo value="+">+</mo><mfrac><mn>1</mn><mrow><mn>1</mn><mo value="+">+</mo><mo value="…">…</mo></mrow></mfrac></mrow></mfrac></mrow></mfrac></mrow></mfrac>'); | ||
test.end(); | ||
}); | ||
tape('Function and variable extraction', function(test) { | ||
test.deepEqual(expr('a + b(c - d(5))').variables, ['a', 'c']); | ||
test.deepEqual(expr('a + b(c - d(5))').functions, ['+', 'b', '-', 'd']); | ||
tape('Super and Subscripts', function(test) { | ||
test.equal(mathML('a_i'), '<msub><mi>a</mi><mi>i</mi></msub>'); | ||
test.equal(mathML('a^2'), '<msup><mi>a</mi><mn>2</mn></msup>'); | ||
test.equal(mathML('a^2 + b^2 = c^2'), '<msup><mi>a</mi><mn>2</mn></msup><mo value="+">+</mo><msup><mi>b</mi><mn>2</mn></msup><mo value="=">=</mo><msup><mi>c</mi><mn>2</mn></msup>'); | ||
// test.equal(mathML('2^2^2^2'), '<msup><mrow><msup><mrow><msup><mn>2</mn><mn>2</mn></msup></mrow><mn>2</mn></msup></mrow><mn>2</mn></msup>'); // TODO Right-to-left | ||
// test.equal(mathML('a_i^2'), '<msubsup><mi>a</mi><mi>i</mi><mn>2</mn></msubsup>'); // TODO Support Super and Subscripts | ||
// test.equal(mathML('a^2_i'), '<msubsup><mi>a</mi><mi>i</mi><mn>2</mn></msubsup>'); // TODO Support Super and Subscripts | ||
test.end(); | ||
}); | ||
tape('Direct evaluation', function(test) { | ||
test.equal(expr('a + b + sqrt(1)').evaluate({a: 1, b: 2}), 4); | ||
test.equal(expr('a + b(a)').evaluate({a: 10, b: (x => x/2)}), 15); | ||
tape('Roots', function(test) { | ||
test.equal(mathML('sqrt(x)'), '<msqrt><mi>x</mi></msqrt>'); | ||
test.equal(mathML('root(x, 3)'), '<mroot><mi>x</mi><mn>3</mn></mroot>'); | ||
test.equal(mathML('sqrt(2) ~~ 1.414213562'), '<msqrt><mn>2</mn></msqrt><mo value="≈">≈</mo><mn>1.414213562</mn>'); | ||
test.equal(mathML('x = (-b +- sqrt(b^2 - 4a c)) / (2a)'), '<mi>x</mi><mo value="=">=</mo><mfrac><mrow><mo value="-">-</mo><mi>b</mi><mo value="±">±</mo><msqrt><msup><mi>b</mi><mn>2</mn></msup><mo value="-">-</mo><mn>4</mn><mi>a</mi><mi>c</mi></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac>'); | ||
test.equal(mathML('phi = (1 + sqrt(5))/2'), '<mi>φ</mi><mo value="=">=</mo><mfrac><mrow><mn>1</mn><mo value="+">+</mo><msqrt><mn>5</mn></msqrt></mrow><mn>2</mn></mfrac>'); | ||
test.equal(mathML('sqrt(1 + sqrt(1 + sqrt(1 + sqrt(1 + sqrt(1 + sqrt(1 + sqrt(1 + …)))))))'), '<msqrt><mn>1</mn><mo value="+">+</mo><msqrt><mn>1</mn><mo value="+">+</mo><msqrt><mn>1</mn><mo value="+">+</mo><msqrt><mn>1</mn><mo value="+">+</mo><msqrt><mn>1</mn><mo value="+">+</mo><msqrt><mn>1</mn><mo value="+">+</mo><msqrt><mn>1</mn><mo value="+">+</mo><mo value="…">…</mo></msqrt></msqrt></msqrt></msqrt></msqrt></msqrt></msqrt>'); | ||
test.end(); | ||
}); | ||
tape('Basic simplification', function(test) { | ||
test.equal(expr('x+x').toString(), '2*x'); | ||
test.equal(expr('2x+x').toString(), '3*x'); | ||
test.equal(expr('2(x+1)+(x+1)').toString(), '3*(x + 1)'); | ||
test.equal(expr('y*x^2+2*x^2').toString(), '(y+2)*x^2'); | ||
test.equal(expr('x+1+x').toString(), '(2*x) + 1'); | ||
test.equal(expr('x^2+x+3+x^2').toString(), '(2*(x^2)) + x + 3'); | ||
test.equal(expr('x+1+2x').toString(), '(3*x) + 1'); | ||
test.equal(expr('x-1+x').toString(), '(2*x) - 1'); | ||
test.equal(expr('x-1-2x+2').toString(), '1 - x'); | ||
test.equal(expr('x/2*x').toString(), '(x^2)/2'); | ||
test.equal(expr('x*2*x').toString(), '2*(x^2)'); | ||
test.equal(expr('x*y*(-x)/(x^2)').toString(), '-y'); | ||
test.equal(expr('foo(x)').toString(), 'foo(x)'); | ||
tape('Groupings', function(test) { | ||
test.equal(mathML('(a+b)'), '<mfenced open="(" close=")"><mi>a</mi><mo value="+">+</mo><mi>b</mi></mfenced>'); | ||
test.equal(mathML('a,b,c'), '<mi>a</mi><mo value=",">,</mo><mi>b</mi><mo value=",">,</mo><mi>c</mi>'); | ||
test.equal(mathML('(a,b,c)'), '<mfenced open="(" close=")"><mi>a</mi><mo value="," lspace="0">,</mo><mi>b</mi><mo value="," lspace="0">,</mo><mi>c</mi></mfenced>'); | ||
test.equal(mathML('(x+y)(x-y) = x^2-y^2'), '<mfenced open="(" close=")"><mi>x</mi><mo value="+">+</mo><mi>y</mi></mfenced><mfenced open="(" close=")"><mi>x</mi><mo value="-">-</mo><mi>y</mi></mfenced><mo value="=">=</mo><msup><mi>x</mi><mn>2</mn></msup><mo value="-">-</mo><msup><mi>y</mi><mn>2</mn></msup>'); | ||
test.equal(mathML('e^(-x)'), '<msup><mi>e</mi><mrow><mo value="-">-</mo><mi>x</mi></mrow></msup>'); | ||
test.equal(mathML('e^(i tau) = 1'), '<msup><mi>e</mi><mrow><mi>i</mi><mi>τ</mi></mrow></msup><mo value="=">=</mo><mn>1</mn>'); | ||
test.end(); | ||
}); | ||
tape('Advanced simplification', function(test) { | ||
test.equal(expr('1/x + 1/y').toString(), '(x + y)/(x*y)'); | ||
test.equal(expr('4 + 4*x - 2*(2 + 2*x))/(2 + 2*x)').toString(), '0'); | ||
test.equal(expr('-4*x*y^2 - 2*y^3 - 2*x^2*y)/(x + y)^2').toString(), '-2*y'); | ||
test.equal(expr('-x - y - (x + y)^(-1)*y^2 + (x + y)^(-1)*x^2').toString(), '-2*y'); | ||
test.equal(expr('x + x*y)/x').toString(), '1 + y'); | ||
test.equal(expr('f(x) + y*f(x))/f(x)').toString(), '1 + y'); | ||
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(expr('x/-y)').toString(), '-x/y'); | ||
test.equal(expr('-x/0)').toString(), 'Infinity'); | ||
test.equal(expr('-a*x/(-y - b))').toString(), 'a*x/(b + y)'); | ||
test.done(); | ||
}); | ||
tape('Numeric equality checking', function(test) { | ||
test.ok(numEquals('x + y', 'y + x')); | ||
test.ok(numEquals('x + y*z^(w+1)', '(y / z^2) * z^(w+3) + x * 1')); | ||
test.end(); | ||
});*/ |
@@ -20,9 +20,5 @@ // ============================================================================= | ||
test.equal(expr('aa + bb + cc').toString(), 'aa + bb + cc'); | ||
// test.equal(expr('5x + 2').toString(), '5 * x + 2'); | ||
// test.equal(expr('a*(b + c)').toString(), '(a*b) + (a*c)'); | ||
// test.equal(expr('a + b*c^d').toString(), 'a + (b*(c^d))'); | ||
// test.equal(expr('a + (b*c)^d').toString(), 'a + ((b^d)*(c^d))'); | ||
// test.equal(expr('((a + b)*c)^d').toString(), 'a + ((b^d)*(c^d))'); | ||
// test.equal(expr('([{|a|}])').toString(), '|a|'); | ||
test.equal(expr('5x + 2').toString(), '5 x + 2'); | ||
test.equal(expr('|a|').toString(), 'abs(a)'); | ||
test.end(); | ||
}); |
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
14
66
46453
1111