got about as far as I think I can with operator nodes for now, without zach's optimizations

This commit is contained in:
Jeremy Ashkenas 2010-02-10 00:05:56 -05:00
parent 9339058fc3
commit 76dac9c09c
5 changed files with 223 additions and 122 deletions

View File

@ -1,5 +1,5 @@
(function(){ (function(){
var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, Expressions, ExtendsNode, IndexNode, LiteralNode, Node, ObjectNode, OpNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThisNode, ValueNode, WhileNode, any, compact, del, dup, flatten, inherit, merge, statement; var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, Expressions, ExtendsNode, IDENTIFIER, IndexNode, LiteralNode, Node, ObjectNode, OpNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThisNode, ValueNode, WhileNode, any, compact, del, dup, flatten, inherit, merge, statement;
var __hasProp = Object.prototype.hasOwnProperty; var __hasProp = Object.prototype.hasOwnProperty;
process.mixin(require('./scope')); process.mixin(require('./scope'));
// The abstract base class for all CoffeeScript nodes. // The abstract base class for all CoffeeScript nodes.
@ -151,6 +151,8 @@
// Tabs are two spaces for pretty printing. // Tabs are two spaces for pretty printing.
TAB = ' '; TAB = ' ';
TRAILING_WHITESPACE = /\s+$/g; TRAILING_WHITESPACE = /\s+$/g;
// Keep the identifier regex in sync with the Lexer.
IDENTIFIER = /^[a-zA-Z$_](\w|\$)*$/;
// Flatten nested arrays recursively. // Flatten nested arrays recursively.
flatten = function flatten(list) { flatten = function flatten(list) {
var __a, __b, item, memo; var __a, __b, item, memo;
@ -842,8 +844,6 @@
}); });
// Setting the value of a local variable, or the value of an object property. // Setting the value of a local variable, or the value of an object property.
AssignNode = (exports.AssignNode = inherit(Node, { AssignNode = (exports.AssignNode = inherit(Node, {
// Keep the identifier regex in sync with the Lexer.
IDENTIFIER: /^([a-zA-Z$_](\w|\$)*)/,
PROTO_ASSIGN: /^(\S+)\.prototype/, PROTO_ASSIGN: /^(\S+)\.prototype/,
LEADING_DOT: /^\.(prototype\.)?/, LEADING_DOT: /^\.(prototype\.)?/,
constructor: function constructor(variable, value, context) { constructor: function constructor(variable, value, context) {
@ -875,7 +875,7 @@
match = name.match(this.PROTO_ASSIGN); match = name.match(this.PROTO_ASSIGN);
proto = match && match[1]; proto = match && match[1];
if (this.value instanceof CodeNode) { if (this.value instanceof CodeNode) {
if (last.match(this.IDENTIFIER)) { if (last.match(IDENTIFIER)) {
this.value.name = last; this.value.name = last;
} }
if (proto) { if (proto) {
@ -1058,9 +1058,60 @@
return this.CHAINABLE.indexOf(this.operator) >= 0; return this.CHAINABLE.indexOf(this.operator) >= 0;
}, },
compile_node: function compile_node(o) { compile_node: function compile_node(o) {
if (this.is_chainable() && (this.first.unwrap() instanceof OpNode) && this.first.unwrap().is_chainable()) { if (this.is_chainable() && this.first.unwrap() instanceof OpNode && this.first.unwrap().is_chainable()) {
return this.compile_chain(o); return this.compile_chain(o);
} }
if (this.ASSIGNMENT.indexOf(this.operator) >= 0) {
return this.compile_assignment(o);
}
if (this.is_unary()) {
return this.compile_unary(o);
}
if (this.operator === '?') {
return this.compile_existence(o);
}
return this.first.compile(o) + ' ' + this.operator + ' ' + this.second.compile(o);
},
// Mimic Python's chained comparisons. See:
// http://docs.python.org/reference/expressions.html#notin
compile_chain: function compile_chain(o) {
var __a, shared;
shared = this.first.unwrap().second;
if (shared instanceof CallNode) {
__a = shared.compile_reference(o);
this.first.second = __a[0];
shared = __a[1];
}
return '(' + this.first.compile(o) + ') && (' + shared.compile(o) + ' ' + this.operator + ' ' + this.second.compile(o) + ')';
},
compile_assignment: function compile_assignment(o) {
var __a, first, second;
__a = [this.first.compile(o), this.second.compile(o)];
first = __a[0];
second = __a[1];
if (this.first.unwrap.match(IDENTIFIER)) {
o.scope.find(first);
}
if (this.operator === '?=') {
return first + ' = ' + ExistenceNode.compile_test(o, this.first) + ' ? ' + first + ' : ' + second;
}
return first + ' = ' + first + ' ' + this.operator.substr(0, 2) + ' ' + second;
},
compile_existence: function compile_existence(o) {
var __a, first, second;
__a = [this.first.compile(o), this.second.compile(o)];
first = __a[0];
second = __a[1];
return ExistenceNode.compile_test(o, this.first) + ' ? ' + first + ' : ' + second;
},
compile_unary: function compile_unary(o) {
var parts, space;
space = this.PREFIX_OPERATORS.indexOf(this.operator) >= 0 ? ' ' : '';
parts = [this.operator, space, this.first.compile(o)];
if (this.flip) {
parts = parts.reverse();
}
return parts.join('');
} }
})); }));
})(); })();

View File

@ -783,9 +783,8 @@ module CoffeeScript
def compile_assignment(o) def compile_assignment(o)
first, second = @first.compile(o), @second.compile(o) first, second = @first.compile(o), @second.compile(o)
o[:scope].find(first) if @first.unwrap.is_a?(Value) o[:scope].find(first) if @first.unwrap.is_a?(Value)
sym = @operator[0..1]
return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?=' return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?='
"#{first} = #{first} #{sym} #{second}" "#{first} = #{first} #{@operator[0..1]} #{second}"
end end
def compile_existence(o) def compile_existence(o)

View File

@ -7,17 +7,17 @@
// Detect functions: [ // Detect functions: [
unwrap = /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/; unwrap = /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/;
// Quickie DSL for Jison access. // Quickie DSL for Jison access.
o = function o(pattern_string, func) { o = function o(pattern_string, func, options) {
var match; var match;
if (func) { if (func) {
func = (match = (func + "").match(unwrap)) ? match[1] : '(' + func + '())'; func = (match = (func + "").match(unwrap)) ? match[1] : '(' + func + '())';
return [pattern_string, '$$ = ' + func + ';']; return [pattern_string, '$$ = ' + func + ';', options];
} else { } else {
return [pattern_string, '$$ = $1;']; return [pattern_string, '$$ = $1;', options];
} }
}; };
// Precedence =========================================================== // Precedence ===========================================================
operators = [["left", '?'], ["right", 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY'], ["right", 'THROW', 'FOR', 'NEW', 'SUPER'], ["left", 'EXTENDS'], ["left", '||=', '&&=', '?='], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']]; operators = [["left", '?'], ["right", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY'], ["right", 'THROW', 'FOR', 'NEW', 'SUPER'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']];
// Grammar ============================================================== // Grammar ==============================================================
grammar = { grammar = {
// All parsing will end in this rule, being the trunk of the AST. // All parsing will end in this rule, being the trunk of the AST.
@ -106,45 +106,70 @@
return new CommentNode(yytext); return new CommentNode(yytext);
}) })
], ],
// // Arithmetic and logical operators
// # Arithmetic and logical operators // For Ruby's Operator precedence, see: [
// # For Ruby's Operator precedence, see: [ // https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
// # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html Operation: [o("! Expression", function() {
// Operation: [ return new OpNode('!', $2);
// o "! Expression", -> new OpNode($1, $2) }), o("!! Expression", function() {
// o "!! Expression", -> new OpNode($1, $2) return new OpNode('!!', $2);
// o "- Expression", -> new OpNode($1, $2) }), o("- Expression", (function() {
// o "+ Expression", -> new OpNode($1, $2) return new OpNode('-', $2);
// o "NOT Expression", -> new OpNode($1, $2) }), {
// o "~ Expression", -> new OpNode($1, $2) prec: 'UMINUS'
// o "-- Expression", -> new OpNode($1, $2) }), o("+ Expression", (function() {
// o "++ Expression", -> new OpNode($1, $2) return new OpNode('+', $2);
// o "DELETE Expression", -> new OpNode($1, $2) }), {
// o "TYPEOF Expression", -> new OpNode($1, $2) prec: 'UPLUS'
// o "Expression --", -> new OpNode($2, $1, null, true) }), o("NOT Expression", function() {
// o "Expression ++", -> new OpNode($2, $1, null, true) return new OpNode('not', $2);
// }), o("~ Expression", function() {
// o "Expression * Expression", -> new OpNode($2, $1, $3) return new OpNode('~', $2);
// o "Expression / Expression", -> new OpNode($2, $1, $3) }), o("-- Expression", function() {
// o "Expression % Expression", -> new OpNode($2, $1, $3) return new OpNode('--', $2);
// }), o("++ Expression", function() {
// o "Expression + Expression", -> new OpNode($2, $1, $3) return new OpNode('++', $2);
// o "Expression - Expression", -> new OpNode($2, $1, $3) }), o("DELETE Expression", function() {
// return new OpNode('delete', $2);
// o "Expression << Expression", -> new OpNode($2, $1, $3) }), o("TYPEOF Expression", function() {
// o "Expression >> Expression", -> new OpNode($2, $1, $3) return new OpNode('typeof', $2);
// o "Expression >>> Expression", -> new OpNode($2, $1, $3) }), o("Expression --", function() {
// return new OpNode('--', $1, null, true);
// o "Expression & Expression", -> new OpNode($2, $1, $3) }), o("Expression ++", function() {
// o "Expression | Expression", -> new OpNode($2, $1, $3) return new OpNode('++', $1, null, true);
// o "Expression ^ Expression", -> new OpNode($2, $1, $3) }), o("Expression * Expression", function() {
// return new OpNode('*', $1, $3);
// o "Expression <= Expression", -> new OpNode($2, $1, $3) }), o("Expression / Expression", function() {
// o "Expression < Expression", -> new OpNode($2, $1, $3) return new OpNode('/', $1, $3);
// o "Expression > Expression", -> new OpNode($2, $1, $3) }), o("Expression % Expression", function() {
// o "Expression >= Expression", -> new OpNode($2, $1, $3) return new OpNode('%', $1, $3);
// }), o("Expression + Expression", function() {
// o "Expression == Expression", -> new OpNode($2, $1, $3) return new OpNode('+', $1, $3);
}), o("Expression - Expression", function() {
return new OpNode('-', $1, $3);
}), o("Expression << Expression", function() {
return new OpNode('<<', $1, $3);
}), o("Expression >> Expression", function() {
return new OpNode('>>', $1, $3);
}), o("Expression >>> Expression", function() {
return new OpNode('>>>', $1, $3);
}), o("Expression & Expression", function() {
return new OpNode('&', $1, $3);
}), o("Expression | Expression", function() {
return new OpNode('|', $1, $3);
}), o("Expression ^ Expression", function() {
return new OpNode('^', $1, $3);
}), o("Expression <= Expression", function() {
return new OpNode('<=', $1, $3);
}), o("Expression < Expression", function() {
return new OpNode('<', $1, $3);
}), o("Expression > Expression", function() {
return new OpNode('>', $1, $3);
}), o("Expression >= Expression", function() {
return new OpNode('>=', $1, $3);
}), o("Expression == Expression", function() {
return new OpNode('==', $1, $3);
}),
// o "Expression != Expression", -> new OpNode($2, $1, $3) // o "Expression != Expression", -> new OpNode($2, $1, $3)
// o "Expression IS Expression", -> new OpNode($2, $1, $3) // o "Expression IS Expression", -> new OpNode($2, $1, $3)
// o "Expression ISNT Expression", -> new OpNode($2, $1, $3) // o "Expression ISNT Expression", -> new OpNode($2, $1, $3)
@ -166,7 +191,7 @@
// //
// o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3) // o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3)
// o "Expression IN Expression", -> new OpNode($2, $1, $3) // o "Expression IN Expression", -> new OpNode($2, $1, $3)
// ] ],
// The existence operator. // The existence operator.
Existence: [o("Expression ?", function() { Existence: [o("Expression ?", function() {
return new ExistenceNode($1); return new ExistenceNode($1);

View File

@ -44,6 +44,9 @@ exports.Expressions.wrap : (values) -> @values: values
TAB: ' ' TAB: ' '
TRAILING_WHITESPACE: /\s+$/g TRAILING_WHITESPACE: /\s+$/g
# Keep the identifier regex in sync with the Lexer.
IDENTIFIER: /^[a-zA-Z$_](\w|\$)*$/
# Flatten nested arrays recursively. # Flatten nested arrays recursively.
flatten: (list) -> flatten: (list) ->
memo: [] memo: []
@ -587,8 +590,6 @@ ClosureNode: exports.ClosureNode: {
# Setting the value of a local variable, or the value of an object property. # Setting the value of a local variable, or the value of an object property.
AssignNode: exports.AssignNode: inherit Node, { AssignNode: exports.AssignNode: inherit Node, {
# Keep the identifier regex in sync with the Lexer.
IDENTIFIER: /^([a-zA-Z$_](\w|\$)*)/
PROTO_ASSIGN: /^(\S+)\.prototype/ PROTO_ASSIGN: /^(\S+)\.prototype/
LEADING_DOT: /^\.(prototype\.)?/ LEADING_DOT: /^\.(prototype\.)?/
@ -616,7 +617,7 @@ AssignNode: exports.AssignNode: inherit Node, {
match: name.match(@PROTO_ASSIGN) match: name.match(@PROTO_ASSIGN)
proto: match and match[1] proto: match and match[1]
if @value instanceof CodeNode if @value instanceof CodeNode
@value.name: last if last.match(@IDENTIFIER) @value.name: last if last.match(IDENTIFIER)
@value.proto: proto if proto @value.proto: proto if proto
return name + ': ' + @value.compile(o) if @context is 'object' return name + ': ' + @value.compile(o) if @context is 'object'
o.scope.find(name) unless @is_value() and @variable.has_properties() o.scope.find(name) unless @is_value() and @variable.has_properties()
@ -773,8 +774,34 @@ OpNode: exports.OpNode: inherit Node, {
@CHAINABLE.indexOf(@operator) >= 0 @CHAINABLE.indexOf(@operator) >= 0
compile_node: (o) -> compile_node: (o) ->
return @compile_chain(o) if @is_chainable() and (@first.unwrap() instanceof OpNode) and @first.unwrap().is_chainable() return @compile_chain(o) if @is_chainable() and @first.unwrap() instanceof OpNode and @first.unwrap().is_chainable()
return @compile_assignment(o) if @ASSIGNMENT.indexOf(@operator) >= 0
return @compile_unary(o) if @is_unary()
return @compile_existence(o) if @operator is '?'
@first.compile(o) + ' ' + @operator + ' ' + @second.compile(o)
# Mimic Python's chained comparisons. See:
# http://docs.python.org/reference/expressions.html#notin
compile_chain: (o) ->
shared: @first.unwrap().second
[@first.second, shared]: shared.compile_reference(o) if shared instanceof CallNode
'(' + @first.compile(o) + ') && (' + shared.compile(o) + ' ' + @operator + ' ' + @second.compile(o) + ')'
compile_assignment: (o) ->
[first, second]: [@first.compile(o), @second.compile(o)]
o.scope.find(first) if @first.unwrap.match(IDENTIFIER)
return first + ' = ' + ExistenceNode.compile_test(o, @first) + ' ? ' + first + ' : ' + second if @operator is '?='
first + ' = ' + first + ' ' + @operator.substr(0, 2) + ' ' + second
compile_existence: (o) ->
[first, second]: [@first.compile(o), @second.compile(o)]
ExistenceNode.compile_test(o, @first) + ' ? ' + first + ' : ' + second
compile_unary: (o) ->
space: if @PREFIX_OPERATORS.indexOf(@operator) >= 0 then ' ' else ''
parts: [@operator, space, @first.compile(o)]
parts: parts.reverse() if @flip
parts.join('')
} }

View File

@ -7,18 +7,18 @@ process.mixin require './nodes'
unwrap: /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/ unwrap: /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/
# Quickie DSL for Jison access. # Quickie DSL for Jison access.
o: (pattern_string, func) -> o: (pattern_string, func, options) ->
if func if func
func: if match: (func + "").match(unwrap) then match[1] else '(' + func + '())' func: if match: (func + "").match(unwrap) then match[1] else '(' + func + '())'
[pattern_string, '$$ = ' + func + ';'] [pattern_string, '$$ = ' + func + ';', options]
else else
[pattern_string, '$$ = $1;'] [pattern_string, '$$ = $1;', options]
# Precedence =========================================================== # Precedence ===========================================================
operators: [ operators: [
["left", '?'] ["left", '?']
["right", 'NOT', '!', '!!', '~', '++', '--'] ["right", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--']
["left", '*', '/', '%'] ["left", '*', '/', '%']
["left", '+', '-'] ["left", '+', '-']
["left", '<<', '>>', '>>>'] ["left", '<<', '>>', '>>>']
@ -27,14 +27,13 @@ operators: [
["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'] ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF']
["right", '==', '!=', 'IS', 'ISNT'] ["right", '==', '!=', 'IS', 'ISNT']
["left", '&&', '||', 'AND', 'OR'] ["left", '&&', '||', 'AND', 'OR']
["right", '-=', '+=', '/=', '*=', '%='] ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=']
["left", '.'] ["left", '.']
["right", 'INDENT'] ["right", 'INDENT']
["left", 'OUTDENT'] ["left", 'OUTDENT']
["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY'] ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY']
["right", 'THROW', 'FOR', 'NEW', 'SUPER'] ["right", 'THROW', 'FOR', 'NEW', 'SUPER']
["left", 'EXTENDS'] ["left", 'EXTENDS']
["left", '||=', '&&=', '?=']
["right", 'ASSIGN', 'RETURN'] ["right", 'ASSIGN', 'RETURN']
["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE'] ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']
] ]
@ -133,45 +132,45 @@ grammar: {
Comment: [ Comment: [
o "COMMENT", -> new CommentNode(yytext) o "COMMENT", -> new CommentNode(yytext)
] ]
#
# # Arithmetic and logical operators # Arithmetic and logical operators
# # For Ruby's Operator precedence, see: [ # For Ruby's Operator precedence, see: [
# # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
# Operation: [ Operation: [
# o "! Expression", -> new OpNode($1, $2) o "! Expression", -> new OpNode('!', $2)
# o "!! Expression", -> new OpNode($1, $2) o "!! Expression", -> new OpNode('!!', $2)
# o "- Expression", -> new OpNode($1, $2) o "- Expression", (-> new OpNode('-', $2)), {prec: 'UMINUS'}
# o "+ Expression", -> new OpNode($1, $2) o "+ Expression", (-> new OpNode('+', $2)), {prec: 'UPLUS'}
# o "NOT Expression", -> new OpNode($1, $2) o "NOT Expression", -> new OpNode('not', $2)
# o "~ Expression", -> new OpNode($1, $2) o "~ Expression", -> new OpNode('~', $2)
# o "-- Expression", -> new OpNode($1, $2) o "-- Expression", -> new OpNode('--', $2)
# o "++ Expression", -> new OpNode($1, $2) o "++ Expression", -> new OpNode('++', $2)
# o "DELETE Expression", -> new OpNode($1, $2) o "DELETE Expression", -> new OpNode('delete', $2)
# o "TYPEOF Expression", -> new OpNode($1, $2) o "TYPEOF Expression", -> new OpNode('typeof', $2)
# o "Expression --", -> new OpNode($2, $1, null, true) o "Expression --", -> new OpNode('--', $1, null, true)
# o "Expression ++", -> new OpNode($2, $1, null, true) o "Expression ++", -> new OpNode('++', $1, null, true)
#
# o "Expression * Expression", -> new OpNode($2, $1, $3) o "Expression * Expression", -> new OpNode('*', $1, $3)
# o "Expression / Expression", -> new OpNode($2, $1, $3) o "Expression / Expression", -> new OpNode('/', $1, $3)
# o "Expression % Expression", -> new OpNode($2, $1, $3) o "Expression % Expression", -> new OpNode('%', $1, $3)
#
# o "Expression + Expression", -> new OpNode($2, $1, $3) o "Expression + Expression", -> new OpNode('+', $1, $3)
# o "Expression - Expression", -> new OpNode($2, $1, $3) o "Expression - Expression", -> new OpNode('-', $1, $3)
#
# o "Expression << Expression", -> new OpNode($2, $1, $3) o "Expression << Expression", -> new OpNode('<<', $1, $3)
# o "Expression >> Expression", -> new OpNode($2, $1, $3) o "Expression >> Expression", -> new OpNode('>>', $1, $3)
# o "Expression >>> Expression", -> new OpNode($2, $1, $3) o "Expression >>> Expression", -> new OpNode('>>>', $1, $3)
#
# o "Expression & Expression", -> new OpNode($2, $1, $3) o "Expression & Expression", -> new OpNode('&', $1, $3)
# o "Expression | Expression", -> new OpNode($2, $1, $3) o "Expression | Expression", -> new OpNode('|', $1, $3)
# o "Expression ^ Expression", -> new OpNode($2, $1, $3) o "Expression ^ Expression", -> new OpNode('^', $1, $3)
#
# o "Expression <= Expression", -> new OpNode($2, $1, $3) o "Expression <= Expression", -> new OpNode('<=', $1, $3)
# o "Expression < Expression", -> new OpNode($2, $1, $3) o "Expression < Expression", -> new OpNode('<', $1, $3)
# o "Expression > Expression", -> new OpNode($2, $1, $3) o "Expression > Expression", -> new OpNode('>', $1, $3)
# o "Expression >= Expression", -> new OpNode($2, $1, $3) o "Expression >= Expression", -> new OpNode('>=', $1, $3)
#
# o "Expression == Expression", -> new OpNode($2, $1, $3) o "Expression == Expression", -> new OpNode('==', $1, $3)
# o "Expression != Expression", -> new OpNode($2, $1, $3) # o "Expression != Expression", -> new OpNode($2, $1, $3)
# o "Expression IS Expression", -> new OpNode($2, $1, $3) # o "Expression IS Expression", -> new OpNode($2, $1, $3)
# o "Expression ISNT Expression", -> new OpNode($2, $1, $3) # o "Expression ISNT Expression", -> new OpNode($2, $1, $3)
@ -193,7 +192,7 @@ grammar: {
# #
# o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3) # o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3)
# o "Expression IN Expression", -> new OpNode($2, $1, $3) # o "Expression IN Expression", -> new OpNode($2, $1, $3)
# ] ]
# The existence operator. # The existence operator.
Existence: [ Existence: [