yabfi 0.1.1

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/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'