layo 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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