Skip to content

Commit

Permalink
Parsing of expander assignments.
Browse files Browse the repository at this point in the history
The ruby-lint parser is now capable of parsing code such as `*numbers = 10` and
`*numbers, number = 10`

This commit fixes #6.

Signed-off-by: Yorick Peterse <yorickpeterse@gmail.com>
  • Loading branch information
Yorick Peterse committed Nov 14, 2012
1 parent 3db5a75 commit 348f07e
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 106 deletions.
105 changes: 68 additions & 37 deletions lib/ruby-lint/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class Parser < Ripper::SexpBuilderPP
:block_var,
:const_ref,
:top_const_ref,
:mlhs_add_star
:mlhs_paren
]

##
Expand Down Expand Up @@ -392,6 +392,17 @@ def on_dot2(start, stop)
)
end

##
# Called when a range using 3 dots is found.
#
# @see RubyLint::Parser#on_dot2
# @todo There's a difference between ranges created using two and three
# dots, this method should return a different token in the future.
#
def on_dot3(start, stop)
return on_dot2(start, stop)
end

##
# Called when a regular expression is found.
#
Expand All @@ -403,17 +414,21 @@ def on_regexp_literal(regexp, modes)
regexp = regexp[0]
modes_array = []

value = regexp.respond_to?(:value) ? regexp.value : nil
line = regexp.respond_to?(:line) ? regexp.line : lineno
col = regexp.respond_to?(:column) ? regexp.column : column

if modes
modes_array = modes.value.split('').select { |c| c =~ /\w/ }
end

return Token::RegexpToken.new(
:type => :regexp,
:value => regexp.value,
:line => regexp.line,
:column => regexp.column,
:value => value,
:line => line,
:column => col,
:modes => modes_array,
:code => code(lineno)
:code => code(line)
)
end

Expand Down Expand Up @@ -462,38 +477,23 @@ def on_assign(variable, value)
#
def on_massign(variables, values)
assignments = []
assigned = false

variables.each_with_index do |variable, index|
value = nil

# Determine what value to use for the current variable.
if values.is_a?(Array) and values[index]
value = values[index]

# A single value (e.g. an array or number) is assigned to multiple
# variables.
elsif values.is_a?(Token::Token)
# A token with a list of values is being assigned (e.g. an array).
if values.value.is_a?(Array) and values.value[index]
value = values.value[index]

# A single value is being assigned such as `foo, bar = 10`.
elsif !assigned
value = values
assigned = true
end
end

# Values set using expand assignments are always arrays.
if variable.expand and value
value = Token::Token.new(
variables = variables.flatten
assigned = []
expander = nil
value_index = 0

variables.each_with_index do |variable, var_index|
if variable.expand
expander = var_index
value = Token::Token.new(
:type => :array,
:line => value.line,
:column => value.column,
:code => value.code,
:value => [value]
:line => variable.line,
:column => variable.column,
:code => variable.code,
:value => []
)
else
value = nil
end

assignments << Token::AssignmentToken.new(
Expand All @@ -502,11 +502,42 @@ def on_massign(variables, values)
:column => variable.column,
:code => variable.code,
:type => variable.type,
:expand => variable.expand,
:value => value,
:expand => variable.expand,
:value => value
)
end

# Unpack array based values and ensure that `values` is always an array.
if values.is_a?(Token::Token) and values.type == :array
values = values.value
elsif values.is_a?(Token::Token)
values = [values]
end

equal_length = assignments.length == values.length

# Assign the values to each variable. The remaining values will be
# assigned to the expanding variable.
assignments.each do |assignment|
if !assignment.expand and values[value_index]
assignment.value = values[value_index]

assigned << values[value_index]
value_index += 1
end

# THINK: I really wonder if this is the way to make sure the expander
# value is assigned correctly.
if assignment.expand and equal_length
value_index += 1
end
end

# Assign the remaining values to the expander variable.
if expander
assignments[expander].value.value = values - assigned
end

return assignments
end

Expand Down
183 changes: 183 additions & 0 deletions spec/ruby-lint/parser/expander_assignments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
require File.expand_path('../../../helper', __FILE__)

describe 'Rlint::Parser' do
it 'Parse the assignment of a value to a local and * variable' do
ast = RubyLint::Parser.new('a, * = 10').parse[0]

ast.class.should == Array
ast.length.should == 1

token = ast[0]

token.class.should == RubyLint::Token::AssignmentToken
token.type.should == :local_variable
token.name.should == 'a'

token.value.class.should == RubyLint::Token::Token
token.value.type.should == :integer
token.value.value.should == '10'
end

it 'Parse a left hand expand assignment' do
tokens = RubyLint::Parser.new('*numbers = 10').parse[0]

tokens.class.should == Array
tokens.length.should == 1

token = tokens[0]

token.class.should == RubyLint::Token::AssignmentToken
token.type.should == :local_variable
token.expand.should == true
token.name.should == 'numbers'

token.value.class.should == RubyLint::Token::Token
token.value.type.should == :array

token.value.value.class.should == Array
token.value.value.length.should == 1

val = token.value.value[0]

val.class.should == RubyLint::Token::Token
val.type.should == :integer
val.value.should == '10'
end

it 'Parse a left hand expand and local variable assignment' do
tokens = RubyLint::Parser.new('number, *numbers = 10').parse[0]

tokens.class.should == Array
tokens.length.should == 2

number = tokens[0]
numbers = tokens[1]

number.class.should == RubyLint::Token::AssignmentToken
number.name.should == 'number'
number.type.should == :local_variable
number.expand.should == false

number.value.class.should == RubyLint::Token::Token
number.value.type.should == :integer
number.value.value.should == '10'

numbers.class.should == RubyLint::Token::AssignmentToken
numbers.name.should == 'numbers'
numbers.type.should == :local_variable
numbers.expand.should == true

numbers.value.class.should == RubyLint::Token::Token
numbers.value.type.should == :array
numbers.value.value.length.should == 0
end

it 'Parse a mass assignment using an expand variable on the left' do
tokens = RubyLint::Parser.new('*numbers, number, numberx = 10').parse[0]

tokens.class.should == Array
tokens.length.should == 3

numbers = tokens[0]
number = tokens[1]
numberx = tokens[2]

numbers.class.should == RubyLint::Token::AssignmentToken
numbers.name.should == 'numbers'
numbers.type.should == :local_variable
numbers.expand.should == true

numbers.value.class.should == RubyLint::Token::Token
numbers.value.type.should == :array
numbers.value.value.class.should == Array
numbers.value.value.length.should == 0

number.class.should == RubyLint::Token::AssignmentToken
number.name.should == 'number'
number.type.should == :local_variable
number.expand.should == false

number.value.class.should == RubyLint::Token::Token
number.value.type.should == :integer
number.value.value.should == '10'

numberx.class.should == RubyLint::Token::AssignmentToken
numberx.name.should == 'numberx'
numberx.type.should == :local_variable
numberx.expand.should == false

numberx.value.nil?.should == true
end

it 'Parse the assignment of 2 values to 2 variables using an expander' do
tokens = RubyLint::Parser.new('*numbers, number = 10, 20').parse[0]

tokens.class.should == Array
tokens.length.should == 2

numbers = tokens[0]
number = tokens[1]

numbers.class.should == RubyLint::Token::AssignmentToken
numbers.name.should == 'numbers'
numbers.type.should == :local_variable

numbers.value.class.should == RubyLint::Token::Token
numbers.value.type.should == :array

numbers.value.value.class.should == Array
numbers.value.value.length.should == 1

numbers_val = numbers.value.value[0]

numbers_val.class.should == RubyLint::Token::Token
numbers_val.type.should == :integer
numbers_val.value.should == '10'

number.class.should == RubyLint::Token::AssignmentToken
number.name.should == 'number'
number.type.should == :local_variable

number.value.class.should == RubyLint::Token::Token
number.value.type.should == :integer
number.value.value.should == '20'
end

it 'Parse the assignment of 2 values to 3 variables using an expander' do
tokens = RubyLint::Parser.new('*numbers, number, numberx = 10, 20') \
.parse[0]

tokens.class.should == Array
tokens.length.should == 3

numbers = tokens[0]
number = tokens[1]
numberx = tokens[2]

numbers.class.should == RubyLint::Token::AssignmentToken
numbers.name.should == 'numbers'
numbers.type.should == :local_variable

numbers.value.class.should == RubyLint::Token::Token
numbers.value.type.should == :array

numbers.value.value.class.should == Array
numbers.value.value.length.should == 0

number.class.should == RubyLint::Token::AssignmentToken
number.name.should == 'number'
number.type.should == :local_variable

number.value.class.should == RubyLint::Token::Token
number.value.type.should == :integer
number.value.value.should == '10'

numberx.class.should == RubyLint::Token::AssignmentToken
numberx.name.should == 'numberx'
numberx.type.should == :local_variable

numberx.value.class.should == RubyLint::Token::Token
numberx.value.type.should == :integer
numberx.value.value.should == '20'
end
end
Loading

0 comments on commit 348f07e

Please sign in to comment.