Added a convert_spectre.py script which makes a basic first-pass attempt
to convert spectre files into valid SPICE files.  Note that this does
not attempt to guarantee ngspice compatibility, as that is handled by
other scripts.
diff --git a/VERSION b/VERSION
index bb83058..2ac9634 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.12
+1.0.13
diff --git a/common/convert_spectre.py b/common/convert_spectre.py
new file mode 100755
index 0000000..343748e
--- /dev/null
+++ b/common/convert_spectre.py
@@ -0,0 +1,452 @@
+#!/bin/env python3
+# Script to read all files in a directory of SPECTRE-compatible device model
+# files, and convert them to a form that is compatible with ngspice. 
+
+import os
+import sys
+import re
+import glob
+
+def usage():
+    print('convert_spectre.py <path_to_spectre> <path_to_spice>')
+
+# Check if a parameter value is a valid number (real, float, integer)
+# or is some kind of expression.
+
+def is_number(s):
+    try:
+        float(s)
+        return True
+    except ValueError:
+        return False
+
+# Parse a parameter line.  If "inparam" is true, then this is a continuation
+# line of an existing parameter statement.  If "insub" is not true, then the
+# paramters are global parameters (not part of a subcircuit).
+#
+# If inside a subcircuit, remove the keyword "parameters".  If outside,
+# change it to ".param"
+
+def parse_param_line(line, inparam, insub):
+
+    # Regexp patterns
+    parm1rex = re.compile('parameters' + '[ \t]*(.*)')
+    parm2rex = re.compile('\+[ \t]*(.*)')
+    parm3rex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([^ \t]+)[ \t]*(.*)')
+
+    fmtline = []
+    
+    if inparam:
+        pmatch = parm2rex.match(line)
+        if pmatch:
+            fmtline.append('+')
+            rest = pmatch.group(1)
+        else:
+            return ''
+    else:
+        pmatch = parm1rex.match(line)
+        if pmatch:
+            if insub:
+                fmtline.append('+')
+            else:
+                fmtline.append('.param')
+            rest = pmatch.group(1)
+        else:
+            return ''
+
+    while rest != '':
+        pmatch = parm3rex.match(rest)
+        if pmatch:
+            fmtline.append(pmatch.group(1))
+            fmtline.append('=')
+            value = pmatch.group(2)
+            rest = pmatch.group(3)
+
+            # It is not clear why spectre allows space separators
+            # in a parameter expression when there can be multiple
+            # parameters per line as well (?).
+            nmatch = parm3rex.match(rest)
+            if not nmatch:
+                value += rest.replace(' ', '').replace('\t', '')
+                rest = ''
+
+            if is_number(value):
+                fmtline.append(value)
+            else:
+                fmtline.append("'" + value + "'")
+        else:
+            break
+
+    return ' '.join(fmtline)
+
+def convert_file(in_file, out_file):
+
+    # Regexp patterns
+    statrex = re.compile('[ \t]*statistics[ \t]*\{(.*)')
+    simrex = re.compile('[ \t]*simulator[ \t]+([^= \t]+)[ \t]*=[ \t]*(.+)')
+    insubrex = re.compile('[ \t]*inline[ \t]+subckt[ \t]+([^ \t]+)[ \t]*\(([^)]*)')
+    endsubrex = re.compile('[ \t]*ends[ \t]+(.+)')
+    modelrex = re.compile('[ \t]*model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+\{(.*)')
+    binrex = re.compile('[ \t]*([0-9]+):[ \t]+type[ \t]*=[ \t]*(.*)')
+    shincrex = re.compile('\.inc[ \t]+')
+
+    stdsubrex = re.compile('\.subckt[ \t]+([^ \t]+)[ \t]+([^ \t]*)')
+    stdmodelrex = re.compile('\.model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.*)')
+    stdendsubrex = re.compile('\.ends[ \t]+(.+)')
+
+    # Devices (resistor, capacitor, subcircuit as resistor or capacitor)
+    caprex = re.compile('c([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*capacitor[ \t]*(.*)', re.IGNORECASE)
+    resrex = re.compile('r([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*resistor[ \t]*(.*)', re.IGNORECASE)
+
+    with open(in_file, 'r') as ifile:
+        speclines = ifile.read().splitlines()
+
+    insub = False
+    inparam = False
+    inmodel = False
+    inpinlist = False
+    isspectre = True
+    spicelines = []
+    calllines = []
+    modellines = []
+    savematch = None
+    blockskip = 0
+    subname = ''
+    modname = ''
+    modtype = ''
+
+    for line in speclines:
+
+        # Item 1.  C++-style // comments get replae with * comment character
+        if line.strip().startswith('//'):
+            # Replace the leading "//" with SPICE-style comment "*".
+            if modellines != []:
+                modellines.append(line.strip().replace('//', '*', 1))
+            elif calllines != []:
+                calllines.append(line.strip().replace('//', '*', 1))
+            else:
+                spicelines.append(line.strip().replace('//', '*', 1))
+            continue
+
+        # Item 2.  Handle SPICE-style comment lines
+        if line.strip().startswith('*'):
+            if modellines != []:
+                modellines.append(line.strip())
+            elif calllines != []:
+                calllines.append(line.strip())
+            else:
+                spicelines.append(line.strip())
+            continue
+
+        # Item 3.  Flag continuation lines
+        if line.strip().startswith('+'):
+            contline = True
+        else:
+            contline = False
+            if inparam:
+                inparam = False 
+            if inpinlist:
+                inpinlist = False 
+
+        # Item 4.  Count through { ... } blocks that are not SPICE syntax
+        if blockskip > 0:
+            # Warning:  Assumes one brace per line, may or may not be true
+            if '{' in line:
+                blockskip = blockskip + 1
+            elif '}' in line:
+                blockskip = blockskip - 1
+                if blockskip == 0:
+                    spicelines.append('* ' + line)
+                    continue
+
+        if blockskip > 0:
+            spicelines.append('* ' + line)
+            continue
+
+        # Item 5.  Handle continuation lines
+        if contline:
+            if inparam:
+                # Continue handling parameters
+                fmtline = parse_param_line(line, inparam, insub)
+                if fmtline != '':
+                    if modellines != []:
+                        modellines.append(fmtline)
+                    elif calllines != []:
+                        calllines.append(fmtline)
+                    else:
+                        spicelines.append(fmtline)
+                    continue
+
+        # Item 6.  Regexp matching
+
+        # Catch "simulator lang="
+        smatch = simrex.match(line)
+        if smatch:
+            if smatch.group(1) == 'lang':
+                if smatch.group(2) == 'spice':
+                    isspectre = False
+                elif smatch.group(2) == 'spectre':
+                    isspectre = True
+            continue
+
+        # If inside a subcircuit, remove "parameters".  If outside,
+        # change it to ".param"
+        fmtline = parse_param_line(line, inparam, insub)
+        if fmtline != '':
+            inparam = True
+            spicelines.append(fmtline)
+            continue
+        
+        # statistics---not sure if it is always outside an inline subcircuit
+        smatch = statrex.match(line)
+        if smatch:
+            if '}' not in smatch.group(1):
+                blockskip = 1
+                spicelines.append('* ' + line)
+                continue
+
+        # model---not sure if it is always inside an inline subcircuit
+        if isspectre:
+            mmatch = modelrex.match(line)
+        else:
+            mmatch = stdmodelrex.match(line)
+        if mmatch:
+            modellines = []
+            modname = mmatch.group(1)
+            modtype = mmatch.group(2)
+
+            if isspectre and '}' in mmatch.group(1):
+                savematch = mmatch
+                inmodel = 1
+                # Continue to "if inmodel == 1" block below
+            else:
+                if not isspectre:
+                    modellines.append(line)
+                inmodel = 2
+                continue
+
+        if not insub:
+            # Things to parse if not in a subcircuit
+
+            if isspectre:
+                imatch = insubrex.match(line)
+            else:
+                imatch = stdsubrex.match(line)
+
+            if imatch:
+                insub = True
+                subname = imatch.group(1)
+                calllines = []
+                if isspectre:
+                    devrex = re.compile(subname + '[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
+                    # If there is no close-parenthesis then we should expect it on
+                    # a continuation line
+                    inpinlist = True if ')' not in line else False
+                    # Remove parentheses groups from subcircuit arguments
+                    spicelines.append('.subckt ' + ' ' + subname + ' ' + imatch.group(2))
+                else:
+                    devrex = re.compile(subname + '[ \t]*([^ \t]+)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
+                    inpinlist = True
+                    spicelines.append(line)
+                continue
+
+        else:
+            # Things to parse when inside of an "inline subckt" block
+
+            if inpinlist:
+                # Watch for pin list continuation line.
+                if isspectre:
+                    if ')' in line:
+                        inpinlist = False
+                    pinlist = line.replace(')', '')
+                    spicelines.append(pinlist)
+                else:
+                    spicelines.append(line)
+                continue
+                
+            else:
+                if isspectre:
+                    ematch = endsubrex.match(line)
+                else:
+                    ematch = stdendsubrex.match(line)
+                if ematch:
+                    if ematch.group(1) != subname:
+                        print('Error:  "ends" name does not match "subckt" name!')
+                        print('"ends" name = ' + ematch.group(1))
+                        print('"subckt" name = ' + subname)
+                    if len(calllines) > 0:
+                        line = calllines[0]
+                        if modtype.startswith('bsim'):
+                            line = 'M' + line
+                        elif modtype.startswith('nmos'):
+                            line = 'M' + line
+                        elif modtype.startswith('pmos'):
+                            line = 'M' + line
+                        elif modtype.startswith('res'):
+                            line = 'R' + line
+                        elif modtype.startswith('cap'):
+                            line = 'C' + line
+                        elif modtype.startswith('pnp'):
+                            line = 'Q' + line
+                        elif modtype.startswith('npn'):
+                            line = 'Q' + line
+                        elif modtype.startswith('d'):
+                            line = 'D' + line
+                        spicelines.append(line)
+
+                        # Will need more handling here for other component types. . . 
+
+                    for line in calllines[1:]:
+                        spicelines.append(line)
+
+                    spicelines.append('.ends ' + subname)
+
+                    # Now add the .model after the subcircuit definition
+                    spicelines.append('')
+                    for line in modellines:
+                        spicelines.append(line)
+                    
+                    insub = False
+                    inmodel = False
+                    subname = ''
+                    continue
+
+            # Check for close of model
+            if isspectre and inmodel:
+                if '}' in line:
+                    inmodel = False
+                    continue
+
+            # Check for devices R and C.
+            dmatch = caprex.match(line)
+            if dmatch:
+                fmtline = parse_param_line(dmatch.group(3), True, insub)
+                if fmtline != '':
+                    inparam = True
+                    spicelines.append('c' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline)
+                    continue
+                else:
+                    spicelines.append('c' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
+                    continue
+
+            dmatch = resrex.match(line)
+            if dmatch:
+                fmtline = parse_param_line(dmatch.group(3), True, insub)
+                if fmtline != '':
+                    inparam = True
+                    spicelines.append('r' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline)
+                    continue
+                else:
+                    spicelines.append('r' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
+                    continue
+
+            # Check for a line that begins with the subcircuit name
+          
+            dmatch = devrex.match(line)
+            if dmatch:
+                fmtline = parse_param_line(dmatch.group(3), True, insub)
+                if fmtline != '':
+                    inparam = True
+                    calllines.append(subname + ' ' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline)
+                    continue
+                else:
+                    calllines.append(subname + ' ' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
+                    continue
+
+        if inmodel == 1 or inmodel == 2:
+            # This line should have the model bin, if there is one, and a type.
+            if inmodel == 1:
+                bmatch = binrex.match(savematch.group(3))
+                savematch = None
+            else:
+                bmatch = binrex.match(line)
+
+            if bmatch:
+                bin = bmatch.group(1)
+                type = bmatch.group(2)
+
+                if type == 'n':
+                    convtype = 'nmos'
+                elif type == 'p':
+                    convtype = 'pmos'
+                else:
+                    convtype = type
+
+                modellines.append('')
+                modellines.append('.model ' + modname + '.' + bin + ' ' + convtype)
+                continue
+
+            else:
+                fmtline = parse_param_line(line, True, True)
+                if fmtline != '':
+                    modellines.append(fmtline)
+                    continue
+
+        # Copy line as-is
+        spicelines.append(line)
+
+    # Output the result to out_file.
+    with open(out_file, 'w') as ofile:
+        for line in spicelines:
+            print(line, file=ofile)
+
+if __name__ == '__main__':
+    debug = False
+
+    if len(sys.argv) == 1:
+        print("No options given to convert_spectre.py.")
+        usage()
+        sys.exit(0)
+
+    optionlist = []
+    arguments = []
+
+    for option in sys.argv[1:]:
+        if option.find('-', 0) == 0:
+            optionlist.append(option)
+        else:
+            arguments.append(option)
+
+    if len(arguments) != 2:
+        print("Wrong number of arguments given to convert_spectre.py.")
+        usage()
+        sys.exit(0)
+
+    if '-debug' in optionlist:
+        debug = True
+
+    specpath = arguments[0]
+    spicepath = arguments[1]
+    do_one_file = False
+
+    if not os.path.exists(specpath):
+        print('No such source directory ' + specpath)
+        sys.exit(1)
+
+    if os.path.isfile(specpath):
+        do_one_file = True
+
+    if do_one_file:
+        if os.path.exists(spicepath):
+            print('Error:  File ' + spicepath + ' exists.')
+            sys.exit(1)
+        convert_file(specpath, spicepath)
+
+    else:
+        if not os.path.exists(spicepath):
+            os.makedirs(spicepath)
+
+        specfilelist = glob.glob(specpath + '/*')
+
+        for filename in specfilelist:
+            fileext = os.path.splitext(filename)[1]
+
+            # Ignore verilog or verilog-A files that might be in a model directory
+            if fileext == '.v' or fileext == '.va':
+                continue
+
+            froot = os.path.split(filename)[1]
+            convert_file(filename, spicepath + '/' + froot)
+
+    print('Done.')
+    exit(0)