Skip to content

Commit

Permalink
added existence chains with '?.' -- soaks up attempts to access undef…
Browse files Browse the repository at this point in the history
…ined properties, returning 'undefined'
  • Loading branch information
jashkenas committed Jan 24, 2010
1 parent 9160500 commit d728c3d
Show file tree
Hide file tree
Showing 5 changed files with 1,200 additions and 1,129 deletions.
5 changes: 3 additions & 2 deletions lib/coffee_script/grammar.y
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Parser
token IF ELSE UNLESS
token NUMBER STRING REGEX
token TRUE FALSE YES NO ON OFF
token IDENTIFIER PROPERTY_ACCESS PROTOTYPE_ACCESS
token IDENTIFIER PROPERTY_ACCESS PROTOTYPE_ACCESS SOAK_ACCESS
token CODE PARAM NEW RETURN
token TRY CATCH FINALLY THROW
token BREAK CONTINUE
Expand Down Expand Up @@ -242,7 +242,8 @@ rule
# Accessing into an object or array, through dot or index notation.
Accessor:
PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) }
| PROTOTYPE_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], true) }
| PROTOTYPE_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :prototype) }
| SOAK_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :soak) }
| Index { result = val[0] }
| Range { result = SliceNode.new(val[0]) }
;
Expand Down
9 changes: 8 additions & 1 deletion lib/coffee_script/lexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,15 @@ def identifier_token
# 'if' will result in an [:IF, "if"] token.
tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER
tag = :LEADING_WHEN if tag == :WHEN && [:OUTDENT, :INDENT, "\n"].include?(last_tag)
@tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2] && @tokens[-2][1] == '.')
@tokens[-1][0] = :PROTOTYPE_ACCESS if tag == :IDENTIFIER && last_value == '::'
if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2] && @tokens[-2][1] == '.')
if @tokens[-2][0] == "?"
@tokens[-1][0] = :SOAK_ACCESS
@tokens.delete_at(-2)
else
@tokens[-1][0] = :PROPERTY_ACCESS
end
end
token(tag, identifier)
@i += identifier.length
end
Expand Down
28 changes: 21 additions & 7 deletions lib/coffee_script/nodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ class ValueNode < Node
attr_reader :last, :source

def initialize(base, properties=[])
@base, @properties = base, properties
@base, @properties = base, [properties].flatten
end

def <<(other)
Expand Down Expand Up @@ -367,22 +367,36 @@ def statement?
end

def compile_node(o)
only = o.delete(:only_first)
props = only ? @properties[0...-1] : @properties
parts = [@base, props].flatten.map {|val| val.compile(o) }
soaked = false
only = o.delete(:only_first)
props = only ? @properties[0...-1] : @properties
baseline = @base.compile(o)
parts = [baseline.dup]
props.each do |prop|
if prop.is_a?(AccessorNode) && prop.soak
soaked = true
parts[-1] << " == undefined ? undefined : #{baseline += prop.compile(o)}"
else
parts << prop.compile(o)
end
end
@last = parts.last
@source = parts.length > 1 ? parts[0...-1].join('') : nil
write(parts.join(''))
code = parts.join('')
write(soaked ? "(#{code})" : code)
end
end

# A dotted accessor into a part of a value, or the :: shorthand for
# an accessor into the object's prototype.
class AccessorNode < Node
children :name
attr_reader :soak

def initialize(name, prototype=false)
@name, @prototype = name, prototype
def initialize(name, tag=nil)
@name = name
@prototype = tag == :prototype
@soak = tag == :soak
end

def compile_node(o)
Expand Down
Loading

0 comments on commit d728c3d

Please sign in to comment.