mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-04-11 03:00:13 -04:00
Self-compiler: can now compile functions.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
(function(){
|
(function(){
|
||||||
var AccessorNode, ArrayNode, 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 AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, Expressions, ExtendsNode, IndexNode, LiteralNode, Node, ObjectNode, PushNode, RangeNode, ReturnNode, SliceNode, TAB, TRAILING_WHITESPACE, ThisNode, ValueNode, 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.
|
||||||
@@ -366,7 +366,7 @@
|
|||||||
is_last: function is_last(node) {
|
is_last: function is_last(node) {
|
||||||
var l;
|
var l;
|
||||||
l = this.expressions.length;
|
l = this.expressions.length;
|
||||||
this.last_index = this.last_index || this.expressions[l - 1] instanceof CommentNode ? -2 : -1;
|
this.last_index = this.last_index || this.expressions[l - 1] instanceof CommentNode ? 2 : 1;
|
||||||
return node === this.expressions[l - this.last_index];
|
return node === this.expressions[l - this.last_index];
|
||||||
},
|
},
|
||||||
compile: function compile(o) {
|
compile: function compile(o) {
|
||||||
@@ -435,7 +435,7 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// Otherwise, we can just return the value of the expression.
|
// Otherwise, we can just return the value of the expression.
|
||||||
return this.idt() + 'return ' + node.compile(o);
|
return this.idt() + 'return ' + node.compile(o) + ';';
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
// Wrap up a node as an Expressions, unless it already is one.
|
// Wrap up a node as an Expressions, unless it already is one.
|
||||||
@@ -817,6 +817,29 @@
|
|||||||
return '[' + objects + ending;
|
return '[' + objects + ending;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
// 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.
|
||||||
|
PushNode = (exports.PushNode = {
|
||||||
|
wrap: function wrap(array, expressions) {
|
||||||
|
var expr;
|
||||||
|
expr = expressions.unwrap();
|
||||||
|
if (expr.is_statement_only() || expr.contains(function(n) {
|
||||||
|
return n.is_statement_only();
|
||||||
|
})) {
|
||||||
|
return expressions;
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
ClosureNode = (exports.ClosureNode = {
|
||||||
|
wrap: function wrap(expressions, statement) {
|
||||||
|
var call, func;
|
||||||
|
func = new ParentheticalNode(new CodeNode([], Expressions.wrap(expressions)));
|
||||||
|
call = new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')]);
|
||||||
|
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.
|
||||||
AssignNode = (exports.AssignNode = inherit(Node, {
|
AssignNode = (exports.AssignNode = inherit(Node, {
|
||||||
// Keep the identifier regex in sync with the Lexer.
|
// Keep the identifier regex in sync with the Lexer.
|
||||||
@@ -912,4 +935,49 @@
|
|||||||
return name + '.splice.apply(' + name + ', [' + from + ', ' + to + '].concat(' + this.value.compile(o) + '))';
|
return name + '.splice.apply(' + name + ', [' + from + ', ' + to + '].concat(' + this.value.compile(o) + '))';
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
// A function definition. The only node that creates a new Scope.
|
||||||
|
// A CodeNode does not have any children -- they're within the new scope.
|
||||||
|
CodeNode = (exports.CodeNode = inherit(Node, {
|
||||||
|
constructor: function constructor(params, body, tag) {
|
||||||
|
this.params = params;
|
||||||
|
this.body = body;
|
||||||
|
this.bound = tag === 'boundfunc';
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
compile_node: function compile_node(o) {
|
||||||
|
var __a, __b, code, func, inner, name_part, param, shared_scope, splat, top;
|
||||||
|
shared_scope = del(o, 'shared_scope');
|
||||||
|
top = del(o, 'top');
|
||||||
|
o.scope = shared_scope || new Scope(o.scope, this.body, this);
|
||||||
|
o.returns = true;
|
||||||
|
o.top = true;
|
||||||
|
o.indent = this.idt(this.bound ? 2 : 1);
|
||||||
|
del(o, 'no_wrap');
|
||||||
|
del(o, 'globals');
|
||||||
|
if (this.params[this.params.length - 1] instanceof SplatNode) {
|
||||||
|
splat = this.params.pop();
|
||||||
|
splat.index = this.params.length;
|
||||||
|
this.body.unshift(splat);
|
||||||
|
}
|
||||||
|
__a = this.params;
|
||||||
|
for (__b = 0; __b < __a.length; __b++) {
|
||||||
|
param = __a[__b];
|
||||||
|
(o.scope.parameter(param));
|
||||||
|
}
|
||||||
|
code = this.body.expressions.length ? '\n' + this.body.compile_with_declarations(o) + '\n' : '';
|
||||||
|
name_part = this.name ? ' ' + this.name : '';
|
||||||
|
func = 'function' + (this.bound ? '' : name_part) + '(' + this.params.join(', ') + ') {' + code + this.idt(this.bound ? 1 : 0) + '}';
|
||||||
|
if (top && !this.bound) {
|
||||||
|
func = '(' + func + ')';
|
||||||
|
}
|
||||||
|
if (!(this.bound)) {
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
inner = '(function' + name_part + '() {\n' + this.idt(2) + 'return __func.apply(__this, arguments);\n' + this.idt(1) + '});';
|
||||||
|
return '(function(__this) {\n' + this.idt(1) + 'var __func = ' + func + ';\n' + this.idt(1) + 'return ' + inner + '\n' + this.idt() + '})(this)';
|
||||||
|
},
|
||||||
|
top_sensitive: function top_sensitive() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
})();
|
})();
|
||||||
@@ -639,76 +639,6 @@ module CoffeeScript
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Simple Arithmetic and logical operations. Performs some conversion from
|
|
||||||
# CoffeeScript operations into their JavaScript equivalents.
|
|
||||||
class OpNode < Node
|
|
||||||
children :first, :second
|
|
||||||
attr_reader :operator
|
|
||||||
attr_accessor :second
|
|
||||||
|
|
||||||
CONVERSIONS = {
|
|
||||||
:== => "===",
|
|
||||||
:'!=' => "!==",
|
|
||||||
:and => '&&',
|
|
||||||
:or => '||',
|
|
||||||
:is => '===',
|
|
||||||
:isnt => "!==",
|
|
||||||
:not => '!'
|
|
||||||
}
|
|
||||||
CHAINABLE = [:<, :>, :>=, :<=, :===, :'!===']
|
|
||||||
ASSIGNMENT = [:'||=', :'&&=', :'?=']
|
|
||||||
PREFIX_OPERATORS = [:typeof, :delete]
|
|
||||||
|
|
||||||
def initialize(operator, first, second=nil, flip=false)
|
|
||||||
@first, @second, @flip = first, second, flip
|
|
||||||
@operator = CONVERSIONS[operator.to_sym] || operator
|
|
||||||
end
|
|
||||||
|
|
||||||
def unary?
|
|
||||||
@second.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def chainable?
|
|
||||||
CHAINABLE.include?(operator.to_sym)
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_node(o)
|
|
||||||
return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable?
|
|
||||||
return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym)
|
|
||||||
return write(compile_unary(o)) if unary?
|
|
||||||
return write(compile_existence(o)) if @operator == '?'
|
|
||||||
write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Mimic Python's chained comparisons. See:
|
|
||||||
# http://docs.python.org/reference/expressions.html#notin
|
|
||||||
def compile_chain(o)
|
|
||||||
shared = @first.unwrap.second
|
|
||||||
@first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode)
|
|
||||||
"(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})"
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_assignment(o)
|
|
||||||
first, second = @first.compile(o), @second.compile(o)
|
|
||||||
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 == '?='
|
|
||||||
"#{first} = #{first} #{sym} #{second}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_existence(o)
|
|
||||||
first, second = @first.compile(o), @second.compile(o)
|
|
||||||
"#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_unary(o)
|
|
||||||
space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
|
|
||||||
parts = [@operator.to_s, space, @first.compile(o)]
|
|
||||||
parts.reverse! if @flip
|
|
||||||
parts.join('')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# A function definition. The only node that creates a new Scope.
|
# A function definition. The only node that creates a new Scope.
|
||||||
# A CodeNode does not have any children -- they're within the new scope.
|
# A CodeNode does not have any children -- they're within the new scope.
|
||||||
class CodeNode < Node
|
class CodeNode < Node
|
||||||
@@ -716,19 +646,12 @@ module CoffeeScript
|
|||||||
attr_reader :params, :body, :bound
|
attr_reader :params, :body, :bound
|
||||||
attr_accessor :name, :proto
|
attr_accessor :name, :proto
|
||||||
|
|
||||||
# Constructor functions start with an uppercase letter, by convention.
|
|
||||||
UPPERCASE = /[A-Z]/
|
|
||||||
|
|
||||||
def initialize(params, body, tag=nil)
|
def initialize(params, body, tag=nil)
|
||||||
@params = params
|
@params = params
|
||||||
@body = body
|
@body = body
|
||||||
@bound = tag == :boundfunc
|
@bound = tag == :boundfunc
|
||||||
end
|
end
|
||||||
|
|
||||||
def constructor?
|
|
||||||
@name && @name[0..0][UPPERCASE]
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_node(o)
|
def compile_node(o)
|
||||||
shared_scope = o.delete(:shared_scope)
|
shared_scope = o.delete(:shared_scope)
|
||||||
top = o.delete(:top)
|
top = o.delete(:top)
|
||||||
@@ -808,6 +731,76 @@ module CoffeeScript
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Simple Arithmetic and logical operations. Performs some conversion from
|
||||||
|
# CoffeeScript operations into their JavaScript equivalents.
|
||||||
|
class OpNode < Node
|
||||||
|
children :first, :second
|
||||||
|
attr_reader :operator
|
||||||
|
attr_accessor :second
|
||||||
|
|
||||||
|
CONVERSIONS = {
|
||||||
|
:== => "===",
|
||||||
|
:'!=' => "!==",
|
||||||
|
:and => '&&',
|
||||||
|
:or => '||',
|
||||||
|
:is => '===',
|
||||||
|
:isnt => "!==",
|
||||||
|
:not => '!'
|
||||||
|
}
|
||||||
|
CHAINABLE = [:<, :>, :>=, :<=, :===, :'!===']
|
||||||
|
ASSIGNMENT = [:'||=', :'&&=', :'?=']
|
||||||
|
PREFIX_OPERATORS = [:typeof, :delete]
|
||||||
|
|
||||||
|
def initialize(operator, first, second=nil, flip=false)
|
||||||
|
@first, @second, @flip = first, second, flip
|
||||||
|
@operator = CONVERSIONS[operator.to_sym] || operator
|
||||||
|
end
|
||||||
|
|
||||||
|
def unary?
|
||||||
|
@second.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def chainable?
|
||||||
|
CHAINABLE.include?(operator.to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile_node(o)
|
||||||
|
return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable?
|
||||||
|
return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym)
|
||||||
|
return write(compile_unary(o)) if unary?
|
||||||
|
return write(compile_existence(o)) if @operator == '?'
|
||||||
|
write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Mimic Python's chained comparisons. See:
|
||||||
|
# http://docs.python.org/reference/expressions.html#notin
|
||||||
|
def compile_chain(o)
|
||||||
|
shared = @first.unwrap.second
|
||||||
|
@first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode)
|
||||||
|
"(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile_assignment(o)
|
||||||
|
first, second = @first.compile(o), @second.compile(o)
|
||||||
|
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 == '?='
|
||||||
|
"#{first} = #{first} #{sym} #{second}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile_existence(o)
|
||||||
|
first, second = @first.compile(o), @second.compile(o)
|
||||||
|
"#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile_unary(o)
|
||||||
|
space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
|
||||||
|
parts = [@operator.to_s, space, @first.compile(o)]
|
||||||
|
parts.reverse! if @flip
|
||||||
|
parts.join('')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# The replacement for the for loop is an array comprehension (that compiles)
|
# 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
|
# 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
|
# of the comprehenion. Unlike Python array comprehensions, it's able to pass
|
||||||
|
|||||||
@@ -190,12 +190,12 @@
|
|||||||
ParamList: [o("Param", function() {
|
ParamList: [o("Param", function() {
|
||||||
return [$1];
|
return [$1];
|
||||||
}), o("ParamList , Param", function() {
|
}), o("ParamList , Param", function() {
|
||||||
return $1.push($3);
|
return $1.concat([$3]);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
// A Parameter (or ParamSplat) in a function definition.
|
// A Parameter (or ParamSplat) in a function definition.
|
||||||
Param: [o("PARAM", function() {
|
Param: [o("PARAM", function() {
|
||||||
return new LiteralNode(yytext);
|
return yytext;
|
||||||
}), o("PARAM . . .", function() {
|
}), o("PARAM . . .", function() {
|
||||||
return new SplatNode(yytext);
|
return new SplatNode(yytext);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ Expressions: exports.Expressions: inherit Node, {
|
|||||||
# Is the node last in this block of expressions?
|
# Is the node last in this block of expressions?
|
||||||
is_last: (node) ->
|
is_last: (node) ->
|
||||||
l: @expressions.length
|
l: @expressions.length
|
||||||
@last_index ||= if @expressions[l - 1] instanceof CommentNode then -2 else -1
|
@last_index ||= if @expressions[l - 1] instanceof CommentNode then 2 else 1
|
||||||
node is @expressions[l - @last_index]
|
node is @expressions[l - @last_index]
|
||||||
|
|
||||||
compile: (o) ->
|
compile: (o) ->
|
||||||
@@ -218,7 +218,7 @@ Expressions: exports.Expressions: inherit Node, {
|
|||||||
# If it's a statement, the node knows how to return itself.
|
# If it's a statement, the node knows how to return itself.
|
||||||
return node.compile(merge(o, {returns: true})) if node.is_statement()
|
return node.compile(merge(o, {returns: true})) if node.is_statement()
|
||||||
# Otherwise, we can just return the value of the expression.
|
# Otherwise, we can just return the value of the expression.
|
||||||
return @idt() + 'return ' + node.compile(o)
|
return @idt() + 'return ' + node.compile(o) + ';'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,9 +561,28 @@ ArrayNode: exports.ArrayNode: inherit Node, {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
PushNode: exports.PushNode: {
|
||||||
|
|
||||||
|
wrap: (array, expressions) ->
|
||||||
|
expr: expressions.unwrap()
|
||||||
|
return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only()
|
||||||
|
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.
|
||||||
|
ClosureNode: exports.ClosureNode: {
|
||||||
|
|
||||||
|
wrap: (expressions, statement) ->
|
||||||
|
func: new ParentheticalNode(new CodeNode([], Expressions.wrap(expressions)))
|
||||||
|
call: new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')])
|
||||||
|
if statement then Expressions.wrap(call) else 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.
|
||||||
AssignNode: exports.AssignNode: inherit Node, {
|
AssignNode: exports.AssignNode: inherit Node, {
|
||||||
@@ -635,8 +654,42 @@ AssignNode: exports.AssignNode: inherit Node, {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# A function definition. The only node that creates a new Scope.
|
||||||
|
# A CodeNode does not have any children -- they're within the new scope.
|
||||||
|
CodeNode: exports.CodeNode: inherit Node, {
|
||||||
|
|
||||||
|
constructor: (params, body, tag) ->
|
||||||
|
@params: params
|
||||||
|
@body: body
|
||||||
|
@bound: tag is 'boundfunc'
|
||||||
|
this
|
||||||
|
|
||||||
|
compile_node: (o) ->
|
||||||
|
shared_scope: del o, 'shared_scope'
|
||||||
|
top: del o, 'top'
|
||||||
|
o.scope: shared_scope or new Scope(o.scope, @body, this)
|
||||||
|
o.returns: true
|
||||||
|
o.top: true
|
||||||
|
o.indent: @idt(if @bound then 2 else 1)
|
||||||
|
del o, 'no_wrap'
|
||||||
|
del o, 'globals'
|
||||||
|
if @params[@params.length - 1] instanceof SplatNode
|
||||||
|
splat: @params.pop()
|
||||||
|
splat.index: @params.length
|
||||||
|
@body.unshift(splat)
|
||||||
|
(o.scope.parameter(param)) for param in @params
|
||||||
|
code: if @body.expressions.length then '\n' + @body.compile_with_declarations(o) + '\n' else ''
|
||||||
|
name_part: if @name then ' ' + @name else ''
|
||||||
|
func: 'function' + (if @bound then '' else name_part) + '(' + @params.join(', ') + ') {' + code + @idt(if @bound then 1 else 0) + '}'
|
||||||
|
func: '(' + func + ')' if top and not @bound
|
||||||
|
return func unless @bound
|
||||||
|
inner: '(function' + name_part + '() {\n' + @idt(2) + 'return __func.apply(__this, arguments);\n' + @idt(1) + '});'
|
||||||
|
'(function(__this) {\n' + @idt(1) + 'var __func = ' + func + ';\n' + @idt(1) + 'return ' + inner + '\n' + @idt() + '})(this)'
|
||||||
|
|
||||||
|
top_sensitive: ->
|
||||||
|
true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -215,12 +215,12 @@ grammar: {
|
|||||||
# The parameters to a function definition.
|
# The parameters to a function definition.
|
||||||
ParamList: [
|
ParamList: [
|
||||||
o "Param", -> [$1]
|
o "Param", -> [$1]
|
||||||
o "ParamList , Param", -> $1.push($3)
|
o "ParamList , Param", -> $1.concat [$3]
|
||||||
]
|
]
|
||||||
|
|
||||||
# A Parameter (or ParamSplat) in a function definition.
|
# A Parameter (or ParamSplat) in a function definition.
|
||||||
Param: [
|
Param: [
|
||||||
o "PARAM", -> new LiteralNode(yytext)
|
o "PARAM", -> yytext
|
||||||
o "PARAM . . .", -> new SplatNode(yytext)
|
o "PARAM . . .", -> new SplatNode(yytext)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user