diff options
| author | Matthew Hall <hallmatthew314@gmail.com> | 2023-03-20 23:12:40 +1300 |
|---|---|---|
| committer | Matthew Hall <hallmatthew314@gmail.com> | 2023-03-20 23:12:40 +1300 |
| commit | cdd4b58aa58320a1a0dfd259b7a1301f3ef60998 (patch) | |
| tree | f625d93303bb62e492b83b5c510fb35065d6a49a | |
| parent | 3f9f26ddbaa2cc89f7482b61c5d45134fecd71af (diff) | |
Separate files for practical tests + BF parser/interpreter finished
| -rw-r--r-- | spec/practical/bf_spec.cr (renamed from spec/practical_spec.cr) | 109 | ||||
| -rw-r--r-- | spec/practical/words_spec.cr | 39 |
2 files changed, 99 insertions, 49 deletions
diff --git a/spec/practical_spec.cr b/spec/practical/bf_spec.cr index f992a15..cd14c59 100644 --- a/spec/practical_spec.cr +++ b/spec/practical/bf_spec.cr @@ -1,56 +1,67 @@ -require "./spec_helper" +require "../spec_helper" include Parcom -describe "Text surrounded by whitespace" do - ws_char = Parser(Char, Char).satisfy { |c| c.whitespace? } - normal_char = Parser(Char, Char).satisfy { |c| !c.whitespace? } - - word = normal_char.some.map { |cs| cs.join } - ws = ws_char.some +enum BFOpType + Add + Shift + ByteIn + ByteOut + LoopStart + LoopEnd +end - words = ws.optional >> word.sep_by(ws) << ws.optional +alias BFOp = { + type: BFOpType, + amount: Int32, +} - good_strings = { - "test with no trailing whitespace", - " test with whitespace in the front", - "test with whitespace in the back", - " test surrounded by whitespace ", - } +def find_jumps(ops : Array(BFOp)) : Hash(Int32, Int32) + jumps = {} of Int32 => Int32 + stack = [] of Int32 - good_strings.each do |s| - tokens = Tokens.from_string(s) - result = words.parse(tokens) + ops.each_index do |i| + if ops[i][:type] == BFOpType::LoopStart + stack << i + end - result.value.should eq(s.strip.split(/\s+/)) - result.tokens.empty?.should be_true + if ops[i][:type] == BFOpType::LoopEnd + jumps[i] = stack.pop + jumps[jumps[i]] = i + end end - bad_strings = { - "", - " \t \n ", - } - - bad_strings.each do |s| - tokens = Tokens.from_string(s) - expect_raises(ParserFail) { words.parse(tokens) } - end + jumps end -enum BFOpType - BFAdd - BFShift - BFByteIn - BFByteOut - BFLoopStart - BFLoopEnd +def interpret_bf(ops : Array(BFOp)) : Array(Char) + jumps = find_jumps(ops) + mem = Hash(Int32, Int32).new(0) + i_ptr = 0 + m_ptr = 0 + output = [] of Char + while i_ptr < ops.size + op = ops[i_ptr] + case op[:type] + in BFOpType::Add + mem[m_ptr] = (mem[m_ptr] + op[:amount]) % 256 + in BFOpType::Shift + m_ptr += op[:amount] + in BFOpType::ByteIn + raise "Test suite for BF simulation does not support input instruction" + in BFOpType::ByteOut + c = mem[m_ptr].chr + op[:amount].times { output << c } + in BFOpType::LoopStart + i_ptr = jumps[i_ptr] if mem[m_ptr] == 0 + in BFOpType::LoopEnd + i_ptr = jumps[i_ptr] unless mem[m_ptr] == 0 + end + i_ptr += 1 + end + output end -alias BFOp = { - type: BFOpType, - amount: Int32, -} - describe "brainfuck parser" do # From https://esolangs.org/wiki/Brainfuck#Examples hello_world_str = "1 +++++ +++ Set Cell #0 to 8 @@ -95,31 +106,31 @@ describe "brainfuck parser" do just_bf_chars = other.many >> char_body << other.many loop_start = Parser(Char, Char).token('[').map do |_| - {type: BFOpType::BFLoopStart, amount: 0} + {type: BFOpType::LoopStart, amount: 0} end loop_end = Parser(Char, Char).token(']').map do |_| - {type: BFOpType::BFLoopEnd, amount: 0} + {type: BFOpType::LoopEnd, amount: 0} end read_block = Parser(Char, Char).token(',').some.map do |cs| - {type: BFOpType::BFByteIn, amount: cs.size} + {type: BFOpType::ByteIn, amount: cs.size} end write_block = Parser(Char, Char).token('.').some.map do |cs| - {type: BFOpType::BFByteOut, amount: cs.size} + {type: BFOpType::ByteOut, amount: cs.size} end shift_block = (Parser.token('<') | Parser.token('>')).some.map do |cs| left_count = cs.count { |c| c == '<' } right_count = cs.size - left_count - {type: BFOpType::BFShift, amount: right_count - left_count} + {type: BFOpType::Shift, amount: right_count - left_count} end add_block = (Parser.token('+') | Parser.token('-')).some.map do |cs| minus_count = cs.count { |c| c == '-' } plus_count = cs.size - minus_count - {type: BFOpType::BFAdd, amount: plus_count - minus_count} + {type: BFOpType::Add, amount: plus_count - minus_count} end bf_token = Parser.first_of([ @@ -133,14 +144,14 @@ describe "brainfuck parser" do tokenizer = Parser(Char, Array(BFOp)).new("BF tokenizer") do |tokens| result = just_bf_chars.parse(tokens) - puts result chars = Tokens.new(result.value) bf_token.some.parse(chars) end tokens = Tokens.from_string(hello_world_str) result = tokenizer.parse(tokens) - puts result - # TODO: find a good way to verify this result + + exec_result = interpret_bf(result.value) + exec_result.should eq("Hello World!\n".chars) end diff --git a/spec/practical/words_spec.cr b/spec/practical/words_spec.cr new file mode 100644 index 0000000..5155881 --- /dev/null +++ b/spec/practical/words_spec.cr @@ -0,0 +1,39 @@ +require "../spec_helper" + +include Parcom + +describe "Text surrounded by whitespace" do + ws_char = Parser(Char, Char).satisfy { |c| c.whitespace? } + normal_char = Parser(Char, Char).satisfy { |c| !c.whitespace? } + + word = normal_char.some.map { |cs| cs.join } + ws = ws_char.some + + words = ws.optional >> word.sep_by(ws) << ws.optional + + good_strings = { + "test with no trailing whitespace", + " test with whitespace in the front", + "test with whitespace in the back", + " test surrounded by whitespace ", + } + + good_strings.each do |s| + tokens = Tokens.from_string(s) + result = words.parse(tokens) + + result.value.should eq(s.strip.split(/\s+/)) + result.tokens.empty?.should be_true + end + + bad_strings = { + "", + " \t \n ", + } + + bad_strings.each do |s| + tokens = Tokens.from_string(s) + expect_raises(ParserFail) { words.parse(tokens) } + end +end + |
