From 1e7d638435d35d6479e9117c756a8b715233f84c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 13 Jan 2010 20:59:57 -0500 Subject: [PATCH] adding bound functions, with test --- documentation/speed.html | 76 +++++++------------ .../Syntaxes/CoffeeScript.tmLanguage | 4 +- lib/coffee_script/grammar.y | 12 ++- lib/coffee_script/lexer.rb | 4 +- lib/coffee_script/nodes.rb | 23 ++++-- lib/coffee_script/rewriter.rb | 2 +- lib/coffee_script/scope.rb | 8 +- lib/coffee_script/value.rb | 4 + test/fixtures/execution/test_functions.coffee | 22 ++++++ .../execution/test_named_functions.coffee | 8 -- 10 files changed, 87 insertions(+), 76 deletions(-) create mode 100644 test/fixtures/execution/test_functions.coffee delete mode 100644 test/fixtures/execution/test_named_functions.coffee diff --git a/documentation/speed.html b/documentation/speed.html index 52d99650..c8406ba4 100644 --- a/documentation/speed.html +++ b/documentation/speed.html @@ -14,63 +14,39 @@ 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('regular function', function() { + f1(); }); - 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]; - } + var __this = this; + + var f2 = function f2() { + return (function() { + return arr; + }).apply(__this, arguments); + }; + + JSLitmus.test('bound function', function() { + f2(); }); - JSLitmus.test('current without hasOwnProperty check', function() { - __a = arr; - __c = []; - for (__b in __a) { - num = __a[__b]; - __d = num; - __c.push(num); - } - }); - - JSLitmus.test('raw for..in loop', function() { - __a = arr; - __c = new Array(__a.length); - for (__b in __a) { - __c[__b] = __a[__b]; - } - }); - - 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('prebound function', function() { + f3(); }); - JSLitmus.test('CoffeeScript 0.2.2 comprehensions', function() { - __c = []; __a = arr; - for (__b=0; __b<__a.length; __b++) { - num = __a[__b]; - __c.push(num); - } - }); diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 8d651fe3..d171bb5b 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -43,7 +43,7 @@ comment match stuff like: funcName: => … match - ([a-zA-Z0-9_?.$:*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>) + ([a-zA-Z0-9_?.$:*]*?)\s*(=\b|:\b)\s*([\w,\s]*?)\s*(=+>) name meta.function.coffee @@ -64,7 +64,7 @@ comment match stuff like: a => … match - ([a-zA-Z0-9_?., $*]*)\s*(=>) + ([a-zA-Z0-9_?., $*]*)\s*(=+>) name meta.inline.function.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index aab2339d..f6bf2466 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -39,7 +39,7 @@ prechigh left EXTENDS left ASSIGN '||=' '&&=' right RETURN - right '=>' UNLESS IF ELSE WHILE + right '=>' '==>' UNLESS IF ELSE WHILE preclow rule @@ -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. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 9541548f..1667e299 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -27,7 +27,7 @@ module CoffeeScript 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]*)/ @@ -155,7 +155,7 @@ module CoffeeScript @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 diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 64b854e3..0772add8 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -546,14 +546,23 @@ module CoffeeScript # 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 @@ -568,9 +577,11 @@ module CoffeeScript @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 @@ -726,7 +737,7 @@ module CoffeeScript 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), diff --git a/lib/coffee_script/rewriter.rb b/lib/coffee_script/rewriter.rb index 73d1106c..9635ed18 100644 --- a/lib/coffee_script/rewriter.rb +++ b/lib/coffee_script/rewriter.rb @@ -26,7 +26,7 @@ module CoffeeScript # 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 diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb index a15865e6..2b34cea9 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -47,10 +47,10 @@ module CoffeeScript @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 diff --git a/lib/coffee_script/value.rb b/lib/coffee_script/value.rb index bfd448ab..483ff8f8 100644 --- a/lib/coffee_script/value.rb +++ b/lib/coffee_script/value.rb @@ -41,6 +41,10 @@ module CoffeeScript def hash @value.hash end + + def match(regex) + @value.match(regex) + end end end \ No newline at end of file diff --git a/test/fixtures/execution/test_functions.coffee b/test/fixtures/execution/test_functions.coffee new file mode 100644 index 00000000..6a3aeb79 --- /dev/null +++ b/test/fixtures/execution/test_functions.coffee @@ -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() \ No newline at end of file diff --git a/test/fixtures/execution/test_named_functions.coffee b/test/fixtures/execution/test_named_functions.coffee deleted file mode 100644 index 4fb9998b..00000000 --- a/test/fixtures/execution/test_named_functions.coffee +++ /dev/null @@ -1,8 +0,0 @@ -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') \ No newline at end of file