function _ruleMatch(rule, node, isSplit) {
    //    console.log('Entering _ruleMatch(' + JSON.stringify(rule) + ', ' + JSON.stringify(node) + ')')
    //    console.log('rule = ' + rule)
    //    console.log('node = ' + node)
    //    console.log('Entering _ruleMatch(' + rule.toString() + ', ' + node.toString() + ')')
    var res = [{
      placeholders: {}
    }];

    if (rule instanceof OperatorNode && node instanceof OperatorNode || rule instanceof FunctionNode && node instanceof FunctionNode) {
      // If the rule is an OperatorNode or a FunctionNode, then node must match exactly
      if (rule instanceof OperatorNode) {
        if (rule.op !== node.op || rule.fn !== node.fn) {
          return [];
        }
      } else if (rule instanceof FunctionNode) {
        if (rule.name !== node.name) {
          return [];
        }
      } // rule and node match. Search the children of rule and node.


      if (node.args.length === 1 && rule.args.length === 1 || !isAssociative(node) || isSplit) {
        // Expect non-associative operators to match exactly
        var childMatches = [];

        for (var i = 0; i < rule.args.length; i++) {
          var childMatch = _ruleMatch(rule.args[i], node.args[i]);

          if (childMatch.length === 0) {
            // Child did not match, so stop searching immediately
            return [];
          } // The child matched, so add the information returned from the child to our result


          childMatches.push(childMatch);
        }

        res = mergeChildMatches(childMatches);
      } else if (node.args.length >= 2 && rule.args.length === 2) {
        // node is flattened, rule is not
        // Associative operators/functions can be split in different ways so we check if the rule matches each
        // them and return their union.
        var splits = getSplits(node, rule.context);
        var splitMatches = [];

        for (var _i = 0; _i < splits.length; _i++) {
          var matchSet = _ruleMatch(rule, splits[_i], true); // recursing at the same tree depth here


          splitMatches = splitMatches.concat(matchSet);
        }

        return splitMatches;
      } else if (rule.args.length > 2) {
        throw Error('Unexpected non-binary associative function: ' + rule.toString());
      } else {
        // Incorrect number of arguments in rule and node, so no match
        return [];
      }
    } else if (rule instanceof SymbolNode) {
      // If the rule is a SymbolNode, then it carries a special meaning
      // according to the first character of the symbol node name.
      // c.* matches a ConstantNode
      // n.* matches any node
      if (rule.name.length === 0) {
        throw new Error('Symbol in rule has 0 length...!?');
      }

      if (math.hasOwnProperty(rule.name)) {
        if (!SUPPORTED_CONSTANTS[rule.name]) {
          throw new Error('Built in constant: ' + rule.name + ' is not supported by simplify.');
        } // built-in constant must match exactly


        if (rule.name !== node.name) {
          return [];
        }
      } else if (rule.name[0] === 'n' || rule.name.substring(0, 2) === '_p') {
        // rule matches _anything_, so assign this node to the rule.name placeholder
        // Assign node to the rule.name placeholder.
        // Our parent will check for matches among placeholders.
        res[0].placeholders[rule.name] = node;
      } else if (rule.name[0] === 'v') {
        // rule matches any variable thing (not a ConstantNode)
        if (!type.isConstantNode(node)) {
          res[0].placeholders[rule.name] = node;
        } else {
          // Mis-match: rule was expecting something other than a ConstantNode
          return [];
        }
      } else if (rule.name[0] === 'c') {
        // rule matches any ConstantNode
        if (node instanceof ConstantNode) {
          res[0].placeholders[rule.name] = node;
        } else {
          // Mis-match: rule was expecting a ConstantNode
          return [];
        }
      } else {
        throw new Error('Invalid symbol in rule: ' + rule.name);
      }
    } else if (rule instanceof ConstantNode) {
      // Literal constant must match exactly
      if (!equal(rule.value, node.value)) {
        return [];
      }
    } else {
      // Some other node was encountered which we aren't prepared for, so no match
      return [];
    } // It's a match!
    // console.log('_ruleMatch(' + rule.toString() + ', ' + node.toString() + ') found a match')


    return res;
  }
Determines whether node matches rule.

@param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} rule
@param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node
@return {Object} Information about the match, if it exists.