simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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 }