Skip to content

Commit

Permalink
major internal reworking -- all variable declarations have been pushe…
Browse files Browse the repository at this point in the history
…d up to the first line of the block scope -- all assignment is now an inherent expression
  • Loading branch information
jashkenas committed Dec 26, 2009
1 parent cf46fa8 commit 9adf2e2
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 62 deletions.
12 changes: 6 additions & 6 deletions lib/coffee_script/grammar.y
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ rule

# Assignment within an object literal.
AssignObj:
IDENTIFIER ASSIGN Expression { result = AssignNode.new(val[0], val[2], :object) }
| STRING ASSIGN Expression { result = AssignNode.new(val[0], val[2], :object) }
IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) }
| STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
| Comment { result = val[0] }
;

Expand All @@ -143,10 +143,10 @@ rule
| '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
| NOT Expression { result = OpNode.new(val[0], val[1]) }
| '~' Expression { result = OpNode.new(val[0], val[1]) }
| '--' Expression { result = OpNode.new(val[0], val[1]) }
| '++' Expression { result = OpNode.new(val[0], val[1]) }
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
| '--' Expression { result = OpNode.new(val[0], val[1]) }
| '++' Expression { result = OpNode.new(val[0], val[1]) }
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
Expand Down
51 changes: 25 additions & 26 deletions lib/coffee_script/nodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,18 @@ def last?(node)
# If this is the top-level Expressions, wrap everything in a safety closure.
def root_compile(o={})
indent = o[:no_wrap] ? '' : TAB
code = compile(o.merge(:indent => indent, :scope => Scope.new))
code = compile(o.merge(:indent => indent, :scope => Scope.new), o[:no_wrap] ? nil : :code)
code.gsub!(STRIP_TRAILING_WHITESPACE, '')
o[:no_wrap] ? code : "(function(){\n#{code}\n})();"
end

# The extra fancy is to handle pushing down returns and assignments
# recursively to the final lines of inner statements.
def compile(options={})
# Variables first defined within the Expressions body have their
# declarations pushed up to the top scope.
def compile(options={}, parent=nil)
return root_compile(options) unless options[:scope]
code = @expressions.map { |node|
compiled = @expressions.map do |node|
o = super(options)
if last?(node) && (o[:return] || o[:assign])
if o[:return]
Expand All @@ -99,14 +101,17 @@ def compile(options={})
if node.statement? || node.custom_assign?
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
else
"#{o[:indent]}#{AssignNode.new(ValueNode.new(LiteralNode.new(o[:assign])), node).compile(o)};"
"#{o[:indent]}#{AssignNode.new(o[:assign], node).compile(o)};"
end
end
else
o.delete(:return) and o.delete(:assign)
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
end
}.join("\n")
end
scope = options[:scope]
declarations = scope.any_declared? && parent == :code ? "#{options[:indent]}var #{scope.declared_variables.join(', ')};\n" : ''
code = declarations + compiled.join("\n")
write(code)
end
end
Expand Down Expand Up @@ -338,10 +343,8 @@ def compile(o={})

# Setting the value of a local variable, or the value of an object property.
class AssignNode < Node
LEADING_VAR = /\Avar\s+/
PROTO_ASSIGN = /\A(\S+)\.prototype/

statement
custom_return

attr_reader :variable, :value, :context
Expand All @@ -356,19 +359,16 @@ def line_ending

def compile(o={})
o = super(o)
name = @variable.respond_to?(:compile) ? @variable.compile(o) : @variable.to_s
last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s
name = @variable.compile(o)
last = @variable.last.to_s
proto = name[PROTO_ASSIGN, 1]
o = o.merge(:assign => name, :last_assign => last, :proto_assign => proto)
o = o.merge(:assign => @variable, :last_assign => last, :proto_assign => proto)
postfix = o[:return] ? ";\n#{o[:indent]}return #{name}" : ''
return write("#{@variable}: #{@value.compile(o)}") if @context == :object
return write("#{name}: #{@value.compile(o)}") if @context == :object
return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign?
defined = o[:scope].find(name)
def_part = defined || @variable.properties? || o[:no_wrap] ? "" : "var #{name};\n#{o[:indent]}"
return write(def_part + @value.compile(o)) if @value.custom_assign?
def_part = defined || o[:no_wrap] ? name : "var #{name}"
val_part = @value.compile(o).sub(LEADING_VAR, '')
write("#{def_part} = #{val_part}#{postfix}")
o[:scope].find(name) unless @variable.properties?
return write(@value.compile(o)) if @value.custom_assign?
write("#{name} = #{@value.compile(o)}#{postfix}")
end
end

Expand Down Expand Up @@ -436,8 +436,8 @@ def compile(o={})
o[:indent] += TAB
o.delete(:assign)
o.delete(:no_wrap)
@params.each {|id| o[:scope].find(id.to_s) }
code = @body.compile(o)
@params.each {|id| o[:scope].parameter(id.to_s) }
code = @body.compile(o, :code)
write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}")
end
end
Expand Down Expand Up @@ -533,20 +533,19 @@ def compile(o={})
ivar = scope.free_variable
lvar = scope.free_variable
rvar = scope.free_variable
name_part = name_found ? @name : "var #{@name}"
index_name = @index ? (index_found ? @index : "var #{@index}") : nil
source_part = "var #{svar} = #{@source.compile(o)};"
for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++"
var_part = "\n#{o[:indent] + TAB}#{name_part} = #{svar}[#{ivar}];\n"
index_name = @index ? @index : nil
source_part = "#{svar} = #{@source.compile(o)};"
for_part = "#{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++"
var_part = "\n#{o[:indent] + TAB}#{@name} = #{svar}[#{ivar}];\n"
index_part = @index ? "#{o[:indent] + TAB}#{index_name} = #{ivar};\n" : ''
body = @body
suffix = ';'
set_result = "var #{rvar} = [];\n#{o[:indent]}"
set_result = "#{rvar} = [];\n#{o[:indent]}"
save_result = "#{rvar}[#{ivar}] = "
return_result = rvar

if o[:return] || o[:assign]
return_result = "#{o[:assign]} = #{return_result}" if o[:assign]
return_result = "#{o[:assign].compile(o)} = #{return_result}" if o[:assign]
return_result = "return #{return_result}" if o[:return]
if @filter
body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body])
Expand Down
4 changes: 2 additions & 2 deletions lib/coffee_script/parser.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 18 additions & 3 deletions lib/coffee_script/scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module CoffeeScript
# whether a variable has been seen before or if it needs to be declared.
class Scope

attr_reader :parent, :temp_variable
attr_reader :parent, :variables, :temp_variable

# Initialize a scope with its parent, for lookups up the chain.
def initialize(parent=nil)
Expand All @@ -18,10 +18,16 @@ def initialize(parent=nil)
def find(name, remote=false)
found = check(name, remote)
return found if found || remote
@variables[name.to_sym] = true
@variables[name.to_sym] = :var
found
end

# Define a local variable as originating from a parameter in current scope
# -- no var required.
def parameter(name)
@variables[name.to_sym] = :param
end

# Just check to see if a variable has already been declared.
def check(name, remote=false)
return true if @variables[name.to_sym]
Expand All @@ -36,10 +42,19 @@ def reset(name)
# Find an available, short, name for a compiler-generated variable.
def free_variable
@temp_variable.succ! while check(@temp_variable)
@variables[@temp_variable.to_sym] = true
@variables[@temp_variable.to_sym] = :var
@temp_variable.dup
end

def any_declared?
!declared_variables.empty?
end

# Return the list of variables first declared in current scope.
def declared_variables
@variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort
end

end

end
21 changes: 11 additions & 10 deletions test/fixtures/generation/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@
// The cornerstone, an each implementation.
// Handles objects implementing forEach, arrays, and raw objects.
_.each = function(obj, iterator, context) {
var index = 0;
var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key;
index = 0;
try {
if (obj.forEach) {
obj.forEach(iterator, context);
} else if (_.isArray(obj) || _.isArguments(obj)) {
var __a = obj;
var __d = [];
for (var __b=0, __c=__a.length; __b<__c; __b++) {
var item = __a[__b];
var i = __b;
__a = obj;
__d = [];
for (__b=0, __c=__a.length; __b<__c; __b++) {
item = __a[__b];
i = __b;
__d[__b] = iterator.call(context, item, i, obj);
}
__d;
} else {
var __e = _.keys(obj);
var __h = [];
for (var __f=0, __g=__e.length; __f<__g; __f++) {
var key = __e[__f];
__e = _.keys(obj);
__h = [];
for (__f=0, __g=__e.length; __f<__g; __f++) {
key = __e[__f];
__h[__f] = iterator.call(context, obj[key], key, obj);
}
__h;
Expand Down
21 changes: 11 additions & 10 deletions test/fixtures/generation/each_no_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@
// The cornerstone, an each implementation.
// Handles objects implementing forEach, arrays, and raw objects.
_.each = function(obj, iterator, context) {
var index = 0;
var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key;
index = 0;
try {
if (obj.forEach) {
obj.forEach(iterator, context);
} else if (_.isArray(obj) || _.isArguments(obj)) {
var __a = obj;
var __d = [];
for (var __b=0, __c=__a.length; __b<__c; __b++) {
var item = __a[__b];
var i = __b;
__a = obj;
__d = [];
for (__b=0, __c=__a.length; __b<__c; __b++) {
item = __a[__b];
i = __b;
__d[__b] = iterator.call(context, item, i, obj);
}
__d;
} else {
var __e = _.keys(obj);
var __h = [];
for (var __f=0, __g=__e.length; __f<__g; __f++) {
var key = __e[__f];
__e = _.keys(obj);
__h = [];
for (__f=0, __g=__e.length; __f<__g; __f++) {
key = __e[__f];
__h[__f] = iterator.call(context, obj[key], key, obj);
}
__h;
Expand Down
5 changes: 3 additions & 2 deletions test/fixtures/generation/inner_comments.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
(function(){
var object = {
var array, object;
object = {
a: 1,
// Comments between the elements.
b: 2,
// Like this.
c: 3
};
var array = [1,
array = [1,
// Comments between the elements.
2,
// Like this.
Expand Down
6 changes: 3 additions & 3 deletions test/unit/test_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def test_parsing_an_object_literal
nodes = @par.parse("{one : 1 \n two : 2}").expressions
obj = nodes.first.literal
assert obj.is_a? ObjectNode
assert obj.properties.first.variable == "one"
assert obj.properties.last.variable == "two"
assert obj.properties.first.variable.literal.value == "one"
assert obj.properties.last.variable.literal.value == "two"
end

def test_parsing_an_function_definition
Expand Down Expand Up @@ -79,7 +79,7 @@ def test_no_wrap

def test_no_wrapping_parens_around_statements
assert_raises(SyntaxError) do
@par.parse("(a: 1)").compile
@par.parse("(try thing() catch error fail().)").compile
end
end

Expand Down

0 comments on commit 9adf2e2

Please sign in to comment.