Class: TwistyPuzzles::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/twisty_puzzles/parser.rb

Overview

Parser for commutators and algorithms.

Constant Summary collapse

OPENING_BRACKET =

rubocop:disable Metrics/ClassLength

'['
OPENING_PAREN =
'('
CLOSING_BRACKET =
']'
CLOSING_PAREN =
')'
SLASH =
'/'
COMMA =
','
PLUS =
'+'
SETUP_SEPARATORS =
%w[; :].freeze
PURE_SEPARATORS =
[SLASH, COMMA].freeze
SEPARATORS =
(SETUP_SEPARATORS + PURE_SEPARATORS).freeze
TIMES =
'*'

Instance Method Summary collapse

Constructor Details

#initialize(alg_string, move_parser) ⇒ Parser

Returns a new instance of Parser.



28
29
30
31
32
# File 'lib/twisty_puzzles/parser.rb', line 28

def initialize(alg_string, move_parser)
  @alg_string = alg_string
  @scanner = StringScanner.new(alg_string)
  @move_parser = move_parser
end

Instance Method Details

#check_eos(parsed_object) ⇒ Object



112
113
114
# File 'lib/twisty_puzzles/parser.rb', line 112

def check_eos(parsed_object)
  complain("end of #{parsed_object}") unless @scanner.eos?
end

#complain(parsed_object) ⇒ Object



104
105
106
107
108
109
110
# File 'lib/twisty_puzzles/parser.rb', line 104

def complain(parsed_object)
  raise CommutatorParseError, <<~ERROR.chomp
    Couldn't parse #{parsed_object} here:
      #{@alg_string}
      #{' ' * @scanner.pos}^"
  ERROR
end

#parse_algorithmObject



151
152
153
154
# File 'lib/twisty_puzzles/parser.rb', line 151

def parse_algorithm
  skip_spaces
  parse_moves_with_triggers
end

#parse_close_bracketObject



120
121
122
# File 'lib/twisty_puzzles/parser.rb', line 120

def parse_close_bracket
  complain('end of commutator') unless @scanner.getch == CLOSING_BRACKET
end

#parse_close_parenObject



38
39
40
# File 'lib/twisty_puzzles/parser.rb', line 38

def parse_close_paren
  complain('end of trigger') unless @scanner.getch == CLOSING_PAREN
end

#parse_commutatorObject



124
125
126
127
128
129
130
131
# File 'lib/twisty_puzzles/parser.rb', line 124

def parse_commutator
  skip_spaces
  if @scanner.peek(1) == OPENING_BRACKET
    parse_commutator_internal
  else
    parse_commutator_no_brackets
  end
end

#parse_commutator_internalObject



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/twisty_puzzles/parser.rb', line 226

def parse_commutator_internal
  skip_spaces
  parse_open_bracket
  setup_or_first_part = parse_nonempty_moves_with_triggers
  skip_spaces
  separator = parse_separator
  comm = parse_commutator_internal_after_separator(setup_or_first_part, separator)
  skip_spaces
  parse_close_bracket
  skip_spaces
  return parse_commutator_sum_with_prefix(comm) if @scanner.peek(1) == PLUS

  complain('end of commutator') unless @scanner.eos?
  comm
end

#parse_commutator_internal_after_separator(setup_or_first_part, separator) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
# File 'lib/twisty_puzzles/parser.rb', line 198

def parse_commutator_internal_after_separator(setup_or_first_part, separator)
  if SETUP_SEPARATORS.include?(separator)
    inner_commutator = parse_setup_commutator_inner
    SetupCommutator.new(setup_or_first_part, inner_commutator)
  elsif PURE_SEPARATORS.include?(separator)
    second_part = parse_nonempty_moves_with_triggers
    pseudo_pure_commutator(separator, setup_or_first_part, second_part)
  else
    complain('end of setup or middle of pure commutator') unless @scanner.eos?
  end
end

#parse_commutator_no_bracketsObject



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/twisty_puzzles/parser.rb', line 133

def parse_commutator_no_brackets
  setup_or_first_part_or_algorithm = parse_moves_with_triggers
  skip_spaces
  if @scanner.eos? || !SEPARATORS.include?(@scanner.peek(1))
    return FakeCommutator.new(setup_or_first_part_or_algorithm)
  end

  setup_or_first_part = setup_or_first_part_or_algorithm
  complain('move') if setup_or_first_part.empty?
  separator = parse_separator
  comm = parse_commutator_internal_after_separator(setup_or_first_part, separator)
  skip_spaces
  return parse_commutator_sum_with_prefix(comm) if @scanner.peek(1) == PLUS

  complain('end of commutator') unless @scanner.eos?
  comm
end

#parse_commutator_sum_with_prefix(commutator) ⇒ Object



246
247
248
249
250
251
# File 'lib/twisty_puzzles/parser.rb', line 246

def parse_commutator_sum_with_prefix(commutator)
  skip_spaces
  parse_plus
  skip_spaces
  CommutatorSequence.new([commutator, parse_commutator])
end

#parse_factorObject



46
47
48
49
50
# File 'lib/twisty_puzzles/parser.rb', line 46

def parse_factor
  number = @scanner.scan(/\d+/)
  complain('factor of multiplier') unless number
  Integer(number, 10)
end

#parse_move_internalObject



253
254
255
256
257
258
# File 'lib/twisty_puzzles/parser.rb', line 253

def parse_move_internal
  move = @scanner.scan(@move_parser.regexp)
  return unless move

  @move_parser.parse_move(move)
end

#parse_movesObject

Parses a series of moves.



94
95
96
97
98
99
100
101
102
# File 'lib/twisty_puzzles/parser.rb', line 94

def parse_moves
  moves = []
  while (m = begin skip_spaces; parse_move_internal end)
    moves.push(m)
  end
  skip_spaces
  moves *= parse_multiplier if @scanner.peek(1) == TIMES
  Algorithm.new(moves)
end

#parse_moves_with_triggersObject

Parses at least one move and allows for triggers in parentheses.



77
78
79
80
81
82
83
84
# File 'lib/twisty_puzzles/parser.rb', line 77

def parse_moves_with_triggers
  skip_spaces
  if @scanner.peek(1) == OPENING_PAREN
    parse_trigger + parse_moves_with_triggers
  else
    parse_moves
  end
end

#parse_multiplierObject



52
53
54
55
56
57
# File 'lib/twisty_puzzles/parser.rb', line 52

def parse_multiplier
  skip_spaces
  parse_times
  skip_spaces
  parse_factor
end

#parse_nonempty_moves_with_triggersObject

Parses at least one move.



87
88
89
90
91
# File 'lib/twisty_puzzles/parser.rb', line 87

def parse_nonempty_moves_with_triggers
  moves = parse_moves_with_triggers
  complain('move') if moves.empty?
  moves
end

#parse_open_bracketObject



116
117
118
# File 'lib/twisty_puzzles/parser.rb', line 116

def parse_open_bracket
  complain('beginning of commutator') unless @scanner.getch == OPENING_BRACKET
end

#parse_open_parenObject



34
35
36
# File 'lib/twisty_puzzles/parser.rb', line 34

def parse_open_paren
  complain('beginning of trigger') unless @scanner.getch == OPENING_PAREN
end

#parse_plusObject



242
243
244
# File 'lib/twisty_puzzles/parser.rb', line 242

def parse_plus
  complain('operator of commutator sequence') unless @scanner.getch == PLUS
end

#parse_pure_commutatorObject



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/twisty_puzzles/parser.rb', line 165

def parse_pure_commutator
  skip_spaces
  parse_open_bracket
  first_part = parse_nonempty_moves_with_triggers
  skip_spaces
  separator = parse_pure_separator
  second_part = parse_nonempty_moves_with_triggers
  skip_spaces
  parse_close_bracket
  pseudo_pure_commutator(separator, first_part, second_part)
end

#parse_pure_commutator_no_bracketsObject



177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/twisty_puzzles/parser.rb', line 177

def parse_pure_commutator_no_brackets
  first_part_or_algorithm = parse_moves_with_triggers
  skip_spaces
  if @scanner.eos? || !PURE_SEPARATORS.include?(@scanner.peek(1))
    return FakeCommutator.new(first_part_or_algorithm)
  end

  first_part = first_part_or_algorithm
  complain('move') if first_part.empty?
  separator = parse_pure_separator
  second_part = parse_nonempty_moves_with_triggers
  skip_spaces
  pseudo_pure_commutator(separator, first_part, second_part)
end

#parse_pure_separatorObject



192
193
194
195
196
# File 'lib/twisty_puzzles/parser.rb', line 192

def parse_pure_separator
  separator = @scanner.getch
  complain('middle of pure commutator') unless PURE_SEPARATORS.include?(separator)
  separator
end

#parse_separatorObject



220
221
222
223
224
# File 'lib/twisty_puzzles/parser.rb', line 220

def parse_separator
  separator = @scanner.getch
  complain('separator between commutator parts') unless SEPARATORS.include?(separator)
  separator
end

#parse_setup_commutator_innerObject



156
157
158
159
160
161
162
163
# File 'lib/twisty_puzzles/parser.rb', line 156

def parse_setup_commutator_inner
  skip_spaces
  if @scanner.peek(1) == OPENING_BRACKET
    parse_pure_commutator
  else
    parse_pure_commutator_no_brackets
  end
end

#parse_timesObject



42
43
44
# File 'lib/twisty_puzzles/parser.rb', line 42

def parse_times
  complain('times symbol of multiplier') unless @scanner.getch == TIMES
end

#parse_triggerObject



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/twisty_puzzles/parser.rb', line 59

def parse_trigger
  parse_open_paren
  skip_spaces
  moves = parse_moves_with_triggers
  skip_spaces
  parse_close_paren
  skip_spaces
  case @scanner.peek(1)
  when TIMES
    moves * parse_multiplier
  when ('0'..'9')
    moves * parse_factor
  else
    moves
  end
end

#pseudo_pure_commutator(separator, first_part, second_part) ⇒ Object



210
211
212
213
214
215
216
217
218
# File 'lib/twisty_puzzles/parser.rb', line 210

def pseudo_pure_commutator(separator, first_part, second_part)
  if separator == COMMA
    PureCommutator.new(first_part, second_part)
  elsif separator == SLASH
    SlashCommutator.new(first_part, second_part)
  else
    complain('middle of pure commutator') unless PURE_SEPARATORS.include?(separator)
  end
end

#skip_spacesObject



260
261
262
# File 'lib/twisty_puzzles/parser.rb', line 260

def skip_spaces
  @scanner.skip(/\s+/)
end