layo 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.mkd CHANGED
@@ -5,7 +5,7 @@ Layo is a LOLCODE interpreter written in Ruby. It tries to conform to the
5
5
  everything described there. I purposely did not use any parser generators
6
6
  such as `Racc` for this project.
7
7
 
8
- Latest version is 1.0.0
8
+ Latest version is 1.1.0
9
9
 
10
10
  # Usage
11
11
 
@@ -16,20 +16,43 @@ gem install layo
16
16
  layo program.lol
17
17
  ```
18
18
 
19
- If you don't want to install the gem, you can download sources, unzip somewhere
20
- and run from project's root:
19
+ If you don't want to install the gem, you can download sources, extract somewhere
20
+ and run from the project's root:
21
21
 
22
22
  `bin/layo program.lol`
23
23
 
24
+ Layo treats source files as UTF-8 encoded, if your file contains non-ascii
25
+ characters, be sure to encode it as UTF-8.
26
+
27
+ Without arguments Layo works in interactive mode, i.e. it executes statements
28
+ read from a standard input. A sample session:
29
+
30
+ ```
31
+ [galymzhan@g8host layo]$ bin/layo
32
+ Layo version 1.1.0
33
+ Press Control-C to exit
34
+ > VISIBLE "HAI WORLD!"
35
+ HAI WORLD!
36
+ > I HAS A animal ITZ "cat"
37
+ > BOTH SAEM animal AN "cat", O RLY?
38
+ YA RLY, VISIBLE "I HAV A CAT"
39
+ NO WAI, VISIBLE "J00 SUX"
40
+ OIC
41
+ I HAV A CAT
42
+ > ^CExiting
43
+ ```
44
+
24
45
  Oh, and there are tests too. In order to run them, you have to install `mocha`:
25
- `gem install mocha`. Then go to project's root and execute `rake test`.
46
+ `gem install mocha`. Then go to the project's root and execute `rake test`.
26
47
 
27
48
  # Requirements
28
49
 
29
50
  * Ruby >= 1.9.2
30
51
  * [Mocha](http://mocha.rubyforge.org/) for development
31
52
 
32
- # Example program
53
+ # LOLCODE examples
54
+
55
+ Classical hello world:
33
56
 
34
57
  ```
35
58
  HAI 1.2
@@ -37,6 +60,8 @@ VISIBLE "Hello world!"
37
60
  KTHXBYE
38
61
  ```
39
62
 
63
+ For more examples see `examples` and `spec/source` directories.
64
+
40
65
  # Grammar
41
66
 
42
67
  Layo uses the following context-free grammar in Extended Backus-Naur Form
data/bin/layo CHANGED
@@ -5,18 +5,22 @@ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
5
5
  require 'layo'
6
6
 
7
7
  if ARGV.empty?
8
- puts 'Usage: layo [filename]'
9
- exit
10
- end
11
- raise "File #{ARGV[0]} does not exist" unless File.exists?(ARGV[0])
12
- File.open(ARGV[0]) do |f|
13
- parser = Layo::Parser.new(Layo::Tokenizer.new(Layo::Lexer.new(f)))
14
- interpreter = Layo::Interpreter.new
15
- begin
16
- interpreter.interpret(parser.parse)
17
- rescue Layo::SyntaxError => e
18
- $stderr.puts "Syntax error: #{e}"
19
- rescue Layo::RuntimeError => e
20
- $stderr.puts "Runtime error: #{e}"
8
+ parser = Layo::Parser.new(Layo::Tokenizer.new(Layo::Lexer.new(STDIN)))
9
+ interpreter = Layo::InteractiveInterpreter.new(parser)
10
+ puts "Layo version #{Layo::VERSION}"
11
+ interpreter.interpret
12
+ else
13
+ raise "File #{ARGV[0]} does not exist" unless File.exists?(ARGV[0])
14
+ File.open(ARGV[0]) do |f|
15
+ f.set_encoding(Encoding::UTF_8)
16
+ parser = Layo::Parser.new(Layo::Tokenizer.new(Layo::Lexer.new(f)))
17
+ interpreter = Layo::Interpreter.new
18
+ begin
19
+ interpreter.interpret(parser.parse)
20
+ rescue Layo::SyntaxError => e
21
+ $stderr.puts "Syntax error: #{e}"
22
+ rescue Layo::RuntimeError => e
23
+ $stderr.puts "Runtime error: #{e}"
24
+ end
21
25
  end
22
26
  end
@@ -0,0 +1,27 @@
1
+ OBTW
2
+
3
+ This is solution for the Problem 1 from Project Euler: http://projecteuler.net/problem=1
4
+
5
+ Problem description:
6
+ If we list all the natural numbers below 10 that are multiples of 3 or 5,
7
+ we get 3, 5, 6 and 9. The sum of these multiples is 23.
8
+ Find the sum of all the multiples of 3 or 5 below 1000.
9
+
10
+ TLDR
11
+
12
+ HAI 1.2
13
+
14
+ I HAS A result ITZ 0
15
+
16
+ IM IN YR loop UPPIN YR i TIL BOTH SAEM i AN 1000
17
+ EITHER OF ...
18
+ BOTH SAEM MOD OF i AN 3 AN 0 ...
19
+ AN BOTH SAEM MOD OF i AN 5 AN 0
20
+ O RLY?
21
+ YA RLY
22
+ result R SUM OF result AN i
23
+ OIC
24
+ IM OUTTA YR loop
25
+
26
+ VISIBLE "Result: " result
27
+ KTHXBYE
@@ -0,0 +1,37 @@
1
+ OBTW
2
+
3
+ This is solution for the Problem 2 from Project Euler: http://projecteuler.net/problem=2
4
+
5
+ Problem description:
6
+ Each new term in the Fibonacci sequence is generated by adding the previous
7
+ two terms. By starting with 1 and 2, the first 10 terms will be:
8
+
9
+ 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
10
+
11
+ By considering the terms in the Fibonacci sequence whose values do not exceed
12
+ four million, find the sum of the even-valued terms.
13
+
14
+ TLDR
15
+
16
+ HAI 1.2
17
+ I HAS A a ITZ 1
18
+ I HAS A b ITZ 2
19
+ I HAS A result ITZ 2
20
+
21
+ IM IN YR loop
22
+ b R SUM OF a AN b
23
+ a R DIFF OF b AN a
24
+ BOTH SAEM 0 AN MOD OF b AN 2, O RLY?
25
+ YA RLY
26
+ result R SUM OF result AN b
27
+ OIC
28
+
29
+ DIFFRINT b AN SMALLR OF b AN 4000000, O RLY?
30
+ YA RLY
31
+ GTFO
32
+ OIC
33
+ IM OUTTA YR loop
34
+
35
+ VISIBLE "Result: " result
36
+
37
+ KTHXBYE
@@ -0,0 +1,30 @@
1
+ OBTW
2
+ This is solution for the '99 Bottles Of Beer' challenge from Code Golf:
3
+ http://codegolf.com/99-bottles-of-beer
4
+ TLDR
5
+
6
+ HAI 1.2
7
+
8
+ BTW recursive approach
9
+ HOW DUZ I bottle YR count
10
+ BOTH SAEM count AN 1, O RLY?
11
+ YA RLY
12
+ VISIBLE "1 bottle of beer on the wall, 1 bottle of beer.:)" ...
13
+ "Go to the store and buy some more, 99 bottles of beer on the wall."
14
+ NO WAI
15
+ I HAS A newCount ITZ DIFF OF count AN 1
16
+ BOTH SAEM newCount AN 1, O RLY?
17
+ YA RLY
18
+ I HAS A s ITZ "1 bottle"
19
+ NO WAI
20
+ I HAS A s ITZ ":{newCount} bottles"
21
+ OIC
22
+ VISIBLE ":{count} bottles of beer on the wall, :{count} bottles of beer.:)" ...
23
+ "Take one down and pass it around, :{s} of beer on the wall.:)"
24
+ bottle newCount
25
+ OIC
26
+ IF U SAY SO
27
+
28
+ bottle 99
29
+
30
+ KTHXBYE
@@ -2,11 +2,12 @@ require 'base64'
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'layo'
5
- s.version = '1.0.0'
5
+ s.version = '1.1.0'
6
6
  s.summary = 'LOLCODE interpreter written in plain Ruby'
7
7
  s.description = <<-EOF
8
8
  Layo is a LOLCODE interpreter written in plain Ruby. It tries to conform to
9
9
  the LOLCODE 1.2 specification and supports everything described there.
10
+ Supports interactive mode.
10
11
  EOF
11
12
  s.required_ruby_version = '>= 1.9.2'
12
13
  s.add_development_dependency 'mocha', '~> 0.10.0'
@@ -6,6 +6,12 @@ require_relative 'layo/peekable'
6
6
  require_relative 'layo/lexer'
7
7
  require_relative 'layo/tokenizer'
8
8
  require_relative 'layo/parser'
9
+ require_relative 'layo/base_interpreter'
9
10
  require_relative 'layo/interpreter'
11
+ require_relative 'layo/interactive_interpreter'
10
12
  require_relative 'layo/ast'
11
13
  require_relative 'layo/unicode'
14
+
15
+ module Layo
16
+ VERSION = '1.1.0'
17
+ end
@@ -0,0 +1,348 @@
1
+ module Layo
2
+ class BaseInterpreter
3
+ attr_accessor :input, :output
4
+
5
+ def initialize(input = STDIN, output = STDOUT)
6
+ @input, @output = input, output
7
+ end
8
+
9
+ def create_variable_table
10
+ table = Hash.new do |hash, key|
11
+ raise RuntimeError, "Variable '#{key}' is not declared"
12
+ end
13
+ table['IT'] = { type: :noob, value: nil}
14
+ table
15
+ end
16
+
17
+ # Initializes empty function and variable tables
18
+ def init_tables
19
+ @functions = {}
20
+ @vtable = create_variable_table
21
+ end
22
+
23
+ # Runs piece of code inside guard to catch :break and :return thrown by
24
+ # illegal statements. Also assigns line numbers to RuntimeError's
25
+ def with_guard(&block)
26
+ begin
27
+ illegal = true
28
+ catch(:break) do
29
+ catch(:return) do
30
+ block.call
31
+ illegal = false
32
+ end
33
+ raise RuntimeError, "Illegal return statement" if illegal
34
+ end
35
+ raise RuntimeError, "Illegal break statement" if illegal
36
+ rescue RuntimeError => e
37
+ e.line = @stmt_line
38
+ raise e
39
+ end
40
+ end
41
+
42
+ def eval_block(block)
43
+ block.each do |stmt|
44
+ @stmt_line = stmt.line
45
+ send("eval_#{stmt.type}_stmt", stmt)
46
+ end
47
+ end
48
+
49
+ def eval_assignment_stmt(stmt)
50
+ # We should access by variable name first to ensure that it is defined
51
+ @vtable[stmt.identifier]
52
+ @vtable[stmt.identifier] = eval_expr(stmt.expression)
53
+ end
54
+
55
+ def eval_break_stmt(stmt)
56
+ throw :break
57
+ end
58
+
59
+ def eval_cast_stmt(stmt)
60
+ var = @vtable[stmt.identifier]
61
+ var[:value] = cast(var, stmt.to, false)
62
+ var[:type] = stmt.to
63
+ end
64
+
65
+ def eval_declaration_stmt(stmt)
66
+ if @vtable.has_key?(stmt.identifier)
67
+ raise RuntimeError, "Variable '#{stmt.identifier}' is already declared"
68
+ end
69
+ @vtable[stmt.identifier] = { type: :noob, value: nil }
70
+ unless stmt.initialization.nil?
71
+ @vtable[stmt.identifier] = eval_expr(stmt.initialization)
72
+ end
73
+ end
74
+
75
+ def eval_expression_stmt(stmt)
76
+ @vtable['IT'] = eval_expr(stmt.expression)
77
+ end
78
+
79
+
80
+ def eval_condition_stmt(stmt)
81
+ if cast(@vtable['IT'], :troof)
82
+ # if block
83
+ eval_block(stmt.then)
84
+ else
85
+ # else if blocks
86
+ condition_met = false
87
+ stmt.elseif.each do |elseif|
88
+ condition = eval_expr(elseif[:condition])
89
+ if condition_met = cast(condition, :troof)
90
+ eval_block(elseif[:block])
91
+ break
92
+ end
93
+ end
94
+ unless condition_met || stmt.else.nil?
95
+ # else block
96
+ eval_block(stmt.else)
97
+ end
98
+ end
99
+ end
100
+
101
+ def eval_input_stmt(stmt)
102
+ @vtable[stmt.identifier] = { type: :yarn, value: @input.gets }
103
+ end
104
+
105
+ def eval_loop_stmt(stmt)
106
+ unless stmt.op.nil?
107
+ # Backup any local variable if its name is the same as the counter
108
+ # variable's name
109
+ if @vtable.has_key?(stmt.counter)
110
+ var_backup = @vtable[stmt.counter]
111
+ end
112
+ @vtable[stmt.counter] = { type: :numbr, value: 0 }
113
+ update_op = if stmt.op == :uppin
114
+ lambda { @vtable[stmt.counter][:value] += 1 }
115
+ elsif stmt.op == :nerfin
116
+ lambda { @vtable[stmt.counter][:value] -= 1 }
117
+ else
118
+ lambda {
119
+ @vtable[stmt.counter] = call_func(stmt.op, [@vtable[stmt.counter]])
120
+ }
121
+ end
122
+ end
123
+
124
+ catch :break do
125
+ while true
126
+ unless stmt.guard.nil?
127
+ condition_met = cast(eval_expr(stmt.guard[:expression]), :troof)
128
+ if (stmt.guard[:type] == :wile && !condition_met) or
129
+ (stmt.guard[:type] == :til && condition_met)
130
+ throw :break
131
+ end
132
+ end
133
+ eval_block(stmt.block)
134
+ update_op.call if update_op
135
+ end
136
+ end
137
+ # Restore backed up variable
138
+ unless stmt.op.nil? || var_backup.nil?
139
+ @vtable[stmt.counter] = var_backup
140
+ end
141
+ end
142
+
143
+ def eval_print_stmt(stmt)
144
+ text = ''
145
+ # todo rewrite using map or similar
146
+ stmt.expressions.each do |expr|
147
+ text << cast(eval_expr(expr), :yarn)
148
+ end
149
+ text << "\n" unless stmt.suppress
150
+ @output.print(text)
151
+ end
152
+
153
+ def eval_return_stmt(stmt)
154
+ throw :return, eval_expr(stmt.expression)
155
+ end
156
+
157
+ def eval_switch_stmt(stmt)
158
+ stmt.cases.combination(2) do |c|
159
+ raise RuntimeError, 'Literals must be unique' if c[0] == c[1]
160
+ end
161
+ case_found = false
162
+ it = @vtable['IT']
163
+ stmt.cases.each do |kase|
164
+ unless case_found
165
+ literal = eval_expr(kase[:expression])
166
+ if it == literal
167
+ case_found = true
168
+ end
169
+ end
170
+ if case_found
171
+ breaked = true
172
+ catch :break do
173
+ eval_block(kase[:block])
174
+ breaked = false
175
+ end
176
+ break if breaked
177
+ end
178
+ end
179
+ unless case_found || stmt.default.nil?
180
+ catch :break do
181
+ eval_block(stmt.default)
182
+ end
183
+ end
184
+ end
185
+
186
+ # Casts given variable 'var' into type 'to'
187
+ # Returns only value part of the variable, type will be 'to' anyway
188
+ def cast(var, to, implicit = true)
189
+ return var[:value] if var[:type] == to
190
+ return nil if to == :noob
191
+ case var[:type]
192
+ when :noob
193
+ if implicit && to != :troof
194
+ raise RuntimeError, "NOOB cannot be implicitly cast into #{to.to_s.upcase}"
195
+ end
196
+ return false if to == :troof
197
+ return 0 if to == :numbr
198
+ return 0.0 if to == :numbar
199
+ return ''
200
+ when :troof
201
+ return (var[:value] ? 1 : 0) if to == :numbr
202
+ return (var[:value] ? 1.0 : 0.0) if to == :numbar
203
+ return (var[:value] ? 'WIN' : 'FAIL')
204
+ when :numbr
205
+ return (var[:value].zero? ? false : true) if to == :troof
206
+ return var[:value].to_f if to == :numbar
207
+ return var[:value].to_s
208
+ when :numbar
209
+ return (var[:value].zero? ? false : true) if to == :troof
210
+ return var[:value].to_int if to == :numbr
211
+ # Truncate to 2 digits after decimal point
212
+ return ((var[:value] * 100).floor / 100.0).to_s
213
+ else
214
+ return !var[:value].empty? if to == :troof
215
+ if to == :numbr
216
+ return var[:value].to_i if var[:value].lol_integer?
217
+ raise RuntimeError, "'#{var[:value]}' is not a valid integer"
218
+ end
219
+ return var[:value].to_f if var[:value].lol_float?
220
+ raise RuntimeError, "'#{var[:value]}' is not a valid float"
221
+ end
222
+ end
223
+
224
+ def eval_expr(expr)
225
+ send("eval_#{expr.type}_expr", expr)
226
+ end
227
+
228
+ def eval_binary_expr(expr)
229
+ l = eval_expr(expr.left)
230
+ r = eval_expr(expr.right)
231
+ methods = {
232
+ :sum_of => :+, :diff_of => :-, :produkt_of => :*, :quoshunt_of => :/,
233
+ :mod_of => :modulo, :both_of => :&, :either_of => :|, :won_of => :^,
234
+ :both_saem => :==, :diffrint => :!=
235
+ }
236
+ case expr.operator
237
+ when :sum_of, :diff_of, :produkt_of, :quoshunt_of, :mod_of, :biggr_of, :smallr_of
238
+ type = l[:type] == :numbar || r[:type] == :numbar ||
239
+ (l[:type] == :yarn && l[:value].lol_float?) ||
240
+ (r[:type] == :yarn && r[:value].lol_float?) ? :numbar : :numbr
241
+ l, r = cast(l, type), cast(r, type)
242
+ if expr.operator == :biggr_of
243
+ value = [l, r].max
244
+ elsif expr.operator == :smallr_of
245
+ value = [l, r].min
246
+ else
247
+ value = l.send(methods[expr.operator], r)
248
+ end
249
+ when :both_saem, :diffrint
250
+ type = :troof
251
+ if (l[:type] == :numbr && r[:type] == :numbar) ||
252
+ (l[:type] == :numbar && r[:type] == :numbr)
253
+ l, r = cast(l, :numbar), cast(r, :numbar)
254
+ elsif l[:type] != r[:type]
255
+ raise RuntimeError, 'Operands must have same type'
256
+ end
257
+ value = l.send(methods[expr.operator], r)
258
+ else
259
+ type = :troof
260
+ l, r = cast(l, :troof), cast(r, :troof)
261
+ value = l.send(methods[expr.operator], r)
262
+ end
263
+ { type: type, value: value }
264
+ end
265
+
266
+ def eval_cast_expr(expr)
267
+ casted_expr = eval_expr(expr.being_casted)
268
+ { type: expr.to, value: cast(casted_expr, expr.to, false) }
269
+ end
270
+
271
+ def eval_constant_expr(expr)
272
+ mapping = { boolean: :troof, string: :yarn, integer: :numbr, float: :numbar }
273
+ value = expr.vtype == :string ? interpolate_string(expr.value) : expr.value
274
+ { type: mapping[expr.vtype], value: value }
275
+ end
276
+
277
+ def eval_function_expr(expr)
278
+ parameters = []
279
+ expr.parameters.each do |param|
280
+ parameters << eval_expr(param)
281
+ end
282
+ call_func(expr.name, parameters)
283
+ end
284
+
285
+ def call_func(name, arguments)
286
+ function = @functions[name]
287
+ # Replace variable table by 'clean' variable table inside functions
288
+ old_table = @vtable
289
+ @vtable = create_variable_table
290
+ function[:args].each_index do |index|
291
+ @vtable[function[:args][index]] = arguments[index]
292
+ end
293
+ retval = nil
294
+ retval = catch :return do
295
+ breaked = true
296
+ catch(:break) do
297
+ eval_block(function[:block])
298
+ breaked = false
299
+ end
300
+ retval = { type: :noob, value: nil } if breaked
301
+ end
302
+ retval = @vtable['IT'] if retval.nil?
303
+ @vtable = old_table
304
+ retval
305
+ end
306
+
307
+ def eval_nary_expr(expr)
308
+ case expr.operator
309
+ when :all_of
310
+ type, value = :troof, true
311
+ expr.expressions.each do |operand|
312
+ unless cast(eval_expr(operand), :troof)
313
+ value = false
314
+ break
315
+ end
316
+ end
317
+ when :any_of
318
+ type, value = :troof, false
319
+ expr.expressions.each do |operand|
320
+ if cast(eval_expr(operand), :troof)
321
+ value = true
322
+ break
323
+ end
324
+ end
325
+ when :smoosh
326
+ type, value = :yarn, ''
327
+ expr.expressions.each do |operand|
328
+ value << cast(eval_expr(operand), :yarn)
329
+ end
330
+ end
331
+ { type: type, value: value }
332
+ end
333
+
334
+ def eval_unary_expr(expr)
335
+ # the only unary op in LOLCODE is NOT
336
+ { type: :troof, value: !cast(eval_expr(expr.expression), :troof) }
337
+ end
338
+
339
+ def eval_variable_expr(expr)
340
+ @vtable[expr.name]
341
+ end
342
+
343
+ # Interpolates values of variables in the string
344
+ def interpolate_string(str)
345
+ str.gsub(/:\{([a-zA-Z]\w*)\}/) { cast(@vtable[$1], :yarn, false) }
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,42 @@
1
+ module Layo
2
+ class InteractiveInterpreter < BaseInterpreter
3
+ attr_accessor :parser
4
+
5
+ def initialize(parser)
6
+ @parser = parser
7
+ super
8
+ end
9
+
10
+ def interpret
11
+ init_tables
12
+ @stmt_line = 1
13
+ @output.puts 'Press Control-C to exit'
14
+ while true
15
+ begin
16
+ @output.print ' > '
17
+ break if @parser.tokenizer.try(:eof)
18
+ begin
19
+ statement = @parser.parse_statement
20
+ with_guard { send("eval_#{statement.type}_stmt", statement) }
21
+ end until @parser.tokenizer.lexer.buffer_empty?
22
+ rescue Layo::SyntaxError => e
23
+ @parser.tokenizer.reset
24
+ @parser.tokenizer.lexer.reset
25
+ $stderr.puts "Syntax error: #{e}"
26
+ rescue Layo::RuntimeError => e
27
+ $stderr.puts "Runtime error: #{e}"
28
+ rescue Interrupt
29
+ @output.puts 'Exiting'
30
+ break
31
+ end
32
+ end
33
+ end
34
+
35
+ def eval_function_stmt(stmt)
36
+ if @functions.has_key?(stmt.name)
37
+ raise RuntimeError, "Function '#{stmt.name}' is already declared"
38
+ end
39
+ @functions[stmt.name] = { args: stmt.args, block: stmt.block }
40
+ end
41
+ end
42
+ end
@@ -1,16 +1,10 @@
1
1
  module Layo
2
- class Interpreter
3
- attr_accessor :input, :output
4
-
5
- def initialize(input = STDIN, output = STDOUT)
6
- @input, @output = input, output
7
- end
8
-
2
+ class Interpreter < BaseInterpreter
9
3
  # Interprets program given as an AST node
10
4
  def interpret(program)
11
5
  # We should gather all function definitions along with their bodies
12
6
  # beforehand so we could call them wherever a call appears
13
- @functions = {}
7
+ init_tables
14
8
  program.block.each do |statement|
15
9
  if statement.type == 'function'
16
10
  @functions[statement.name] = {
@@ -21,340 +15,11 @@ module Layo
21
15
  eval_program(program)
22
16
  end
23
17
 
24
- def create_variable_table
25
- table = Hash.new do |hash, key|
26
- raise RuntimeError, "Variable '#{key}' is not declared"
27
- end
28
- table['IT'] = { type: :noob, value: nil}
29
- table
30
- end
31
-
32
18
  def eval_program(program)
33
- @vtable = create_variable_table
34
- begin
35
- illegal = true
36
- catch(:break) do
37
- catch(:return) do
38
- eval_block(program.block)
39
- illegal = false
40
- end
41
- raise RuntimeError, "Illegal return statement" if illegal
42
- end
43
- raise RuntimeError, "Illegal break statement" if illegal
44
- rescue RuntimeError => e
45
- e.line = @stmt_line
46
- raise e
47
- end
48
- end
49
-
50
- def eval_block(block)
51
- block.each do |stmt|
52
- @stmt_line = stmt.line
53
- send("eval_#{stmt.type}_stmt", stmt)
54
- end
55
- end
56
-
57
- def eval_assignment_stmt(stmt)
58
- # We should access by variable name first to ensure that it is defined
59
- @vtable[stmt.identifier]
60
- @vtable[stmt.identifier] = eval_expr(stmt.expression)
61
- end
62
-
63
- def eval_break_stmt(stmt)
64
- throw :break
65
- end
66
-
67
- def eval_cast_stmt(stmt)
68
- var = @vtable[stmt.identifier]
69
- var[:value] = cast(var, stmt.to, false)
70
- var[:type] = stmt.to
71
- end
72
-
73
- def eval_declaration_stmt(stmt)
74
- if @vtable.has_key?(stmt.identifier)
75
- raise RuntimeError, "Variable '#{stmt.identifier}' is already declared"
76
- end
77
- @vtable[stmt.identifier] = { type: :noob, value: nil }
78
- unless stmt.initialization.nil?
79
- @vtable[stmt.identifier] = eval_expr(stmt.initialization)
80
- end
81
- end
82
-
83
- def eval_expression_stmt(stmt)
84
- @vtable['IT'] = eval_expr(stmt.expression)
19
+ with_guard { eval_block(program.block) }
85
20
  end
86
21
 
22
+ # Don't do anything because function table is already ready
87
23
  def eval_function_stmt(stmt); end
88
-
89
- def eval_condition_stmt(stmt)
90
- if cast(@vtable['IT'], :troof)
91
- # if block
92
- eval_block(stmt.then)
93
- else
94
- # else if blocks
95
- condition_met = false
96
- stmt.elseif.each do |elseif|
97
- condition = eval_expr(elseif[:condition])
98
- if condition_met = cast(condition, :troof)
99
- eval_block(elseif[:block])
100
- break
101
- end
102
- end
103
- unless condition_met || stmt.else.nil?
104
- # else block
105
- eval_block(stmt.else)
106
- end
107
- end
108
- end
109
-
110
- def eval_input_stmt(stmt)
111
- @vtable[stmt.identifier] = { type: :yarn, value: @input.gets }
112
- end
113
-
114
- def eval_loop_stmt(stmt)
115
- unless stmt.op.nil?
116
- # Backup any local variable if its name is the same as the counter
117
- # variable's name
118
- if @vtable.has_key?(stmt.counter)
119
- var_backup = @vtable[stmt.counter]
120
- end
121
- @vtable[stmt.counter] = { type: :numbr, value: 0 }
122
- update_op = if stmt.op == :uppin
123
- lambda { @vtable[stmt.counter][:value] += 1 }
124
- elsif stmt.op == :nerfin
125
- lambda { @vtable[stmt.counter][:value] -= 1 }
126
- else
127
- lambda {
128
- @vtable[stmt.counter] = call_func(stmt.op, [@vtable[stmt.counter]])
129
- }
130
- end
131
- end
132
-
133
- catch :break do
134
- while true
135
- unless stmt.guard.nil?
136
- condition_met = cast(eval_expr(stmt.guard[:expression]), :troof)
137
- if (stmt.guard[:type] == :wile && !condition_met) or
138
- (stmt.guard[:type] == :til && condition_met)
139
- throw :break
140
- end
141
- end
142
- eval_block(stmt.block)
143
- update_op.call if update_op
144
- end
145
- end
146
- # Restore backed up variable
147
- unless stmt.op.nil? || var_backup.nil?
148
- @vtable[stmt.counter] = var_backup
149
- end
150
- end
151
-
152
- def eval_print_stmt(stmt)
153
- text = ''
154
- # todo rewrite using map or similar
155
- stmt.expressions.each do |expr|
156
- text << cast(eval_expr(expr), :yarn)
157
- end
158
- if stmt.suppress
159
- @output.print text
160
- else
161
- @output.puts text
162
- end
163
- end
164
-
165
- def eval_return_stmt(stmt)
166
- throw :return, eval_expr(stmt.expression)
167
- end
168
-
169
- def eval_switch_stmt(stmt)
170
- stmt.cases.combination(2) do |c|
171
- raise RuntimeError, 'Literals must be unique' if c[0] == c[1]
172
- end
173
- case_found = false
174
- it = @vtable['IT']
175
- stmt.cases.each do |kase|
176
- unless case_found
177
- literal = eval_expr(kase[:expression])
178
- if it == literal
179
- case_found = true
180
- end
181
- end
182
- if case_found
183
- breaked = true
184
- catch :break do
185
- eval_block(kase[:block])
186
- breaked = false
187
- end
188
- break if breaked
189
- end
190
- end
191
- unless case_found || stmt.default.nil?
192
- catch :break do
193
- eval_block(stmt.default)
194
- end
195
- end
196
- end
197
-
198
- # Casts given variable 'var' into type 'to'
199
- # Returns only value part of the variable, type will be 'to' anyway
200
- def cast(var, to, implicit = true)
201
- return var[:value] if var[:type] == to
202
- return nil if to == :noob
203
- case var[:type]
204
- when :noob
205
- if implicit && to != :troof
206
- raise RuntimeError, "NOOB cannot be implicitly cast into #{to.to_s.upcase}"
207
- end
208
- return false if to == :troof
209
- return 0 if to == :numbr
210
- return 0.0 if to == :numbar
211
- return ''
212
- when :troof
213
- return (var[:value] ? 1 : 0) if to == :numbr
214
- return (var[:value] ? 1.0 : 0.0) if to == :numbar
215
- return (var[:value] ? 'WIN' : 'FAIL')
216
- when :numbr
217
- return (var[:value].zero? ? false : true) if to == :troof
218
- return var[:value].to_f if to == :numbar
219
- return var[:value].to_s
220
- when :numbar
221
- return (var[:value].zero? ? false : true) if to == :troof
222
- return var[:value].to_int if to == :numbr
223
- # Truncate to 2 digits after decimal point
224
- return ((var[:value] * 100).floor / 100.0).to_s
225
- else
226
- return !var[:value].empty? if to == :troof
227
- if to == :numbr
228
- return var[:value].to_i if var[:value].lol_integer?
229
- raise RuntimeError, "'#{var[:value]}' is not a valid integer"
230
- end
231
- return var[:value].to_f if var[:value].lol_float?
232
- raise RuntimeError, "'#{var[:value]}' is not a valid float"
233
- end
234
- end
235
-
236
- def eval_expr(expr)
237
- send("eval_#{expr.type}_expr", expr)
238
- end
239
-
240
- def eval_binary_expr(expr)
241
- l = eval_expr(expr.left)
242
- r = eval_expr(expr.right)
243
- methods = {
244
- :sum_of => :+, :diff_of => :-, :produkt_of => :*, :quoshunt_of => :/,
245
- :mod_of => :modulo, :both_of => :&, :either_of => :|, :won_of => :^,
246
- :both_saem => :==, :diffrint => :!=
247
- }
248
- case expr.operator
249
- when :sum_of, :diff_of, :produkt_of, :quoshunt_of, :mod_of, :biggr_of, :smallr_of
250
- type = l[:type] == :numbar || r[:type] == :numbar ||
251
- (l[:type] == :yarn && l[:value].lol_float?) ||
252
- (r[:type] == :yarn && r[:value].lol_float?) ? :numbar : :numbr
253
- l, r = cast(l, type), cast(r, type)
254
- if expr.operator == :biggr_of
255
- value = [l, r].max
256
- elsif expr.operator == :smallr_of
257
- value = [l, r].min
258
- else
259
- value = l.send(methods[expr.operator], r)
260
- end
261
- when :both_saem, :diffrint
262
- type = :troof
263
- if (l[:type] == :numbr && r[:type] == :numbar) ||
264
- (l[:type] == :numbar && r[:type] == :numbr)
265
- l, r = cast(l, :numbar), cast(r, :numbar)
266
- elsif l[:type] != r[:type]
267
- raise RuntimeError, 'Operands must have same type'
268
- end
269
- value = l.send(methods[expr.operator], r)
270
- else
271
- type = :troof
272
- l, r = cast(l, :troof), cast(r, :troof)
273
- value = l.send(methods[expr.operator], r)
274
- end
275
- { type: type, value: value }
276
- end
277
-
278
- def eval_cast_expr(expr)
279
- casted_expr = eval_expr(expr.being_casted)
280
- { type: expr.to, value: cast(casted_expr, expr.to, false) }
281
- end
282
-
283
- def eval_constant_expr(expr)
284
- mapping = { boolean: :troof, string: :yarn, integer: :numbr, float: :numbar }
285
- value = expr.vtype == :string ? interpolate_string(expr.value) : expr.value
286
- { type: mapping[expr.vtype], value: value }
287
- end
288
-
289
- def eval_function_expr(expr)
290
- parameters = []
291
- expr.parameters.each do |param|
292
- parameters << eval_expr(param)
293
- end
294
- call_func(expr.name, parameters)
295
- end
296
-
297
- def call_func(name, arguments)
298
- function = @functions[name]
299
- # Replace variable table by 'clean' variable table inside functions
300
- old_table = @vtable
301
- @vtable = create_variable_table
302
- function[:args].each_index do |index|
303
- @vtable[function[:args][index]] = arguments[index]
304
- end
305
- retval = nil
306
- retval = catch :return do
307
- breaked = true
308
- catch(:break) do
309
- eval_block(function[:block])
310
- breaked = false
311
- end
312
- retval = { type: :noob, value: nil } if breaked
313
- end
314
- retval = @vtable['IT'] if retval.nil?
315
- @vtable = old_table
316
- retval
317
- end
318
-
319
- def eval_nary_expr(expr)
320
- case expr.operator
321
- when :all_of
322
- type, value = :troof, true
323
- expr.expressions.each do |operand|
324
- unless cast(eval_expr(operand), :troof)
325
- value = false
326
- break
327
- end
328
- end
329
- when :any_of
330
- type, value = :troof, false
331
- expr.expressions.each do |operand|
332
- if cast(eval_expr(operand), :troof)
333
- value = true
334
- break
335
- end
336
- end
337
- when :smoosh
338
- type, value = :yarn, ''
339
- expr.expressions.each do |operand|
340
- value << cast(eval_expr(operand), :yarn)
341
- end
342
- end
343
- { type: type, value: value }
344
- end
345
-
346
- def eval_unary_expr(expr)
347
- # the only unary op in LOLCODE is NOT
348
- { type: :troof, value: !cast(eval_expr(expr.expression), :troof) }
349
- end
350
-
351
- def eval_variable_expr(expr)
352
- @vtable[expr.name]
353
- end
354
-
355
- # Interpolates values of variables in the string
356
- def interpolate_string(str)
357
- str.gsub(/:\{([a-zA-Z]\w*)\}/) { cast(@vtable[$1], :yarn, false) }
358
- end
359
24
  end
360
25
  end
@@ -38,7 +38,7 @@ module Layo
38
38
  def next_item
39
39
  return @last_lexeme if @last_lexeme[0].nil?
40
40
  while true
41
- @line = next_line if @line_no.zero? || @pos > @line.length - 1
41
+ @line = next_line if buffer_empty?
42
42
  if @line.nil?
43
43
  lexeme = [nil, @line_no, 1]
44
44
  break
@@ -158,5 +158,10 @@ module Layo
158
158
  end
159
159
  end
160
160
  end
161
+
162
+ # Returns true if input buffer is fully processed
163
+ def buffer_empty?
164
+ @line_no.zero? || @pos > @line.length - 1
165
+ end
161
166
  end
162
167
  end
@@ -100,9 +100,13 @@ module Layo
100
100
  nil
101
101
  end
102
102
 
103
- def parse_statement(name)
103
+ def parse_statement(name = nil)
104
104
  token = @tokenizer.peek
105
105
  @tokenizer.unpeek
106
+ name = next_statement unless name
107
+ unless name
108
+ raise SyntaxError.new(token[:line], token[:pos], 'Expected statement')
109
+ end
106
110
  statement = send("parse_#{name}_statement".to_sym)
107
111
  expect_token(:newline)
108
112
  statement.line = token[:line]
@@ -69,8 +69,6 @@ module Layo
69
69
  end
70
70
  root[lexemes.last] = { match: t.gsub(' ', '_').downcase.to_sym }
71
71
  end
72
- @token_table[:list]["\n"] = { match: :newline }
73
- @token_table[:list]['!'] = { match: :exclamation }
74
72
  end
75
73
 
76
74
  def match_longest(lexeme, root)
@@ -98,6 +96,10 @@ module Layo
98
96
  token = { type: :float, data: lexeme[0].to_f }
99
97
  elsif lexeme[0].lol_boolean?
100
98
  token = { type: :boolean, data: (lexeme[0] == 'WIN') }
99
+ elsif lexeme[0] == '!'
100
+ token = { type: :exclamation }
101
+ elsif lexeme[0] == "\n"
102
+ token = { type: :newline }
101
103
  else
102
104
  # Try to match keyword
103
105
  token_type = match_longest(lexeme[0], @token_table)
@@ -13,6 +13,7 @@ describe Interpreter do
13
13
  mask = File.join(File.dirname(__FILE__), 'source', '**', '*.lol')
14
14
  Dir.glob(mask).each do |source_filename|
15
15
  lexer = Lexer.new(File.new(source_filename))
16
+ lexer.input.set_encoding(Encoding::UTF_8)
16
17
  parser = Parser.new(Tokenizer.new(lexer))
17
18
  @interpreter.output.string = ''
18
19
 
@@ -49,4 +50,17 @@ describe Interpreter do
49
50
  infile.close if infile.instance_of?(File)
50
51
  end
51
52
  end
53
+
54
+ describe "#eval_print_stmt" do
55
+ it "should print trailing newline" do
56
+ string = Ast::Expression.new('constant', { vtype: :string, value: "a\n" })
57
+ stmt = Ast::Statement.new('print', {
58
+ expressions: [string], suppress: false
59
+ })
60
+
61
+ @interpreter.eval_print_stmt(stmt)
62
+
63
+ @interpreter.output.string.must_equal "a\n\n"
64
+ end
65
+ end
52
66
  end
@@ -1 +1,2 @@
1
1
  Alisa
2
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: layo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-10 00:00:00.000000000 Z
12
+ date: 2012-01-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mocha
16
- requirement: &78148240 !ruby/object:Gem::Requirement
16
+ requirement: &70607720 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.10.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *78148240
24
+ version_requirements: *70607720
25
25
  description: ! " Layo is a LOLCODE interpreter written in plain Ruby. It tries
26
26
  to conform to\n the LOLCODE 1.2 specification and supports everything described
27
- there.\n"
27
+ there.\n Supports interactive mode.\n"
28
28
  email: kozhayev@gmail.com
29
29
  executables:
30
30
  - layo
@@ -36,6 +36,9 @@ files:
36
36
  - Rakefile
37
37
  - UnicodeData.txt
38
38
  - bin/layo
39
+ - examples/1.lol
40
+ - examples/2.lol
41
+ - examples/3.lol
39
42
  - layo.gemspec
40
43
  - lib/layo.rb
41
44
  - lib/layo/ast.rb
@@ -44,6 +47,8 @@ files:
44
47
  - lib/layo/ast/node.rb
45
48
  - lib/layo/ast/program.rb
46
49
  - lib/layo/ast/statement.rb
50
+ - lib/layo/base_interpreter.rb
51
+ - lib/layo/interactive_interpreter.rb
47
52
  - lib/layo/interpreter.rb
48
53
  - lib/layo/lexer.rb
49
54
  - lib/layo/parser.rb