#!/usr/bin/perl -w
#
# autotest: generate a stack checking C program,
#	    from a mini language, in which "20 30 - -" means
#	    push 20, push 30, pop, pop, doing all obvious tests
#	    as we go.  In particular, we need to simulate the
#	    stack ourselves to know what the correct answer is!
#
use strict;

die "Usage: autotest opstr maxdepth\n" unless @ARGV == 2;
my $opstr = shift;
my $maxdepth = shift;

#
# preamble($outfh): Append the standard C checking preamble to $outfh
#
sub preamble ($)
{
	my( $outfh ) = @_;
	print $outfh <<EOF;
#include <stdio.h>
#include <stdlib.h>
#include "bool.h"
#include "check.h"
#include "stack.h"

#define CHECK_ELEMENT CHECK_INT

int main( void )
{
	stack s = empty_stack();       \tcheck_stack(s,"[]");
EOF
}

#
# postamble( $outfh ): Append the standard C postamble to $outfh
#
sub postamble ($)
{
	my( $outfh ) = @_;

	print $outfh <<EOF;
	destroy_stack(s);
	return 0;
}
EOF
}


#
# my $error = gentest( $outfh, $opstr, $maxdepth ):
#	Generate the tests for the given $opstr, simulating what the
#	stack should contain as the tests run.
#	Returns undef if everything should be fine, or
#	a specific error message (eg. would lead to stack underflow)
#
sub gentest ($$$)
{
	my( $outfh, $opstr, $maxdepth ) = @_;
	my @stack;		 # our simulated stack, maxdepth $maxdepth
	while( $opstr =~ s/^(\d+|-)\s*// )
	{
		my $op = $1;
		if( $op =~ /^\d/ )
		{
			push @stack, $op;
			my $depth = @stack;
			print $outfh "\tpush($op, s);      \t\t";
			if( $maxdepth > 0 && $depth > $maxdepth )
			{
				print $outfh "/* <--- STACK OVERFLOW HERE */\n";
				return "stack overflow";
			}
			my $stkstr = join(',', reverse @stack);
			print $outfh qq(check_stack(s,"[$stkstr]");\n);
		} elsif( $op eq '-' )
		{
			unless( @stack )
			{
				print $outfh "\t(void)pop(s);\t\t\t";
				print $outfh "/* <--- STACK UNDERFLOW HERE */\n";
				return "stack underflow";
			}
			my $expect = pop @stack;
			my $depth = @stack;
			print $outfh "\tCHECK_ELEMENT(pop(s), $expect);\t";
			my $stkstr = join(',', reverse @stack);
			print $outfh qq(check_stack(s,"[$stkstr]");\n);
		} else
		{
			die "logic error: number/- expected, $op found\n";
		}
	}
	warn "error: leftover rubbish in opstr: $opstr\n" if $opstr;
	return undef;
}

my $name = "atest";
open( my $outfh, '>', "$name.c" ) ||
	die "autotest: can't create $name.c, $!\n";
preamble( $outfh );
my $error = gentest( $outfh, $opstr, $maxdepth );
postamble( $outfh );
close( $outfh );
print "compile $name.c via 'gcc $name.c stack.o', then run ./a.out, the expected result is ". ($error//"OK"). "\n";

exit 0;
