operators.js (7405B)
1 // list of identifiers of nodes in order of their precedence 2 // also contains information about left/right associativity 3 // and which other operator the operator is associative with 4 // Example: 5 // addition is associative with addition and subtraction, because: 6 // (a+b)+c=a+(b+c) 7 // (a+b)-c=a+(b-c) 8 // 9 // postfix operators are left associative, prefix operators 10 // are right associative 11 // 12 // It's also possible to set the following properties: 13 // latexParens: if set to false, this node doesn't need to be enclosed 14 // in parentheses when using LaTeX 15 // latexLeftParens: if set to false, this !OperatorNode's! 16 // left argument doesn't need to be enclosed 17 // in parentheses 18 // latexRightParens: the same for the right argument 19 import { hasOwnProperty } from '../utils/object.js'; 20 export var properties = [{ 21 // assignment 22 AssignmentNode: {}, 23 FunctionAssignmentNode: {} 24 }, { 25 // conditional expression 26 ConditionalNode: { 27 latexLeftParens: false, 28 latexRightParens: false, 29 latexParens: false // conditionals don't need parentheses in LaTeX because 30 // they are 2 dimensional 31 32 } 33 }, { 34 // logical or 35 'OperatorNode:or': { 36 associativity: 'left', 37 associativeWith: [] 38 } 39 }, { 40 // logical xor 41 'OperatorNode:xor': { 42 associativity: 'left', 43 associativeWith: [] 44 } 45 }, { 46 // logical and 47 'OperatorNode:and': { 48 associativity: 'left', 49 associativeWith: [] 50 } 51 }, { 52 // bitwise or 53 'OperatorNode:bitOr': { 54 associativity: 'left', 55 associativeWith: [] 56 } 57 }, { 58 // bitwise xor 59 'OperatorNode:bitXor': { 60 associativity: 'left', 61 associativeWith: [] 62 } 63 }, { 64 // bitwise and 65 'OperatorNode:bitAnd': { 66 associativity: 'left', 67 associativeWith: [] 68 } 69 }, { 70 // relational operators 71 'OperatorNode:equal': { 72 associativity: 'left', 73 associativeWith: [] 74 }, 75 'OperatorNode:unequal': { 76 associativity: 'left', 77 associativeWith: [] 78 }, 79 'OperatorNode:smaller': { 80 associativity: 'left', 81 associativeWith: [] 82 }, 83 'OperatorNode:larger': { 84 associativity: 'left', 85 associativeWith: [] 86 }, 87 'OperatorNode:smallerEq': { 88 associativity: 'left', 89 associativeWith: [] 90 }, 91 'OperatorNode:largerEq': { 92 associativity: 'left', 93 associativeWith: [] 94 }, 95 RelationalNode: { 96 associativity: 'left', 97 associativeWith: [] 98 } 99 }, { 100 // bitshift operators 101 'OperatorNode:leftShift': { 102 associativity: 'left', 103 associativeWith: [] 104 }, 105 'OperatorNode:rightArithShift': { 106 associativity: 'left', 107 associativeWith: [] 108 }, 109 'OperatorNode:rightLogShift': { 110 associativity: 'left', 111 associativeWith: [] 112 } 113 }, { 114 // unit conversion 115 'OperatorNode:to': { 116 associativity: 'left', 117 associativeWith: [] 118 } 119 }, { 120 // range 121 RangeNode: {} 122 }, { 123 // addition, subtraction 124 'OperatorNode:add': { 125 associativity: 'left', 126 associativeWith: ['OperatorNode:add', 'OperatorNode:subtract'] 127 }, 128 'OperatorNode:subtract': { 129 associativity: 'left', 130 associativeWith: [] 131 } 132 }, { 133 // multiply, divide, modulus 134 'OperatorNode:multiply': { 135 associativity: 'left', 136 associativeWith: ['OperatorNode:multiply', 'OperatorNode:divide', 'Operator:dotMultiply', 'Operator:dotDivide'] 137 }, 138 'OperatorNode:divide': { 139 associativity: 'left', 140 associativeWith: [], 141 latexLeftParens: false, 142 latexRightParens: false, 143 latexParens: false // fractions don't require parentheses because 144 // they're 2 dimensional, so parens aren't needed 145 // in LaTeX 146 147 }, 148 'OperatorNode:dotMultiply': { 149 associativity: 'left', 150 associativeWith: ['OperatorNode:multiply', 'OperatorNode:divide', 'OperatorNode:dotMultiply', 'OperatorNode:doDivide'] 151 }, 152 'OperatorNode:dotDivide': { 153 associativity: 'left', 154 associativeWith: [] 155 }, 156 'OperatorNode:mod': { 157 associativity: 'left', 158 associativeWith: [] 159 } 160 }, { 161 // unary prefix operators 162 'OperatorNode:unaryPlus': { 163 associativity: 'right' 164 }, 165 'OperatorNode:unaryMinus': { 166 associativity: 'right' 167 }, 168 'OperatorNode:bitNot': { 169 associativity: 'right' 170 }, 171 'OperatorNode:not': { 172 associativity: 'right' 173 } 174 }, { 175 // exponentiation 176 'OperatorNode:pow': { 177 associativity: 'right', 178 associativeWith: [], 179 latexRightParens: false // the exponent doesn't need parentheses in 180 // LaTeX because it's 2 dimensional 181 // (it's on top) 182 183 }, 184 'OperatorNode:dotPow': { 185 associativity: 'right', 186 associativeWith: [] 187 } 188 }, { 189 // factorial 190 'OperatorNode:factorial': { 191 associativity: 'left' 192 } 193 }, { 194 // matrix transpose 195 'OperatorNode:transpose': { 196 associativity: 'left' 197 } 198 }]; 199 /** 200 * Get the precedence of a Node. 201 * Higher number for higher precedence, starting with 0. 202 * Returns null if the precedence is undefined. 203 * 204 * @param {Node} _node 205 * @param {string} parenthesis 206 * @return {number | null} 207 */ 208 209 export function getPrecedence(_node, parenthesis) { 210 var node = _node; 211 212 if (parenthesis !== 'keep') { 213 // ParenthesisNodes are only ignored when not in 'keep' mode 214 node = _node.getContent(); 215 } 216 217 var identifier = node.getIdentifier(); 218 219 for (var i = 0; i < properties.length; i++) { 220 if (identifier in properties[i]) { 221 return i; 222 } 223 } 224 225 return null; 226 } 227 /** 228 * Get the associativity of an operator (left or right). 229 * Returns a string containing 'left' or 'right' or null if 230 * the associativity is not defined. 231 * 232 * @param {Node} _node 233 * @param {string} parenthesis 234 * @return {string|null} 235 * @throws {Error} 236 */ 237 238 export function getAssociativity(_node, parenthesis) { 239 var node = _node; 240 241 if (parenthesis !== 'keep') { 242 // ParenthesisNodes are only ignored when not in 'keep' mode 243 node = _node.getContent(); 244 } 245 246 var identifier = node.getIdentifier(); 247 var index = getPrecedence(node, parenthesis); 248 249 if (index === null) { 250 // node isn't in the list 251 return null; 252 } 253 254 var property = properties[index][identifier]; 255 256 if (hasOwnProperty(property, 'associativity')) { 257 if (property.associativity === 'left') { 258 return 'left'; 259 } 260 261 if (property.associativity === 'right') { 262 return 'right'; 263 } // associativity is invalid 264 265 266 throw Error('\'' + identifier + '\' has the invalid associativity \'' + property.associativity + '\'.'); 267 } // associativity is undefined 268 269 270 return null; 271 } 272 /** 273 * Check if an operator is associative with another operator. 274 * Returns either true or false or null if not defined. 275 * 276 * @param {Node} nodeA 277 * @param {Node} nodeB 278 * @param {string} parenthesis 279 * @return {boolean | null} 280 */ 281 282 export function isAssociativeWith(nodeA, nodeB, parenthesis) { 283 // ParenthesisNodes are only ignored when not in 'keep' mode 284 var a = parenthesis !== 'keep' ? nodeA.getContent() : nodeA; 285 var b = parenthesis !== 'keep' ? nodeA.getContent() : nodeB; 286 var identifierA = a.getIdentifier(); 287 var identifierB = b.getIdentifier(); 288 var index = getPrecedence(a, parenthesis); 289 290 if (index === null) { 291 // node isn't in the list 292 return null; 293 } 294 295 var property = properties[index][identifierA]; 296 297 if (hasOwnProperty(property, 'associativeWith') && property.associativeWith instanceof Array) { 298 for (var i = 0; i < property.associativeWith.length; i++) { 299 if (property.associativeWith[i] === identifierB) { 300 return true; 301 } 302 } 303 304 return false; 305 } // associativeWith is not defined 306 307 308 return null; 309 }