#!/usr/bin/perl

# Copyright (C) 1999, 2000, 2001, 2002 Marco Roveri.
# Originally written by Marco Roveri <roveri@irst.itc.it>

# 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.

# Accepts a circuit description in KISS2 format and outputs the
# equivalent SMV model. Ouput constructs are parsed but they are not
# yet handled.

use Getopt::Std;

my $version = "1b -- Jan 1 2001";

my %state_vars;
my $did_once = 0;
my $init_state = "";
my $states_number = 0;
my $output_number = 0;
my $inputs_number = 0;
my $fsmlines = 0;
my %state_renamed;
my @state_transitions;

my %smv_reserved = (
                    'MODULE' => 1,
                    'DEFINE' => 1,
                    'INIT'   => 1,
                    'INVAR'  => 1,
                    'TRANS'  => 1,
                    'VAR'    => 1,
                    'IVAR'   => 1,
                    'next'   => 1,
                    'init'   => 1,
                    'process' => 1,
                    'TRUE'    => 1,
                    'FALSE'   => 1
                    );

sub help {
    print STDERR "KISS2 to SMV file converter by roveri\@irst.itc.it\n";
    print STDERR "Version $version\n";
    print STDERR "Usage: kiss2-2smv [-h] [-m] [-d] [-o file] [-f file]\n";
    print STDERR "\t -h      This help message\n";
    print STDERR "\t -d      Run debug mode\n";
    print STDERR "\t -o file Specify a file to save the SMV model.\n";
    print STDERR "\t -f file Specify a file to read in KISS2 model.\n";
    print STDERR "\t -m With this option the default transition correspond to inertiality,\n\t    otherwise the default transition is don't care.\n";
    print STDERR "If neither -f nor -o are used, then STDIN, STDOUT respectively are used\n";
    print STDERR "States with the same name of a SMV token are prefixed with \"_smv_\"\n";
    print STDERR "Output constructs are parsed but not yet handled.\n";

    exit;
}

# Escapes a KISS2 state name which corresponds to a SMV reserved word with
# the prefix '_smv_'
sub escape_state {
    my ($state) = @_;
    my $oldstate = $state;

    if (defined($smv_reserved{$state})) {
        $state = "_smv_$state";
        $state_renamed{$oldstate} = $state;
    }
    return($state);
}

sub die_and_remove {
    my ($message) = @_;
    if ( defined($opt_o) ) {
        unlink "$opt_o";
    }
    die "$message\n";  
}

sub print_case {
    my ($inputvector, $current, $next) = @_;
    my $liv = $inputvector;
    my $n;

    if ($current eq "*") {
        print STDOUT "\t1";
    } else {
        print STDOUT "\tK2_State = $state_vars{$current}";
    }

    data: for ($n = $inputs_number; $n > 0; $n--) {
        local $_;
        $_ = $inputvector;
        if (/^1([01-]*)/) {
            print STDOUT " & I_input$n = 1";
            $inputvector = $1;
            next data;
        } 
        if (/^0([01-]*)/) {
            print STDOUT " & I_input$n = 0";
            $inputvector = $1;
            next data;
        }
        if (/^-([01-]*)/) {
            print STDOUT "";
            $inputvector = $1;
            next data;
        }
    }
    if ($inputvector =~ /^([01-]+)/) {
        print STDERR "WARNING: $liv $current $next\n";
        print STDERR "expected $inputs_number input bits.\n";
    }
    print STDOUT " :  $state_vars{$next};\n";
}

sub print_range {
    my (%h) = @_;
    my $n = 0;

    foreach $k (keys %h) {
        if ($n++ == 0) {
            print STDOUT "$h{$k}";
        } else {
            print STDOUT ", $h{$k}";
        }
    }
}

sub print_ivar {
    my ($innumber) = @_;
    my $i = 0;

    for($i = 1; $i <= $innumber; $i++) {
        print STDOUT "IVAR\n\tI_input${i} : boolean;\n";
    }
}

sub print_var {
    my (%state) = @_;
    my $i = 0;
    foreach $k (keys %state) {
        print STDOUT "VAR\n\tK2_State : {$state{$k}" if ($i eq 0);
        if ($i eq 0) {
            $i++;
        }
        else {
            print STDOUT ", $state{$k}";
        }
    }
    print STDOUT "};\n";
}

sub print_state_trans {
    my $i = $fsmlines -1 ;

    print STDOUT "ASSIGN\n";
    print STDOUT "  init(k2_State) := $state_vars{$init_state};\n" if (not ($init_state eq ""));
    print STDOUT "  next(K2_State) := case\n";
    for(; $i >= 0; $i--) {
        my $iv = $state_transitions[$i]{'Input'};
        my $cs = $state_transitions[$i]{'Current'};
        my $ns = $state_transitions[$i]{'Next'};
        print STDOUT "--\t$iv $cs $ns $state_transitions[$i]{'Output'}\n" if ($opt_d);
        &print_case($state_transitions[$i]{'Input'},$state_transitions[$i]{'Current'},$state_transitions[$i]{'Next'});
    }
    if ($opt_m) {
        print STDOUT "\t1 : K2_State;\n";
    } else {
        print STDOUT "\t1 : {";
        print_range(%state_vars);
        print STDOUT "};\n";
    }
    print STDOUT "esac;\n\n";
}

sub print_mapping {
    my (%map) = @_;
    my $i = 0;
    foreach $k (keys %map) {
        print STDOUT "\n\n-- State Name Mapping for conflicting state names\n" if ($i++ eq 0);
        print STDOUT "-- $k -> $map{$k} \n";
    }
    print STDOUT "\n";
}

sub print_header {
    print STDOUT "-- Generated with a translator from Kiss2 to SMV by\n";
    print STDOUT "-- Marco Roveri roveri\@irst.itc.it\n";
    print STDOUT "MODULE main\n";
}

sub parsekiss {
    my $num = 0;
    my $fsmlinesaux = 0;

    while(<STDIN>) {
        if ($opt_d) { print STDERR "Read $_"; }

        # Ignores trailing comments
        if (/^#/ || /^ #/) {
            next;
        }
        # Ignores trailing comments
        if (/^[\s\t]*$/) {
            next;
        }

        # Define input variables
        if (/.i\s*([0-9]+)\s*/) {
            $inputs_number = $1;
            if ($opt_d) { print STDERR ".i $1\n"; }
            next;
        }
        elsif (/.o\s*([0-9]+)\s*/) {
            if ($opt_d) { print STDERR ".o = $1\n"; }
            $output_number = $1;
            next;
        }
        elsif (/\.p\s*([0-9]+)\s*/) {
            if ($opt_d) { print STDERR ".p $1\n"; }
            $fsmlines = $1;
            $fsmlinesaux = $1;
            next;
        }
        elsif (/\.r\s*([\w\*]+)\s*/) {
            if ($opt_d) { print STDERR ".r $1\n"; }
            $init_state = $1;
            next;
        }
        elsif (/\.s\s*([0-9]+)\s*/) {
            if ($opt_d) { print STDERR ".s $1\n"; }
            $states_number = $1;
            $num = $1;
            next;
        }
        if (/([01-]+)\s*([\w\*]+)\s*(\w+)\s*([01-]*)/) {
            my $s = {};
            if ($opt_d) { print STDERR "$_"; }
            &die_and_remove("ERROR: * not yet handled\n$_") if ( $3 eq "*");
            $fsmlinesaux--;
            if ((not (defined($state_vars{$2}))) && (not ($2 eq "*"))) {
                &die_and_remove("$_ ERROR: Undefined state $2\n") if ($num < 0);
                $state_vars{$2} = escape_state($2);
                $num--;
            }
            if (not (defined($state_vars{$3}))) {
                &die_and_remove("$_ ERROR: Undefined state $3\n") if ($num < 0);
                $state_vars{$3} = escape_state($3);
                $num--;
            }
            $s->{'Input'} = $1;
            $s->{'Current'} = $2;
            $s->{'Next'} = $3;
            $s->{'Output'} = $4;
            push @state_transitions, $s;
            next;
        } else {
            &die_and_remove("ERROR: \"$_\" undefined construct\n");
        }
    }
    if ($num > 0) {
        print STDERR "WARNING: There are $num unused states out of $states_number\nUsed states = {";
        foreach $i (keys %state_vars) {
            print STDERR " $i ";
        } 
        print STDERR "}\n";
    }
    if (($fsmlinesaux > 0) || ($fsmlinesaux < 0)) {
        print STDERR "ERROR: There are ", $fsmlines - $fsmlinesaux, " product lines, out of the expected ", $fsmlines, "\n";
        die_and_remove("");
    }
}

sub main {
    parsekiss();
    print_header();
    print_ivar($inputs_number);
    print_var(%state_vars);
    print_state_trans();
    print_mapping(%state_renamed);
}

###############################################################
&getopts('o:f:hdm') || &help;

&help if ($opt_h);

if ( defined($opt_o) ) {
    open(STDOUT,"> $opt_o") or
    die "ERROR: opening file \"$opt_o\" for writing.";
}

if ( defined($opt_f) ) {
    open(STDIN,"< $opt_f") or
    die_and_remove("ERROR: opening file \"$opt_f\" for reading.");
}

print STDERR "Warning!!! Outputs not yet handled\n";

&main;

