Navigation Menu

Skip to content

Commit

Permalink
added children macro to Node, using it so that all nodes now have a '…
Browse files Browse the repository at this point in the history
…children' method -- used for safe references to 'this' within closure wrappers
  • Loading branch information
jashkenas committed Jan 16, 2010
1 parent 701cdb4 commit 1cd7fa8
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 42 deletions.
14 changes: 8 additions & 6 deletions lib/coffee_script/grammar.y
Expand Up @@ -16,6 +16,7 @@ token ARGUMENTS
token NEWLINE
token COMMENT
token JS
token THIS
token INDENT OUTDENT

# Declare order of operations.
Expand Down Expand Up @@ -102,12 +103,12 @@ rule
| BREAK { result = LiteralNode.new(val[0]) }
| CONTINUE { result = LiteralNode.new(val[0]) }
| ARGUMENTS { result = LiteralNode.new(val[0]) }
| TRUE { result = LiteralNode.new(true) }
| FALSE { result = LiteralNode.new(false) }
| YES { result = LiteralNode.new(true) }
| NO { result = LiteralNode.new(false) }
| ON { result = LiteralNode.new(true) }
| OFF { result = LiteralNode.new(false) }
| TRUE { result = LiteralNode.new(Value.new(true)) }
| FALSE { result = LiteralNode.new(Value.new(false)) }
| YES { result = LiteralNode.new(Value.new(true)) }
| NO { result = LiteralNode.new(Value.new(false)) }
| ON { result = LiteralNode.new(Value.new(true)) }
| OFF { result = LiteralNode.new(Value.new(false)) }
;

# Assignment to a variable (or index).
Expand Down Expand Up @@ -235,6 +236,7 @@ rule
| Range { result = ValueNode.new(val[0]) }
| Value Accessor { result = val[0] << val[1] }
| Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
| THIS { result = ValueNode.new(ThisNode.new) }
;
# Accessing into an object or array, through dot or index notation.
Expand Down
3 changes: 2 additions & 1 deletion lib/coffee_script/lexer.rb
Expand Up @@ -13,10 +13,11 @@ class Lexer
"try", "catch", "finally", "throw",
"break", "continue",
"for", "in", "of", "by", "where", "while",
"delete", "instanceof", "typeof",
"switch", "when",
"super", "extends",
"arguments",
"delete", "instanceof", "typeof"]
"this"]

# Token matching regexes.
IDENTIFIER = /\A([a-zA-Z$_](\w|\$)*)/
Expand Down
101 changes: 67 additions & 34 deletions lib/coffee_script/nodes.rb
Expand Up @@ -24,6 +24,13 @@ def self.statement_only
class_eval "def statement_only?; true; end"
end

# Provide a quick implementation of a children method.
def self.children(*attributes)
attr_reader *attributes
attrs = attributes.map {|a| "[@#{a}]" }.join(', ')
class_eval "def children; [#{attrs}].flatten.compact; end"
end

def write(code)
puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
code
Expand All @@ -42,16 +49,32 @@ def compile(o={})
end

def compile_closure(o={})
indent = o[:indent]
@indent = (o[:indent] = idt(1))
"(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()"
indent = o[:indent]
@indent = (o[:indent] = idt(1))
pass_this = !o[:closure] && contains? {|node| node.is_a?(ThisNode) }
param = pass_this ? '__this' : ''
"(function(#{param}) {\n#{compile_node(o.merge(:return => true, :closure => true))}\n#{indent}})(#{pass_this ? 'this' : ''})"
end

# Quick short method for the current indentation level, plus tabbing in.
def idt(tabs=0)
@indent + (TAB * tabs)
end

# Does this node, or any of it's children, contain a node of a certain kind?
def contains?(&block)
children.each do |node|
return true if yield(node)
node.is_a?(Node) && node.contains?(&block)
false
end
end

# All Nodes must implement a "children" method that returns child nodes.
def children
raise NotImplementedError, "#{self.class} is missing a 'children' method"
end

# Default implementations of the common node methods.
def unwrap; self; end
def statement?; false; end
Expand All @@ -62,7 +85,7 @@ def top_sensitive?; false; end
# A collection of nodes, each one representing an expression.
class Expressions < Node
statement
attr_reader :expressions
children :expressions

TRAILING_WHITESPACE = /\s+$/
UPPERCASE = /[A-Z]/
Expand Down Expand Up @@ -156,6 +179,7 @@ def compile_expression(node, o)
# Literals are static values that have a Ruby representation, eg.: a string, a number,
# true, false, nil, etc.
class LiteralNode < Node
children :value

# Values of a literal node that much be treated as a statement -- no
# sense returning or assigning them.
Expand All @@ -165,8 +189,6 @@ class LiteralNode < Node
# it to an array.
ARG_ARRAY = 'Array.prototype.slice.call(arguments, 0)'

attr_reader :value

# Wrap up a compiler-generated string as a LiteralNode.
def self.wrap(string)
self.new(Value.new(string))
Expand All @@ -192,8 +214,7 @@ def compile_node(o)
# Return an expression, or wrap it in a closure and return it.
class ReturnNode < Node
statement_only

attr_reader :expression
children :expression

def initialize(expression)
@expression = expression
Expand Down Expand Up @@ -225,7 +246,7 @@ def compile_node(o={})
# Node for a function invocation. Takes care of converting super() calls into
# calls against the prototype's function of the same name.
class CallNode < Node
attr_reader :variable, :arguments
children :variable, :arguments

def initialize(variable, arguments=[])
@variable, @arguments = variable, arguments
Expand Down Expand Up @@ -285,8 +306,8 @@ def compile_splat(o)
# Node to extend an object's prototype with an ancestor object.
# After goog.inherits from the Closure Library.
class ExtendsNode < Node
children :sub_object, :super_object
statement
attr_reader :sub_object, :super_object

def initialize(sub_object, super_object)
@sub_object, @super_object = sub_object, super_object
Expand All @@ -307,7 +328,8 @@ def compile_node(o={})

# A value, indexed or dotted into, or vanilla.
class ValueNode < Node
attr_reader :base, :properties, :last, :source
children :base, :properties
attr_reader :last, :source

def initialize(base, properties=[])
@base, @properties = base, properties
Expand Down Expand Up @@ -352,7 +374,7 @@ def compile_node(o)
# A dotted accessor into a part of a value, or the :: shorthand for
# an accessor into the object's prototype.
class AccessorNode < Node
attr_reader :name
children :name

def initialize(name, prototype=false)
@name, @prototype = name, prototype
Expand All @@ -366,7 +388,7 @@ def compile_node(o)

# An indexed accessor into a part of an array or object.
class IndexNode < Node
attr_reader :index
children :index

def initialize(index)
@index = index
Expand All @@ -377,10 +399,20 @@ def compile_node(o)
end
end

# A node to represent a reference to "this". Needs to be transformed into a
# reference to the correct value of "this", when used within a closure wrapper.
class ThisNode < Node

def compile_node(o)
write(o[:closure] ? "__this" : "this")
end

end

# A range literal. Ranges can be used to extract portions (slices) of arrays,
# or to specify a range for array comprehensions.
class RangeNode < Node
attr_reader :from, :to
children :from, :to

def initialize(from, to, exclusive=false)
@from, @to, @exclusive = from, to, exclusive
Expand Down Expand Up @@ -423,7 +455,7 @@ def compile_array(o)
# specifies the index of the end of the slice (just like the first parameter)
# is the index of the beginning.
class SliceNode < Node
attr_reader :range
children :range

def initialize(range)
@range = range
Expand All @@ -439,11 +471,11 @@ def compile_node(o)

# Setting the value of a local variable, or the value of an object property.
class AssignNode < Node
children :variable, :value

PROTO_ASSIGN = /\A(\S+)\.prototype/
LEADING_DOT = /\A\.(prototype\.)?/

attr_reader :variable, :value, :context

def initialize(variable, value, context=nil)
@variable, @value, @context = variable, value, context
end
Expand Down Expand Up @@ -505,6 +537,9 @@ def compile_splice(o)
# Simple Arithmetic and logical operations. Performs some conversion from
# CoffeeScript operations into their JavaScript equivalents.
class OpNode < Node
children :first, :second
attr_reader :operator

CONVERSIONS = {
:== => "===",
:'!=' => "!==",
Expand All @@ -517,8 +552,6 @@ class OpNode < Node
CONDITIONALS = [:'||=', :'&&=']
PREFIX_OPERATORS = [:typeof, :delete]

attr_reader :operator, :first, :second

def initialize(operator, first, second=nil, flip=false)
@first, @second, @flip = first, second, flip
@operator = CONVERSIONS[operator.to_sym] || operator
Expand Down Expand Up @@ -550,7 +583,8 @@ def compile_unary(o)

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

def initialize(params, body, tag=nil)
@params = params
Expand All @@ -566,6 +600,7 @@ def compile_node(o)
o[:indent] = idt(@bound ? 2 : 1)
o.delete(:no_wrap)
o.delete(:globals)
o.delete(:closure)
name = o.delete(:immediate_assign)
if @params.last.is_a?(SplatNode)
splat = @params.pop
Expand All @@ -584,8 +619,8 @@ def compile_node(o)
# A splat, either as a parameter to a function, an argument to a call,
# or in a destructuring assignment.
class SplatNode < Node
children :name
attr_accessor :index
attr_reader :name

def initialize(name)
@name = name
Expand All @@ -612,7 +647,7 @@ def compile_value(o, name, index)

# An object literal.
class ObjectNode < Node
attr_reader :properties
children :properties
alias_method :objects, :properties

def initialize(properties = [])
Expand All @@ -639,7 +674,7 @@ def compile_node(o)

# An array literal.
class ArrayNode < Node
attr_reader :objects
children :objects

def initialize(objects=[])
@objects = objects
Expand Down Expand Up @@ -672,10 +707,9 @@ def self.wrap(array, expressions)
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
# it, all other loops can be manufactured.
class WhileNode < Node
children :condition, :body
statement

attr_reader :condition, :body

def initialize(condition, body)
@condition, @body = condition, body
end
Expand Down Expand Up @@ -707,10 +741,10 @@ def compile_node(o)
# of the comprehenion. Unlike Python array comprehensions, it's able to pass
# the current index of the loop as a second parameter.
class ForNode < Node
children :body, :source, :filter
attr_reader :name, :index, :step
statement

attr_reader :body, :source, :name, :index, :filter, :step

def initialize(body, source, name, index=nil)
@body, @name, @index = body, name, index
@source = source[:source]
Expand Down Expand Up @@ -780,10 +814,10 @@ def compile_node(o)

# A try/catch/finally block.
class TryNode < Node
children :try, :recovery, :finally
attr_reader :error
statement

attr_reader :try, :error, :recovery, :finally

def initialize(try, error, recovery, finally=nil)
@try, @error, @recovery, @finally = try, error, recovery, finally
end
Expand All @@ -800,10 +834,9 @@ def compile_node(o)

# Throw an exception.
class ThrowNode < Node
children :expression
statement_only

attr_reader :expression

def initialize(expression)
@expression = expression
end
Expand All @@ -815,7 +848,7 @@ def compile_node(o)

# Check an expression for existence (meaning not null or undefined).
class ExistenceNode < Node
attr_reader :expression
children :expression

def initialize(expression)
@expression = expression
Expand All @@ -831,7 +864,7 @@ def compile_node(o)
# You can't wrap parentheses around bits that get compiled into JS statements,
# unfortunately.
class ParentheticalNode < Node
attr_reader :expressions
children :expressions

def initialize(expressions, line=nil)
@expressions = expressions.unwrap
Expand All @@ -850,7 +883,7 @@ def compile_node(o)
# Single-expression IfNodes are compiled into ternary operators if possible,
# because ternaries are first-class returnable assignable expressions.
class IfNode < Node
attr_reader :condition, :body, :else_body
children :condition, :body, :else_body

def initialize(condition, body, else_body=nil, tags={})
@condition = condition
Expand Down
4 changes: 4 additions & 0 deletions lib/coffee_script/value.rb
Expand Up @@ -45,6 +45,10 @@ def hash
def match(regex)
@value.match(regex)
end

def children
[]
end
end

end

0 comments on commit 1cd7fa8

Please sign in to comment.