Self-compiler: object literals.

This commit is contained in:
Jeremy Ashkenas 2010-02-09 20:53:25 -05:00
parent 91a7102f11
commit 001c915c21
7 changed files with 403 additions and 175 deletions

View File

@ -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]*?)([^\\]|\\\\)')/;

View File

@ -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) + '))';
}
}));
})();

View File

@ -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

View File

@ -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];
})
],

View File

@ -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]*?)([^\\]|\\\\)')/

View File

@ -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) + '))'
}

View File

@ -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.