"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAssociativity = getAssociativity; exports.getOperator = getOperator; exports.getPrecedence = getPrecedence; exports.isAssociativeWith = isAssociativeWith; exports.properties = void 0; var _object = require("../utils/object.js"); var _is = require("../utils/is.js"); // 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 = exports.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': { op: 'or', associativity: 'left', associativeWith: [] } }, { // logical xor 'OperatorNode:xor': { op: 'xor', associativity: 'left', associativeWith: [] } }, { // logical and 'OperatorNode:and': { op: 'and', associativity: 'left', associativeWith: [] } }, { // bitwise or 'OperatorNode:bitOr': { op: '|', associativity: 'left', associativeWith: [] } }, { // bitwise xor 'OperatorNode:bitXor': { op: '^|', associativity: 'left', associativeWith: [] } }, { // bitwise and 'OperatorNode:bitAnd': { op: '&', associativity: 'left', associativeWith: [] } }, { // relational operators 'OperatorNode:equal': { op: '==', associativity: 'left', associativeWith: [] }, 'OperatorNode:unequal': { op: '!=', associativity: 'left', associativeWith: [] }, 'OperatorNode:smaller': { op: '<', associativity: 'left', associativeWith: [] }, 'OperatorNode:larger': { op: '>', associativity: 'left', associativeWith: [] }, 'OperatorNode:smallerEq': { op: '<=', associativity: 'left', associativeWith: [] }, 'OperatorNode:largerEq': { op: '>=', associativity: 'left', associativeWith: [] }, RelationalNode: { associativity: 'left', associativeWith: [] } }, { // bitshift operators 'OperatorNode:leftShift': { op: '<<', associativity: 'left', associativeWith: [] }, 'OperatorNode:rightArithShift': { op: '>>', associativity: 'left', associativeWith: [] }, 'OperatorNode:rightLogShift': { op: '>>>', associativity: 'left', associativeWith: [] } }, { // unit conversion 'OperatorNode:to': { op: 'to', associativity: 'left', associativeWith: [] } }, { // range RangeNode: {} }, { // addition, subtraction 'OperatorNode:add': { op: '+', associativity: 'left', associativeWith: ['OperatorNode:add', 'OperatorNode:subtract'] }, 'OperatorNode:subtract': { op: '-', associativity: 'left', associativeWith: [] } }, { // multiply, divide, modulus 'OperatorNode:multiply': { op: '*', associativity: 'left', associativeWith: ['OperatorNode:multiply', 'OperatorNode:divide', 'Operator:dotMultiply', 'Operator:dotDivide'] }, 'OperatorNode:divide': { op: '/', 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': { op: '.*', associativity: 'left', associativeWith: ['OperatorNode:multiply', 'OperatorNode:divide', 'OperatorNode:dotMultiply', 'OperatorNode:doDivide'] }, 'OperatorNode:dotDivide': { op: './', associativity: 'left', associativeWith: [] }, 'OperatorNode:mod': { op: 'mod', associativity: 'left', associativeWith: [] } }, { // Repeat multiplication for implicit multiplication 'OperatorNode:multiply': { associativity: 'left', associativeWith: ['OperatorNode:multiply', 'OperatorNode:divide', 'Operator:dotMultiply', 'Operator:dotDivide'] } }, { // unary prefix operators 'OperatorNode:unaryPlus': { op: '+', associativity: 'right' }, 'OperatorNode:unaryMinus': { op: '-', associativity: 'right' }, 'OperatorNode:bitNot': { op: '~', associativity: 'right' }, 'OperatorNode:not': { op: 'not', associativity: 'right' } }, { // exponentiation 'OperatorNode:pow': { op: '^', associativity: 'right', associativeWith: [], latexRightParens: false // the exponent doesn't need parentheses in // LaTeX because it's 2 dimensional // (it's on top) }, 'OperatorNode:dotPow': { op: '.^', associativity: 'right', associativeWith: [] } }, { // factorial 'OperatorNode:factorial': { op: '!', associativity: 'left' } }, { // matrix transpose 'OperatorNode:ctranspose': { op: "'", associativity: 'left' } }]; /** * Returns the first non-parenthesis internal node, but only * when the 'parenthesis' option is unset or auto. * @param {Node} _node * @param {string} parenthesis * @return {Node} */ function unwrapParen(_node, parenthesis) { if (!parenthesis || parenthesis !== 'auto') return _node; let node = _node; while ((0, _is.isParenthesisNode)(node)) node = node.content; return node; } /** * Get the precedence of a Node. * Higher number for higher precedence, starting with 0. * Returns null if the precedence is undefined. * * @param {Node} _node * @param {string} parenthesis * @param {string} implicit * @param {Node} parent (for determining context for implicit multiplication) * @return {number | null} */ function getPrecedence(_node, parenthesis, implicit, parent) { let node = _node; if (parenthesis !== 'keep') { // ParenthesisNodes are only ignored when not in 'keep' mode node = _node.getContent(); } const identifier = node.getIdentifier(); let precedence = null; for (let i = 0; i < properties.length; i++) { if (identifier in properties[i]) { precedence = i; break; } } // Bump up precedence of implicit multiplication, except when preceded // by a "Rule 2" fraction ( [unaryOp]constant / constant ) if (identifier === 'OperatorNode:multiply' && node.implicit && implicit !== 'show') { const leftArg = unwrapParen(node.args[0], parenthesis); if (!((0, _is.isConstantNode)(leftArg) && parent && parent.getIdentifier() === 'OperatorNode:divide' && (0, _is.rule2Node)(unwrapParen(parent.args[0], parenthesis))) && !(leftArg.getIdentifier() === 'OperatorNode:divide' && (0, _is.rule2Node)(unwrapParen(leftArg.args[0], parenthesis)) && (0, _is.isConstantNode)(unwrapParen(leftArg.args[1])))) { precedence += 1; } } return precedence; } /** * 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} _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 ((0, _object.hasOwnProperty)(property, '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 {boolean | 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 ((0, _object.hasOwnProperty)(property, '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; } /** * Get the operator associated with a function name. * Returns a string with the operator symbol, or null if the * input is not the name of a function associated with an * operator. * * @param {string} Function name * @return {string | null} Associated operator symbol, if any */ function getOperator(fn) { const identifier = 'OperatorNode:' + fn; for (const group of properties) { if (identifier in group) { return group[identifier].op; } } return null; }