Skip to content

Commit

Permalink
adding bound functions, with test
Browse files Browse the repository at this point in the history
  • Loading branch information
jashkenas committed Jan 14, 2010
1 parent 0ceca07 commit 1e7d638
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 74 deletions.
72 changes: 24 additions & 48 deletions documentation/speed.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,63 +14,39 @@ <h1>Quickie CoffeeScript Speed Tests</h1>
var arr = [];
while (num--) arr.push(num);

JSLitmus.test('current comprehensions', function() {
__a = arr;
__c = [];
for (__b in __a) {
if (__a.hasOwnProperty(__b)) {
num = __a[__b];
__d = num;
__c.push(num);
}
}
});
var f1 = function f1() {
return arr;
};

JSLitmus.test('raw for loop (best we can do)', function() {
__a = arr;
__c = new Array(__a.length);
for (__b=0; __b < __a.length; __b++) {
__c[__b] = __a[__b];
}
JSLitmus.test('regular function', function() {
f1();
});

JSLitmus.test('current without hasOwnProperty check', function() {
__a = arr;
__c = [];
for (__b in __a) {
num = __a[__b];
__d = num;
__c.push(num);
}
});
var __this = this;

var f2 = function f2() {
return (function() {
return arr;
}).apply(__this, arguments);
};

JSLitmus.test('raw for..in loop', function() {
__a = arr;
__c = new Array(__a.length);
for (__b in __a) {
__c[__b] = __a[__b];
}
JSLitmus.test('bound function', function() {
f2();
});

JSLitmus.test('weepy\'s comprehensions', function() {
__c = []; __a = arr;
__d = function(num, __b) {
__c.push(num);
var f3 = (function() {
__b = function() {
return arr;
};
if (__a instanceof Array) {
for (__b=0; __b<__a.length; __b++) __d(__a[__b], __b);
} else {
for (__b in __a) { if (__a.hasOwnProperty(__b)) __d(__a[__b], __b); }
}
});
return (function f2() {
return __b.apply(__this, arguments);
});
})();

JSLitmus.test('CoffeeScript 0.2.2 comprehensions', function() {
__c = []; __a = arr;
for (__b=0; __b<__a.length; __b++) {
num = __a[__b];
__c.push(num);
}
JSLitmus.test('prebound function', function() {
f3();
});

</script>

</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<key>comment</key>
<string>match stuff like: funcName: =&gt;</string>
<key>match</key>
<string>([a-zA-Z0-9_?.$:*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=&gt;)</string>
<string>([a-zA-Z0-9_?.$:*]*?)\s*(=\b|:\b)\s*([\w,\s]*?)\s*(=+&gt;)</string>
<key>name</key>
<string>meta.function.coffee</string>
</dict>
Expand All @@ -64,7 +64,7 @@
<key>comment</key>
<string>match stuff like: a =&gt;</string>
<key>match</key>
<string>([a-zA-Z0-9_?., $*]*)\s*(=&gt;)</string>
<string>([a-zA-Z0-9_?., $*]*)\s*(=+&gt;)</string>
<key>name</key>
<string>meta.inline.function.coffee</string>
</dict>
Expand Down
12 changes: 9 additions & 3 deletions lib/coffee_script/grammar.y
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ prechigh
left EXTENDS
left ASSIGN '||=' '&&='
right RETURN
right '=>' UNLESS IF ELSE WHILE
right '=>' '==>' UNLESS IF ELSE WHILE
preclow

rule
Expand Down Expand Up @@ -198,8 +198,14 @@ rule
# Function definition.
Code:
ParamList "=>" Block { result = CodeNode.new(val[0], val[2]) }
| "=>" Block { result = CodeNode.new([], val[1]) }
ParamList FuncGlyph Block { result = CodeNode.new(val[0], val[2], val[1]) }
| FuncGlyph Block { result = CodeNode.new([], val[1], val[0]) }
;
# The symbols to signify functions, and bound functions.
FuncGlyph:
'=>' { result = :func }
| '==>' { result = :boundfunc }
;
# The parameters to a function definition.
Expand Down
4 changes: 2 additions & 2 deletions lib/coffee_script/lexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Lexer
OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/
WHITESPACE = /\A([ \t]+)/
COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
CODE = /\A(=>)/
CODE = /\A(=?=>)/
REGEX = /\A(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
MULTI_DENT = /\A((\n([ \t]*))+)(\.)?/
LAST_DENT = /\n([ \t]*)/
Expand Down Expand Up @@ -155,7 +155,7 @@ def indent_token
@line += indent.scan(MULTILINER).size
@i += indent.size
next_character = @chunk[MULTI_DENT, 4]
no_newlines = next_character == '.' || (last_value.to_s.match(NO_NEWLINE) && last_value != "=>")
no_newlines = next_character == '.' || (last_value.to_s.match(NO_NEWLINE) && !last_value.match(CODE))
return suppress_newlines(indent) if no_newlines
size = indent.scan(LAST_DENT).last.last.length
return newline_token(indent) if size == @indent
Expand Down
23 changes: 17 additions & 6 deletions lib/coffee_script/nodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -546,14 +546,23 @@ def compile_unary(o)

# A function definition. The only node that creates a new Scope.
class CodeNode < Node
attr_reader :params, :body
attr_reader :params, :body, :bound

def initialize(params, body)
def initialize(params, body, tag=nil)
@params = params
@body = body
@body = body
@bound = tag == :boundfunc
end

def statement?
@bound
end

def compile_node(o)
if @bound
o[:scope].assign("__this", "this")
fvar = o[:scope].free_variable
end
shared_scope = o.delete(:shared_scope)
o[:scope] = shared_scope || Scope.new(o[:scope], @body)
o[:return] = true
Expand All @@ -568,9 +577,11 @@ def compile_node(o)
@body.unshift(splat)
end
@params.each {|id| o[:scope].parameter(id.to_s) }
code = @body.compile_with_declarations(o)
code = "\n#{@body.compile_with_declarations(o)}\n"
name_part = name ? " #{name}" : ''
write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{idt}}")
func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt}}"
return write(func) unless @bound
write("#{idt}#{fvar} = #{func};\n#{idt}#{o[:return] ? 'return ' : ''}(function#{name_part}() {\n#{idt(1)}return #{fvar}.apply(__this, arguments);\n#{idt}});")
end
end

Expand Down Expand Up @@ -726,7 +737,7 @@ def compile_node(o)
body = Expressions.wrap(IfNode.new(@filter, body))
end
if @object
o[:scope].top_level_assign("__hasProp", "Object.prototype.hasOwnProperty")
o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
body = Expressions.wrap(IfNode.new(
CallNode.new(ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]), [LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]),
Expressions.wrap(body),
Expand Down
2 changes: 1 addition & 1 deletion lib/coffee_script/rewriter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Rewriter

# Single-line flavors of block expressions that have unclosed endings.
# The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN]
SINGLE_LINERS = [:ELSE, "=>", "==>", :TRY, :FINALLY, :THEN]
SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN]

# Rewrite the token stream in multiple passes, one logical filter at
Expand Down
8 changes: 4 additions & 4 deletions lib/coffee_script/scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ def free_variable
@temp_variable.dup
end

# Ensure that an assignment is made at the top-level scope.
# Takes two strings.
def top_level_assign(name, value)
return @parent.top_level_assign(name, value) if @parent
# Ensure that an assignment is made at the top of scope (or top-level
# scope, if requested).
def assign(name, value, top=false)
return @parent.assign(name, value, top) if top && @parent
@variables[name.to_sym] = Value.new(value)
end

Expand Down
4 changes: 4 additions & 0 deletions lib/coffee_script/value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def eql?(other)
def hash
@value.hash
end

def match(regex)
@value.match(regex)
end
end

end
22 changes: 22 additions & 0 deletions test/fixtures/execution/test_functions.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
x: 1
y: {}
y.x: => 3

print(x is 1)
print(typeof(y.x) is 'function')
print(y.x() is 3)
print(y.x.name is 'x')


obj: {
name: "Fred"

bound: =>
(==> print(this.name is "Fred"))()

unbound: =>
(=> print(!this.name?))()
}

obj.unbound()
obj.bound()
8 changes: 0 additions & 8 deletions test/fixtures/execution/test_named_functions.coffee

This file was deleted.

0 comments on commit 1e7d638

Please sign in to comment.