#!/usr/bin/env perl

# vtm -- command line interface for the Virtual Turing Machine 2.
# Copyright (C) 2000-2001 Paul R. C. Ming
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# 

# This program uses `VTM.pm' to implement the Turing machine.

# ------------------------------------------------------------
# This is the "with Jim's changes" version; Jim Mahoney | Marlboro College
#
#   March 2011 - several more small changes
#
#    * added 'use lib' so that I could move the C wrapper elsewhere,
#      and still have it find this VTM.pm module here.
#
#      *** NOTE THAT YOU'LL NEED TO EDIT THE 'use lib' BELOW
#          OR INSTALL VTM.pm WITHIN YOUR PERL @INC SEARCH PATH ***
#
#    * the ARGV array is coming in like this:
#
#       $ vtm -t111
#       pre-process ARGV = (-L|-V|-S|-b_|-sEven|-t10110010100|./even.vtm|-t111)
#
#      where the first arguments are the ones on the shebang line,
#      and the final one is what I typed on the command line.
#      To make that command line one override the one in the file's shebang,
#      I got rid of all the "unless defined" line ends in parse_arguments().
#
#    * added a few more default values to parse_arguments
#
#    * dropped printing of $o, the tape shift amount (typically pretty clear)
#
#    * For the -0 "final position only", I'm printing the next-to-last state,
#      which is typically the 'Accept' or 'Reject' that we want to see,
#      rather than the "undefined" actual final state.
#
#   March 2008 - a few more apparent bugs
#    * added code to catch some undefined strings in output
#    * stopped machine before trying to step when state is undefined.
#   Feb 2006 - some fix-ups by Jim Mahoney, Marlboro College
#   since it wasn't doing what I expected.
#    * in parse_arguments, changed "pop" to "shift"
#    * added use strict and use warnings
#    * added my (...) for global variables
#    * added my JIM_DEBUG=0;  (or 1)
#    * added various print "..." if $JIM_DEBUG
#    * altered @ARGV to split apart things like '-t -L' to ('-t', '-L')
# -----------------------------------------------------------

my $JIM_DEBUG = 0;

$| = 0;

# Edit this next line to be the folder where VTM.pm lives (e.g. here) :
use lib '/var/www/cs/htdocs/courses/spring2011/formal_languages/code/vtm/';
# So that we're using that version of VTM.pm :
use VTM;

use strict;
use warnings;

my ($file, $line, $first, $Machine, $show_state, $show_tape,
    $ignore_echo, $ret_val, $DEBUG, $tape, $blank,
    $start_state, $infinite_tape, $lines_per_step, $verbose, $previous_state,
    );

print " -- in vtm.pl -- \n"                              if $JIM_DEBUG;
print " pre-process ARGV = (" . join('|',@ARGV) . ")\n"  if $JIM_DEBUG;

my @args = ();
while (@ARGV){
  my $arg = shift(@ARGV);
  if ($arg=~/ /){
    push @args, split(/ /,$arg);
  }
  else {
    push @args,$arg;
  }
}

print " pre-process args = (" . join('|',@args) . ")\n"  if $JIM_DEBUG;

# If no args given, just print the help.
if (@args == 0){
  $args[0] = '-h';
}

# parse_arguments(@ARGV);
parse_arguments(@args);

if ($JIM_DEBUG){
  print " file='$file'\n";
  print " tape='$tape'\n";
  print " blank='$blank'\n";
}

# If all we needed do was print help, then quit now.
exit if (@args == 1 && $args[0] eq "-h");

unless ($file) {
    print "Specify instructions file: ";
    chomp($file = <STDIN>);
}

# Open the instructions file and start parsing it.
open(INSTRUCTIONS, $file) || die "Can't open $file: $!";

$first = <INSTRUCTIONS>;
if ($first =~ /^\#!\/.*vtml (.*)$/) {
    &parse_arguments(split(/\s+/, $1));
    $line = <INSTRUCTIONS>;
}
else {
    $line = $first;
}

$Machine = new VTM() || die "Couldn't create a VTM: $!";
do {
    # First, look for ## directives.
    if (!$ignore_echo && $line =~ /^\#\#echo([ \t])(.*)$/) {
	print "\t" if $1 eq "\t";
	print "$2\n";
    }
    elsif (!$ignore_echo && $line =~ /^\#\#echo$/) {    # Echo empty line.
	print "\n";
    }
    else {    # Otherwise, feed it to the Machine.
	$ret_val = $Machine->parse_instruction($line);
	if ($ret_val == 0) {
	    print "$.: syntax error\n";
	    print $line if $DEBUG;
	    exit;
	}
	elsif ($ret_val == 2) {
	    print "$.: Warning: condition already has an instruction.  "
		. "Using new instruction\n";
	}
    }
} while ($line = <INSTRUCTIONS>);
close(INSTRUCTIONS);
#&pretty_print($Machine->{'instructions'}) if $DEBUG;

unless (defined($tape) && &VTM::valid_tape($tape)) {
    print "Specify initial tape: ";
    chomp($tape = <STDIN>);
    while (!&VTM::valid_tape($tape)) {
	print "Invalid tape.\n";
	print "Specify initial tape: ";
	chomp($tape = <STDIN>);
    }
}
$Machine->tape($tape);
print "Tape: `" . join("", $Machine->tape()) . "'\n" if $DEBUG;

unless (defined($blank) && &VTM::valid_cell_value($blank)) {
    print "Specify blank character: ";
    chomp($blank = <STDIN>);
    while (!&VTM::valid_cell_value($blank)) {
	print "Invalid blank character.\n";
	print "Specify blank character: ";
	chomp($blank = <STDIN>);
    }
}
$Machine->blank($blank);
print "Blank: `" . $Machine->blank() . "'\n" if $DEBUG;

unless (defined($start_state) && &VTM::valid_state_name($start_state)) {
    print "Specify start state: ";
    chomp($start_state = <STDIN>);
    while (!&VTM::valid_state_name($start_state)) {
	print "Invalid start state.\n";
	print "Specify start state: ";
	chomp($start_state = <STDIN>);
    }
}
$Machine->start_state($start_state);
print "Start state: `" . $Machine->start_state() . "'\n" if $DEBUG;

if (defined($infinite_tape)) {
    $Machine->infinite_tape($infinite_tape);
}
if (defined($lines_per_step)) {
  if ($lines_per_step == 2) {
      $Machine->half_step(1);
    }
}
else {
  $lines_per_step = 1;
}

if ($verbose) {
  print "Tape : " . join("", $Machine->tape()) . "\n";
  print "Start: " . $Machine->start_state() . "\n";
  print "Blank: " . $Machine->blank() . "\n";
}

# Okay, time to run the Machine.
print "Starting machine.\n" if $DEBUG;
$Machine->start() || die "Couldn't start the Turing machine";
do {
  if ($JIM_DEBUG){
    print " ... state,halted = " . $Machine->state() . ',' . $Machine->is_halted()
      . "\n";
  }
  &print_line if $lines_per_step != 0;
  $previous_state = $Machine->state;
  $Machine->step();
} while (!$Machine->is_halted()
	 && !$Machine->is_in_infinite_loop() 
	 && $Machine->state());

&print_line($previous_state) if $lines_per_step == 0;

if ($Machine->is_in_infinite_loop()) {
  &print_line if $lines_per_step != 0;
  print "Infinite loop detected.  Exiting.\n";
}

exit;


sub print_line {
    my $prev = shift || 0;
    my @t = $Machine->tape();
    my $p = $Machine->position();
    my $s = $Machine->state() if $show_state;
    my $o = $Machine->offset() if $Machine->infinite_tape();
    my $i;

    # printf("%4d: ", $o) if $Machine->infinite_tape();
    printf("  ");

    for ($i = 0; $i < $p; $i++) {
      my $symbol = defined($t[$i]) ? $t[$i] : '';
      print " $symbol";
    }
    my $sym = defined($t[$p]) ? $t[$p] : '';
    print "<$sym>";
    for ($i = $p + 1; $i < @t; $i++) {
      my $symbol = defined($t[$i]) ? $t[$i] : '';
      print "$symbol ";
    }
    $s = defined($s) ? $s : '*undefined*';
    $s = $prev if $prev;
    print ": $s" if ($show_state);

    # if ($Machine->infinite_tape() and $o != 0){
    #  printf(" (shift %d)", $o);
    #}

    print "\n";
}

sub print_help {
    print <<EOT;
Usage: vtm [programfile] [switches]
-h         Display this help.
-F         Finite tape mode.
-I         Infinite tape mode (well, it grows anyway) (default).
-S         Show the state at the end of each line (default).
-T         Don't show the state at the end of each line.
-V         Shows tape, blank character, and starting state before running.
-W         Don't show anything before running.
-Q         Ignore ##echo directives.
-L         Enable ##echo directives.
-0         Show zero lines of output per step; only show the final tape.
-1         Show one line of output per step of the Turing machine (default).
-2         Show two lines of output per step of the Turing machine.
-tXXX...   Set the value of the initial tape.
-bC        Set the value of the blank character.
-sState    Set the value of the starting tape.

If the values of the tape, blank character, or starting state are not set with
command-line arguments, and the program file does not specify default values,
then you will be prompted for the missing values.

Command-line arguments will override progam file's default settings.

Example:
    vtm -t_111_111_ -b_ -spass_first add.vtm
will execute the add.vtm with the tape set to "_111_111_", blank to "_", and 
the starting state to "pass_first".
EOT
}

sub parse_arguments {
    # Parse the arguments.  Because the argument list may contain arguments 
    # from the file (and those arguments are overridden).
    print " in parse_args input = (" . join('|',@_) . ")\n" if $JIM_DEBUG;
    while ($_ = shift(@_)) {
      print " matching '$_' \n" if $JIM_DEBUG;
      if (/^-h$/) {
	&print_help;
      }
      elsif (/^-F$/) {
	$infinite_tape = 0; # unless defined($infinite_tape);
      }
      elsif (/^-I$/) {
	$infinite_tape = 1; # unless defined($infinite_tape);
      }
      elsif (/^-S$/) {
	$show_state = 1;  # unless defined($show_tape);
      }
      elsif (/^-T$/) {
	$show_state = 0;  # unless defined($show_tape);
      }
      elsif (/^-V$/) {
	$verbose = 1;  # unless defined($verbose);
      }
      elsif (/^-W$/) {
	$verbose = 0;  # unless defined($verbose);
      }
      elsif (/^-Q$/) {
	$ignore_echo = 1;  # unless defined($ignore_echo);
      }
      elsif (/^-L$/) {
	$ignore_echo = 0;  # unless defined($ignore_echo);
      }
      elsif (/^-([0-2])$/) {
	$lines_per_step = $1; # unless defined($lines_per_step);
      }
      elsif (/^-t(.*)$/) {
	print " found '-t' with '$1' \n" if $JIM_DEBUG;
	$tape = $1;   # unless defined($tape);
      }
      elsif (/^-b(.)$/) {
	$blank = $1; # unless defined($blank);
      }
      elsif (/^-s(.*)$/) {
	$start_state = $1; # unless defined($start_state);
      }
      else {    # Assume an argument without switch (i.e. -X) is *.vtm file.
	$file = $_ unless defined($file);
	# last;
      }
    }
    # default values :
    $show_state     = 1 unless defined($show_state);
    $infinite_tape  = 1 unless defined($infinite_tape);
    $verbose        = 0 unless defined($verbose);
    $lines_per_step = 1 unless defined($lines_per_step);
    $ignore_echo    = 0 unless defined($ignore_echo);
    $blank          = '_' unless defined($blank);
}
