operators.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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. import { isConstantNode, isParenthesisNode, rule2Node } from '../utils/is.js';
  21. export var properties = [{
  22. // assignment
  23. AssignmentNode: {},
  24. FunctionAssignmentNode: {}
  25. }, {
  26. // conditional expression
  27. ConditionalNode: {
  28. latexLeftParens: false,
  29. latexRightParens: false,
  30. latexParens: false
  31. // conditionals don't need parentheses in LaTeX because
  32. // they are 2 dimensional
  33. }
  34. }, {
  35. // logical or
  36. 'OperatorNode:or': {
  37. op: 'or',
  38. associativity: 'left',
  39. associativeWith: []
  40. }
  41. }, {
  42. // logical xor
  43. 'OperatorNode:xor': {
  44. op: 'xor',
  45. associativity: 'left',
  46. associativeWith: []
  47. }
  48. }, {
  49. // logical and
  50. 'OperatorNode:and': {
  51. op: 'and',
  52. associativity: 'left',
  53. associativeWith: []
  54. }
  55. }, {
  56. // bitwise or
  57. 'OperatorNode:bitOr': {
  58. op: '|',
  59. associativity: 'left',
  60. associativeWith: []
  61. }
  62. }, {
  63. // bitwise xor
  64. 'OperatorNode:bitXor': {
  65. op: '^|',
  66. associativity: 'left',
  67. associativeWith: []
  68. }
  69. }, {
  70. // bitwise and
  71. 'OperatorNode:bitAnd': {
  72. op: '&',
  73. associativity: 'left',
  74. associativeWith: []
  75. }
  76. }, {
  77. // relational operators
  78. 'OperatorNode:equal': {
  79. op: '==',
  80. associativity: 'left',
  81. associativeWith: []
  82. },
  83. 'OperatorNode:unequal': {
  84. op: '!=',
  85. associativity: 'left',
  86. associativeWith: []
  87. },
  88. 'OperatorNode:smaller': {
  89. op: '<',
  90. associativity: 'left',
  91. associativeWith: []
  92. },
  93. 'OperatorNode:larger': {
  94. op: '>',
  95. associativity: 'left',
  96. associativeWith: []
  97. },
  98. 'OperatorNode:smallerEq': {
  99. op: '<=',
  100. associativity: 'left',
  101. associativeWith: []
  102. },
  103. 'OperatorNode:largerEq': {
  104. op: '>=',
  105. associativity: 'left',
  106. associativeWith: []
  107. },
  108. RelationalNode: {
  109. associativity: 'left',
  110. associativeWith: []
  111. }
  112. }, {
  113. // bitshift operators
  114. 'OperatorNode:leftShift': {
  115. op: '<<',
  116. associativity: 'left',
  117. associativeWith: []
  118. },
  119. 'OperatorNode:rightArithShift': {
  120. op: '>>',
  121. associativity: 'left',
  122. associativeWith: []
  123. },
  124. 'OperatorNode:rightLogShift': {
  125. op: '>>>',
  126. associativity: 'left',
  127. associativeWith: []
  128. }
  129. }, {
  130. // unit conversion
  131. 'OperatorNode:to': {
  132. op: 'to',
  133. associativity: 'left',
  134. associativeWith: []
  135. }
  136. }, {
  137. // range
  138. RangeNode: {}
  139. }, {
  140. // addition, subtraction
  141. 'OperatorNode:add': {
  142. op: '+',
  143. associativity: 'left',
  144. associativeWith: ['OperatorNode:add', 'OperatorNode:subtract']
  145. },
  146. 'OperatorNode:subtract': {
  147. op: '-',
  148. associativity: 'left',
  149. associativeWith: []
  150. }
  151. }, {
  152. // multiply, divide, modulus
  153. 'OperatorNode:multiply': {
  154. op: '*',
  155. associativity: 'left',
  156. associativeWith: ['OperatorNode:multiply', 'OperatorNode:divide', 'Operator:dotMultiply', 'Operator:dotDivide']
  157. },
  158. 'OperatorNode:divide': {
  159. op: '/',
  160. associativity: 'left',
  161. associativeWith: [],
  162. latexLeftParens: false,
  163. latexRightParens: false,
  164. latexParens: false
  165. // fractions don't require parentheses because
  166. // they're 2 dimensional, so parens aren't needed
  167. // in LaTeX
  168. },
  169. 'OperatorNode:dotMultiply': {
  170. op: '.*',
  171. associativity: 'left',
  172. associativeWith: ['OperatorNode:multiply', 'OperatorNode:divide', 'OperatorNode:dotMultiply', 'OperatorNode:doDivide']
  173. },
  174. 'OperatorNode:dotDivide': {
  175. op: './',
  176. associativity: 'left',
  177. associativeWith: []
  178. },
  179. 'OperatorNode:mod': {
  180. op: 'mod',
  181. associativity: 'left',
  182. associativeWith: []
  183. }
  184. }, {
  185. // Repeat multiplication for implicit multiplication
  186. 'OperatorNode:multiply': {
  187. associativity: 'left',
  188. associativeWith: ['OperatorNode:multiply', 'OperatorNode:divide', 'Operator:dotMultiply', 'Operator:dotDivide']
  189. }
  190. }, {
  191. // unary prefix operators
  192. 'OperatorNode:unaryPlus': {
  193. op: '+',
  194. associativity: 'right'
  195. },
  196. 'OperatorNode:unaryMinus': {
  197. op: '-',
  198. associativity: 'right'
  199. },
  200. 'OperatorNode:bitNot': {
  201. op: '~',
  202. associativity: 'right'
  203. },
  204. 'OperatorNode:not': {
  205. op: 'not',
  206. associativity: 'right'
  207. }
  208. }, {
  209. // exponentiation
  210. 'OperatorNode:pow': {
  211. op: '^',
  212. associativity: 'right',
  213. associativeWith: [],
  214. latexRightParens: false
  215. // the exponent doesn't need parentheses in
  216. // LaTeX because it's 2 dimensional
  217. // (it's on top)
  218. },
  219. 'OperatorNode:dotPow': {
  220. op: '.^',
  221. associativity: 'right',
  222. associativeWith: []
  223. }
  224. }, {
  225. // factorial
  226. 'OperatorNode:factorial': {
  227. op: '!',
  228. associativity: 'left'
  229. }
  230. }, {
  231. // matrix transpose
  232. 'OperatorNode:ctranspose': {
  233. op: "'",
  234. associativity: 'left'
  235. }
  236. }];
  237. /**
  238. * Returns the first non-parenthesis internal node, but only
  239. * when the 'parenthesis' option is unset or auto.
  240. * @param {Node} _node
  241. * @param {string} parenthesis
  242. * @return {Node}
  243. */
  244. function unwrapParen(_node, parenthesis) {
  245. if (!parenthesis || parenthesis !== 'auto') return _node;
  246. var node = _node;
  247. while (isParenthesisNode(node)) {
  248. node = node.content;
  249. }
  250. return node;
  251. }
  252. /**
  253. * Get the precedence of a Node.
  254. * Higher number for higher precedence, starting with 0.
  255. * Returns null if the precedence is undefined.
  256. *
  257. * @param {Node} _node
  258. * @param {string} parenthesis
  259. * @param {string} implicit
  260. * @param {Node} parent (for determining context for implicit multiplication)
  261. * @return {number | null}
  262. */
  263. export function getPrecedence(_node, parenthesis, implicit, parent) {
  264. var node = _node;
  265. if (parenthesis !== 'keep') {
  266. // ParenthesisNodes are only ignored when not in 'keep' mode
  267. node = _node.getContent();
  268. }
  269. var identifier = node.getIdentifier();
  270. var precedence = null;
  271. for (var i = 0; i < properties.length; i++) {
  272. if (identifier in properties[i]) {
  273. precedence = i;
  274. break;
  275. }
  276. }
  277. // Bump up precedence of implicit multiplication, except when preceded
  278. // by a "Rule 2" fraction ( [unaryOp]constant / constant )
  279. if (identifier === 'OperatorNode:multiply' && node.implicit && implicit !== 'show') {
  280. var leftArg = unwrapParen(node.args[0], parenthesis);
  281. if (!(isConstantNode(leftArg) && parent && parent.getIdentifier() === 'OperatorNode:divide' && rule2Node(unwrapParen(parent.args[0], parenthesis))) && !(leftArg.getIdentifier() === 'OperatorNode:divide' && rule2Node(unwrapParen(leftArg.args[0], parenthesis)) && isConstantNode(unwrapParen(leftArg.args[1])))) {
  282. precedence += 1;
  283. }
  284. }
  285. return precedence;
  286. }
  287. /**
  288. * Get the associativity of an operator (left or right).
  289. * Returns a string containing 'left' or 'right' or null if
  290. * the associativity is not defined.
  291. *
  292. * @param {Node} _node
  293. * @param {string} parenthesis
  294. * @return {string|null}
  295. * @throws {Error}
  296. */
  297. export function getAssociativity(_node, parenthesis) {
  298. var node = _node;
  299. if (parenthesis !== 'keep') {
  300. // ParenthesisNodes are only ignored when not in 'keep' mode
  301. node = _node.getContent();
  302. }
  303. var identifier = node.getIdentifier();
  304. var index = getPrecedence(node, parenthesis);
  305. if (index === null) {
  306. // node isn't in the list
  307. return null;
  308. }
  309. var property = properties[index][identifier];
  310. if (hasOwnProperty(property, 'associativity')) {
  311. if (property.associativity === 'left') {
  312. return 'left';
  313. }
  314. if (property.associativity === 'right') {
  315. return 'right';
  316. }
  317. // associativity is invalid
  318. throw Error('\'' + identifier + '\' has the invalid associativity \'' + property.associativity + '\'.');
  319. }
  320. // associativity is undefined
  321. return null;
  322. }
  323. /**
  324. * Check if an operator is associative with another operator.
  325. * Returns either true or false or null if not defined.
  326. *
  327. * @param {Node} nodeA
  328. * @param {Node} nodeB
  329. * @param {string} parenthesis
  330. * @return {boolean | null}
  331. */
  332. export function isAssociativeWith(nodeA, nodeB, parenthesis) {
  333. // ParenthesisNodes are only ignored when not in 'keep' mode
  334. var a = parenthesis !== 'keep' ? nodeA.getContent() : nodeA;
  335. var b = parenthesis !== 'keep' ? nodeA.getContent() : nodeB;
  336. var identifierA = a.getIdentifier();
  337. var identifierB = b.getIdentifier();
  338. var index = getPrecedence(a, parenthesis);
  339. if (index === null) {
  340. // node isn't in the list
  341. return null;
  342. }
  343. var property = properties[index][identifierA];
  344. if (hasOwnProperty(property, 'associativeWith') && property.associativeWith instanceof Array) {
  345. for (var i = 0; i < property.associativeWith.length; i++) {
  346. if (property.associativeWith[i] === identifierB) {
  347. return true;
  348. }
  349. }
  350. return false;
  351. }
  352. // associativeWith is not defined
  353. return null;
  354. }
  355. /**
  356. * Get the operator associated with a function name.
  357. * Returns a string with the operator symbol, or null if the
  358. * input is not the name of a function associated with an
  359. * operator.
  360. *
  361. * @param {string} Function name
  362. * @return {string | null} Associated operator symbol, if any
  363. */
  364. export function getOperator(fn) {
  365. var identifier = 'OperatorNode:' + fn;
  366. for (var group of properties) {
  367. if (identifier in group) {
  368. return group[identifier].op;
  369. }
  370. }
  371. return null;
  372. }