Class: Tailor::LexedLine

Inherits:
Array
  • Object
show all
Includes:
LexerConstants
Defined in:
lib/tailor/lexed_line.rb

Overview

This class provides methods for finding info about the current line. It works off the format that results from Lexer.

Constant Summary

Constants included from LexerConstants

Tailor::LexerConstants::CONTINUATION_KEYWORDS, Tailor::LexerConstants::KEYWORDS_AND_MODIFIERS, Tailor::LexerConstants::KEYWORDS_TO_INDENT, Tailor::LexerConstants::LOOP_KEYWORDS, Tailor::LexerConstants::MODIFIERS, Tailor::LexerConstants::MULTILINE_OPERATORS

Instance Method Summary collapse

Constructor Details

#initialize(lexed_file, lineno) ⇒ LexedLine

Returns a new instance of LexedLine.



13
14
15
16
# File 'lib/tailor/lexed_line.rb', line 13

def initialize(lexed_file, lineno)
  @lineno = lineno
  super(current_line_lex(lexed_file, lineno))
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &blk) ⇒ Object

Allows for calling a couple styles of methods:

  • #ends_with_(.+)? - Allows for checking if the line ends with (.+)

  • #only_(.+)? - Allows for checking if the line is only spaces and (.+)



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/tailor/lexed_line.rb', line 106

def method_missing(meth, *args, &blk)
  if meth.to_s =~ /^ends_with_(.+)\?$/
    event = "on_#{$1}".to_sym

    if event == :on_ignored_nl || event == :on_nl
      does_line_end_with(event, false)
    else
      does_line_end_with event
    end
  elsif meth.to_s =~ /^only_(.+)\?$/
    event = "on_#{$1}".to_sym
    is_line_only_a(event)
  else
    super(meth, *args, &blk)
  end
end

Instance Method Details

#comment_line?Boolean

Returns:

  • (Boolean)


35
36
37
# File 'lib/tailor/lexed_line.rb', line 35

def comment_line?
  first_non_space_element[1] == :on_comment
end

#contains_keyword_to_indent?Boolean

Returns true if the line contains an keyword and it is in +KEYWORDS_TO_INDENT.

Returns:

  • (Boolean)

    true if the line contains an keyword and it is in +KEYWORDS_TO_INDENT.



147
148
149
150
151
# File 'lib/tailor/lexed_line.rb', line 147

def contains_keyword_to_indent?
  self.any? do |e|
    e[1] == :on_kw && KEYWORDS_TO_INDENT.include?(e[2])
  end
end

#current_line_lex(lexed_output, lineno) ⇒ Array

Parameters:

  • lexed_output (Array)

    The lexed output for the whole file.

Returns:

  • (Array)


20
21
22
# File 'lib/tailor/lexed_line.rb', line 20

def current_line_lex(lexed_output, lineno)
  lexed_output.find_all { |token| token.first.first == lineno }.uniq
end

#does_line_end_with(event, exclude_newlines = true) ⇒ Boolean

Returns:

  • (Boolean)


76
77
78
79
80
81
82
83
84
85
86
# File 'lib/tailor/lexed_line.rb', line 76

def does_line_end_with(event, exclude_newlines=true)
  if exclude_newlines
    if last_non_line_feed_event.first.empty?
      false
    else
      last_non_line_feed_event[1] == event
    end
  else
    self.last[1] == :on_ignored_nl || self.last[1] == :on_nl
  end
end

#end_of_multi_line_string?Boolean

Determines if the current lexed line is just the end of a tstring.

Returns:

  • (Boolean)

    true if the line contains a :on_tstring_end and not a :on_tstring_beg.



239
240
241
242
# File 'lib/tailor/lexed_line.rb', line 239

def end_of_multi_line_string?
  self.any? { |e| e[1] == :on_tstring_end } &&
    self.none? { |e| e[1] == :on_tstring_beg }
end

#ends_with_modifier_kw?Boolean

Checks to see if the line ends with a keyword, and that the keyword is used as a modifier.

Returns:

  • (Boolean)


66
67
68
69
70
71
72
73
# File 'lib/tailor/lexed_line.rb', line 66

def ends_with_modifier_kw?
  return false unless ends_with_kw?

  token = Tailor::Lexer::Token.new(last.last,
    { full_line_of_text: to_s })

  token.modifier_keyword?
end

#ends_with_op?Boolean

Checks to see if the current line ends with an operator (not counting the newline that might come after it).

Returns:

  • (Boolean)

    true if the line ends with an operator; false if not.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/tailor/lexed_line.rb', line 43

def ends_with_op?
  lexed_line = self.dup
  tokens_in_line = lexed_line.map { |e| e[1] }

  until tokens_in_line.last != (:on_ignored_nl || :on_nl)
    tokens_in_line.pop
    lexed_line.pop
  end

  return false if lexed_line.empty?

  if MULTILINE_OPERATORS.include?(lexed_line.last.last) &&
    tokens_in_line.last == :on_op
    true
  else
    false
  end
end

#event_at(column) ⇒ Array

Returns The event at the given column.

Parameters:

  • column (Fixnum)

    Number of the column to get the event for.

Returns:

  • (Array)

    The event at the given column.



177
178
179
# File 'lib/tailor/lexed_line.rb', line 177

def event_at(column)
  self.find { |e| e.first.last == column }
end

#event_index(column) ⇒ Fixnum

Useful for inspecting events relevant to this one.

Examples:

i = lexed_line.event_index(11)
previous_event = lexed_line.at(i - 1)

Parameters:

  • column (Fixnum)

    Number of the column of which event to get the index for.

Returns:

  • (Fixnum)

    The index within self that the event is at.



189
190
191
192
# File 'lib/tailor/lexed_line.rb', line 189

def event_index(column)
  column_event = self.event_at column
  self.index(column_event)
end

#first_non_space_elementArray

Gets the first non-space element from a line of lexed output.

Returns:

  • (Array)

    The element; nil if none is found.



126
127
128
129
130
# File 'lib/tailor/lexed_line.rb', line 126

def first_non_space_element
  self.find do |e|
    e[1] != :on_sp && e[1] != :on_nl && e[1] != :on_ignored_nl
  end
end

#is_line_only_a(event) ⇒ Boolean

Checks to see if the line contains only event (where it may or may not be preceded by spaces, and is proceeded by a newline).

Parameters:

  • event (Symbol)

    The type of event to check for.

Returns:

  • (Boolean)


93
94
95
96
97
98
99
100
101
# File 'lib/tailor/lexed_line.rb', line 93

def is_line_only_a(event)
  last_event = last_non_line_feed_event
  return false if last_event[1] != event

  index = event_index(last_event.first.last)
  previous_event = self.at(index - 1)

  previous_event.first.last.zero? || previous_event.first.last.nil?
end

#keyword_is_symbol?Boolean

When Ripper lexes a Symbol, it generates one event for :on_symbeg, which is the ‘:’ token, and one for the name of the Symbol. Since your Symbol name can be anything, the second event could be something like “class”, in which case :on_kw will get called and probably result in unexpected behavior.

This assumes the keyword in question is the last event in the line.

Returns:

  • (Boolean)


253
254
255
256
257
258
259
260
261
262
# File 'lib/tailor/lexed_line.rb', line 253

def keyword_is_symbol?
  current_index = self.index(self.last)
  previous_event = self.at(current_index - 1)

  return false if previous_event.nil?
  return false unless self.last[1] == :on_kw
  return false unless previous_event[1] == :on_symbeg

  true
end

#last_non_line_feed_eventArray

Returns The lexed event that represents the last event in the line that’s not a line-feed. Line-feed events are signified by :on_nl and on_ignored_nl events, and by :on_sp events when they equal +“\n” (which occurs when a line is broken by a backslash).

Returns:

  • (Array)

    The lexed event that represents the last event in the line that’s not a line-feed. Line-feed events are signified by :on_nl and on_ignored_nl events, and by :on_sp events when they equal +“\n” (which occurs when a line is broken by a backslash).



157
158
159
160
161
162
163
164
165
# File 'lib/tailor/lexed_line.rb', line 157

def last_non_line_feed_event
  events = self.find_all do |e|
    e[1] != :on_nl &&
      e[1] != :on_ignored_nl &&
      e.last != "\\\n"
  end

  events.last || [[]]
end

#line_lengthFixnum

Returns The length of the line minus the \n.

Returns:

  • (Fixnum)

    The length of the line minus the \n.



168
169
170
171
172
173
# File 'lib/tailor/lexed_line.rb', line 168

def line_length
  event = last_non_line_feed_event
  return 0 if event.first.empty?

  event.first.last + event.last.size
end

#loop_with_do?Boolean

Checks to see if the current line is a keyword loop (for, while, until) that uses the optional ‘do’ at the end of the statement.

Returns:

  • (Boolean)


136
137
138
139
140
141
142
143
# File 'lib/tailor/lexed_line.rb', line 136

def loop_with_do?
  keyword_elements = self.find_all { |e| e[1] == :on_kw }
  keyword_tokens = keyword_elements.map { |e| e.last }
  loop_start = keyword_tokens.any? { |t| LOOP_KEYWORDS.include? t }
  with_do = keyword_tokens.any? { |t| t == 'do' }

  loop_start && with_do
end

#only_spaces?Boolean

Looks at self and determines if it’ s a line of just space characters: spaces, newlines.

Returns:

  • (Boolean)


28
29
30
31
32
# File 'lib/tailor/lexed_line.rb', line 28

def only_spaces?
  element = first_non_space_element
  log "first non-space element '#{element}'"
  element.nil? || element.empty?
end

#remove_trailing_comment(file_text) ⇒ LexedLine

If a trailing comment exists in the line, remove it and the spaces that come before it. This is necessary, as Ripper doesn’t trigger an event for the end of the line when the line ends with a comment. Without this observers that key off ending the line will never get triggered, and thus style won’t get checked for that line.

Parameters:

  • file_text (String)

    The whole file’s worth of text. Required in order to be able to reconstruct the context in which the line exists.

Returns:

  • (LexedLine)

    The current lexed line, but with the trailing comment removed.



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/tailor/lexed_line.rb', line 209

def remove_trailing_comment(file_text)
  file_lines = file_text.split("\n")
  lineno = self.last.first.first
  column = self.last.first.last
  log "Removing comment event at #{lineno}:#{column}."

  comment_index = event_index(column)
  self.delete_at(comment_index)
  self.insert(comment_index, [[lineno, column], :on_nl, "\n"])
  log "Inserted newline for comma; self is now #{self.inspect}"

  if self.at(comment_index - 1)[1] == :on_sp
    self.delete_at(comment_index - 1)
  end

  new_text = self.to_s
  log "New line as text: '#{new_text}'"

  file_lines.delete_at(lineno - 1)
  file_lines.insert(lineno - 1, new_text)
  file_lines = file_lines.join("\n")

  ripped_output = ::Ripper.lex(file_lines)
  LexedLine.new(ripped_output, lineno)
end

#to_sString

Returns The string reassembled from self’s tokens.

Returns:

  • (String)

    The string reassembled from self’s tokens.



195
196
197
# File 'lib/tailor/lexed_line.rb', line 195

def to_s
  self.inject('') { |new_string, e| new_string << e.last }
end