aboutsummaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorMatthew Hall <hallmatthew314@gmail.com>2023-03-20 23:12:40 +1300
committerMatthew Hall <hallmatthew314@gmail.com>2023-03-20 23:12:40 +1300
commitcdd4b58aa58320a1a0dfd259b7a1301f3ef60998 (patch)
treef625d93303bb62e492b83b5c510fb35065d6a49a /spec
parent3f9f26ddbaa2cc89f7482b61c5d45134fecd71af (diff)
Separate files for practical tests + BF parser/interpreter finished
Diffstat (limited to 'spec')
-rw-r--r--spec/practical/bf_spec.cr (renamed from spec/practical_spec.cr)109
-rw-r--r--spec/practical/words_spec.cr39
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
+