#!/usr/bin/perl

print "Assembler for SISC 0.13 written by George Ho (kyho\@csie.nctu.edu.tw).\n\n";
if($ARGV[0] eq ''|| $ARGV[1] eq '')
{
 print "usage:siscasm.pl <input file> <output file>\n";
 exit;
}

if (! -e $ARGV[0])
{
 print "Input file doesn't exist!\n";
 exit;
}

@all_inst=("LOAD","LDI","STORE","MOVE","ADD","FADD","OR","AND","XOR",
           "ROTR","BR","HALT","NOP","GETC","PUTC","GETI","PUTI","GETS","PUTS",
           "CMP","JLT","JEQ","JGT","CALL","RET");
@all_direc=("ORG","DB","END");

%num_param=("LOAD"=>2,"LDI"=>2,"STORE"=>2,"MOVE"=>2,"ADD"=>3,
            "FADD"=>3,"OR"=>3,"AND"=>3,"XOR"=>3,"ROTR"=>2,"BR"=>2,"HALT"=>0,
            "NOP"=>0,"GETC"=>0,"PUTC"=>0,"GETI"=>0,"PUTI"=>0,"GETS"=>1,"PUTS"=>1,
            "CMP"=>2,"JLT"=>1,"JEQ"=>1,"JGT"=>1,"CALL"=>1,"RET"=>0);
%inst_class=("LOAD"=>1,"LDI"=>1,"STORE"=>1,"MOVE"=>3,"ADD"=>2,
             "FADD"=>2,"OR"=>2,"AND"=>2,"XOR"=>2,"ROTR"=>4,"BR"=>1,"HALT"=>5,
             "NOP"=>5,"GETC"=>5,"PUTC"=>5,"GETI"=>5,"PUTI"=>5,"GETS"=>6,"PUTS"=>6,
             "CMP"=>3,"JLT"=>7,"JEQ"=>7,"JGT"=>7,"CALL"=>7,"RET"=>5);
%inst_code=("LOAD"=>"1","LDI"=>"2","STORE"=>"3","MOVE"=>"40","ADD"=>"5",
            "FADD"=>"6","OR"=>"7","AND"=>"8","XOR"=>"9","ROTR"=>"A","BR"=>"B","HALT"=>"C000",
            "NOP"=>"0000","GETC"=>"D000","PUTC"=>"D100","GETI"=>"D200","PUTI"=>"D300",
            "GETS"=>"D5","PUTS"=>"D6","CMP"=>"F0","JLT"=>"F1","JEQ"=>"F2","JGT"=>"F3","CALL"=>"E2","RET"=>"E300");

open (IN,$ARGV[0]);

$pc=0; #program counter
%symtab=();

#pass 1

local($temp,@ops,@tmparr,$n);
while(<IN>)
{
 chop;
 $temp=$_;
print "{$temp}";
  $temp=~s/;.+//g; #delete comments
  $temp=~s/\s+$//; #delete ending spaces      
 if($temp !~/^\s+/) #no leading space -> label or comment
 {
  if($temp=~/:\s*$/) #ending with ':'
  {
   $temp=~s/\s//g; #delete space
   $temp=~s/://; #delete ':'
   $symtab{$temp}=$pc;
   next;
  }
  elsif($temp =~/^;/) #comment
  {
   next;
  }
  else
  {
   if ($temp !~/\s*/)
   {
    print "warning:$temp !!! Please make blanks in front of commands !!!\n";
   }
  }
 }
 else #instructions or directives or comment
 {
  if($temp=~/^\s+;/) #comment but has leading spaces MODIFIED BY MIGI
  {
   next;
  }
  $temp=~s/^\s+//; #delete leading spaces
print "->$temp\n";
#
#	MODIFIED BY MIGI
#

  @ops=split(' ',$temp); #seperate into opcode and operands
  if(&is_inst($ops[0]))
  {
   $pc+=2;
   @tmparr=split(/,/,$ops[1]);
   $n=@tmparr;
   if($num_param{uc($ops[0])} != $n)
   {
    print "$temp: $ops[0] must have $num_param{uc($ops[0])} parameters!!\n";
    exit;
   }
   next;
  }

  if(&is_direc($ops[0]))
  {
   if(uc($ops[0]) eq "ORG") #change the pc
   {    
#    $pc=ord(pack("H",$ops[1]));
     $pc=hex($ops[1]);
    print "$temp: the PC is now set to $pc.\n";
    next;
   }
   if(uc($ops[0]) eq "DB") #define bytes
   {
    @tmparr=split(/,/,$ops[1]); 
    $n=@tmparr;
    $pc+=$n;
   }
   if(uc($ops[0]) eq "END") #end of the program
   {
    break; #jump to pass 2
   }
  }
 }
} 

print "\n------------\n";
foreach (%symtab)
{
 print "$_, ";
}
print "\n------------\n";


#pass 2

close(IN);
open (IN,$ARGV[0]);  
open (OUT,">$ARGV[1]");

$pc=0;
while(<IN>)
{
 chop;
 $org_line = $_;
 $org_line =~ s/\t+/ /g; #change TAB to SPACE
 $temp=$org_line;
 $temp=~s/;.+//g; #delete ending comments
 $temp=~s/\s+$//; #delete ending spaces      
 if($temp !~/^\s+/) #label or comment -> skip
 {
  print OUT "     ; $org_line\n";
  next;
 } 
 else #instructions or directives or comments
 {
  if($temp=~/^\s+;/) #comment but has leading spaces MODIFIED BY MIGI
  {                                          
   next;
  }
  $temp=~s/^\s+//; #delete leading spaces
  @ops=split(' ',$temp); #seperate into opcode and operands
  if(&is_inst($ops[0]))
  {
   @tmparr=split(/,/,$ops[1]);
   print OUT &process_inst($ops[0],@tmparr)," ;$org_line","\n";
   $pc+=2;
   next;
  }
  elsif(&is_direc($ops[0]))
  {
   if(uc($ops[0]) eq "ORG")
   {
#    $tmp_pc=ord(pack("H",$ops[1]));
     $tmp_pc=hex($ops[1]);
    if ($pc > $tmp_pc)
    {
     print "ORG is too small!!";
     exit;
    }
    for ($i=$pc; $i < $tmp_pc; $i+=2)
    {
     print OUT "0000\n";
    }
    $pc = $tmp_pc;
#-------------------    ADDED BY MIGI   --------------
#    print "$temp: the PC is now set to $pc.\n";
    next;
   }
   if(uc($ops[0]) eq "DB")
   {
    @tmparr=split(/,/,$ops[1]);
    $n=@tmparr;
    $pc+=$n;
    for($i=0;$i<$n;$i++)
    {
     print OUT $tmparr[$i];
     print OUT "\n" if $i%2==1;     
    }
    next;
   }
   if(uc($ops[0]) eq "END") #end of the program
   {
    break;
   }
  }
  else
  {
   print "I don't know: $temp\n";
  }
 }
}

close(IN);
close(OUT);

print "Last PC = $pc.\n";

sub is_inst
{
 local($i,$len);
 $len=@all_inst;
 for($i=0;$i<$len;$i++)
 {
  if(@_[0]=~/^$all_inst[$i]$/i) 
  {
   return 1;
  }
 }
 return 0;
}

sub is_direc
{
 local($i,$len);
 $len=@all_direc;
 for($i=0;$i<$len;$i++)
 {
  if(@_[0]=~/^$all_direc[$i]$/i)
  {
   return 1;
  }
 } 
 return 0;
}

sub process_inst #@_[0]==opcode,@_[1]...=operands
{
 local($type,$i,$temp,$convtmp,$offset);
 $temp="";
 $convtmp="";
 $type=$inst_class{uc(@_[0])};
 if($type==0)
 {
  print "@_[0]:No type zero!!!\n";
  exit;
 }
 if($type==1)
 {
  $temp=$inst_code{uc(@_[0])};
  $temp.=reg_conv(@_[1]);
  $convtmp=@_[2];
  if($convtmp =~/^[0-9A-F][0-9A-F]$/)
  {
   $temp.=$convtmp;
   return $temp;
  }
  elsif($convtmp =~/^[0-9a-f][0-9a-f]$/)
  {
   $temp.=$convtmp;
   return $temp;
  }
  else
  {
   if(@_[2]=~/^@/) #using $pc
   {
    if(@_[2]=~/[+-]/) #using offset
    {
     $offset=(split(/[+-]/,@_[2]))[1];
     if(@_[2]=~/\+/)
     {
      $temp.=sprintf("%02X",$pc+$offset);
      return $temp;
     }
     else
     {
      $temp.=sprintf("%02X",$pc-$offset);
      return $temp;
     }
    }
    $temp.=sprintf("%02X",$pc);
    return $temp;
   }
   if( !exists($symtab{@_[2]})) # label not found
   {
    if(@_[2]=~/[+-]/) #using offset
    {
     $offset=(split(/[+-]/,@_[2]))[1];
     $convtmp=(split(/[+-]/,@_[2]))[0];
     if( !exists($symtab{$convtmp})) #still not found
     {
      print "Label ",$convtmp," is not found!!\n";
      exit;
     }
     if(@_[2]=~/\+/)
     {
      $temp.=sprintf("%02X",$symtab{$convtmp}+$offset);
      return $temp;
     }
     else
     {
      $temp.=sprintf("%02X",$symtab{$convtmp}-$offset);
      return $temp;
     }
    }
    print "Label ",@_[2]," is not found!!\n";
    exit;
   }
   else  #Label found
   {
    $temp.=sprintf("%02X",$symtab{@_[2]});
    return $temp;
   }
  }
 }
 if($type==2)
 {
  $temp=$inst_code{uc(@_[0])};
  $temp.=reg_conv(@_[1]);
  $temp.=reg_conv(@_[2]);
  $temp.=reg_conv(@_[3]);
  return $temp;
 }
 if($type==3)
 {
  $temp=$inst_code{uc(@_[0])};
  $temp.=reg_conv(@_[1]);
  $temp.=reg_conv(@_[2]);
  return $temp;
 }
 if($type==4)
 {
  $temp=$inst_code{uc(@_[0])};
  $temp.=reg_conv(@_[1]);
  if(@_[2]>7 || @_[2]==0)
  {
   print "X must be less than 8 in ROTR !!!\n";
   exit;
  }
  $temp.=sprintf("%02X",@_[2]);
  return $temp;
 }
 if($type==5)
 {
  $temp=$inst_code{uc(@_[0])};
  return $temp;
 }
 if($type==6)
 {
  $temp=$inst_code{uc(@_[0])};
  $convtmp=@_[1];
  if($convtmp =~/^[0-9A-F][0-9A-F]$/)
  {
   $temp.=$convtmp;
   return $temp;
  }   
  else
  {
   if( !exists($symtab{@_[1]} )) # label not found
   {
    print "Label ",@_[1]," is not found!!\n";
    exit;
   }
   else
   {
    $temp.=sprintf("%02X",$symtab{@_[1]});
    return $temp;
   }
  }
 }
 if($type==7)  #almost the same as type 6
 {
  $temp=$inst_code{uc(@_[0])};
  $convtmp=@_[1];
  local($data);
  if($convtmp =~/^[0-9A-F][0-9A-F]$/)
  {
   $data=$convtmp;
  }   
  else
  {
   if( !exists($symtab{@_[1]} )) # label not found
   {
    print "Label ",@_[1]," is not found!!\n";
    exit;
   }
   else
   {
    $data=sprintf("%02X",$symtab{@_[1]});
   }
  }
  if (hex($data)>=256)
  {
   $temp=sprintf("%02X",hex($temp)+int(hex($data)/256)*4);
   $data=~s/^.//;	#remove first letter
  }
  $temp.=$data;
 }
}

sub reg_conv
{
 local($temp,$t);
 $temp=@_[0];
# if($temp=~/r[0-9]/i || $temp=~/r1[0-5]/i)
 if($temp=~/r[0-9a-f]/i  || $temp=~/r[0-9A-F]/i)
 {
  $temp=~s/^r//i;
 }
 else
 {
  print "Error: pc=$pc !!! unknown register !!!\n";
  exit;
 }
# return sprintf("%X",$temp);
 return $temp;
}
