Self-compiler: object literals.
This commit is contained in:
parent
91a7102f11
commit
001c915c21
|
@ -8,7 +8,7 @@
|
|||
// Constants ============================================================
|
||||
// The list of keywords passed verbatim to the parser.
|
||||
KEYWORDS = ["if", "else", "then", "unless", "true", "false", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "new", "return", "arguments", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "of", "by", "where", "while", "delete", "instanceof", "typeof", "switch", "when", "super", "extends"];
|
||||
// Token matching regexes.
|
||||
// Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
|
||||
IDENTIFIER = /^([a-zA-Z$_](\w|\$)*)/;
|
||||
NUMBER = /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i;
|
||||
STRING = /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
(function(){
|
||||
var AccessorNode, CallNode, CommentNode, Expressions, ExtendsNode, IndexNode, LiteralNode, Node, RangeNode, ReturnNode, SliceNode, TAB, TRAILING_WHITESPACE, ThisNode, ValueNode, any, compact, del, dup, flatten, inherit, merge, statement;
|
||||
var AccessorNode, AssignNode, CallNode, CommentNode, Expressions, ExtendsNode, IndexNode, LiteralNode, Node, ObjectNode, RangeNode, ReturnNode, SliceNode, TAB, TRAILING_WHITESPACE, ThisNode, ValueNode, any, compact, del, dup, flatten, inherit, merge, statement;
|
||||
var __hasProp = Object.prototype.hasOwnProperty;
|
||||
process.mixin(require('./scope'));
|
||||
// The abstract base class for all CoffeeScript nodes.
|
||||
|
@ -341,8 +341,7 @@
|
|||
// A collection of nodes, each one representing an expression.
|
||||
Expressions = (exports.Expressions = inherit(Node, {
|
||||
constructor: function constructor(nodes) {
|
||||
this.expressions = flatten(nodes);
|
||||
this.children = this.expressions;
|
||||
this.children = (this.expressions = flatten(nodes));
|
||||
return this;
|
||||
},
|
||||
// Tack an expression on to the end of this expression list.
|
||||
|
@ -451,8 +450,7 @@
|
|||
// JavaScript without translation, eg.: strings, numbers, true, false, null...
|
||||
LiteralNode = (exports.LiteralNode = inherit(Node, {
|
||||
constructor: function constructor(value) {
|
||||
this.value = value;
|
||||
this.children = [value];
|
||||
this.children = [(this.value = value)];
|
||||
return this;
|
||||
},
|
||||
// Break and continue must be treated as statements -- they lose their meaning
|
||||
|
@ -471,8 +469,7 @@
|
|||
// Return an expression, or wrap it in a closure and return it.
|
||||
ReturnNode = (exports.ReturnNode = inherit(Node, {
|
||||
constructor: function constructor(expression) {
|
||||
this.expression = expression;
|
||||
this.children = [expression];
|
||||
this.children = [(this.expression = expression)];
|
||||
return this;
|
||||
},
|
||||
compile_node: function compile_node(o) {
|
||||
|
@ -489,9 +486,7 @@
|
|||
ValueNode = (exports.ValueNode = inherit(Node, {
|
||||
SOAK: " == undefined ? undefined : ",
|
||||
constructor: function constructor(base, properties) {
|
||||
this.base = base;
|
||||
this.properties = flatten(properties || []);
|
||||
this.children = flatten(this.base, this.properties);
|
||||
this.children = flatten((this.base = base), (this.properties = (properties || [])));
|
||||
return this;
|
||||
},
|
||||
push: function push(prop) {
|
||||
|
@ -572,9 +567,7 @@
|
|||
// calls against the prototype's function of the same name.
|
||||
CallNode = (exports.CallNode = inherit(Node, {
|
||||
constructor: function constructor(variable, args) {
|
||||
this.variable = variable;
|
||||
this.args = args || [];
|
||||
this.children = flatten([this.variable, this.args]);
|
||||
this.children = flatten([(this.variable = variable), (this.args = (args || []))]);
|
||||
this.prefix = '';
|
||||
return this;
|
||||
},
|
||||
|
@ -648,9 +641,7 @@
|
|||
// After goog.inherits from the Closure Library.
|
||||
ExtendsNode = (exports.ExtendsNode = inherit(Node, {
|
||||
constructor: function constructor(child, parent) {
|
||||
this.child = child;
|
||||
this.parent = parent;
|
||||
this.children = [child, parent];
|
||||
this.children = [(this.child = child), (this.parent = parent)];
|
||||
return this;
|
||||
},
|
||||
// Hooking one constructor into another's prototype chain.
|
||||
|
@ -667,8 +658,7 @@
|
|||
// an accessor into the object's prototype.
|
||||
AccessorNode = (exports.AccessorNode = inherit(Node, {
|
||||
constructor: function constructor(name, tag) {
|
||||
this.name = name;
|
||||
this.children = [this.name];
|
||||
this.children = [(this.name = name)];
|
||||
this.prototype = tag === 'prototype';
|
||||
this.soak = tag === 'soak';
|
||||
return this;
|
||||
|
@ -701,9 +691,7 @@
|
|||
// or to specify a range for list comprehensions.
|
||||
RangeNode = (exports.RangeNode = inherit(Node, {
|
||||
constructor: function constructor(from, to, exclusive) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.children = [from, to];
|
||||
this.children = [(this.from = from), (this.to = to)];
|
||||
this.exclusive = !!exclusive;
|
||||
return this;
|
||||
},
|
||||
|
@ -754,4 +742,143 @@
|
|||
return ".slice(" + from + ', ' + to + plus_part + ')';
|
||||
}
|
||||
}));
|
||||
// An object literal.
|
||||
ObjectNode = (exports.ObjectNode = inherit(Node, {
|
||||
constructor: function constructor(props) {
|
||||
this.objects = (this.properties = props || []);
|
||||
return this;
|
||||
},
|
||||
// All the mucking about with commas is to make sure that CommentNodes and
|
||||
// AssignNodes get interleaved correctly, with no trailing commas or
|
||||
// commas affixed to comments. TODO: Extract this and add it to ArrayNode.
|
||||
compile_node: function compile_node(o) {
|
||||
var __a, __b, __c, __d, __e, i, indent, join, last_noncom, non_comments, prop, props;
|
||||
o.indent = this.idt(1);
|
||||
non_comments = (function() {
|
||||
__a = []; __b = this.properties;
|
||||
for (__c = 0; __c < __b.length; __c++) {
|
||||
prop = __b[__c];
|
||||
if (!(prop instanceof CommentNode)) {
|
||||
__a.push(prop);
|
||||
}
|
||||
}
|
||||
return __a;
|
||||
}).call(this);
|
||||
last_noncom = non_comments[non_comments.length - 1];
|
||||
props = (function() {
|
||||
__d = []; __e = this.properties;
|
||||
for (i = 0; i < __e.length; i++) {
|
||||
prop = __e[i];
|
||||
__d.push((function() {
|
||||
join = ",\n";
|
||||
if (prop === last_noncom || prop instanceof CommentNode) {
|
||||
join = "\n";
|
||||
}
|
||||
if (i === non_comments.length - 1) {
|
||||
join = '';
|
||||
}
|
||||
indent = prop instanceof CommentNode ? '' : this.idt(1);
|
||||
return indent + prop.compile(o) + join;
|
||||
}).call(this));
|
||||
}
|
||||
return __d;
|
||||
}).call(this);
|
||||
return '{\n' + props.join('') + '\n' + this.idt() + '}';
|
||||
}
|
||||
}));
|
||||
// Setting the value of a local variable, or the value of an object property.
|
||||
AssignNode = (exports.AssignNode = inherit(Node, {
|
||||
// Keep the identifier regex in sync with the Lexer.
|
||||
IDENTIFIER: /^([a-zA-Z$_](\w|\$)*)/,
|
||||
PROTO_ASSIGN: /^(\S+)\.prototype/,
|
||||
LEADING_DOT: /^\.(prototype\.)?/,
|
||||
constructor: function constructor(variable, value, context) {
|
||||
this.children = [(this.variable = variable), (this.value = value)];
|
||||
this.context = context;
|
||||
return this;
|
||||
},
|
||||
top_sensitive: function top_sensitive() {
|
||||
return true;
|
||||
},
|
||||
is_value: function is_value() {
|
||||
return this.variable instanceof ValueNode;
|
||||
},
|
||||
is_statement: function is_statement() {
|
||||
return this.is_value() && (this.variable.is_array() || this.variable.is_object());
|
||||
},
|
||||
compile_node: function compile_node(o) {
|
||||
var last, match, name, proto, stmt, top, val;
|
||||
top = del(o, 'top');
|
||||
if (this.is_statement()) {
|
||||
return this.compile_pattern_match(o);
|
||||
}
|
||||
if (this.is_value() && this.variable.is_splice()) {
|
||||
return this.compile_splice(o);
|
||||
}
|
||||
stmt = del(o, 'as_statement');
|
||||
name = this.variable.compile(o);
|
||||
last = this.is_value() ? this.variable.last.replace(this.LEADING_DOT, '') : name;
|
||||
match = name.match(this.PROTO_ASSIGN);
|
||||
proto = match && match[1];
|
||||
if (this.value instanceof CodeNode) {
|
||||
if (last.match(this.IDENTIFIER)) {
|
||||
this.value.name = last;
|
||||
}
|
||||
if (proto) {
|
||||
this.value.proto = proto;
|
||||
}
|
||||
}
|
||||
if (this.context === 'object') {
|
||||
return name + ': ' + this.value.compile(o);
|
||||
}
|
||||
if (!(this.is_value() && this.variable.has_properties())) {
|
||||
o.scope.find(name);
|
||||
}
|
||||
val = name + ' = ' + this.value.compile(o);
|
||||
if (stmt) {
|
||||
return this.idt() + val + ';';
|
||||
}
|
||||
if (!top || o.returns) {
|
||||
val = '(' + val + ')';
|
||||
}
|
||||
if (o.returns) {
|
||||
val = this.idt() + 'return ' + val;
|
||||
}
|
||||
return val;
|
||||
},
|
||||
// Implementation of recursive pattern matching, when assigning array or
|
||||
// object literals to a value. Peeks at their properties to assign inner names.
|
||||
// See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
|
||||
compile_pattern_match: function compile_pattern_match(o) {
|
||||
var __a, __b, access_class, assigns, i, obj, val, val_var;
|
||||
val_var = o.scope.free_variable();
|
||||
assigns = [this.idt() + val_var + ' = ' + this.value.compile(o) + ';'];
|
||||
o.top = true;
|
||||
o.as_statement = true;
|
||||
__a = this.variable.base.objects;
|
||||
for (i = 0; i < __a.length; i++) {
|
||||
obj = __a[i];
|
||||
if (this.variable.is_object()) {
|
||||
__b = [obj.value, obj.variable.base];
|
||||
obj = __b[0];
|
||||
i = __b[1];
|
||||
}
|
||||
access_class = this.variable.is_array() ? IndexNode : AccessorNode;
|
||||
obj instanceof SplatNode ? (val = new LiteralNode(obj.compile_value(o, val_var, this.variable.base.objects.indexOf(obj)))) : (val = new ValueNode(val_var, [new access_class(new LiteralNode(i))]));
|
||||
assigns.push(new AssignNode(obj, val).compile(o));
|
||||
}
|
||||
return assigns.join("\n");
|
||||
},
|
||||
compile_splice: function compile_splice(o) {
|
||||
var from, name, plus, range, to;
|
||||
name = this.variable.compile(merge(o, {
|
||||
only_first: true
|
||||
}));
|
||||
range = this.variable.properties.last.range;
|
||||
plus = range.exclusive ? '' : ' + 1';
|
||||
from = range.from.compile(o);
|
||||
to = range.to.compile(o) + ' - ' + from + plus;
|
||||
return name + '.splice.apply(' + name + ', [' + from + ', ' + to + '].concat(' + this.value.compile(o) + '))';
|
||||
}
|
||||
}));
|
||||
})();
|
|
@ -499,6 +499,75 @@ module CoffeeScript
|
|||
end
|
||||
end
|
||||
|
||||
# An object literal.
|
||||
class ObjectNode < Node
|
||||
children :properties
|
||||
alias_method :objects, :properties
|
||||
|
||||
def initialize(properties = [])
|
||||
@properties = properties
|
||||
end
|
||||
|
||||
# All the mucking about with commas is to make sure that CommentNodes and
|
||||
# AssignNodes get interleaved correctly, with no trailing commas or
|
||||
# commas affixed to comments. TODO: Extract this and add it to ArrayNode.
|
||||
def compile_node(o)
|
||||
o[:indent] = idt(1)
|
||||
joins = Hash.new("\n")
|
||||
non_comments = @properties.select {|p| !p.is_a?(CommentNode) }
|
||||
non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" }
|
||||
props = @properties.map { |prop|
|
||||
join = joins[prop]
|
||||
join = '' if prop == @properties.last
|
||||
indent = prop.is_a?(CommentNode) ? '' : idt(1)
|
||||
"#{indent}#{prop.compile(o)}#{join}"
|
||||
}.join('')
|
||||
write("{\n#{props}\n#{idt}}")
|
||||
end
|
||||
end
|
||||
|
||||
# An array literal.
|
||||
class ArrayNode < Node
|
||||
children :objects
|
||||
|
||||
def initialize(objects=[])
|
||||
@objects = objects
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
o[:indent] = idt(1)
|
||||
objects = @objects.map { |obj|
|
||||
code = obj.compile(o)
|
||||
obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" :
|
||||
obj == @objects.last ? code : "#{code}, "
|
||||
}.join('')
|
||||
ending = objects.include?("\n") ? "\n#{idt}]" : ']'
|
||||
write("[#{objects}#{ending}")
|
||||
end
|
||||
end
|
||||
|
||||
# A faux-node that is never created by the grammar, but is used during
|
||||
# code generation to generate a quick "array.push(value)" tree of nodes.
|
||||
class PushNode
|
||||
def self.wrap(array, expressions)
|
||||
expr = expressions.unwrap
|
||||
return expressions if expr.statement_only? || expr.contains? {|n| n.statement_only? }
|
||||
Expressions.wrap(CallNode.new(
|
||||
ValueNode.new(LiteralNode.new(array), [AccessorNode.new(Value.new('push'))]),
|
||||
[expr]
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
# A faux-node used to wrap an expressions body in a closure.
|
||||
class ClosureNode
|
||||
def self.wrap(expressions, statement=false)
|
||||
func = ParentheticalNode.new(CodeNode.new([], Expressions.wrap(expressions)))
|
||||
call = CallNode.new(ValueNode.new(func, AccessorNode.new(Value.new('call'))), [Value.new('this')])
|
||||
statement ? Expressions.wrap(call) : call
|
||||
end
|
||||
end
|
||||
|
||||
# Setting the value of a local variable, or the value of an object property.
|
||||
class AssignNode < Node
|
||||
top_sensitive
|
||||
|
@ -511,14 +580,22 @@ module CoffeeScript
|
|||
@variable, @value, @context = variable, value, context
|
||||
end
|
||||
|
||||
def value?
|
||||
@variable.is_a?(ValueNode)
|
||||
end
|
||||
|
||||
def statement?
|
||||
value? && (@variable.array? || @variable.object?)
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
top = o.delete(:top)
|
||||
return compile_pattern_match(o) if statement?
|
||||
return compile_splice(o) if value? && @variable.splice?
|
||||
stmt = o.delete(:as_statement)
|
||||
name = @variable.compile(o)
|
||||
last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
|
||||
proto = name[PROTO_ASSIGN, 1]
|
||||
top = o.delete(:top)
|
||||
return compile_pattern_match(o) if statement?
|
||||
return compile_splice(o) if value? && @variable.splice?
|
||||
stmt = o.delete(:as_statement)
|
||||
name = @variable.compile(o)
|
||||
last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
|
||||
proto = name[PROTO_ASSIGN, 1]
|
||||
if @value.is_a?(CodeNode)
|
||||
@value.name = last if last.match(Lexer::IDENTIFIER)
|
||||
@value.proto = proto if proto
|
||||
|
@ -532,14 +609,6 @@ module CoffeeScript
|
|||
write(val)
|
||||
end
|
||||
|
||||
def value?
|
||||
@variable.is_a?(ValueNode)
|
||||
end
|
||||
|
||||
def statement?
|
||||
value? && (@variable.array? || @variable.object?)
|
||||
end
|
||||
|
||||
# Implementation of recursive pattern matching, when assigning array or
|
||||
# object literals to a value. Peeks at their properties to assign inner names.
|
||||
# See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
|
||||
|
@ -710,75 +779,6 @@ module CoffeeScript
|
|||
|
||||
end
|
||||
|
||||
# An object literal.
|
||||
class ObjectNode < Node
|
||||
children :properties
|
||||
alias_method :objects, :properties
|
||||
|
||||
def initialize(properties = [])
|
||||
@properties = properties
|
||||
end
|
||||
|
||||
# All the mucking about with commas is to make sure that CommentNodes and
|
||||
# AssignNodes get interleaved correctly, with no trailing commas or
|
||||
# commas affixed to comments. TODO: Extract this and add it to ArrayNode.
|
||||
def compile_node(o)
|
||||
o[:indent] = idt(1)
|
||||
joins = Hash.new("\n")
|
||||
non_comments = @properties.select {|p| !p.is_a?(CommentNode) }
|
||||
non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" }
|
||||
props = @properties.map { |prop|
|
||||
join = joins[prop]
|
||||
join = '' if prop == @properties.last
|
||||
indent = prop.is_a?(CommentNode) ? '' : idt(1)
|
||||
"#{indent}#{prop.compile(o)}#{join}"
|
||||
}.join('')
|
||||
write("{\n#{props}\n#{idt}}")
|
||||
end
|
||||
end
|
||||
|
||||
# An array literal.
|
||||
class ArrayNode < Node
|
||||
children :objects
|
||||
|
||||
def initialize(objects=[])
|
||||
@objects = objects
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
o[:indent] = idt(1)
|
||||
objects = @objects.map { |obj|
|
||||
code = obj.compile(o)
|
||||
obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" :
|
||||
obj == @objects.last ? code : "#{code}, "
|
||||
}.join('')
|
||||
ending = objects.include?("\n") ? "\n#{idt}]" : ']'
|
||||
write("[#{objects}#{ending}")
|
||||
end
|
||||
end
|
||||
|
||||
# A faux-node that is never created by the grammar, but is used during
|
||||
# code generation to generate a quick "array.push(value)" tree of nodes.
|
||||
class PushNode
|
||||
def self.wrap(array, expressions)
|
||||
expr = expressions.unwrap
|
||||
return expressions if expr.statement_only? || expr.contains? {|n| n.statement_only? }
|
||||
Expressions.wrap(CallNode.new(
|
||||
ValueNode.new(LiteralNode.new(array), [AccessorNode.new(Value.new('push'))]),
|
||||
[expr]
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
# A faux-node used to wrap an expressions body in a closure.
|
||||
class ClosureNode
|
||||
def self.wrap(expressions, statement=false)
|
||||
func = ParentheticalNode.new(CodeNode.new([], Expressions.wrap(expressions)))
|
||||
call = CallNode.new(ValueNode.new(func, AccessorNode.new(Value.new('call'))), [Value.new('this')])
|
||||
statement ? Expressions.wrap(call) : call
|
||||
end
|
||||
end
|
||||
|
||||
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
|
||||
# it, all other loops can be manufactured.
|
||||
class WhileNode < Node
|
||||
|
|
|
@ -45,11 +45,19 @@
|
|||
return new Expressions();
|
||||
})
|
||||
],
|
||||
// All hard-coded values. These can be printed straight to JavaScript.
|
||||
Literal: [o("NUMBER", function() {
|
||||
Identifier: [o("IDENTIFIER", function() {
|
||||
return new LiteralNode(yytext);
|
||||
})
|
||||
],
|
||||
AlphaNumeric: [o("NUMBER", function() {
|
||||
return new LiteralNode(yytext);
|
||||
}), o("STRING", function() {
|
||||
return new LiteralNode(yytext);
|
||||
})
|
||||
],
|
||||
// All hard-coded values. These can be printed straight to JavaScript.
|
||||
Literal: [o("AlphaNumeric", function() {
|
||||
return $1;
|
||||
}), o("JS", function() {
|
||||
return new LiteralNode(yytext);
|
||||
}), o("REGEX", function() {
|
||||
|
@ -80,12 +88,10 @@
|
|||
})
|
||||
],
|
||||
// Assignment within an object literal (can be quoted).
|
||||
AssignObj: [o("IDENTIFIER ASSIGN Expression", function() {
|
||||
return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object');
|
||||
}), o("STRING ASSIGN Expression", function() {
|
||||
return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object');
|
||||
}), o("NUMBER ASSIGN Expression", function() {
|
||||
return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object');
|
||||
AssignObj: [o("Identifier ASSIGN Expression", function() {
|
||||
return new AssignNode(new ValueNode($1), $3, 'object');
|
||||
}), o("AlphaNumeric ASSIGN Expression", function() {
|
||||
return new AssignNode(new ValueNode($1), $3, 'object');
|
||||
}), o("Comment")
|
||||
],
|
||||
// A return statement.
|
||||
|
@ -200,8 +206,8 @@
|
|||
})
|
||||
],
|
||||
// Expressions that can be treated as values.
|
||||
Value: [o("IDENTIFIER", function() {
|
||||
return new ValueNode(new LiteralNode(yytext));
|
||||
Value: [o("Identifier", function() {
|
||||
return new ValueNode($1);
|
||||
}), o("Literal", function() {
|
||||
return new ValueNode($1);
|
||||
}), o("Array", function() {
|
||||
|
@ -221,12 +227,12 @@
|
|||
})
|
||||
],
|
||||
// Accessing into an object or array, through dot or index notation.
|
||||
Accessor: [o("PROPERTY_ACCESS IDENTIFIER", function() {
|
||||
return new AccessorNode(new LiteralNode(yytext));
|
||||
}), o("PROTOTYPE_ACCESS IDENTIFIER", function() {
|
||||
return new AccessorNode(new LiteralNode(yytext), 'prototype');
|
||||
}), o("SOAK_ACCESS IDENTIFIER", function() {
|
||||
return new AccessorNode(new LiteralNode(yytext), 'soak');
|
||||
Accessor: [o("PROPERTY_ACCESS Identifier", function() {
|
||||
return new AccessorNode($2);
|
||||
}), o("PROTOTYPE_ACCESS Identifier", function() {
|
||||
return new AccessorNode($2, 'prototype');
|
||||
}), o("SOAK_ACCESS Identifier", function() {
|
||||
return new AccessorNode($2, 'soak');
|
||||
}), o("Index"), o("Slice", function() {
|
||||
return new SliceNode($1);
|
||||
})
|
||||
|
@ -247,11 +253,11 @@
|
|||
}), o("AssignObj", function() {
|
||||
return [$1];
|
||||
}), o("AssignList , AssignObj", function() {
|
||||
return $1.push($3);
|
||||
return $1.concat([$3]);
|
||||
}), o("AssignList TERMINATOR AssignObj", function() {
|
||||
return $1.push($3);
|
||||
return $1.concat([$3]);
|
||||
}), o("AssignList , TERMINATOR AssignObj", function() {
|
||||
return $1.push($4);
|
||||
return $1.concat([$4]);
|
||||
}), o("INDENT AssignList OUTDENT", function() {
|
||||
return $2;
|
||||
})
|
||||
|
@ -290,8 +296,8 @@
|
|||
// This references, either naked or to a property.
|
||||
This: [o("@", function() {
|
||||
return new ThisNode();
|
||||
}), o("@ IDENTIFIER", function() {
|
||||
return new ThisNode(yytext);
|
||||
}), o("@ Identifier", function() {
|
||||
return new ThisNode($2);
|
||||
})
|
||||
],
|
||||
// The range literal.
|
||||
|
@ -321,13 +327,13 @@
|
|||
}), o("INDENT Expression", function() {
|
||||
return [$2];
|
||||
}), o("ArgList , Expression", function() {
|
||||
return $1.push($3);
|
||||
return $1.concat([$3]);
|
||||
}), o("ArgList TERMINATOR Expression", function() {
|
||||
return $1.push($3);
|
||||
return $1.concat([$3]);
|
||||
}), o("ArgList , TERMINATOR Expression", function() {
|
||||
return $1.push($4);
|
||||
return $1.concat([$4]);
|
||||
}), o("ArgList , INDENT Expression", function() {
|
||||
return $1.push($4);
|
||||
return $1.concat([$4]);
|
||||
}), o("ArgList OUTDENT", function() {
|
||||
return $1;
|
||||
})
|
||||
|
@ -351,7 +357,7 @@
|
|||
})
|
||||
],
|
||||
// A catch clause.
|
||||
Catch: [o("CATCH IDENTIFIER Block", function() {
|
||||
Catch: [o("CATCH Identifier Block", function() {
|
||||
return [$2, $3];
|
||||
})
|
||||
],
|
||||
|
@ -383,9 +389,9 @@
|
|||
})
|
||||
],
|
||||
// An array comprehension has variables for the current element and index.
|
||||
ForVariables: [o("IDENTIFIER", function() {
|
||||
ForVariables: [o("Identifier", function() {
|
||||
return [$1];
|
||||
}), o("IDENTIFIER , IDENTIFIER", function() {
|
||||
}), o("Identifier , Identifier", function() {
|
||||
return [$1, $3];
|
||||
})
|
||||
],
|
||||
|
|
|
@ -21,7 +21,7 @@ KEYWORDS: [
|
|||
"super", "extends"
|
||||
]
|
||||
|
||||
# Token matching regexes.
|
||||
# Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
|
||||
IDENTIFIER : /^([a-zA-Z$_](\w|\$)*)/
|
||||
NUMBER : /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
|
||||
STRING : /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/
|
||||
|
|
128
src/nodes.coffee
128
src/nodes.coffee
|
@ -153,8 +153,7 @@ Node::top_sensitive: -> false
|
|||
Expressions: exports.Expressions: inherit Node, {
|
||||
|
||||
constructor: (nodes) ->
|
||||
@expressions: flatten nodes
|
||||
@children: @expressions
|
||||
@children: @expressions: flatten nodes
|
||||
this
|
||||
|
||||
# Tack an expression on to the end of this expression list.
|
||||
|
@ -235,8 +234,7 @@ statement Expressions
|
|||
LiteralNode: exports.LiteralNode: inherit Node, {
|
||||
|
||||
constructor: (value) ->
|
||||
@value: value
|
||||
@children: [value]
|
||||
@children: [@value: value]
|
||||
this
|
||||
|
||||
# Break and continue must be treated as statements -- they lose their meaning
|
||||
|
@ -257,8 +255,7 @@ LiteralNode::is_statement_only: LiteralNode::is_statement
|
|||
ReturnNode: exports.ReturnNode: inherit Node, {
|
||||
|
||||
constructor: (expression) ->
|
||||
@expression: expression
|
||||
@children: [expression]
|
||||
@children: [@expression: expression]
|
||||
this
|
||||
|
||||
compile_node: (o) ->
|
||||
|
@ -275,9 +272,7 @@ ValueNode: exports.ValueNode: inherit Node, {
|
|||
SOAK: " == undefined ? undefined : "
|
||||
|
||||
constructor: (base, properties) ->
|
||||
@base: base
|
||||
@properties: flatten(properties or [])
|
||||
@children: flatten(@base, @properties)
|
||||
@children: flatten(@base: base, @properties: (properties or []))
|
||||
this
|
||||
|
||||
push: (prop) ->
|
||||
|
@ -356,9 +351,7 @@ statement CommentNode
|
|||
CallNode: exports.CallNode: inherit Node, {
|
||||
|
||||
constructor: (variable, args) ->
|
||||
@variable: variable
|
||||
@args: args or []
|
||||
@children: flatten([@variable, @args])
|
||||
@children: flatten [@variable: variable, @args: (args or [])]
|
||||
@prefix: ''
|
||||
this
|
||||
|
||||
|
@ -412,9 +405,7 @@ CallNode: exports.CallNode: inherit Node, {
|
|||
ExtendsNode: exports.ExtendsNode: inherit Node, {
|
||||
|
||||
constructor: (child, parent) ->
|
||||
@child: child
|
||||
@parent: parent
|
||||
@children: [child, parent]
|
||||
@children: [@child: child, @parent: parent]
|
||||
this
|
||||
|
||||
# Hooking one constructor into another's prototype chain.
|
||||
|
@ -437,8 +428,7 @@ statement ExtendsNode
|
|||
AccessorNode: exports.AccessorNode: inherit Node, {
|
||||
|
||||
constructor: (name, tag) ->
|
||||
@name: name
|
||||
@children: [@name]
|
||||
@children: [@name: name]
|
||||
@prototype: tag is 'prototype'
|
||||
@soak: tag is 'soak'
|
||||
this
|
||||
|
@ -477,9 +467,7 @@ ThisNode: exports.ThisNode: inherit Node, {
|
|||
RangeNode: exports.RangeNode: inherit Node, {
|
||||
|
||||
constructor: (from, to, exclusive) ->
|
||||
@from: from
|
||||
@to: to
|
||||
@children: [from, to]
|
||||
@children: [@from: from, @to: to]
|
||||
@exclusive: !!exclusive
|
||||
this
|
||||
|
||||
|
@ -526,6 +514,106 @@ SliceNode: exports.SliceNode: inherit Node, {
|
|||
|
||||
}
|
||||
|
||||
# An object literal.
|
||||
ObjectNode: exports.ObjectNode: inherit Node, {
|
||||
|
||||
constructor: (props) ->
|
||||
@objects: @properties: props or []
|
||||
this
|
||||
|
||||
# All the mucking about with commas is to make sure that CommentNodes and
|
||||
# AssignNodes get interleaved correctly, with no trailing commas or
|
||||
# commas affixed to comments. TODO: Extract this and add it to ArrayNode.
|
||||
compile_node: (o) ->
|
||||
o.indent: @idt(1)
|
||||
non_comments: prop for prop in @properties when not (prop instanceof CommentNode)
|
||||
last_noncom: non_comments[non_comments.length - 1]
|
||||
props: for prop, i in @properties
|
||||
join: ",\n"
|
||||
join: "\n" if prop is last_noncom or prop instanceof CommentNode
|
||||
join: '' if i is non_comments.length - 1
|
||||
indent: if prop instanceof CommentNode then '' else @idt(1)
|
||||
indent + prop.compile(o) + join
|
||||
'{\n' + props.join('') + '\n' + @idt() + '}'
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Setting the value of a local variable, or the value of an object property.
|
||||
AssignNode: exports.AssignNode: inherit Node, {
|
||||
|
||||
# Keep the identifier regex in sync with the Lexer.
|
||||
IDENTIFIER: /^([a-zA-Z$_](\w|\$)*)/
|
||||
PROTO_ASSIGN: /^(\S+)\.prototype/
|
||||
LEADING_DOT: /^\.(prototype\.)?/
|
||||
|
||||
constructor: (variable, value, context) ->
|
||||
@children: [@variable: variable, @value: value]
|
||||
@context: context
|
||||
this
|
||||
|
||||
top_sensitive: ->
|
||||
true
|
||||
|
||||
is_value: ->
|
||||
@variable instanceof ValueNode
|
||||
|
||||
is_statement: ->
|
||||
@is_value() and (@variable.is_array() or @variable.is_object())
|
||||
|
||||
compile_node: (o) ->
|
||||
top: del o, 'top'
|
||||
return @compile_pattern_match(o) if @is_statement()
|
||||
return @compile_splice(o) if @is_value() and @variable.is_splice()
|
||||
stmt: del o, 'as_statement'
|
||||
name: @variable.compile(o)
|
||||
last: if @is_value() then @variable.last.replace(@LEADING_DOT, '') else name
|
||||
match: name.match(@PROTO_ASSIGN)
|
||||
proto: match and match[1]
|
||||
if @value instanceof CodeNode
|
||||
@value.name: last if last.match(@IDENTIFIER)
|
||||
@value.proto: proto if proto
|
||||
return name + ': ' + @value.compile(o) if @context is 'object'
|
||||
o.scope.find(name) unless @is_value() and @variable.has_properties()
|
||||
val: name + ' = ' + @value.compile(o)
|
||||
return @idt() + val + ';' if stmt
|
||||
val: '(' + val + ')' if not top or o.returns
|
||||
val: @idt() + 'return ' + val if o.returns
|
||||
val
|
||||
|
||||
# Implementation of recursive pattern matching, when assigning array or
|
||||
# object literals to a value. Peeks at their properties to assign inner names.
|
||||
# See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
|
||||
compile_pattern_match: (o) ->
|
||||
val_var: o.scope.free_variable()
|
||||
assigns: [@idt() + val_var + ' = ' + @value.compile(o) + ';']
|
||||
o.top: true
|
||||
o.as_statement: true
|
||||
for obj, i in @variable.base.objects
|
||||
[obj, i]: [obj.value, obj.variable.base] if @variable.is_object()
|
||||
access_class: if @variable.is_array() then IndexNode else AccessorNode
|
||||
if obj instanceof SplatNode
|
||||
val: new LiteralNode(obj.compile_value(o, val_var, @variable.base.objects.indexOf(obj)))
|
||||
else
|
||||
val: new ValueNode(val_var, [new access_class(new LiteralNode(i))])
|
||||
assigns.push(new AssignNode(obj, val).compile(o))
|
||||
assigns.join("\n")
|
||||
|
||||
compile_splice: (o) ->
|
||||
name: @variable.compile(merge(o, {only_first: true}))
|
||||
range: @variable.properties.last.range
|
||||
plus: if range.exclusive then '' else ' + 1'
|
||||
from: range.from.compile(o)
|
||||
to: range.to.compile(o) + ' - ' + from + plus
|
||||
name + '.splice.apply(' + name + ', [' + from + ', ' + to + '].concat(' + @value.compile(o) + '))'
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -86,10 +86,18 @@ grammar: {
|
|||
o "INDENT OUTDENT", -> new Expressions()
|
||||
]
|
||||
|
||||
# All hard-coded values. These can be printed straight to JavaScript.
|
||||
Literal: [
|
||||
Identifier: [
|
||||
o "IDENTIFIER", -> new LiteralNode(yytext)
|
||||
]
|
||||
|
||||
AlphaNumeric: [
|
||||
o "NUMBER", -> new LiteralNode(yytext)
|
||||
o "STRING", -> new LiteralNode(yytext)
|
||||
]
|
||||
|
||||
# All hard-coded values. These can be printed straight to JavaScript.
|
||||
Literal: [
|
||||
o "AlphaNumeric", -> $1
|
||||
o "JS", -> new LiteralNode(yytext)
|
||||
o "REGEX", -> new LiteralNode(yytext)
|
||||
o "BREAK", -> new LiteralNode(yytext)
|
||||
|
@ -110,9 +118,8 @@ grammar: {
|
|||
|
||||
# Assignment within an object literal (can be quoted).
|
||||
AssignObj: [
|
||||
o "IDENTIFIER ASSIGN Expression", -> new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object')
|
||||
o "STRING ASSIGN Expression", -> new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object')
|
||||
o "NUMBER ASSIGN Expression", -> new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object')
|
||||
o "Identifier ASSIGN Expression", -> new AssignNode(new ValueNode($1), $3, 'object')
|
||||
o "AlphaNumeric ASSIGN Expression", -> new AssignNode(new ValueNode($1), $3, 'object')
|
||||
o "Comment"
|
||||
]
|
||||
|
||||
|
@ -224,7 +231,7 @@ grammar: {
|
|||
|
||||
# Expressions that can be treated as values.
|
||||
Value: [
|
||||
o "IDENTIFIER", -> new ValueNode(new LiteralNode(yytext))
|
||||
o "Identifier", -> new ValueNode($1)
|
||||
o "Literal", -> new ValueNode($1)
|
||||
o "Array", -> new ValueNode($1)
|
||||
o "Object", -> new ValueNode($1)
|
||||
|
@ -237,9 +244,9 @@ grammar: {
|
|||
|
||||
# Accessing into an object or array, through dot or index notation.
|
||||
Accessor: [
|
||||
o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode(new LiteralNode(yytext))
|
||||
o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode(new LiteralNode(yytext), 'prototype')
|
||||
o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode(new LiteralNode(yytext), 'soak')
|
||||
o "PROPERTY_ACCESS Identifier", -> new AccessorNode($2)
|
||||
o "PROTOTYPE_ACCESS Identifier", -> new AccessorNode($2, 'prototype')
|
||||
o "SOAK_ACCESS Identifier", -> new AccessorNode($2, 'soak')
|
||||
o "Index"
|
||||
o "Slice", -> new SliceNode($1)
|
||||
]
|
||||
|
@ -258,9 +265,9 @@ grammar: {
|
|||
AssignList: [
|
||||
o "", -> []
|
||||
o "AssignObj", -> [$1]
|
||||
o "AssignList , AssignObj", -> $1.push $3
|
||||
o "AssignList TERMINATOR AssignObj", -> $1.push $3
|
||||
o "AssignList , TERMINATOR AssignObj", -> $1.push $4
|
||||
o "AssignList , AssignObj", -> $1.concat [$3]
|
||||
o "AssignList TERMINATOR AssignObj", -> $1.concat [$3]
|
||||
o "AssignList , TERMINATOR AssignObj", -> $1.concat [$4]
|
||||
o "INDENT AssignList OUTDENT", -> $2
|
||||
]
|
||||
|
||||
|
@ -295,7 +302,7 @@ grammar: {
|
|||
# This references, either naked or to a property.
|
||||
This: [
|
||||
o "@", -> new ThisNode()
|
||||
o "@ IDENTIFIER", -> new ThisNode(yytext)
|
||||
o "@ Identifier", -> new ThisNode($2)
|
||||
]
|
||||
|
||||
# The range literal.
|
||||
|
@ -320,10 +327,10 @@ grammar: {
|
|||
o "", -> []
|
||||
o "Expression", -> [$1]
|
||||
o "INDENT Expression", -> [$2]
|
||||
o "ArgList , Expression", -> $1.push $3
|
||||
o "ArgList TERMINATOR Expression", -> $1.push $3
|
||||
o "ArgList , TERMINATOR Expression", -> $1.push $4
|
||||
o "ArgList , INDENT Expression", -> $1.push $4
|
||||
o "ArgList , Expression", -> $1.concat [$3]
|
||||
o "ArgList TERMINATOR Expression", -> $1.concat [$3]
|
||||
o "ArgList , TERMINATOR Expression", -> $1.concat [$4]
|
||||
o "ArgList , INDENT Expression", -> $1.concat [$4]
|
||||
o "ArgList OUTDENT", -> $1
|
||||
]
|
||||
|
||||
|
@ -343,7 +350,7 @@ grammar: {
|
|||
|
||||
# A catch clause.
|
||||
Catch: [
|
||||
o "CATCH IDENTIFIER Block", -> [$2, $3]
|
||||
o "CATCH Identifier Block", -> [$2, $3]
|
||||
]
|
||||
|
||||
# Throw an exception.
|
||||
|
@ -372,8 +379,8 @@ grammar: {
|
|||
|
||||
# An array comprehension has variables for the current element and index.
|
||||
ForVariables: [
|
||||
o "IDENTIFIER", -> [$1]
|
||||
o "IDENTIFIER , IDENTIFIER", -> [$1, $3]
|
||||
o "Identifier", -> [$1]
|
||||
o "Identifier , Identifier", -> [$1, $3]
|
||||
]
|
||||
|
||||
# The source of the array comprehension can optionally be filtered.
|
||||
|
|
Loading…
Reference in New Issue