From 8e1f3c0eca0bb2af756e0be60b3edb92a7fa009c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 17 Jan 2010 14:23:41 -0500 Subject: [PATCH] generating multiple calls to the same function should use compile_double_reference to ensure a single evaluation of the call itself. --- lib/coffee_script/nodes.rb | 26 ++++++++++++------- test/fixtures/execution/test_existence.coffee | 12 ++++++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index f462336a..836fb8ee 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -306,6 +306,14 @@ module CoffeeScript end "#{prefix}#{meth}.apply(#{obj}, #{args.join('')})" end + + # If the code generation wished to use the result of a function call + # in multiple places, ensure that the function is only ever called once. + def compile_double_reference(o) + reference = o[:scope].free_variable + call = ParentheticalNode.new(AssignNode.new(reference, self)) + return call, reference + end end # Node to extend an object's prototype with an ancestor object. @@ -594,11 +602,7 @@ module CoffeeScript # http://docs.python.org/reference/expressions.html#notin def compile_chain(o) shared = @first.unwrap.second - if shared.is_a?(CallNode) - temp = o[:scope].free_variable - @first.second = ParentheticalNode.new(AssignNode.new(temp, shared)) - shared = temp - end + @first.second, shared = *shared.compile_double_reference(o) if shared.is_a?(CallNode) "(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})" end @@ -606,13 +610,13 @@ module CoffeeScript 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(first)} ? #{first} : #{second}" if @operator == '?=' + 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(first)} ? #{first} : #{second}" + "#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" end def compile_unary(o) @@ -897,8 +901,10 @@ module CoffeeScript class ExistenceNode < Node children :expression - def self.compile_test(variable) - "(typeof #{variable} !== \"undefined\" && #{variable} !== null)" + def self.compile_test(o, variable) + first, second = variable, variable + first, second = *variable.compile_double_reference(o) if variable.is_a?(CallNode) + "(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)" end def initialize(expression) @@ -906,7 +912,7 @@ module CoffeeScript end def compile_node(o) - write(ExistenceNode.compile_test(@expression.compile(o))) + write(ExistenceNode.compile_test(o, @expression)) end end diff --git a/test/fixtures/execution/test_existence.coffee b/test/fixtures/execution/test_existence.coffee index 7450b257..798cd5ab 100644 --- a/test/fixtures/execution/test_existence.coffee +++ b/test/fixtures/execution/test_existence.coffee @@ -20,4 +20,14 @@ print(a is 10 and b is 10) z: null x: z ? "EX" -print(z is null and x is "EX") \ No newline at end of file +print(z is null and x is "EX") + + +# Only evaluate once. + +counter: 0 +get_next_node: => + throw "up" if counter + counter++ + +print(if get_next_node()? then true else false) \ No newline at end of file