mirror of
https://github.com/josdejong/mathjs.git
synced 2026-01-18 14:59:29 +00:00
* Minimally working * Added tests, toString * Added tests for evaluation * Minor changes * Added a line in the docs about chained conditionals * Documentation, replacing var and let with const
317 lines
7.8 KiB
JavaScript
317 lines
7.8 KiB
JavaScript
'use strict'
|
|
|
|
// list of identifiers of nodes in order of their precedence
|
|
// also contains information about left/right associativity
|
|
// and which other operator the operator is associative with
|
|
// Example:
|
|
// addition is associative with addition and subtraction, because:
|
|
// (a+b)+c=a+(b+c)
|
|
// (a+b)-c=a+(b-c)
|
|
//
|
|
// postfix operators are left associative, prefix operators
|
|
// are right associative
|
|
//
|
|
// It's also possible to set the following properties:
|
|
// latexParens: if set to false, this node doesn't need to be enclosed
|
|
// in parentheses when using LaTeX
|
|
// latexLeftParens: if set to false, this !OperatorNode's!
|
|
// left argument doesn't need to be enclosed
|
|
// in parentheses
|
|
// latexRightParens: the same for the right argument
|
|
const properties = [
|
|
{ // assignment
|
|
'AssignmentNode': {},
|
|
'FunctionAssignmentNode': {}
|
|
},
|
|
{ // conditional expression
|
|
'ConditionalNode': {
|
|
latexLeftParens: false,
|
|
latexRightParens: false,
|
|
latexParens: false
|
|
// conditionals don't need parentheses in LaTeX because
|
|
// they are 2 dimensional
|
|
}
|
|
},
|
|
{ // logical or
|
|
'OperatorNode:or': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
|
|
},
|
|
{ // logical xor
|
|
'OperatorNode:xor': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // logical and
|
|
'OperatorNode:and': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // bitwise or
|
|
'OperatorNode:bitOr': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // bitwise xor
|
|
'OperatorNode:bitXor': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // bitwise and
|
|
'OperatorNode:bitAnd': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // relational operators
|
|
'OperatorNode:equal': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
},
|
|
'OperatorNode:unequal': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
},
|
|
'OperatorNode:smaller': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
},
|
|
'OperatorNode:larger': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
},
|
|
'OperatorNode:smallerEq': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
},
|
|
'OperatorNode:largerEq': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
},
|
|
'RelationalNode': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // bitshift operators
|
|
'OperatorNode:leftShift': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
},
|
|
'OperatorNode:rightArithShift': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
},
|
|
'OperatorNode:rightLogShift': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // unit conversion
|
|
'OperatorNode:to': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // range
|
|
'RangeNode': {}
|
|
},
|
|
{ // addition, subtraction
|
|
'OperatorNode:add': {
|
|
associativity: 'left',
|
|
associativeWith: ['OperatorNode:add', 'OperatorNode:subtract']
|
|
},
|
|
'OperatorNode:subtract': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // multiply, divide, modulus
|
|
'OperatorNode:multiply': {
|
|
associativity: 'left',
|
|
associativeWith: [
|
|
'OperatorNode:multiply',
|
|
'OperatorNode:divide',
|
|
'Operator:dotMultiply',
|
|
'Operator:dotDivide'
|
|
]
|
|
},
|
|
'OperatorNode:divide': {
|
|
associativity: 'left',
|
|
associativeWith: [],
|
|
latexLeftParens: false,
|
|
latexRightParens: false,
|
|
latexParens: false
|
|
// fractions don't require parentheses because
|
|
// they're 2 dimensional, so parens aren't needed
|
|
// in LaTeX
|
|
},
|
|
'OperatorNode:dotMultiply': {
|
|
associativity: 'left',
|
|
associativeWith: [
|
|
'OperatorNode:multiply',
|
|
'OperatorNode:divide',
|
|
'OperatorNode:dotMultiply',
|
|
'OperatorNode:doDivide'
|
|
]
|
|
},
|
|
'OperatorNode:dotDivide': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
},
|
|
'OperatorNode:mod': {
|
|
associativity: 'left',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // unary prefix operators
|
|
'OperatorNode:unaryPlus': {
|
|
associativity: 'right'
|
|
},
|
|
'OperatorNode:unaryMinus': {
|
|
associativity: 'right'
|
|
},
|
|
'OperatorNode:bitNot': {
|
|
associativity: 'right'
|
|
},
|
|
'OperatorNode:not': {
|
|
associativity: 'right'
|
|
}
|
|
},
|
|
{ // exponentiation
|
|
'OperatorNode:pow': {
|
|
associativity: 'right',
|
|
associativeWith: [],
|
|
latexRightParens: false
|
|
// the exponent doesn't need parentheses in
|
|
// LaTeX because it's 2 dimensional
|
|
// (it's on top)
|
|
},
|
|
'OperatorNode:dotPow': {
|
|
associativity: 'right',
|
|
associativeWith: []
|
|
}
|
|
},
|
|
{ // factorial
|
|
'OperatorNode:factorial': {
|
|
associativity: 'left'
|
|
}
|
|
},
|
|
{ // matrix transpose
|
|
'OperatorNode:transpose': {
|
|
associativity: 'left'
|
|
}
|
|
}
|
|
]
|
|
|
|
/**
|
|
* Get the precedence of a Node.
|
|
* Higher number for higher precedence, starting with 0.
|
|
* Returns null if the precedence is undefined.
|
|
*
|
|
* @param {Node}
|
|
* @param {string} parenthesis
|
|
* @return {number|null}
|
|
*/
|
|
function getPrecedence (_node, parenthesis) {
|
|
let node = _node
|
|
if (parenthesis !== 'keep') {
|
|
// ParenthesisNodes are only ignored when not in 'keep' mode
|
|
node = _node.getContent()
|
|
}
|
|
const identifier = node.getIdentifier()
|
|
for (let i = 0; i < properties.length; i++) {
|
|
if (identifier in properties[i]) {
|
|
return i
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* Get the associativity of an operator (left or right).
|
|
* Returns a string containing 'left' or 'right' or null if
|
|
* the associativity is not defined.
|
|
*
|
|
* @param {Node}
|
|
* @param {string} parenthesis
|
|
* @return {string|null}
|
|
* @throws {Error}
|
|
*/
|
|
function getAssociativity (_node, parenthesis) {
|
|
let node = _node
|
|
if (parenthesis !== 'keep') {
|
|
// ParenthesisNodes are only ignored when not in 'keep' mode
|
|
node = _node.getContent()
|
|
}
|
|
const identifier = node.getIdentifier()
|
|
const index = getPrecedence(node, parenthesis)
|
|
if (index === null) {
|
|
// node isn't in the list
|
|
return null
|
|
}
|
|
const property = properties[index][identifier]
|
|
|
|
if (property.hasOwnProperty('associativity')) {
|
|
if (property.associativity === 'left') {
|
|
return 'left'
|
|
}
|
|
if (property.associativity === 'right') {
|
|
return 'right'
|
|
}
|
|
// associativity is invalid
|
|
throw Error('\'' + identifier + '\' has the invalid associativity \'' +
|
|
property.associativity + '\'.')
|
|
}
|
|
|
|
// associativity is undefined
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* Check if an operator is associative with another operator.
|
|
* Returns either true or false or null if not defined.
|
|
*
|
|
* @param {Node} nodeA
|
|
* @param {Node} nodeB
|
|
* @param {string} parenthesis
|
|
* @return {bool|null}
|
|
*/
|
|
function isAssociativeWith (nodeA, nodeB, parenthesis) {
|
|
// ParenthesisNodes are only ignored when not in 'keep' mode
|
|
const a = (parenthesis !== 'keep') ? nodeA.getContent() : nodeA
|
|
const b = (parenthesis !== 'keep') ? nodeA.getContent() : nodeB
|
|
const identifierA = a.getIdentifier()
|
|
const identifierB = b.getIdentifier()
|
|
const index = getPrecedence(a, parenthesis)
|
|
if (index === null) {
|
|
// node isn't in the list
|
|
return null
|
|
}
|
|
const property = properties[index][identifierA]
|
|
|
|
if (property.hasOwnProperty('associativeWith') &&
|
|
(property.associativeWith instanceof Array)) {
|
|
for (let i = 0; i < property.associativeWith.length; i++) {
|
|
if (property.associativeWith[i] === identifierB) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// associativeWith is not defined
|
|
return null
|
|
}
|
|
|
|
module.exports.properties = properties
|
|
module.exports.getPrecedence = getPrecedence
|
|
module.exports.getAssociativity = getAssociativity
|
|
module.exports.isAssociativeWith = isAssociativeWith
|