#!/usr/bin/perl -w
######################################################################
# Contents:
# §1 License
# §2 TODO/Buglist/Changes
# §3 Definitions of terms
# §4 Program
# §4.1 Declaration of options and their defaults
# §4.2 Declaration of global variables
# §4.3 The sub help(); Help on program invocation
# §4.4 General auxiliary subroutines
# §4.5 Main tasks of spicep (bigger subroutines)
# §4.6 Subroutines for parameterized subcircuits
# §4.7 Rules
# §4.8 Main program
######################################################################
# §1 License of this program: LGPL (library general public licency)
# see http://www.gnu.org/ for the full text of the license.

######################################################################
# §2 TODO: (1) Incompatibility with spicep.cc, no conversion to lower case text.
# The problem here is the case sensitivity of file names in .include statements.
# Currently, all tests for spice control charts are case insensitive. This can be
# simplified when to-lower-case conversation is added.

# (2) Cannot read binary raw-files.

# (3) use strict; doesnot work :-(

# (4) Transform RuleSpiceComment to an optional rule.
#
# Changes:
# 2004-01-10:
# Bug-Description: Appearences of .end in spice comment lines
# end the spice-template.
# Bug-Fix:  Changed in RuleCallSpice /\.end\b/ to /^\.end\b/
######################################################################
# §3 Definitions of terms:

# Template file: the spice-netlist file with additional charts for
# parametric analysis. The template file is stored in @Template.

# Rules: Perl subroutines that transform the lines in @Template into
# lines in PlProg. The rules are gathered in the array @Rules.
# Normally, all rules from @Rules are applied to each line of
# @Template.  However, there are Possibilities to stop the
# interpretation of @Rules.  Look into the program source for greater
# detail.
# TODO: Thorough description of Rules.

# PlProg: Perl program (stored in @PlProg) which runs the parametric
# analysis. That is, for PlProg creates the spice-netlist @Netlist,
# which can directly feed to spice3. Then PlProg calls spice3 and
# catches the output of spice3 for transformation into a gnuplot data
# file.
# ***Donot look for a sub PlProg inside this file.***
# As already mentioned: The program PlProg is automatically created by
# applying the rules from @Rules to @Template.

# Netlist: @PlProg translates the Template into the netlist @Netlist
# and feeds it to spice3.  If there are .step param lines in the
# Template, then @Netlist is recreated for each spice3 simulation.

# Ordered Hash: Here, we use a pair of a hash and an array which both
# have the same name as odered hash.


######################################################################
# §4 Program
######################################################################

package Spicep;

######################################################################
# §4.1 Declaration of options and their defaults:
######################################################################
# Defaults for the options:
our $OptFlatten = 1;
our $OptVerbose = 0;
our $OptQuiet = 0;
our $OptDataSeparator = "\n";
our $OptTwoNewlines = 0;
our $OptDeleteNongrid = 0;
our $OptGnuplotComments = 1;
our $SyscallSpice = "spice";
our $SpiceOutFile = "spice.out";
use constant SPICE_SAVE => 1;
use constant SPICE_PRINT => 2;
use constant RULES_CHECK_LINE_AGAIN => -1;
our $SpiceOutputFormat = 0; # may be SPICE_SAVE or SPICE_PRINT
our $DataFile = "gnuplot.dat";
our $RawFile = "rawfile";
######################################################################
# §4.2 Declaration of global variables
######################################################################
# Constants:
# $RegExpFloat='([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?';
######################################################################
# Global Variables:
$NoSimulations = 0;
$firstNoPoints = 0; # For deleting non-grid data.
# VarList and ParamList are ordered hashes.
%ParamList=(); # The parameter hash table. ( Parameter name => Value ).
@ParamList=(); # The parameter names in an array. (In the order of occurence.)
@VarList=(); # $VarList[n] = nth variable name
%VarList=(); # $VarList{variable name} =  array of values
# Template:
$Template=0; # Counter for the line of the templatefile.
@Template=(); # Stores the template file line-wise.
###################################
# Perl-program created from @Template:
@PlProg=();
###################################
# Spice-netlist created by PlProg:
#@Netlist=();
######################################################################
# Just boolean Variables:
$StopRules = 0;
$StopTemplate = 0;
$FlagComplex = 0;

######################################################################
# §4.3 Help for program invocation:
sub help
{
    print <<EOF;
======================================================================
Program for parametric network analysis with the help of spice3.
Usage: spicep [Options] <cir-file>
 Valid Options: 

 -h help

 -v verbose

 -q quiet (no output for error free runs)

 -o spice-dat-file (default:spice.dat):
      name of the produced data file

 -g gnuplot-dat-file (default:'gnuplot.dat'):
      name of the produced gnuplot data file

 -s spice-file (default:'spice.cir'): name of the temporary spice-file

 -2 insert two new lines between two parameter sets

 -d delete non-grid data from gnuplot-dat-file

 -l DEBUG

 See the spicep manpage for a full documentation.

======================================================================
EOF
}

######################################################################
# §4.4 General auxiliary subroutines:
######################################################################

###################################
# Access to ordered hashes via index and name:
sub byIdx(*$) {
    local *name = shift;
    my $idx = shift;
    if( $idx > $#name ) { return undef; };
    return $name{$name[$idx]};
}

sub byName(*$)
{
    local *HashArray = shift;
    my $name = shift;
    if( !exists( $HashArray{$name} ) )
    { push( @HashArray, $name ); $HashArray{$name}=[]; }
    return $HashArray{$name};
}

###################################
# When parsing the template line-wise:
##########

sub stopRules() {
    $StopRules = 1;
}

sub applyRulesAgain() {
  $Template --;
  stopRules();
}

# Finish parsing the template
# at .end in the template file:
sub stopTemplate() {
  stopRules();
  $StopTemplate = 1;
}

###################################
# Converting spice numbers to floats:
# At first some required constants:
%UNITS = (
	  "mil" => 25.4e-6,
	  "t" => 1e12,
	  "g" => 1e9,
	  "meg" => 1e6,
	  "k" => 1e3,
	  "m" => 1e-3,
	  "u" => 1e-6,
	  "n" => 1e-9,
	  "p" => 1e-12,
	  "f" => 1e-15,
	  "" => 1.0
	  );

$RXUNITS="(mil|t|g|meg|k|m|u|n|p|f|)";
$RXFLOAT='([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?';
$RXNUMBER="($RXFLOAT)$RXUNITS?";
$IDXFLOAT=1; $IDXUNIT=6;

# subroutine for converting spice numbers to floats:
# input: some array
# output: the same array (modified in place)
#         with the spice numbers replaced by the corresponding floats.
sub spiceNumbersToFloat
{
    foreach (@_) {
	if( /$RXNUMBER/i ) {
	    $_ = ${$IDXFLOAT} * $UNITS{${$IDXUNIT}};
	} }
    return @_;
}

######################################################################
# §4.5 Main tasks of spicep
######################################################################

###################################
# Filter Spice output and write it to disk:
sub readSpiceRawFile (**){
  local(*myRawFile, *VarList) = @_;
  ##########
  my( $i, $Line, $RawDataType, $NoVariables, $NoPoints );
  ##########
  foreach (["Title:",],
	   ["Date:",],
	   ["Plotname:",],
	   ["Flags:\\s(complex|real)",\$RawDataType],
	   ["No. Variables:\\s(\\d*)",\$NoVariables],
	   ["No. Points:\\s(\\d*)",\$NoPoints]
	  ) {
    if( eof(myRawFile) ) { die "Unexpected end of Spice-rawfile.\n" };
    $Line = <myRawFile>;
    ( ( ${${$_}[1]} ) = ($Line =~  /^${$_}[0]/) ) ||
      die "Failed to read \"$_\" from rawfile.\n"
    }
  $FlagComplex = ($RawDataType =~ "complex");
  
  # Eat "Command:" lines:
  do { $Line = <myRawFile>; } while ( $Line =~ /^Command:/ );
  
  $Line =~ /^Variables:/ ||
    die "Missing variable names in the rawfile.\n";
  
  for($i=0; $i<$NoVariables; $i++) {
    (!eof(myRawFile)) && (  ( $Line = <myRawFile> ) =~ /\s+\d+\s+(\S+)/  ) ||
      die "Failed to read name of variable $i.\n";
    byName(VarList,$1);
  }
  
    (!eof(myRawFile)) && ( ( $Line = <myRawFile> ) =~ /^Values:/ ) ||
      die "Didn't find line \"Values:\" in the rawfile.\n";
  
  readSpiceRawDataASCII(*myRawFile,*VarList,$NoPoints,$FlagComplex);    
}

###################################
sub readSpiceRawDataASCII (**$$){
    local *RawFile = shift;
    local *VarList = shift;
    my ($NoPoints,$FlagComplex) = @_;
    my $i;
    ##########
    for($i=0; $i < $NoPoints; $i++ ) {
	# Reading the variable values of one data set:
	for($j=0; $j<=$#VarList; $j++) {
	    eof(RawFile) && die "Unexpected end of file\n";
	    $Line = <RawFile>;
	    if( $FlagComplex ) {
		( $Line =~ /^(\d*)?\s+($RXFLOAT)\s+($RXFLOAT)/ ) ||
		    die "Failed to read complex data from $RawFile.\n";
		push @{byIdx(*VarList,$j)}, {0.0+$2, 0.0+$3};
	    } else {
		$Line =~ /^(\d*)?\s+($RXFLOAT)/ ||
		    die "Failed to read real data from $RawFile.\n";
		push @{byIdx(VarList,$j)}, 0.0+$2;
	    }}}
}

######################################################################
sub readSpicePrintFile (**){
    local *SpiceOut = shift; # filehandle
    local *VarList = shift; # Get variable list as ordered Hash
    ##########
    my( $line, @myvars, $var, @mynums, $myindex, $i );
    while(!eof(SpiceOut))
    {
	do { $line = <SpiceOut>; } until ( $line =~ /^Index/ ) || eof(SpiceOut);
	if( eof(SpiceOut) ) { last; }
	@myvars = split( " ", $line );
	shift( @myvars ); # Remove "Index"
	
	$line = <SpiceOut>; # /^-*$/
	while( $line = <SpiceOut>,
	       ( $line =~ /^\d/ ) && ( !eof(SpiceOut) )
	       ){
	    @mynums = split( " ", $line );
	    # The first number is the index.
	    $myindex = 0 + shift(@mynums);
	    for( $i=0; $i <= $#myvars; $i++ )
	    {   ${byName(VarList,$myvars[$i])}[$myindex] = 0.0+$mynums[$i];   }
	}
    }
} # End of readSpicePrint

######################################################################
sub writeGnuplotData (***){
  local *DataFile = shift; # we donot open or close here since it might be STDOUT
  local *VarList = shift; # (ordered hash)
  local *ParamList = shift; # Parameter names and values.
  ##########
  my( $ptidx, $par, $var );
  for( $ptidx=0; $ptidx <= $#{byIdx(VarList,0)}; $ptidx++ ) {
    foreach $par (@ParamList)
      {
	print DataFile " " . $ParamList{$par};
      }
    foreach $var (@VarList)
      { print DataFile " " . $VarList{$var}[$ptidx]; }
    print DataFile "\n";
  }
}

###################################
# all variables here are global
sub spiceOutput {
    open( SpiceOutFile, "$SpiceOutFile" ) 
	|| die "Cannot open spice output $SpiceOutFile\n";

    $SpiceOutputFormat == SPICE_SAVE && readSpiceRawFile( *SpiceOutFile, *VarList );
    $SpiceOutputFormat == SPICE_PRINT && readSpicePrintFile( *SpiceOutFile, *VarList );

    close SpiceOutFile;

    open( DataFile, ">>$DataFile" )
	|| die "Cannot open gnuplot data file $DataFile\n";
    if( $NoSimulations == 0 ) {
      local $"=" ";
      $OptGnuplotComments && print( DataFile "# @ParamList @VarList\n");
      $firstNoPoints = @{byIdx(VarList,0)};
    }

    if( !$OptDeleteNongrid || $firstNoPoints == @{byIdx(VarList,0)} ){
      printf DataFile "$OptDataSeparator" unless $NoSimulations == 0;
      writeGnuplotData( *DataFile, *VarList, *ParamList );
    }

    close DataFile;
    ++$NoSimulations;
}
###################################
# applyRules( \@Rules , rest )
# First argument is a reference of an array with rules.
# Which are to be applied to @Template from line $Template on.
# applyRules normally ends up with $StopTemplate = 1
# When not, file end is reached while looking for something like `.end'

sub applyRules {
  my @Rules = @{shift()}; # The remaining arguemts are passed to the Rules.
  my $myRule;
  $StopTemplate = 0; # Else we would not have been called.
  for(; $Template <= $#Template; $Template++) {
    $StopRules = 0;
    foreach my $myRule (@Rules) {
      &$myRule($Template[$Template],@_);
      if( $StopRules ) { last; }
    }
    if( $StopTemplate ) { last; }
  }
}

sub translateTemplateToPlProg {
  ############
  # There follows the translation loop. Inside this loop the template
  # file is translated into a perl file which in turn creates the
  # spice-netlist file.
  # The @Template is accessed via $Template.
  # So you can change @Template inside the translation loop
  ###################################
  # Composing PlProg which in turn
  # launches the simulation:
  $Template = 0; # Apply rules to @Template from line $Template=0 on.
  applyRules(\@Rules);
  $StopTemplate || print "Warning: Template did not end with \`.end\'\n" unless $OptQuiet;
  @PlProg = (@Head, @Body, @Tail);
}

sub simulationInfo() {
  return "Variables: @ParamList @VarList\n".
    "Number of data points of one simulation: ".@{byIdx(VarList,0)}.
      "\nNumber of simulations: $NoSimulations\n";
}

######################################################################
# §4.6 Subroutines for parameterized subcircuits
######################################################################

# Definition of all spice3 network elements:
%SpiceElements = (
		  # elementary devices:
		  'r' => ['n','n'],
		  'l' => ['n','n'],
		  'c' => ['n','n'],
		  'k' => ['v','v'],
		  's' => ['n','n','n','n'],
		  'w' => ['n','n','v'],
		  # independent sources:
		  'v' => ['n','n'],
		  'i' => ['n','n'],
		  # linear controlled sources:
		  'g' => ['n','n','n','n'],
		  'e' => ['n','n','n','n'],
		  'f' => ['n','n','v'],
		  'h' => ['n','n','v'],
		  # nonlinear dependent sources:
		  'b' => ['n','n'],
		  # transmission lines:
		  't' => ['n','n','n','n'],
		  'o' => ['n','n','n','n'],
		  'u' => ['n','n','n'],
		  # transistors and diodes:
		  'd' => ['n','n'],
		  'q' => ['n','n','n'], # note the 4 node form is not supported
		  'j' => ['n','n','n'],
		  'm' => ['n','n','n','n'],
		  'z' => ['n','n','n']
);

sub parseSubcktParams($$) {
# first argument: reference of %SubcktParams
# second argument: parameter string
  my $SubcktParams = $_[0];
  my($param, $val);
  @_ = split(/\s+/,$_[1]);
  foreach (@_){
    ( ($param, $val) = ( $_ =~ /([^=]*)=(.*)/ ) ) || ( $param = $_, $val = 0 );
    ${$SubcktParams}{$param} = $val;
  }
}

###################################
sub SubcktNode ($$){
  my $node = shift;
  my $SubcktNodes = shift; # reference to %SubcktNodes.
  return '0' if $node eq '0';
  return '$SubcktNodes['.$$SubcktNodes{$node}.']' if exists( $$SubcktNodes{$node}  );
  return '$SubcktInstance:'. $node;
}

######################################################################

bless( $RuleSubcktElements = sub {
	 $SubcktElement = lc(substr($_[0],0,1));
	 exists($SpiceElements{$SubcktElement}) || return;

	 local *SubcktRuleLines = $_[1];

	 @_ = split( /\s/, $Template[$Template]);
	 # Decompose the start string on the Template into:
	 $SubcktElementInstance = substr($_[0],1);

	 # Check whether we know SubcktElement:
	 $SubcktRuleLines .= 'push @SubcktTemplate, "';
	 # The name of the subckt element in the instance:
	 $SubcktRuleLines .= $SubcktElement .':$SubcktInstance:'. $SubcktElementInstance;
	 die "Incomplete network element definition in subckt.\n" if( $#_ < $#{$SpiceElements{$SubcktElement}} + 1 );
	 # process element nodes / current measuring dummy voltage sources (TODO:Models):
	 for($i=0; $i < @{$SpiceElements{$SubcktElement}}; $i++) {
	   $SubcktRuleLines .= ' ';
	   if( 'n' eq ${$SpiceElements{$SubcktElement}}[$i] ) { #entry is a node
	     $SubcktRuleLines .= SubcktNode($_[$i+1],\%SubcktNodes);
	   }
	   elsif( 'v' eq $SpiceElements{$SubcktElement}[$i] ) { # entry is a vsrc (or something similar)
	     $SubcktRuleLines .= ' '. substr($_[$i+1],0,1) . ':$SubcktInstance:' . substr($_[$i+1],1);
	   }}
	 # All the rest of this subckt line:
	 my $Line =();
	 foreach my $PositionalArgument (@_[$#{$SpiceElements{$SubcktElement}}+2..$#_]) {
	   $Line .= ' '. $PositionalArgument;
	 }
	 # Special case of the b source:
	 if( $SubcktElement eq 'b' ) {
	   # Replacing nodes in v(...):
	   $Line =~ s/v\(([^\)]*)\)(?{SubcktNode($1,\%SubcktNodes)})/v\($^R\)/gi;
	   # Replacing v-sources in i(...):
	   $Line =~ s/i\(([^\)])([^\)]*)\)/i\($1:\$SubcktInstance:$2\)/gi;
	 }

	 # Parameter substitution:
	 $Line =~ s/\{(\w*)\}(?{ ( exists $SubcktParams{$1} ? '$SubcktParams{'.$1.'}' : '{' . $1 . '}' ) })/$^R/g;
	 # End this subckt line:
	 $SubcktRuleLines .= $Line . '";
';
	 # ... next subckt element...
       }, "RuleSubcktElements");

bless( $RuleSubcktEnd = sub {
	 lc(substr($_[0],0,7)) eq ".ends" || return;
	 stopTemplate();
       }, "RuleSubcktEnd");

$RuleSubckt = sub {
  my( $params, $val );
  ( ($_, $params) = ( $_[0] =~ /^.subckt (.*) params: (.*)$/i ) ) || return;

  ###################################
  # Parsing the .subckt - line:
  @_ = split;

  $SubcktName = shift;

  $SubcktNodes = 0; # Number of nodes.
  foreach (@_) { $SubcktNodes{$_}=$SubcktNodes++; }

  parseSubcktParams(\%SubcktParams,$params);
  ###################################
  # Composing SubcktRule:
  # the list of nodes and the list of parameters are
  # each passed as string arguments
  $SubcktRuleLines = ' sub {
$SubcktInstance = shift;
@SubcktNodes = split(/\s/,$_[0]);
%SubcktParams = (';
    # set the default parameter values:
  @SubcktParams = keys(%SubcktParams);
  for ($i=0;$i<$#SubcktParams;$i++) { $SubcktRuleLines .= $SubcktParams[$i] . ' => ' . $SubcktParams{$SubcktParams[$i]} . ','; }
  $SubcktRuleLines .= $SubcktParams[$i] . ' => ' . $SubcktParams{$SubcktParams[$i]} .');
'; # last parameter without comma but with line end.

  # Now initialize the parameters by the values in the subckt instantiation
  $SubcktRuleLines .= 'parseSubcktParams(\%SubcktParams,$_[1]);
@SubcktTemplate = ();
';
  # Next, translate the lines between .subckt and .ends into the
  # corresponding rule in $XRule.  If some 'x' statement with
  # name $SubcktName appears in the netlist &$XRule{$SubcktName} writes the
  # modified netlist into @Template

  applyRules(\@SubcktRules,\$SubcktRuleLines);
  $StopTemplate || die "Reached end of file while parsing .subckt.\n";
  $StopTemplate = 0;

  # TODO: Here, we should process the non-known elements.

  # Finish $SubcktRuleLines:
  # $SubcktRule: replace line x$SubcktInstance by the well prepared subckt itself:
  $SubcktRuleLines .= 'splice @Template, $Template, 1, @SubcktTemplate;
}'; # and that's the end of SubcktRule = sub {...}

  # End of $SubcktRuleLines:
  $XRules{$SubcktName} = eval $SubcktRuleLines;
};
bless( $RuleSubckt, "RuleSubckt" );
###################################

######################################################################
# §4.7 Rules
######################################################################
# There follow the rules for transformation of Template into  PlProg.
# The rules are blessed. Therefore you can determine their names with
# the Perl command ref.
# The rules are ordered with respect to their importance here.
# This isnot possible in the @Rules array since the rules may depend on
# each other.
#
# Syntax for the rules:
# The argument $_[0] to a rule is the current line in @Template.

###################################
# Raw spice lines are almost copied into the @Netlist.
# The only exception is when there are parameters in the Template line.
# Then, in PlProg, the normal substitution of Perl variables is employed
# to replace the parameter by its numerical value.
bless( $RuleRawSpice = sub {
    local $_= $_[0];
    # Translate pspice parameters {PARAM} to Perl variables $PARAM
#    while( /^(.*[^\$]){([^\}]*)}(.*)$/ )
#     {
# 	$_ = "$1\$$2 $3";
#     }
    s/{/\${/;
    push @Body, "push \@Netlist, \"$_\";";
},
       "RuleRawSpice");

###################################
# Template is read in up to the end.
# Write lines for the spice-analysis and stop reading Template:
bless( $RuleCallSpice = sub {
    if( $_[0] =~ /^\.end\b/i || $Template == $#Template )
    {
	unshift @Body, "\@Netlist = ();";
#		 push @Body, "{ my  \@Output;";
	push @Body, "push \@Netlist, \"\n.control\nset filetype=ascii\n.endc\n.end\n\";";
	if($SpiceOutputFormat == SPICE_SAVE) {
	  push @Body, "\@Output = ();";	  
	  push @Body, "push \@Output, \`$SyscallSpice -b -r $SpiceOutFile <<EOF\n\@Netlist\nEOF\n\`;";
	}
	else {
	  # It would be better to push the output into @Output.
	  # For compatibility with spicep.c we do it per redirection:
	  push @Body, "\`$SyscallSpice -b <<EOF >$SpiceOutFile\n\@Netlist\nEOF\n\`;";
	}
	push @Body, "Spicep::spiceOutput();";
#		 push @Body, "} # end of my\n";
	push @Tail, "return \@Output;";
	stopTemplate();
    }
},
       "RuleCallSpice" );

###################################
# Detect the output format:
bless( $RuleOutputFormat = sub {
	 if( $SpiceOutputFormat != SPICE_SAVE ) {
	   $_[0] =~ /^ ?\.(save)|(print)/i;
	   if( $1 ) { $SpiceOutputFormat = SPICE_SAVE; }
	   if( $2 ) { $SpiceOutputFormat = SPICE_PRINT; }
	 }
},
       "RuleOutputFormat");

###################################
# Remarks and line continuation of
# pspice:
bless( $RulePSpiceComment = sub {
	 my $commentPos = index($_[0], ";");
	 if( $commentPos > -1 ) { $Template[$Template]=substr($_[0],0,$commentPos); }
       }, "RulePSpiceComment");


###################################
# Its better to remove also spice comments,
# Since unwanted substitutions can occur in comments.
bless( $RuleSpiceComment = sub { # TODO: Change this rule to an optional one.
	 ( substr($_[0],0,1) eq "*" ) || return;
	 $Template || return; #First line may not be removed!
	 stopRules(); # ignore this Template line.
       }, "RuleSpiceComment");

###################################
# corresponding hack:
bless( $RuleSubcktHack = sub { # Add a comment line between .subckt and .ends.
	 $_[0] =~ /^.ends\b/ || return;
	 push @Body, 'push @Netlist, "*";';
	 }, "RuleHackSubcktHack" );

bless( $RulePSpiceLineCont = sub {
	 if( $Template < $#Template && substr($Template[$Template+1],0,1) eq "+" ) {
	   $Line=$_[0].substr($Template[$Template+1],1);
	   splice( @Template, $Template, 2, $Line);
	   applyRulesAgain();
	 }
       }, "RulePSpiceContinuation");

###################################
# .step param list lines: (for parameter lists, what else?)
# Note: also .stepparam is admissible.
bless( $RuleStepParamList = sub {
    my( $Param, $ParamValues );
    if( ($Param, $ParamValues ) =
	( $_[0] =~ /^.step ?param ([\w]*) list (.*)$/i ) )
    {
	push @ParamList, $Param;
	@ParamValues = split( /\s/, $ParamValues );
	spiceNumbersToFloat( @ParamValues );
	local $" =",";
	push @Head, "foreach \$$Param (@ParamValues) {";
	push @Head, "\$Spicep::ParamList{$Param}=\$$Param;";
	unshift @Tail, "}# Loop for parameter $Param";
	stopRules();
    }
},
       "RuleStepParamList");

###################################
# Rule for substituting
# x... <nodelist> params: <paramlist>
# by the corresponding subckt-netlist.

$RuleX = sub {
  ( lc(substr($_[0],0,1)) eq "x" ) || return;
  ( ($SubcktInstance, $SubcktNodes, $name, $SubcktParams) = ($_[0] =~ /x(\S*)\s+(.*)\s+(\S*)(?:\sparams: (.*))?$/i) )
    || die "Error while parsing subckt instance near:\n$_[0]\n";
  if( exists($XRules{$name}) ) {
    &{$XRules{$name}}( $SubcktInstance, $SubcktNodes, ( $SubcktParams ? $SubcktParams : "" ));
  }
};
bless( $RuleX, "RuleX" );


###################################
# .step param lines for parameter ranges:
bless( $RuleStepParam = sub {
    my( $Param, $ParamBegin, $ParamEnd, $ParamStep );
    if( ( $Param, $ParamBegin, $ParamEnd, $ParamStep )
	= ( $_[0] =~ /^ ?\.step ?param ([\w]*) ([\S]*) ([\S]*) ([\S]*)/i) )
    {
	push @ParamList, $Param;
	spiceNumbersToFloat( ( $ParamBegin, $ParamEnd, $ParamStep ) );
	push @Head, "for( \$$Param=$ParamBegin;";
	push @Head, "\t( $ParamStep>0 ?";
	push @Head, "\t\t\$$Param <= $ParamEnd :";
	push @Head, "\t\t\$$Param >= $ParamEnd );";
	push @Head, "\t\$$Param += $ParamStep ) {";
	push @Head, "\$Spicep::ParamList{$Param}=\$$Param;";
	unshift @Tail, "}# Loop for parameter $Param";
	stopRules();
    }
},
       "RuleStepParam");

###################################
# Rule for .Body source code lines
bless( $RulePlProgBody = sub {
    my $BodyLine;
    if( ($BodyLine) = ( $_[0] =~ /^\.Body (.*)$/i ) ) {
	push @Body, $BodyLine;
	stopRules();
    }
},
       "RulePlProgBody");

###################################
# Rule for .Tail source code lines
bless( $RulePlProgTail = sub {
    my $TailLine;
    if( ($TailLine) = ( $_[0] =~ /^\.Tail (.*)$/i ) ) {
	unshift @Tail, $TailLine . "\n";
	stopRules();
    }
},
       "RulePlProgTail");
###################################
# Rule for .Head source code lines
bless( $RulePlProgHead = sub {
    my $HeadLine;
    if( ($HeadLine) = ( $_[0] =~ /^\.Head (.*)$/i ) ) {
	push @Head , $HeadLine;
	stopRules();
    }
},
       "RulePlProgHead");
###################################
#  Just .Perl:
bless( $RulePerl = sub {
    my @Result;
    if( @Result = ( $_[0] =~ /^ ?\.Perl (.*)$/i ) ) {
	eval "$Result[0]";
	stopRules();
    }
},
       "RulePerl");

###################################
# nobody realy needs .param lines:
bless( $RuleKillDotParam = sub { 
    if($_[0] =~ /^ ?.param /i) { stopRules(); }
},
       "RuleKillDotParam");

######################################################################
# @Rules is the heart of spicep.pl. @Rules stores the rules which are
# applied to the lines of the template to translate the template file
# to a perl file which in turn creates the final spice-netlist file.
# You can change @Rules from within the Template via the .Perl chart.
# Note, that you must call stopRules() in this case!
# Rules we start with:
@Rules = (
	  $RulePlProgBody,
	  $RulePlProgTail,
	  $RulePlProgHead,
	  $RulePerl,
 # Note, line pspice comments and line continuation won't work for perl lines.
	  $RulePSpiceLineCont,
	  $RuleSpiceComment,
	  $RuleSubcktHack,
	  $RuleKillDotParam,
	  $RuleStepParam,
	  $RuleStepParamList,
	  $RuleSubckt,
	  $RuleX,
	  $RuleOutputFormat,
	  $RuleCallSpice,
	  $RuleRawSpice
	  ); # End of standard Rules in @Rules.

###################################
# Optional Rules:
###################################
# Option: $OptFlatten
# If there occurs some
# .include includeFile
# in the Template file then the includeFile
# file is read in and inserted into @Template.
# Short: the netlist is ***flattened***.
# That means, all Rules are applied to the whole
# netlist including the includeFile.

bless( $RuleFlatten =  sub {
	 my( $FileName, @Lines );
	 if( ($FileName) = ( $_[0] =~ /.include (.*)/i ) ) {
	   print "\nIncluding file $FileName\n" if $OptVerbose;
	   open(IncludeFile,"$FileName") or die "\nCannot find included Template file $FileName\n";
	   
	   @Lines = <IncludeFile>;
	   chop(@Lines);
	   splice( @Template, $Template, 1, @Lines );
	   
	   close(IncludeFile);
	   applyRulesAgain();
	 }
       },
       "RuleFlatten");

###################################
# Subcircuit rules:
###################################

@SubcktRules= (
	       $RulePSpiceComment,
	       $RulePSpiceLineCont,
	       $RuleSubcktElements,
	       $RuleSubcktEnd
	       );


# End of all Rules.

#end of package spicep

######################################################################
# §4.8 Main program
######################################################################
######################################################################
package main;
######################################################################
######################################################################

###################################
# Processing the command line options:
use Getopt::Long;

GetOptions(
	   "help" => \$Spicep::OptHelp,
	   "verbose" => \$Spicep::OptVerbose,
	   "quiet" => \$Spicep::OptQuiet,
	   "outputSpice=s" => \$Spicep::SpiceOutFile,
	   "gnuplotDat=s" => \$Spicep::DataFile,
	   "2" => \$Spicep::OptTwoNewlines,
	   "deleteNonGrid" => \$Spicep::OptDeleteNongrid,
	   "log" => \$Spicep::DEBUG
	  )
  or
  ( Spicep::help, die "Wrong option.\n" );



if( $Spicep::OptHelp || !@ARGV ) { Spicep::help, exit; }
if( $Spicep::OptFlatten ) { unshift( @Spicep::Rules, $Spicep::RuleFlatten ); }
if( $Spicep::OptTwoNewlines ) { $Spicep::OptDataSeparator = "\n\n"; }
###################################


open( TemplateFile,  "$ARGV[0]" ) or die "Cannot open Template file $ARGV[0]\n";
@Spicep::Template = <TemplateFile>;
chop(@Spicep::Template);
close(TemplateFile);


# Just truncate the data file to length 0:
open( Spicep::DataFile, ">$Spicep::DataFile" ) ||
    die "Cannot truncate $Spicep::DataFile to zero length.\n";
close( Spicep::DataFile );

Spicep::translateTemplateToPlProg();

if( !$Spicep::SpiceOutputFormat ) { die "No .print or .save lines. No simulation run.\n"; }

###################################
# Launching the PlProg
# and therefore
# the parametric spice simulation:
    $"="\n";
    if( $Spicep::DEBUG ) {
	open plProg, ">spice-plProg.pl";
	print plProg "@Spicep::PlProg";
    }
    eval "@Spicep::PlProg";

if( $@ ) { die " Translation error (PlProg failed!). Perl error message: $@\n"; }


$"=" ";


print Spicep::simulationInfo() . "Good bye.\n"  unless $Spicep::OptQuiet;

# End of spicep.pl
######################################################################
