Class: Layo::Parser

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tokenizer) ⇒ Parser

Returns a new instance of Parser.



6
7
8
# File 'lib/layo/parser.rb', line 6

def initialize(tokenizer)
  @tokenizer, @functions = tokenizer, {}
end

Instance Attribute Details

#functionsObject (readonly)

Returns the value of attribute functions.



4
5
6
# File 'lib/layo/parser.rb', line 4

def functions
  @functions
end

#tokenizerObject

Returns the value of attribute tokenizer.



3
4
5
# File 'lib/layo/parser.rb', line 3

def tokenizer
  @tokenizer
end

Instance Method Details

#expect_token(*types) ⇒ Object



63
64
65
66
67
# File 'lib/layo/parser.rb', line 63

def expect_token(*types)
  token = @tokenizer.next
  raise UnexpectedTokenError, token unless types.include?(token[:type])
  token
end

#next_expressionObject

Returns internal name of the next expression



281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/layo/parser.rb', line 281

def next_expression
  return 'binary' if @tokenizer.try([
    :sum_of, :diff_of, :produkt_of, :quoshunt_of, :mod_of, :biggr_of,
    :smallr_of, :both_of, :either_of, :won_of, :both_saem, :diffrint
  ])
  return 'cast' if @tokenizer.try(:maek)
  return 'constant' if @tokenizer.try([:boolean, :integer, :float, :string])
  return 'identifier' if @tokenizer.try(:identifier)
  return 'nary' if @tokenizer.try([:all_of, :any_of, :smoosh])
  return 'unary' if @tokenizer.try(:not)
  nil
end

#next_statementObject



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/layo/parser.rb', line 87

def next_statement
  return 'assignment' if @tokenizer.try(:identifier, :r)
  return 'break' if @tokenizer.try(:gtfo)
  return 'cast' if @tokenizer.try(:identifier, :is_now_a)
  return 'condition' if @tokenizer.try(:o_rly?)
  return 'declaration' if @tokenizer.try(:i_has_a)
  return 'function' if @tokenizer.try(:how_duz_i)
  return 'input' if @tokenizer.try(:gimmeh)
  return 'loop' if @tokenizer.try(:im_in_yr)
  return 'print' if @tokenizer.try(:visible)
  return 'return' if @tokenizer.try(:found_yr)
  return 'switch' if @tokenizer.try(:wtf?)
  return 'expression' if !next_expression.nil?
  nil
end

#parse_assignment_statementObject



116
117
118
119
120
121
# File 'lib/layo/parser.rb', line 116

def parse_assignment_statement
  attrs = { identifier: expect_token(:identifier)[:data] }
  expect_token(:r)
  attrs[:expression] = parse_expression
  Ast::Statement.new('assignment', attrs)
end

#parse_binary_expressionObject



304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/layo/parser.rb', line 304

def parse_binary_expression
  attrs = {
    operator: expect_token(
      :sum_of, :diff_of, :produkt_of, :quoshunt_of, :mod_of, :biggr_of,
      :smallr_of, :both_of, :either_of, :won_of, :both_saem, :diffrint
    )[:type]
  }
  attrs[:left] = parse_expression
  @tokenizer.next if @tokenizer.peek[:type] == :an
  @tokenizer.unpeek
  attrs[:right] = parse_expression
  Ast::Expression.new('binary', attrs)
end

#parse_blockObject



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

def parse_block
  statements = []
  begin
    skip_newlines
    unless (name = next_statement).nil?
      statements << parse_statement(name)
    end
  end until name.nil?
  Ast::Block.new(statements)
end

#parse_break_statementObject



123
124
125
126
# File 'lib/layo/parser.rb', line 123

def parse_break_statement
  expect_token(:gtfo)
  Ast::Statement.new('break')
end

#parse_cast_expressionObject



318
319
320
321
322
323
324
# File 'lib/layo/parser.rb', line 318

def parse_cast_expression
  expect_token(:maek)
  attrs = { being_casted: parse_expression }
  expect_token(:a)
  attrs[:to] = expect_token(:noob, :troof, :numbr, :numbar, :yarn)[:type]
  Ast::Expression.new('cast', attrs)
end

#parse_cast_statementObject



128
129
130
131
132
133
# File 'lib/layo/parser.rb', line 128

def parse_cast_statement
  attrs = { identifier: expect_token(:identifier)[:data] }
  expect_token(:is_now_a)
  attrs[:to] = expect_token(:noob, :troof, :numbr, :numbar, :yarn)[:type]
  Ast::Statement.new('cast', attrs)
end

#parse_condition_statementObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/layo/parser.rb', line 135

def parse_condition_statement
  expect_token(:o_rly?)
  expect_token(:newline)
  expect_token(:ya_rly)
  expect_token(:newline)
  attrs = { then: parse_block, elseif: [] }
  while @tokenizer.peek[:type] == :mebbe
    expect_token(:mebbe)
    condition = parse_expression
    expect_token(:newline)
    attrs[:elseif] << { condition: condition, block: parse_block }
  end
  @tokenizer.unpeek
  if @tokenizer.peek[:type] == :no_wai
    expect_token(:no_wai)
    expect_token(:newline)
    attrs[:else] = parse_block
  end
  @tokenizer.unpeek
  expect_token(:oic)
  Ast::Statement.new('condition', attrs)
end

#parse_constant_expressionObject



326
327
328
329
# File 'lib/layo/parser.rb', line 326

def parse_constant_expression
  token = expect_token(:boolean, :integer, :float, :string)
  Ast::Expression.new('constant', { vtype: token[:type], value: token[:data] })
end

#parse_declaration_statementObject



158
159
160
161
162
163
164
165
166
167
# File 'lib/layo/parser.rb', line 158

def parse_declaration_statement
  expect_token(:i_has_a)
  attrs = { identifier: expect_token(:identifier)[:data] }
  if @tokenizer.peek[:type] == :itz
    @tokenizer.next
    attrs[:initialization] = parse_expression
  end
  @tokenizer.unpeek
  Ast::Statement.new('declaration', attrs)
end

#parse_expression(name = nil) ⇒ Object



294
295
296
297
298
299
300
301
302
# File 'lib/layo/parser.rb', line 294

def parse_expression(name = nil)
  token = @tokenizer.peek
  @tokenizer.unpeek
  name = next_expression unless name
  unless name
    raise SyntaxError.new(token[:line], token[:pos], 'Expected expression')
  end
  send("parse_#{name}_expression".to_sym)
end

#parse_expression_statementObject



169
170
171
172
# File 'lib/layo/parser.rb', line 169

def parse_expression_statement
  attrs = { expression: parse_expression }
  Ast::Statement.new('expression', attrs)
end

#parse_function_declarationsObject

Function declarations should be parsed first in order to properly parse argument list and allow calling functions before their definition. So this method should be called as the first pass before parsing begins



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/layo/parser.rb', line 13

def parse_function_declarations
  @functions = {}
  @tokenizer.reset_peek
  until (token = @tokenizer.peek)[:type] == :eof
    if token[:type] == :how_duz_i
      # Function name must follow
      token = @tokenizer.peek
      unless token[:type] == :identifier
        raise UnexpectedTokenError, token
      end
      name = token[:data]
      args = []
      token = @tokenizer.peek
      if token[:type] == :yr
        # Function arguments must follow
        begin
          token = @tokenizer.peek
          unless token[:type] == :identifier
            raise UnexpectedTokenError, token
          end
          args << token[:data]
        end while @tokenizer.peek[:type] == :an_yr
      end
      @tokenizer.unpeek
      @functions[name] = args
      # Newline must follow
      token = @tokenizer.peek
      unless token[:type] == :newline
        raise UnexpectedTokenError, token
      end
    end
  end
  @tokenizer.reset_peek
end

#parse_function_statementObject



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/layo/parser.rb', line 174

def parse_function_statement
  expect_token(:how_duz_i)
  name = expect_token(:identifier)[:data]
  if @functions.has_key?(name)
    # Function definition was parsed in the first pass
    until @tokenizer.next[:type] == :newline; end
    args = @functions[name]
  else
    # Parse argument list as usual
    args = []
    if @tokenizer.peek[:type] == :yr
      begin
        @tokenizer.next
        args << expect_token(:identifier)[:data]
      end while @tokenizer.peek[:type] == :an_yr
    end
    @tokenizer.unpeek
    expect_token(:newline)
    @functions[name] = args
  end
  block = parse_block
  expect_token(:if_u_say_so)
  Ast::Statement.new('function', { name: name, args: args, block: block })
end

#parse_identifier_expressionObject

Identifier expression represents two types of expressions:

variable expression: returns value of variable
function call expression: returns value of function call


334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/layo/parser.rb', line 334

def parse_identifier_expression
  name = expect_token(:identifier)[:data]
  begin
    function = self.functions.fetch(name)
    # Function call
    attrs = { name: name, parameters: [] }
    function.size.times do |c|
      attrs[:parameters] << parse_expression
    end
    return Ast::Expression.new('function', attrs)
  rescue KeyError
    # Variable name
    return Ast::Expression.new('variable', name: name)
  end
end

#parse_input_statementObject



199
200
201
202
203
# File 'lib/layo/parser.rb', line 199

def parse_input_statement
  expect_token(:gimmeh)
  attrs = { identifier: expect_token(:identifier)[:data] }
  Ast::Statement.new('input', attrs)
end

#parse_loop_statementObject



205
206
207
208
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/layo/parser.rb', line 205

def parse_loop_statement
  loop_start = expect_token(:im_in_yr)
  label_begin = expect_token(:identifier)[:data]
  attrs = {}
  if [:uppin, :nerfin, :identifier].include?(@tokenizer.peek[:type])
    attrs[:op] = expect_token(:uppin, :nerfin, :identifier)
    expect_token(:yr)
    attrs[:op] = attrs[:op][:type] == :identifier ? attrs[:op][:data] :
      attrs[:op][:type]
    attrs[:counter] = expect_token(:identifier)[:data]
  end
  @tokenizer.unpeek
  if [:til, :wile].include?(@tokenizer.peek[:type])
    attrs[:guard] = { type: expect_token(:til, :wile)[:type] }
    attrs[:guard][:expression] = parse_expression
  end
  @tokenizer.unpeek
  attrs[:block] = parse_block
  expect_token(:im_outta_yr)
  label_end = expect_token(:identifier)[:data]
  unless label_begin == label_end
    raise SyntaxError.new(
      loop_start[:line], loop_start[:pos],
      "Loop labels don't match: '#{label_begin}' and '#{label_end}'"
    )
  end
  attrs[:label] = label_begin
  Ast::Statement.new('loop', attrs)
end

#parse_nary_expressionObject



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/layo/parser.rb', line 350

def parse_nary_expression
  attrs = { operator: expect_token(:all_of, :any_of, :smoosh)[:type] }
  attrs[:expressions] = [parse_expression]
  while true
    @tokenizer.next if @tokenizer.peek[:type] == :an
    @tokenizer.unpeek
    name = next_expression
    if name.nil? then break else attrs[:expressions] << parse_expression(name) end
  end
  # We need either MKAY or Newline here, but
  # should consume only MKAY if present
  token = @tokenizer.peek
  unless [:mkay, :newline].include?(token[:type])
    raise UnexpectedTokenError, token
  end
  @tokenizer.next if token[:type] == :mkay
  @tokenizer.unpeek
  Ast::Expression.new('nary', attrs)
end

#parse_print_statementObject



235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/layo/parser.rb', line 235

def parse_print_statement
  expect_token(:visible)
  attrs = { expressions: [parse_expression] }
  until (name = next_expression).nil?
    attrs[:expressions] << parse_expression(name)
  end
  attrs[:suppress] = false
  if @tokenizer.peek[:type] == :exclamation
    @tokenizer.next
    attrs[:suppress] = true
  end
  @tokenizer.unpeek
  Ast::Statement.new('print', attrs)
end

#parse_programObject Also known as: parse



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/layo/parser.rb', line 48

def parse_program
  parse_function_declarations
  skip_newlines
  expect_token(:hai)
  version = expect_token(:float)[:data]
  expect_token(:newline)
  block = parse_block
  expect_token(:kthxbye)
  skip_newlines
  expect_token(:eof)
  Ast::Program.new(version, block)
end

#parse_return_statementObject



250
251
252
253
254
# File 'lib/layo/parser.rb', line 250

def parse_return_statement
  expect_token(:found_yr)
  attrs = { expression: parse_expression }
  Ast::Statement.new('return', attrs)
end

#parse_statement(name = nil) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/layo/parser.rb', line 103

def parse_statement(name = nil)
  token = @tokenizer.peek
  @tokenizer.unpeek
  name = next_statement unless name
  unless name
    raise SyntaxError.new(token[:line], token[:pos], 'Expected statement')
  end
  statement = send("parse_#{name}_statement".to_sym)
  expect_token(:newline)
  statement.line = token[:line]
  statement
end

#parse_switch_statementObject



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/layo/parser.rb', line 256

def parse_switch_statement
  expect_token(:wtf?)
  expect_token(:newline)
  parse_case = lambda do
    expect_token(:omg)
    expression = parse_expression('constant')
    expect_token(:newline)
    { expression: expression, block: parse_block }
  end
  attrs = { cases: [parse_case.call] }
  while @tokenizer.peek[:type] == :omg
    attrs[:cases] << parse_case.call
  end
  @tokenizer.unpeek
  if @tokenizer.peek[:type] == :omgwtf
    expect_token(:omgwtf)
    expect_token(:newline)
    attrs[:default] = parse_block
  end
  @tokenizer.unpeek
  expect_token(:oic)
  Ast::Statement.new('switch', attrs)
end

#parse_unary_expressionObject



370
371
372
373
# File 'lib/layo/parser.rb', line 370

def parse_unary_expression
  expect_token(:not)
  Ast::Expression.new('unary', { expression: parse_expression } )
end

#skip_newlinesObject



69
70
71
72
73
74
# File 'lib/layo/parser.rb', line 69

def skip_newlines
  while @tokenizer.peek[:type] == :newline
    @tokenizer.next
  end
  @tokenizer.unpeek
end