#!/usr/bin/env perl
use strict;
use warnings;

# Write the Brainfuck code for a brainfuck-to-C compiler to stdout.

my @primes = reverse qw(
 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
);

my %phash;
map { $phash{$_} = 1; } @primes;

my $header = q(
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
	char *p,*a;
	p=a=calloc(100000,1);
);


bf_output($header);
print "[,\n";
bf_printif('+',"++*p;\n");
bf_printif('-',"--*p;\n");
bf_printif('.',"putchar(*p);\n");
bf_printif(',',"*p = getchar();\n");
bf_printif('>',"p++;\n");
bf_printif('<',"p--;\n");
bf_printif('[',"while (*p) {");
bf_printif(']',"}\n");

print "+]\n";
bf_output("return 0;}\n");


###################################################################
# Subroutines

# Output the BF code to increment whatever is under the pointer
# by the given value. This should really be optimized by 
# factorizing the arguments and using a loop.
sub bf_plus {
	my $delta = shift;
	if (($delta>6)&&($delta<100)) {
		my $f = firstfactor($delta);
		if (defined($f)) {
			my ($a,$b) = @$f;
			print ">[-]";
			print "+"x$a;
			print "[<";
			print "+"x$b;
			print ">-]<";
		} else {
			bf_plus($delta-1);
			print "+";
		}
	} else {
	print "+"x$delta;
	}
}

sub bf_char {
	my $char = shift;
	bf_plus(ord($char));
}

sub firstfactor {
	my $arg = shift;
	die("Argument too big") unless ($arg<100);
	if ($phash{$arg}) {
		return; # Cannot factorize if prime.
	}

	for my $n (@primes) {
		if (0==($arg % $n)) {
			my $remainder = int($arg/$n);
			return [ $n, $remainder ];
		}
	}
	die("should not happen arg=$arg");

}

# Take a string, print to stdout the BrainFuck code required to
# print that string.
sub bf_output {
	my $string = shift;
	my $lastval=0;
	print "[-]";
	for my $char (split //,$string) {
		my $diff = ord($char)-$lastval;
		if ($diff>0) { 
			#print "+"x$diff;
			bf_plus($diff);
		} else {
			print "-"x(-$diff);
		}
		$lastval += $diff;
		print ".";
	}
}

# Take a numeric or character value, and a string.
# Output the BF code to compare whatever is under the pointer
# with the value, and output the string if they are equal.
sub bf_printif {
	my ($compval,$outstring)=@_;
	print ">[-]>[-]<<[->+>+<<]>>[-<<+>>][-]"; # dup ; zero
	bf_char($compval);
	print "[-<->]+<[>-<[-]]>[";
	bf_output($outstring);
	print "[-]]<<";
}
