mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
now self-compiling array/object/range comprehensions.
This commit is contained in:
parent
0c2a13b468
commit
13d3b3a3ce
3 changed files with 209 additions and 26 deletions
|
@ -1,5 +1,5 @@
|
||||||
(function(){
|
(function(){
|
||||||
var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, IDENTIFIER, IndexNode, LiteralNode, Node, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThisNode, ThrowNode, TryNode, ValueNode, WhileNode, any, compact, del, dup, flatten, inherit, merge, statement;
|
var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IndexNode, LiteralNode, Node, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThisNode, ThrowNode, TryNode, 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.
|
||||||
|
@ -144,9 +144,6 @@
|
||||||
this.name = this.constructor.name;
|
this.name = this.constructor.name;
|
||||||
return this.values = arguments;
|
return this.values = arguments;
|
||||||
};
|
};
|
||||||
exports.Expressions.wrap = function wrap(values) {
|
|
||||||
return this.values = values;
|
|
||||||
};
|
|
||||||
// Some helper functions
|
// Some helper functions
|
||||||
// Tabs are two spaces for pretty printing.
|
// Tabs are two spaces for pretty printing.
|
||||||
TAB = ' ';
|
TAB = ' ';
|
||||||
|
@ -205,15 +202,24 @@
|
||||||
};
|
};
|
||||||
// Merge objects.
|
// Merge objects.
|
||||||
merge = function merge(src, dest) {
|
merge = function merge(src, dest) {
|
||||||
var __a, key, val;
|
var __a, __b, fresh, key, val;
|
||||||
|
fresh = {
|
||||||
|
};
|
||||||
__a = src;
|
__a = src;
|
||||||
for (key in __a) {
|
for (key in __a) {
|
||||||
val = __a[key];
|
val = __a[key];
|
||||||
if (__hasProp.call(__a, key)) {
|
if (__hasProp.call(__a, key)) {
|
||||||
((dest[key] = val));
|
((fresh[key] = val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dest;
|
__b = dest;
|
||||||
|
for (key in __b) {
|
||||||
|
val = __b[key];
|
||||||
|
if (__hasProp.call(__b, key)) {
|
||||||
|
((fresh[key] = val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fresh;
|
||||||
};
|
};
|
||||||
// Do any of the elements in the list pass a truth test?
|
// Do any of the elements in the list pass a truth test?
|
||||||
any = function any(list, test) {
|
any = function any(list, test) {
|
||||||
|
@ -721,10 +727,11 @@
|
||||||
// TODO: This generates pretty ugly code ... shrink it.
|
// TODO: This generates pretty ugly code ... shrink it.
|
||||||
compile_array: function compile_array(o) {
|
compile_array: function compile_array(o) {
|
||||||
var arr, body;
|
var arr, body;
|
||||||
body = Expressions.wrap(new LiteralNode('i'));
|
body = Expressions.wrap([new LiteralNode('i')]);
|
||||||
arr = Expressions.wrap(new ForNode(body, {
|
arr = Expressions.wrap([new ForNode(body, {
|
||||||
source: (new ValueNode(this))
|
source: (new ValueNode(this))
|
||||||
}, 'i'));
|
}, 'i')
|
||||||
|
]);
|
||||||
return (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o);
|
return (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -830,16 +837,16 @@
|
||||||
})) {
|
})) {
|
||||||
return expressions;
|
return expressions;
|
||||||
}
|
}
|
||||||
return Expressions.wrap(new CallNode(new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr]));
|
return Expressions.wrap([new CallNode(new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr])]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// A faux-node used to wrap an expressions body in a closure.
|
// A faux-node used to wrap an expressions body in a closure.
|
||||||
ClosureNode = (exports.ClosureNode = {
|
ClosureNode = (exports.ClosureNode = {
|
||||||
wrap: function wrap(expressions, statement) {
|
wrap: function wrap(expressions, statement) {
|
||||||
var call, func;
|
var call, func;
|
||||||
func = new ParentheticalNode(new CodeNode([], Expressions.wrap(expressions)));
|
func = new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])));
|
||||||
call = new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')]);
|
call = new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')]);
|
||||||
return statement ? Expressions.wrap(call) : call;
|
return statement ? Expressions.wrap([call]) : call;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 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.
|
||||||
|
@ -1183,4 +1190,104 @@
|
||||||
return '(' + code + ')';
|
return '(' + code + ')';
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
// The replacement for the for loop is an array comprehension (that compiles)
|
||||||
|
// into a for loop. Also acts as an expression, able to return the result
|
||||||
|
// of the comprehenion. Unlike Python array comprehensions, it's able to pass
|
||||||
|
// the current index of the loop as a second parameter.
|
||||||
|
ForNode = (exports.ForNode = inherit(Node, {
|
||||||
|
constructor: function constructor(body, source, name, index) {
|
||||||
|
var __a;
|
||||||
|
this.body = body;
|
||||||
|
this.name = name;
|
||||||
|
this.index = index || null;
|
||||||
|
this.source = source.source;
|
||||||
|
this.filter = source.filter;
|
||||||
|
this.step = source.step;
|
||||||
|
this.object = !!source.object;
|
||||||
|
if (this.object) {
|
||||||
|
__a = [this.index, this.name];
|
||||||
|
this.name = __a[0];
|
||||||
|
this.index = __a[1];
|
||||||
|
}
|
||||||
|
this.children = [this.body, this.source, this.filter];
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
top_sensitive: function top_sensitive() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
compile_node: function compile_node(o) {
|
||||||
|
var body, body_dent, call, for_part, index, index_found, index_var, ivar, name, name_found, range, return_result, rvar, scope, set_result, source, source_part, step_part, svar, top_level, var_part, vars;
|
||||||
|
top_level = del(o, 'top') && !o.returns;
|
||||||
|
range = this.source instanceof ValueNode && this.source.base instanceof RangeNode && !this.source.properties.length;
|
||||||
|
source = range ? this.source.base : this.source;
|
||||||
|
scope = o.scope;
|
||||||
|
name = this.name && this.name.compile(o);
|
||||||
|
index = this.index && this.index.compile(o);
|
||||||
|
name_found = name && scope.find(name);
|
||||||
|
index_found = index && scope.find(index);
|
||||||
|
body_dent = this.idt(1);
|
||||||
|
if (!(top_level)) {
|
||||||
|
rvar = scope.free_variable();
|
||||||
|
}
|
||||||
|
svar = scope.free_variable();
|
||||||
|
ivar = range ? name : index || scope.free_variable();
|
||||||
|
var_part = '';
|
||||||
|
body = Expressions.wrap([this.body]);
|
||||||
|
if (range) {
|
||||||
|
index_var = scope.free_variable();
|
||||||
|
source_part = source.compile_variables(o);
|
||||||
|
for_part = index_var + '=0, ' + source.compile(merge(o, {
|
||||||
|
index: ivar,
|
||||||
|
step: this.step
|
||||||
|
})) + ', ' + index_var + '++';
|
||||||
|
} else {
|
||||||
|
index_var = null;
|
||||||
|
source_part = svar + ' = ' + this.source.compile(o) + ';\n' + this.idt();
|
||||||
|
step_part = this.step ? ivar + ' += ' + this.step.compile(o) : ivar + '++';
|
||||||
|
for_part = this.object ? ivar + ' in ' + svar : ivar + ' = 0; ' + ivar + ' < ' + svar + '.length; ' + step_part;
|
||||||
|
if (name) {
|
||||||
|
var_part = body_dent + name + ' = ' + svar + '[' + ivar + '];\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_result = rvar ? this.idt() + rvar + ' = []; ' : this.idt();
|
||||||
|
return_result = rvar || '';
|
||||||
|
if (top_level && this.contains(function(n) {
|
||||||
|
return n instanceof CodeNode;
|
||||||
|
})) {
|
||||||
|
body = ClosureNode.wrap(body, true);
|
||||||
|
}
|
||||||
|
if (!(top_level)) {
|
||||||
|
body = PushNode.wrap(rvar, body);
|
||||||
|
}
|
||||||
|
if (o.returns) {
|
||||||
|
return_result = 'return ' + return_result;
|
||||||
|
del(o, 'returns');
|
||||||
|
if (this.filter) {
|
||||||
|
body = new IfNode(this.filter, body, null, {
|
||||||
|
statement: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (this.filter) {
|
||||||
|
body = Expressions.wrap([new IfNode(this.filter, body)]);
|
||||||
|
}
|
||||||
|
if (this.object) {
|
||||||
|
o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true);
|
||||||
|
call = new CallNode(new ValueNode(new LiteralNode('__hasProp'), [new AccessorNode(new LiteralNode('call'))]), [new LiteralNode(svar), new LiteralNode(ivar)]);
|
||||||
|
body = Expressions.wrap([new IfNode(call, Expressions.wrap([body]), null, {
|
||||||
|
statement: true
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (!(top_level)) {
|
||||||
|
return_result = '\n' + this.idt() + return_result + ';';
|
||||||
|
}
|
||||||
|
body = body.compile(merge(o, {
|
||||||
|
indent: body_dent,
|
||||||
|
top: true
|
||||||
|
}));
|
||||||
|
vars = range ? name : name + ', ' + ivar;
|
||||||
|
return set_result + source_part + 'for (' + for_part + ') {\n' + var_part + body + '\n' + this.idt() + '}\n' + this.idt() + return_result;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
statement(ForNode);
|
||||||
})();
|
})();
|
|
@ -911,14 +911,13 @@ module CoffeeScript
|
||||||
step_part = @step ? "#{ivar} += #{@step.compile(o)}" : "#{ivar}++"
|
step_part = @step ? "#{ivar} += #{@step.compile(o)}" : "#{ivar}++"
|
||||||
for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{step_part}"
|
for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{step_part}"
|
||||||
var_part = "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" if @name
|
var_part = "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" if @name
|
||||||
# body.unshift(AssignNode.new(@name, ValueNode.new(svar, [IndexNode.new(ivar)]))) if @name
|
|
||||||
end
|
end
|
||||||
set_result = rvar ? "#{idt}#{rvar} = []; " : idt
|
set_result = rvar ? "#{idt}#{rvar} = []; " : idt
|
||||||
return_result = rvar || ''
|
return_result = rvar || ''
|
||||||
body = ClosureNode.wrap(body, true) if top_level && contains? {|n| n.is_a? CodeNode }
|
body = ClosureNode.wrap(body, true) if top_level && contains? {|n| n.is_a? CodeNode }
|
||||||
body = PushNode.wrap(rvar, body) unless top_level
|
body = PushNode.wrap(rvar, body) unless top_level
|
||||||
if o[:return]
|
if o[:return]
|
||||||
return_result = "return #{return_result}" if o[:return]
|
return_result = "return #{return_result}"
|
||||||
o.delete(:return)
|
o.delete(:return)
|
||||||
body = IfNode.new(@filter, body, nil, :statement => true) if @filter
|
body = IfNode.new(@filter, body, nil, :statement => true) if @filter
|
||||||
elsif @filter
|
elsif @filter
|
||||||
|
|
|
@ -36,8 +36,6 @@ exports.ExistenceNode : -> @name: this.constructor.name; @values: arguments
|
||||||
exports.ParentheticalNode : -> @name: this.constructor.name; @values: arguments
|
exports.ParentheticalNode : -> @name: this.constructor.name; @values: arguments
|
||||||
exports.IfNode : -> @name: this.constructor.name; @values: arguments
|
exports.IfNode : -> @name: this.constructor.name; @values: arguments
|
||||||
|
|
||||||
exports.Expressions.wrap : (values) -> @values: values
|
|
||||||
|
|
||||||
# Some helper functions
|
# Some helper functions
|
||||||
|
|
||||||
# Tabs are two spaces for pretty printing.
|
# Tabs are two spaces for pretty printing.
|
||||||
|
@ -71,8 +69,10 @@ dup: (input) ->
|
||||||
|
|
||||||
# Merge objects.
|
# Merge objects.
|
||||||
merge: (src, dest) ->
|
merge: (src, dest) ->
|
||||||
(dest[key]: val) for key, val of src
|
fresh: {}
|
||||||
dest
|
(fresh[key]: val) for key, val of src
|
||||||
|
(fresh[key]: val) for key, val of dest
|
||||||
|
fresh
|
||||||
|
|
||||||
# Do any of the elements in the list pass a truth test?
|
# Do any of the elements in the list pass a truth test?
|
||||||
any: (list, test) ->
|
any: (list, test) ->
|
||||||
|
@ -494,8 +494,8 @@ RangeNode: exports.RangeNode: inherit Node, {
|
||||||
# part of a comprehension, slice, or splice.
|
# part of a comprehension, slice, or splice.
|
||||||
# TODO: This generates pretty ugly code ... shrink it.
|
# TODO: This generates pretty ugly code ... shrink it.
|
||||||
compile_array: (o) ->
|
compile_array: (o) ->
|
||||||
body: Expressions.wrap(new LiteralNode 'i')
|
body: Expressions.wrap([new LiteralNode('i')])
|
||||||
arr: Expressions.wrap(new ForNode(body, {source: (new ValueNode(this))}, 'i'))
|
arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, 'i')])
|
||||||
(new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)
|
(new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -571,9 +571,9 @@ PushNode: exports.PushNode: {
|
||||||
wrap: (array, expressions) ->
|
wrap: (array, expressions) ->
|
||||||
expr: expressions.unwrap()
|
expr: expressions.unwrap()
|
||||||
return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only()
|
return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only()
|
||||||
Expressions.wrap(new CallNode(
|
Expressions.wrap([new CallNode(
|
||||||
new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr]
|
new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr]
|
||||||
))
|
)])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,9 +581,9 @@ PushNode: exports.PushNode: {
|
||||||
ClosureNode: exports.ClosureNode: {
|
ClosureNode: exports.ClosureNode: {
|
||||||
|
|
||||||
wrap: (expressions, statement) ->
|
wrap: (expressions, statement) ->
|
||||||
func: new ParentheticalNode(new CodeNode([], Expressions.wrap(expressions)))
|
func: new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])))
|
||||||
call: new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')])
|
call: new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')])
|
||||||
if statement then Expressions.wrap(call) else call
|
if statement then Expressions.wrap([call]) else call
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -871,6 +871,83 @@ ParentheticalNode: exports.ParentheticalNode: inherit Node, {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The replacement for the for loop is an array comprehension (that compiles)
|
||||||
|
# into a for loop. Also acts as an expression, able to return the result
|
||||||
|
# of the comprehenion. Unlike Python array comprehensions, it's able to pass
|
||||||
|
# the current index of the loop as a second parameter.
|
||||||
|
ForNode: exports.ForNode: inherit Node, {
|
||||||
|
|
||||||
|
constructor: (body, source, name, index) ->
|
||||||
|
@body: body
|
||||||
|
@name: name
|
||||||
|
@index: index or null
|
||||||
|
@source: source.source
|
||||||
|
@filter: source.filter
|
||||||
|
@step: source.step
|
||||||
|
@object: !!source.object
|
||||||
|
[@name, @index]: [@index, @name] if @object
|
||||||
|
@children: [@body, @source, @filter]
|
||||||
|
this
|
||||||
|
|
||||||
|
top_sensitive: ->
|
||||||
|
true
|
||||||
|
|
||||||
|
compile_node: (o) ->
|
||||||
|
top_level: del(o, 'top') and not o.returns
|
||||||
|
range: @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length
|
||||||
|
source: if range then @source.base else @source
|
||||||
|
scope: o.scope
|
||||||
|
name: @name and @name.compile(o)
|
||||||
|
index: @index and @index.compile(o)
|
||||||
|
name_found: name and scope.find(name)
|
||||||
|
index_found: index and scope.find(index)
|
||||||
|
body_dent: @idt(1)
|
||||||
|
rvar: scope.free_variable() unless top_level
|
||||||
|
svar: scope.free_variable()
|
||||||
|
ivar: if range then name else index or scope.free_variable()
|
||||||
|
var_part: ''
|
||||||
|
body: Expressions.wrap([@body])
|
||||||
|
if range
|
||||||
|
index_var: scope.free_variable()
|
||||||
|
source_part: source.compile_variables(o)
|
||||||
|
for_part: index_var + '=0, ' + source.compile(merge(o, {index: ivar, step: @step})) + ', ' + index_var + '++'
|
||||||
|
else
|
||||||
|
index_var: null
|
||||||
|
source_part: svar + ' = ' + @source.compile(o) + ';\n' + @idt()
|
||||||
|
step_part: if @step then ivar + ' += ' + @step.compile(o) else ivar + '++'
|
||||||
|
for_part: if @object then ivar + ' in ' + svar else ivar + ' = 0; ' + ivar + ' < ' + svar + '.length; ' + step_part
|
||||||
|
var_part: body_dent + name + ' = ' + svar + '[' + ivar + '];\n' if name
|
||||||
|
set_result: if rvar then @idt() + rvar + ' = []; ' else @idt()
|
||||||
|
return_result: rvar or ''
|
||||||
|
body: ClosureNode.wrap(body, true) if top_level and @contains (n) -> n instanceof CodeNode
|
||||||
|
body: PushNode.wrap(rvar, body) unless top_level
|
||||||
|
if o.returns
|
||||||
|
return_result: 'return ' + return_result
|
||||||
|
del o, 'returns'
|
||||||
|
body: new IfNode(@filter, body, null, {statement: true}) if @filter
|
||||||
|
else if @filter
|
||||||
|
body: Expressions.wrap([new IfNode(@filter, body)])
|
||||||
|
if @object
|
||||||
|
o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true)
|
||||||
|
call: new CallNode(
|
||||||
|
new ValueNode(new LiteralNode('__hasProp'), [new AccessorNode(new LiteralNode('call'))])
|
||||||
|
[new LiteralNode(svar), new LiteralNode(ivar)]
|
||||||
|
)
|
||||||
|
body: Expressions.wrap([new IfNode(
|
||||||
|
call, Expressions.wrap([body]), null, {statement: true}
|
||||||
|
)])
|
||||||
|
return_result: '\n' + @idt() + return_result + ';' unless top_level
|
||||||
|
body: body.compile(merge(o, {indent: body_dent, top: true}))
|
||||||
|
vars: if range then name else name + ', ' + ivar
|
||||||
|
set_result + source_part + 'for (' + for_part + ') {\n' + var_part + body + '\n' + @idt() + '}\n' + @idt() + return_result
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
statement ForNode
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue