diff --git a/TODO b/TODO index 8fc6ae7c..9878620e 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,7 @@ TODO: +* Super in methods. (reserve it). + * Need *way* better syntax errors. * Is it possible to close blocks (functions, ifs, trys) without an explicit diff --git a/examples/code.cs b/examples/code.cs index 621e8e17..aa497f1e 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -128,4 +128,12 @@ three_to_six: zero_to_nine[3, 6] # Multiline strings with inner quotes. story: "Lorem ipsum dolor \"sit\" amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna -aliquam erat volutpat. Ut wisi enim ad." \ No newline at end of file +aliquam erat volutpat. Ut wisi enim ad." + +# Calling super from an overridden method. +Greeter: => . # Create the parent object. +Greeter.prototype.hello: name => alert('Hello ' + name). # Define a "hello" method. +Exclaimer: name => this.name: name. # Create the child object. +Exclaimer.prototype: new Greeter() # Set the child to inherit from the parent. +Exclaimer.prototype.hello: => super(this.name + "!"). # The child's "hello" calls the parent's via "super". +(new Exclaimer('Bob')).hello() # Run it. diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 2ff987d3..2fb3fe7f 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -10,6 +10,7 @@ token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN WHILE token SWITCH CASE DEFAULT +token SUPER token NEWLINE token JS @@ -149,10 +150,16 @@ rule # Method definition Code: - ParamList "=>" Expressions "." { result = CodeNode.new(val[0], val[2]) } - | "=>" Expressions "." { result = CodeNode.new([], val[1]) } + ParamList "=>" CodeBody "." { result = CodeNode.new(val[0], val[2]) } + | "=>" CodeBody "." { result = CodeNode.new([], val[1]) } ; + CodeBody: + /* nothing */ { result = Nodes.new([]) } + | Expressions { result = val[0] } + ; + + ParamList: PARAM { result = val } | ParamList "," PARAM { result = val[0] << val[2] } @@ -196,12 +203,17 @@ rule Call: Invocation { result = val[0] } | NEW Invocation { result = val[1].new_instance } + | Super { result = val[0] } ; Invocation: Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) } ; + Super: + SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) } + ; + # An Array. Array: "[" ArgList "]" { result = ArrayNode.new(val[1]) } diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index debd3aeb..7269d54c 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -7,7 +7,8 @@ class Lexer "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", - "switch", "case", "default"] + "switch", "case", "default", + "super"] IDENTIFIER = /\A([a-zA-Z$_]\w*)/ NUMBER = /\A([0-9]+(\.[0-9]+)?)/ diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 9a9bfc09..96c1da76 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -122,6 +122,8 @@ end # receiver.method(argument1, argument2) # class CallNode < Node + LEADING_DOT = /\A\./ + def initialize(variable, arguments=[]) @variable, @arguments = variable, arguments end @@ -131,14 +133,26 @@ class CallNode < Node self end + def super? + @variable == :super + end + def compile(indent, scope, opts={}) args = @arguments.map{|a| a.compile(indent, scope, :no_paren => true) }.join(', ') + return compile_super(args, indent, scope, opts) if super? prefix = @new ? "new " : '' "#{prefix}#{@variable.compile(indent, scope)}(#{args})" end + + def compile_super(args, indent, scope, opts) + methname = opts[:last_assign].sub(LEADING_DOT, '') + "this.constructor.prototype.#{methname}.call(this, #{args})" + end end class ValueNode < Node + attr_reader :last + def initialize(name, properties=[]) @name, @properties = name, properties end @@ -153,9 +167,11 @@ class ValueNode < Node end def compile(indent, scope, opts={}) - [@name, @properties].flatten.map { |v| + parts = [@name, @properties].flatten.map do |v| v.respond_to?(:compile) ? v.compile(indent, scope) : v.to_s - }.join('') + end + @last = parts.last + parts.join('') end end @@ -200,16 +216,18 @@ class AssignNode < Node end def compile(indent, scope, opts={}) - value = @value.compile(indent + TAB, scope) + name = @variable.compile(indent, scope) if @variable.respond_to?(:compile) + last = @variable.respond_to?(:last) ? @variable.last : name + opts = opts.merge({:assign => name, :last_assign => last}) + value = @value.compile(indent, scope, opts) return "#{@variable}: #{value}" if @context == :object - name = @variable.compile(indent, scope) return "#{name} = #{value}" if @variable.properties? - defined = scope.find(name) - postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : '' - def_part = defined ? "" : "var #{name};\n#{indent}" - return def_part + @value.compile(indent, scope, opts.merge(:assign => name)) if @value.custom_assign? - def_part = defined ? name : "var #{name}" - "#{def_part} = #{@value.compile(indent, scope)}#{postfix}" + defined = scope.find(name) + postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : '' + def_part = defined ? "" : "var #{name};\n#{indent}" + return def_part + @value.compile(indent, scope, opts) if @value.custom_assign? + def_part = defined ? name : "var #{name}" + "#{def_part} = #{@value.compile(indent, scope, opts)}#{postfix}" end end @@ -260,7 +278,8 @@ class CodeNode < Node end def compile(indent, scope, opts={}) - code = @body.compile(indent + TAB, Scope.new(scope), {:return => true}) + opts = opts.merge(:return => true) + code = @body.compile(indent + TAB, Scope.new(scope), opts) "function(#{@params.join(', ')}) {\n#{code}\n#{indent}}" end end