use v6;
module Brainfuck;
my grammar Grammar {
rule TOP { ^ <code> $ }
rule code { <block>* }
rule block { <move> | <incr> | <print> | <ask> | <comment> | <loop> }
rule loop { '[' ~ ']' <code> }
# Parse commutative blocks in one piece
token move { <[\s < >]>+ }
token incr { <[\s +\-]>+ }
token print { <[\s \.]>+ }
token ask { <[\s \,]>+ }
# Everything that's not an instruction is a comment.
token comment { <-[ \[ \] + \- < > , .]>+ }
}
my class InstructionBlock {
has Int $.n;
method new(Int $n) { return self.bless(*, :$n); }
}
my class Incr is InstructionBlock { }
my class Move is InstructionBlock { }
my class Print is InstructionBlock { }
my class Ask is InstructionBlock { }
my class Actions {
method TOP($/) { make $<code>.ast }
method code($/) { make $<block>>>.ast }
method block($/) { make $/.values.[0].ast; }
method loop($/) { make $<code>.ast }
# Since +/- and >/< cancel each other out when they appear in direct succession, they
# can be handled in large chunks. So for example ++-+-+ becomes Incr.new(2).
method move($/) { make Move.new($/.comb(/\>/).elems - $/.comb(/\</).elems); }
method incr($/) { make Incr.new($/.comb(/\+/).elems - $/.comb(/\-/).elems); }
# . and , can be handled blockwise but not in a mix. This will be less useful than
# block handling for >/< and especially +/-, but since I'm already at it...
method print($/) { make Print.new($/.comb(/\./).elems); }
method ask($/) { make Ask.new($/.comb(/\,/).elems); }
# Comments will be ignored. No special handling.
method comment($/) { make ~$/; }
}
my class State {
has Int @!buffer = 0;
has Int $!dataptr = 0;
method get_data () returns Int { return @!buffer[$!dataptr]; }
method set_data (Int $i) { @!buffer[$!dataptr] = $i; }
method move (Int $n) { $!dataptr += $n; }
method increment(Int $n) {
@!buffer[$!dataptr] += $n;
@!buffer[$!dataptr] %= 256;
}
}
class Interpreter {
has @.ast;
method new(Str $bf_code) {
Grammar.parse($bf_code, actions => Actions.new);
return self.bless(*, ast => $/.ast);
}
# run-recursive should be private, but rakudo doesn't seem to support
# private multi methods. (yet?)
multi method run-recursive(State $state, Str $comment) { }
multi method run-recursive(State $state, Move $iblock ) { $state.move($iblock.n()); }
multi method run-recursive(State $state, Incr $iblock ) { $state.increment($iblock.n()); }
multi method run-recursive(State $state, Print $iblock ) { print $state.get_data().chr x $iblock.n(); }
multi method run-recursive(State $state, Ask $iblock ) { $state.set_data($*IN.read($iblock.n())[*-1]); }
multi method run-recursive(State $state, @p) {
while $state.get_data() != 0 {
for @p {
self.run-recursive($state, $_);
}
}
}
method run {
my State $state .= new;
for @!ast {
self.run-recursive($state, $_);
}
}
}