UNPKG

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