yabfi 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/ext/yabfi/vm.c ADDED
@@ -0,0 +1,340 @@
1
+ #include <ruby.h>
2
+
3
+ /**
4
+ * Initially, the 256 ints are allocated for the VM. Whenever the memory_cursor
5
+ * advances beyond that, the memory size is doubled until it reaches 32768, at
6
+ * which point it will only allocate chunks of that size.
7
+ */
8
+ #define INITIAL_MEMORY_SIZE 256
9
+ #define MAX_REALLOCATION 32768
10
+
11
+ /**
12
+ * Size of the temporary buffer used by PUT instructions.
13
+ */
14
+ #define INITIAL_BUFFER_SIZE 32
15
+
16
+ /**
17
+ * Constants that map human readable names to instruction codes.
18
+ */
19
+ #define INSTRUCTION_CHANGE_VALUE 0
20
+ #define INSTRUCTION_CHANGE_POINTER 1
21
+ #define INSTRUCTION_GET 2
22
+ #define INSTRUCTION_PUT 3
23
+ #define INSTRUCTION_BRANCH_IF_ZERO 4
24
+ #define INSTRUCTION_BRANCH_NOT_ZERO 5
25
+
26
+ /**
27
+ * This struct represents a VM instruction.
28
+ */
29
+ typedef struct {
30
+ int code;
31
+ int argument;
32
+ } instruction;
33
+
34
+ /**
35
+ * This struct contains the state of the VM.
36
+ */
37
+ typedef struct {
38
+ VALUE input;
39
+ VALUE output;
40
+ int eof;
41
+
42
+ instruction *instructions;
43
+ size_t instructions_length;
44
+ size_t program_counter;
45
+
46
+ int *memory;
47
+ size_t memory_length;
48
+ size_t memory_cursor;
49
+ } vm;
50
+
51
+ /**
52
+ * Ruby classes in C!
53
+ */
54
+
55
+ static VALUE rb_cYABFI;
56
+ static VALUE rb_cBaseError;
57
+
58
+ /**
59
+ * Document-class: YABFI::VM
60
+ *
61
+ * This class, which is implemented as a C extension, executes the
62
+ * instructions generated by the upstream ruby pipeline.
63
+ */
64
+ static VALUE rb_cVM;
65
+ /**
66
+ * Document-class: YABFI::VM::InvalidCommand
67
+ *
68
+ * Raised when an InvalidCommand is received by the VM.
69
+ */
70
+ static VALUE rb_cInvalidCommand;
71
+
72
+ /**
73
+ * Document-class: YABFI::VM::MemoryOutOfBounds
74
+ *
75
+ * Raised when the memory cursor is moved below zero.
76
+ */
77
+ static VALUE rb_cMemoryOutOfBounds;
78
+
79
+ /**
80
+ * Free the allocated memory for the virtual machine.
81
+ */
82
+ static void
83
+ vm_free(void *p) {
84
+ vm *ptr = p;
85
+
86
+ if (ptr->instructions_length > 0) {
87
+ free(ptr->instructions);
88
+ }
89
+
90
+ if (ptr->memory_length > 0) {
91
+ free(ptr->memory);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Allocate a new VM.
97
+ */
98
+ static VALUE
99
+ vm_alloc(VALUE klass) {
100
+ VALUE instance;
101
+ vm *ptr;
102
+
103
+ instance = Data_Make_Struct(klass, vm, NULL, vm_free, ptr);
104
+
105
+ ptr->input = Qnil;
106
+ ptr->output = Qnil;
107
+ ptr->eof = 0;
108
+
109
+ ptr->instructions = NULL;
110
+ ptr->instructions_length = 0;
111
+ ptr->program_counter = 0;
112
+
113
+ ptr->memory = NULL;
114
+ ptr->memory_length = 0;
115
+ ptr->memory_cursor = 0;
116
+
117
+ return instance;
118
+ }
119
+
120
+ /**
121
+ * Initialize a new VM.
122
+ *
123
+ * @param input [IO] the input from which the VM reads.
124
+ * @param output [IO] the output to which the VM writes.
125
+ * @param eof [Fixnum] the value to return when EOF is reached.
126
+ *
127
+ * @!parse [ruby]
128
+ * class YABFI::VM
129
+ * def initialize(input, output, eof)
130
+ * end
131
+ * end
132
+ */
133
+ static VALUE
134
+ vm_initialize(VALUE self, VALUE input, VALUE output, VALUE rb_eof) {
135
+ vm *ptr;
136
+
137
+ Check_Type(rb_eof, T_FIXNUM);
138
+ Data_Get_Struct(self, vm, ptr);
139
+
140
+ ptr->input = input;
141
+ ptr->output = output;
142
+ ptr->eof = NUM2INT(rb_eof);
143
+
144
+ return self;
145
+ };
146
+
147
+ /**
148
+ * Load the VM with new instructions.
149
+ *
150
+ * @param ary [Array<Object>] list of instructions to execute.
151
+ * @return [nil] unconditionally.
152
+ *
153
+ * @!parse [ruby]
154
+ * class YABFI::VM
155
+ * def load!(ary)
156
+ * end
157
+ * end
158
+ */
159
+ static VALUE
160
+ vm_load(VALUE self, VALUE ary) {
161
+ int iter;
162
+ vm *ptr;
163
+ VALUE entry, code, arg;
164
+
165
+ Data_Get_Struct(self, vm, ptr);
166
+
167
+ Check_Type(ary, T_ARRAY);
168
+
169
+ ptr->memory_cursor = 0;
170
+ ptr->memory_length = INITIAL_MEMORY_SIZE;
171
+ ptr->memory = calloc(INITIAL_MEMORY_SIZE, sizeof(int));
172
+
173
+ ptr->program_counter = 0;
174
+ ptr->instructions_length = RARRAY_LEN(ary);
175
+ ptr->instructions = malloc(sizeof(instruction) * ptr->instructions_length);
176
+
177
+ for (iter = 0; iter < (int) ptr->instructions_length; iter++) {
178
+ entry = rb_ary_entry(ary, iter);
179
+ Check_Type(entry, T_ARRAY);
180
+ if (RARRAY_LEN(entry) != 2) {
181
+ rb_raise(rb_cInvalidCommand, "Commands must be tuples");
182
+ }
183
+ code = rb_ary_entry(entry, 0);
184
+ arg = rb_ary_entry(entry, 1);
185
+ Check_Type(code, T_FIXNUM);
186
+ Check_Type(arg, T_FIXNUM);
187
+ ptr->instructions[iter] = (instruction) { FIX2INT(code), FIX2INT(arg) };
188
+ }
189
+
190
+ return Qnil;
191
+ }
192
+
193
+ /**
194
+ * Execute the instructions loaded into the VM.
195
+ *
196
+ * @raise [MemoryOutOfBounds] when the memory cursor goes below zero.
197
+ * @raise [InvalidCommand] when an invalid command is executed.
198
+ * @return [nil] unconditionally.
199
+ *
200
+ * @!parse [ruby]
201
+ * class YABFI::VM
202
+ * def execute!
203
+ * end
204
+ * end
205
+ */
206
+ static VALUE
207
+ vm_execute(VALUE self) {
208
+ vm *ptr;
209
+ char *buffer;
210
+ int *tmp_memory;
211
+ int buffer_size;
212
+ int delta;
213
+ int iter;
214
+ instruction curr;
215
+
216
+ Data_Get_Struct(self, vm, ptr);
217
+ buffer_size = INITIAL_BUFFER_SIZE;
218
+ buffer = malloc(buffer_size * sizeof(char));
219
+
220
+ while (ptr->program_counter < ptr->instructions_length) {
221
+ curr = ptr->instructions[ptr->program_counter];
222
+ switch (curr.code) {
223
+ case INSTRUCTION_CHANGE_VALUE:
224
+ ptr->memory[ptr->memory_cursor] += curr.argument;
225
+ ptr->program_counter++;
226
+ break;
227
+ case INSTRUCTION_CHANGE_POINTER:
228
+ if (((int) ptr->memory_cursor + curr.argument) < 0) {
229
+ rb_raise(rb_cMemoryOutOfBounds, "The memory cursor went below zero");
230
+ }
231
+ ptr->memory_cursor += curr.argument;
232
+ while (ptr->memory_cursor >= ptr->memory_length) {
233
+ delta = ptr->memory_length;
234
+ if (delta > MAX_REALLOCATION) {
235
+ delta = MAX_REALLOCATION;
236
+ }
237
+ tmp_memory = ptr->memory;
238
+ ptr->memory = malloc((ptr->memory_length + delta) * sizeof(int));
239
+ memcpy(ptr->memory, tmp_memory, ptr->memory_length * sizeof(int));
240
+ memset(ptr->memory + ptr->memory_length, 0, delta * sizeof(int));
241
+ ptr->memory_length += delta;
242
+ free(tmp_memory);
243
+ }
244
+ ptr->program_counter++;
245
+ break;
246
+ case INSTRUCTION_BRANCH_IF_ZERO:
247
+ if (ptr->memory[ptr->memory_cursor] == 0) {
248
+ ptr->program_counter += curr.argument;
249
+ } else {
250
+ ptr->program_counter++;
251
+ }
252
+ break;
253
+ case INSTRUCTION_BRANCH_NOT_ZERO:
254
+ if (ptr->memory[ptr->memory_cursor] != 0) {
255
+ ptr->program_counter += curr.argument;
256
+ } else {
257
+ ptr->program_counter++;
258
+ }
259
+ break;
260
+ case INSTRUCTION_GET:
261
+ for (iter = 0; iter < curr.argument; iter++) {
262
+ if (rb_funcall(ptr->input, rb_intern("eof?"), 0)) {
263
+ ptr->memory[ptr->memory_cursor] = ptr->eof;
264
+ } else {
265
+ ptr->memory[ptr->memory_cursor] =
266
+ FIX2INT(rb_funcall(ptr->input, rb_intern("getbyte"), 0));
267
+ }
268
+ }
269
+ ptr->program_counter++;
270
+ break;
271
+ case INSTRUCTION_PUT:
272
+ if (buffer_size < curr.argument) {
273
+ free(buffer);
274
+ buffer_size = curr.argument;
275
+ buffer = malloc(buffer_size * sizeof(char));
276
+ }
277
+ memset(buffer, ptr->memory[ptr->memory_cursor],
278
+ curr.argument * sizeof(char));
279
+ rb_funcall(ptr->output, rb_intern("write"), 1,
280
+ rb_str_new(buffer, curr.argument));
281
+ ptr->program_counter++;
282
+ break;
283
+ default:
284
+ free(buffer);
285
+ rb_raise(rb_cInvalidCommand, "Invalid command code: %i", curr.code);
286
+ }
287
+ }
288
+
289
+ free(buffer);
290
+
291
+ return Qnil;
292
+ }
293
+
294
+ /**
295
+ * Return the VM's internal state -- used in testing and debugging.
296
+ */
297
+ static VALUE
298
+ vm_state(VALUE self) {
299
+ vm *ptr;
300
+ VALUE hash;
301
+
302
+ Data_Get_Struct(self, vm, ptr);
303
+ hash = rb_hash_new();
304
+
305
+ rb_hash_aset(hash, ID2SYM(rb_intern("memory_cursor")),
306
+ INT2FIX(ptr->memory_cursor));
307
+ rb_hash_aset(hash, ID2SYM(rb_intern("memory_length")),
308
+ INT2FIX(ptr->memory_length));
309
+ rb_hash_aset(hash, ID2SYM(rb_intern("program_counter")),
310
+ INT2FIX(ptr->program_counter));
311
+ if (ptr->memory_cursor < ptr->memory_length) {
312
+ rb_hash_aset(hash, ID2SYM(rb_intern("current_value")),
313
+ INT2FIX(ptr->memory[ptr->memory_cursor]));
314
+ } else {
315
+ rb_hash_aset(hash, ID2SYM(rb_intern("current_value")), Qnil);
316
+ }
317
+
318
+ return hash;
319
+ }
320
+
321
+ /**
322
+ * Initialize the C extension by defining all of the classes and methods.
323
+ */
324
+ void
325
+ Init_vm(void) {
326
+ rb_cYABFI = rb_const_get(rb_cObject, rb_intern("YABFI"));
327
+ rb_cBaseError = rb_const_get(rb_cYABFI, rb_intern("BaseError"));
328
+
329
+ rb_cVM = rb_define_class_under(rb_cYABFI, "VM", rb_cObject);
330
+ rb_cInvalidCommand =
331
+ rb_define_class_under(rb_cVM, "InvalidCommand", rb_cBaseError);
332
+ rb_cMemoryOutOfBounds =
333
+ rb_define_class_under(rb_cVM, "MemoryOutOfBounds", rb_cBaseError);
334
+
335
+ rb_define_alloc_func(rb_cVM, vm_alloc);
336
+ rb_define_method(rb_cVM, "initialize", vm_initialize, 3);
337
+ rb_define_method(rb_cVM, "load!", vm_load, 1);
338
+ rb_define_method(rb_cVM, "execute!", vm_execute, 0);
339
+ rb_define_method(rb_cVM, "state", vm_state, 0);
340
+ }
@@ -0,0 +1,131 @@
1
+ module YABFI
2
+ # This class provides generic methods to declaratively consume an Array of
3
+ # input.
4
+ class Consumer
5
+ # Raised when the expected input does not match the given input.
6
+ Unsatisfied = Class.new(BaseError)
7
+
8
+ # Raised when the end of input is reached.
9
+ EndOfInput = Class.new(BaseError)
10
+
11
+ # @attr_reader [Array<Object>] tokens to consume.
12
+ attr_reader :tokens
13
+
14
+ # Create a new Consumer.
15
+ #
16
+ # @param tokens [Array<Object>] consumer input.
17
+ def initialize(tokens)
18
+ @tokens = tokens
19
+ end
20
+
21
+ # Lazily evaluated _conumer_idx instnace variable.
22
+ #
23
+ # @return [Integer] of the current index of the input consumption.
24
+ def consume_index
25
+ @consume_index ||= 0
26
+ end
27
+
28
+ # Seek to the given posision.
29
+ #
30
+ # @param n [Integer] the integer to seek to.
31
+ def seek(n)
32
+ @consume_index = n
33
+ end
34
+
35
+ # Test if the parse has completed.
36
+ #
37
+ # @return [true, false] whether or not the input has been fully consumed.
38
+ def end_of_input?
39
+ consume_index >= tokens.length
40
+ end
41
+
42
+ # Look at the next character of input without advancing the consumption.
43
+ #
44
+ # @return [Object] the next token in the parse.
45
+ # @raise [EndOfInput] if the parse has completed.
46
+ def peek
47
+ fail EndOfInput, '#peek: end of input' if end_of_input?
48
+ tokens[consume_index]
49
+ end
50
+
51
+ # Look at the next character of input and advance the parse by one element.
52
+ #
53
+ # @return [Object] the next token in the parse.
54
+ # @raise [EndOfInput] if the parse has completed.
55
+ def advance
56
+ peek.tap { seek(consume_index.succ) }
57
+ end
58
+
59
+ # Given an optional error message and predicate, test if the next token in
60
+ # the parse satisfies the predicate.
61
+ #
62
+ # @param message [String] error message to throw when the condition is not
63
+ # satisfied.
64
+ # @yieldparam token [Object] the token to test.
65
+ # @return [Object] the satisfied token.
66
+ # @raise [EndOfInput] if the parse has completed.
67
+ # @raise [Unsatisfied] if the condition is not met.
68
+ def satisfy(message = nil)
69
+ message ||= '#satisfy:'
70
+ tok = peek
71
+ fail Unsatisfied, "#{message} '#{tok}'" unless yield(tok)
72
+ seek(consume_index.succ)
73
+ tok
74
+ end
75
+
76
+ # Declare that the next token in the stream should be the given token.
77
+ #
78
+ # @param expected [Object] next expected object in the parse.
79
+ # @return [Object] the satisfied token.
80
+ # @raise [EndOfInput] if the parse has completed.
81
+ # @raise [Unsatisfied] if the token does not equal the argument.
82
+ def eq(expected)
83
+ satisfy("Expected #{expected}, got:") { |tok| tok == expected }
84
+ end
85
+
86
+ # Declare that the next token in the stream should match the given token.
87
+ #
88
+ # @param toks [Array<Object>] list of objects that could match.
89
+ # @return [Object] the satisfied token.
90
+ # @raise [EndOfInput] if the parse has completed.
91
+ # @raise [Unsatisfied] if the token cannot me matched.
92
+ def one_of(*toks)
93
+ satisfy("Expected one of #{toks}, got:") { |tok| toks.include?(tok) }
94
+ end
95
+
96
+ # Try a block of code, resetting the parse state on failure.
97
+ #
98
+ # @return [Object, nil] the result of the block, or nil if the block fails.
99
+ def attempt
100
+ idx = consume_index
101
+ yield
102
+ rescue
103
+ seek(idx)
104
+ nil
105
+ end
106
+
107
+ # Consume 0 or more occurrences of the given block.
108
+ #
109
+ # @return [Object, nil] the result of the block, or nil if the block fails.
110
+ def many
111
+ idx = consume_index
112
+ results = []
113
+ loop do
114
+ idx = consume_index
115
+ results << yield
116
+ end
117
+ rescue
118
+ seek(idx)
119
+ results
120
+ end
121
+
122
+ # Consume 1 or more occurrences of the given block.
123
+ #
124
+ # @return [Object, nil] the result of the block, or nil if the block fails.
125
+ def many_one(&block)
126
+ many(&block).tap do |results|
127
+ fail Unsatisfied, '#many_one: got no results' if results.empty?
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,21 @@
1
+ module YABFI
2
+ # This module encodes the human-readable instruction names to integers.
3
+ module Encoder
4
+ # Mapping of human readable instruction names to their encoded integers.
5
+ INSTRUCTIONS = {
6
+ change_value: 0,
7
+ change_pointer: 1,
8
+ get: 2,
9
+ put: 3,
10
+ branch_if_zero: 4,
11
+ branch_not_zero: 5
12
+ }
13
+
14
+ module_function
15
+
16
+ # Encode a list of instructions into
17
+ def encode(ary)
18
+ ary.map { |(code, argument)| [INSTRUCTIONS[code], argument] }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,73 @@
1
+ module YABFI
2
+ # This module consumes tokens produced by the Parser and produces an
3
+ # unoptimized syntax tree.
4
+ class Lexer < Consumer
5
+ # This Hash maps tokens to method names to optimize the performance of the
6
+ # lexer.
7
+ DISPATCH_TABLE = {
8
+ loop: :while_loop,
9
+ succ: :change_value,
10
+ pred: :change_value,
11
+ next: :change_pointer,
12
+ prev: :change_pointer,
13
+ get: :get,
14
+ put: :put
15
+ }
16
+
17
+ # Run the lexer on the given tokens.
18
+ #
19
+ # @param tokens [Array<Symbol>] the input tokens.
20
+ # @raise [Consumer::Error] when the lexing fails.
21
+ # @return [Array<Object>] the lexed syntax tree.
22
+ def self.run!(tokens)
23
+ new(tokens).send(:run!)
24
+ end
25
+
26
+ private
27
+
28
+ def run!
29
+ forest = commands
30
+ return forest if end_of_input?
31
+ fail Consumer::Unsatisfied, "Unexpected token #{peek}"
32
+ end
33
+
34
+ def commands
35
+ many { command }
36
+ end
37
+
38
+ def command
39
+ method = DISPATCH_TABLE[peek]
40
+ fail Consumer::Unsatisfied, "Unexpected token #{peek}" unless method
41
+ send(method)
42
+ end
43
+
44
+ def while_loop
45
+ eq(:loop)
46
+ inner = commands
47
+ eq(:end)
48
+ [:loop, inner]
49
+ end
50
+
51
+ def get
52
+ count = many_one { eq(:get) }.count
53
+ [:get, count]
54
+ end
55
+
56
+ def put
57
+ count = many_one { eq(:put) }.count
58
+ [:put, count]
59
+ end
60
+
61
+ def change_value
62
+ toks = many_one { one_of(:succ, :pred) }
63
+ total = toks.reduce(0) { |a, e| e == :succ ? a.succ : a.pred }
64
+ [:change_value, total]
65
+ end
66
+
67
+ def change_pointer
68
+ toks = many_one { one_of(:next, :prev) }
69
+ total = toks.reduce(0) { |a, e| e == :next ? a.succ : a.pred }
70
+ [:change_pointer, total]
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,54 @@
1
+ module YABFI
2
+ # This module contains a set of functions that lazily parse an IO object and
3
+ # yield a symbol for each non-comment character that is read in.
4
+ module Parser
5
+ # Maximum number of bytes to read in from the IO object at a time.
6
+ DEFAULT_BUFFER_SIZE = 1_024
7
+
8
+ # Maps characters to human-readable Symbol command names.
9
+ COMMAND_MAPPINGS = {
10
+ '+' => :succ,
11
+ '-' => :pred,
12
+ '>' => :next,
13
+ '<' => :prev,
14
+ ',' => :get,
15
+ '.' => :put,
16
+ '[' => :loop,
17
+ ']' => :end
18
+ }
19
+
20
+ module_function
21
+
22
+ # Lazily parse an IO object while it still has input.
23
+ #
24
+ # @param io [IO] the object from which the parser lazily reads.
25
+ # @param buffer_size [Integer] maximum size to request from the IO at once.
26
+ # @yield [command] Symbol that represents the parsed command.
27
+ # @return [Enumator<Symbol>] of commands when no block is given.
28
+ def parse(io, buffer_size = DEFAULT_BUFFER_SIZE)
29
+ return enum_for(:parse, io, buffer_size) unless block_given?
30
+ loop do
31
+ buffer = read(io, buffer_size)
32
+ break unless buffer
33
+ buffer.each_char do |char|
34
+ command = COMMAND_MAPPINGS[char]
35
+ yield command if command
36
+ end
37
+ end
38
+ end
39
+
40
+ # Block waiting for the next set of commands.
41
+ #
42
+ # @param io [IO] the object from which the parser lazily reads.
43
+ # @param size [Integer] the maximum number of bytes to read in.
44
+ # @return [String, nil] the buffer of bytes read in, or nil on EOF.
45
+ def read(io, size)
46
+ io.read_nonblock(size)
47
+ rescue IO::WaitReadable
48
+ IO.select([io])
49
+ retry
50
+ rescue EOFError
51
+ nil
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,33 @@
1
+ module YABFI
2
+ # This module is used to transforms unrolls loops into multiple
3
+ # branch_if_zero and branch_not_zero instructions.
4
+ module Unroll
5
+ module_function
6
+
7
+ # Unroll an entire syntax forest.
8
+ #
9
+ # @param forest [Array<Object>] the forest to unroll.
10
+ # @return [Array<Object>] the unrolled commands.
11
+ def unroll(forest)
12
+ forest.each_with_object([]) do |(command, arg), ary|
13
+ if command == :loop
14
+ ary.push(*unroll_loop(arg))
15
+ else
16
+ ary.push([command, arg])
17
+ end
18
+ end
19
+ end
20
+
21
+ # Unroll a single loop of commands.
22
+ #
23
+ # @param commands [Array<Object>] the loop to unroll.
24
+ # @return [Array<Object>] the unrolled commands.
25
+ def unroll_loop(commands)
26
+ unroll(commands).tap do |unrolled|
27
+ offset = unrolled.length
28
+ unrolled.unshift([:branch_if_zero, offset + 2])
29
+ unrolled.push([:branch_not_zero, -1 * offset])
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ # YABFI is the top level module for the gem.
2
+ module YABFI
3
+ # Gem (semantic) version.
4
+ VERSION = '0.1.1'
5
+ end
data/lib/yabfi.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'stringio'
2
+
3
+ # YABFI (Yet Another BrainFuck Interpreter) is the top level module for the gem.
4
+ module YABFI
5
+ # This is the base error for the gem from which the rest of the errors
6
+ # subclass.
7
+ BaseError = Class.new(StandardError)
8
+
9
+ module_function
10
+
11
+ # Evaluate an IO of commands
12
+ #
13
+ # @param commands [String, IO] the commands to execute.
14
+ # @param input [IO] the input from which the commands read.
15
+ # @param output [IO] the output to which the commands write.
16
+ # @param eof [Integer] the value to set when EOF is reached.
17
+ # @raise [BaseError] when there is a compiling or execution error.
18
+ def eval!(commands, input: $stdin, output: $stdout, eof: 0)
19
+ io = commands.is_a?(String) ? StringIO.new(commands) : commands
20
+ tokens = Parser.parse(io)
21
+ lexed = Lexer.run!(tokens.to_a)
22
+ commands = Unroll.unroll(lexed)
23
+ encoded = Encoder.encode(commands)
24
+ vm = VM.new(input, output, eof)
25
+ vm.load!(encoded)
26
+ vm.execute!
27
+ end
28
+ end
29
+
30
+ require 'yabfi/version'
31
+ require 'yabfi/consumer'
32
+ require 'yabfi/parser'
33
+ require 'yabfi/lexer'
34
+ require 'yabfi/unroll'
35
+ require 'yabfi/encoder'
36
+ require 'yabfi/vm'