#!/usr/bin/python3.14
from __future__ import print_function
#    This is 'halcompile', a tool to write HAL boilerplate
#    Copyright 2006 Jeff Epler <jepler@unpythonic.net>
#
#    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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


# Begin -- grammar generated by Yapps
import sys, re
from yapps import runtime

class HalScanner(runtime.Scanner):
    patterns = [
        ('"FALSE"', re.compile('FALSE')),
        ('"TRUE"', re.compile('TRUE')),
        ('"false"', re.compile('false')),
        ('"true"', re.compile('true')),
        ('"no"', re.compile('no')),
        ('"yes"', re.compile('yes')),
        ('"nofp"', re.compile('nofp')),
        ('"fp"', re.compile('fp')),
        ('"="', re.compile('=')),
        ('":"', re.compile(':')),
        ('"\\\\]"', re.compile('\\]')),
        ('"\\\\["', re.compile('\\[')),
        ('"if"', re.compile('if')),
        ('"modparam"', re.compile('modparam')),
        ('"include"', re.compile('include')),
        ('"author"', re.compile('author')),
        ('"license"', re.compile('license')),
        ('"examples"', re.compile('examples')),
        ('"description"', re.compile('description')),
        ('"notes"', re.compile('notes')),
        ('"see_also"', re.compile('see_also')),
        ('"option"', re.compile('option')),
        ('"variable"', re.compile('variable')),
        ('"function"', re.compile('function')),
        ('"param"', re.compile('param')),
        ('"pin"', re.compile('pin')),
        ('";"', re.compile(';')),
        ('"component"', re.compile('component')),
        ('"$"', re.compile('$')),
        ('//.*', re.compile('//.*')),
        ('/[*](.|\n)*?[*]/', re.compile('/[*](.|\n)*?[*]/')),
        ('[ \t\r\n]+', re.compile('[ \t\r\n]+')),
        ('END', re.compile(';;')),
        ('PARAMDIRECTION', re.compile('rw|r')),
        ('PINDIRECTION', re.compile('in|out|io')),
        ('TYPE', re.compile('float|bit|signed|unsigned|u32|s32|u64|s64|port')),
        ('NAME', re.compile('[a-zA-Z_][a-zA-Z0-9_]*')),
        ('STARREDNAME', re.compile('[*]*[a-zA-Z_][a-zA-Z0-9_]*')),
        ('HALNAME', re.compile('[#a-zA-Z_][-#a-zA-Z0-9_.]*')),
        ('FPNUMBER', re.compile('[+-]?((([0-9]*\\.[0-9]+|[0-9]+\\.)([Ee][+-]?[0-9]+)?)|([0-9]+[Ee][+-]?[0-9]+))[fF]?')),
        ('NUMBER', re.compile('0x[0-9a-fA-F]+|0b[01]+|0o[0-7]+|[+-]?[0-9]+')),
        ('STRING', re.compile('"(\\.|[^\\"])*"')),
        ('HEADER', re.compile('<.*?>')),
        ('POP', re.compile('[-()+*/:?]|&&|\\|\\||personality|==|&|!=|<<|<|<=|>>|>|>=')),
        ('TSTRING', re.compile('r?"""(\\.|\\\n|[^\\"]|"(?!"")|\n)*"""')),
    ]
    def __init__(self, str,*args,**kw):
        runtime.Scanner.__init__(self,None,{'//.*':None,'/[*](.|\n)*?[*]/':None,'[ \t\r\n]+':None,},str,*args,**kw)

class Hal(runtime.Parser):
    Context = runtime.Context
    def File(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'File', [])
        ComponentDeclaration = self.ComponentDeclaration(_context)
        while self._peek('"$"', '"pin"', '"param"', '"function"', '"variable"', '"option"', '"see_also"', '"notes"', '"description"', '"examples"', '"license"', '"author"', '"include"', '"modparam"', context=_context) != '"$"':
            Declaration = self.Declaration(_context)
        self._scan('"$"', context=_context)
        return True

    def ComponentDeclaration(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'ComponentDeclaration', [])
        self._scan('"component"', context=_context)
        NAME = self._scan('NAME', context=_context)
        OptString = self.OptString(_context)
        self._scan('";"', context=_context)
        comp(NAME, OptString);

    def Declaration(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'Declaration', [])
        _token = self._peek('"pin"', '"param"', '"function"', '"variable"', '"option"', '"see_also"', '"notes"', '"description"', '"examples"', '"license"', '"author"', '"include"', '"modparam"', context=_context)
        if _token == '"pin"':
            self._scan('"pin"', context=_context)
            PINDIRECTION = self._scan('PINDIRECTION', context=_context)
            TYPE = self._scan('TYPE', context=_context)
            HALNAME = self._scan('HALNAME', context=_context)
            OptArray = self.OptArray(_context)
            OptSAssign = self.OptSAssign(_context)
            OptPersonality = self.OptPersonality(_context)
            OptString = self.OptString(_context)
            self._scan('";"', context=_context)
            pin(HALNAME, TYPE, OptArray, PINDIRECTION, OptString, OptSAssign, OptPersonality)
        elif _token == '"param"':
            self._scan('"param"', context=_context)
            PARAMDIRECTION = self._scan('PARAMDIRECTION', context=_context)
            TYPE = self._scan('TYPE', context=_context)
            HALNAME = self._scan('HALNAME', context=_context)
            OptArray = self.OptArray(_context)
            OptSAssign = self.OptSAssign(_context)
            OptPersonality = self.OptPersonality(_context)
            OptString = self.OptString(_context)
            self._scan('";"', context=_context)
            param(HALNAME, TYPE, OptArray, PARAMDIRECTION, OptString, OptSAssign, OptPersonality)
        elif _token == '"function"':
            self._scan('"function"', context=_context)
            NAME = self._scan('NAME', context=_context)
            OptFP = self.OptFP(_context)
            OptString = self.OptString(_context)
            self._scan('";"', context=_context)
            function(NAME, OptFP, OptString)
        elif _token == '"variable"':
            self._scan('"variable"', context=_context)
            NAME = self._scan('NAME', context=_context)
            STARREDNAME = self._scan('STARREDNAME', context=_context)
            OptSimpleArray = self.OptSimpleArray(_context)
            OptAssign = self.OptAssign(_context)
            self._scan('";"', context=_context)
            variable(NAME, STARREDNAME, OptSimpleArray, OptAssign)
        elif _token == '"option"':
            self._scan('"option"', context=_context)
            NAME = self._scan('NAME', context=_context)
            OptValue = self.OptValue(_context)
            self._scan('";"', context=_context)
            option(NAME, OptValue)
        elif _token == '"see_also"':
            self._scan('"see_also"', context=_context)
            String = self.String(_context)
            self._scan('";"', context=_context)
            see_also(String)
        elif _token == '"notes"':
            self._scan('"notes"', context=_context)
            String = self.String(_context)
            self._scan('";"', context=_context)
            notes(String)
        elif _token == '"description"':
            self._scan('"description"', context=_context)
            String = self.String(_context)
            self._scan('";"', context=_context)
            description(String)
        elif _token == '"examples"':
            self._scan('"examples"', context=_context)
            String = self.String(_context)
            self._scan('";"', context=_context)
            examples(String)
        elif _token == '"license"':
            self._scan('"license"', context=_context)
            String = self.String(_context)
            self._scan('";"', context=_context)
            license_(String)
        elif _token == '"author"':
            self._scan('"author"', context=_context)
            String = self.String(_context)
            self._scan('";"', context=_context)
            author(String)
        elif _token == '"include"':
            self._scan('"include"', context=_context)
            Header = self.Header(_context)
            self._scan('";"', context=_context)
            include(Header)
        else: # == '"modparam"'
            self._scan('"modparam"', context=_context)
            NAME = self._scan('NAME', context=_context)
            NAME1=NAME;
            NAME = self._scan('NAME', context=_context)
            OptSAssign = self.OptSAssign(_context)
            OptString = self.OptString(_context)
            self._scan('";"', context=_context)
            modparam(NAME1, NAME, OptSAssign, OptString)

    def Header(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'Header', [])
        _token = self._peek('STRING', 'HEADER', context=_context)
        if _token == 'STRING':
            STRING = self._scan('STRING', context=_context)
            return STRING
        else: # == 'HEADER'
            HEADER = self._scan('HEADER', context=_context)
            return HEADER

    def String(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'String', [])
        _token = self._peek('TSTRING', 'STRING', context=_context)
        if _token == 'TSTRING':
            TSTRING = self._scan('TSTRING', context=_context)
            return eval(TSTRING)
        else: # == 'STRING'
            STRING = self._scan('STRING', context=_context)
            return eval(STRING)

    def OptPersonality(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptPersonality', [])
        _token = self._peek('"if"', '";"', 'TSTRING', 'STRING', context=_context)
        if _token == '"if"':
            self._scan('"if"', context=_context)
            Personality = self.Personality(_context)
            return Personality
        else: # in ['";"', 'TSTRING', 'STRING']
            return None

    def Personality(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'Personality', [])
        pp = []
        while self._peek('NUMBER', 'POP', '"\\\\]"', '";"', 'TSTRING', 'STRING', context=_context) in ['NUMBER', 'POP']:
            PersonalityPart = self.PersonalityPart(_context)
            pp.append(PersonalityPart)
        return " ".join(pp)

    def PersonalityPart(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'PersonalityPart', [])
        _token = self._peek('NUMBER', 'POP', context=_context)
        if _token == 'NUMBER':
            NUMBER = self._scan('NUMBER', context=_context)
            return NUMBER
        else: # == 'POP'
            POP = self._scan('POP', context=_context)
            return POP

    def OptSimpleArray(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptSimpleArray', [])
        _token = self._peek('"\\\\["', '";"', '"="', context=_context)
        if _token == '"\\\\["':
            self._scan('"\\\\["', context=_context)
            NUMBER = self._scan('NUMBER', context=_context)
            self._scan('"\\\\]"', context=_context)
            return int(NUMBER)
        else: # in ['";"', '"="']
            return 0

    def OptArray(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptArray', [])
        _token = self._peek('"\\\\["', '"="', '"if"', '";"', 'TSTRING', 'STRING', context=_context)
        if _token == '"\\\\["':
            self._scan('"\\\\["', context=_context)
            NUMBER = self._scan('NUMBER', context=_context)
            OptArrayPersonality = self.OptArrayPersonality(_context)
            self._scan('"\\\\]"', context=_context)
            return OptArrayPersonality and (int(NUMBER), OptArrayPersonality) or int(NUMBER)
        else: # in ['"="', '"if"', '";"', 'TSTRING', 'STRING']
            return 0

    def OptArrayPersonality(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptArrayPersonality', [])
        _token = self._peek('":"', '"\\\\]"', context=_context)
        if _token == '":"':
            self._scan('":"', context=_context)
            Personality = self.Personality(_context)
            return Personality
        else: # == '"\\\\]"'
            return None

    def OptString(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptString', [])
        _token = self._peek('TSTRING', 'STRING', '";"', context=_context)
        if _token == 'TSTRING':
            TSTRING = self._scan('TSTRING', context=_context)
            return eval(TSTRING)
        elif _token == 'STRING':
            STRING = self._scan('STRING', context=_context)
            return eval(STRING)
        else: # == '";"'
            return ''

    def OptAssign(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptAssign', [])
        _token = self._peek('"="', '";"', context=_context)
        if _token == '"="':
            self._scan('"="', context=_context)
            Value = self.Value(_context)
            return Value;
        else: # == '";"'
            return None

    def OptSAssign(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptSAssign', [])
        _token = self._peek('"="', '"if"', '";"', 'TSTRING', 'STRING', context=_context)
        if _token == '"="':
            self._scan('"="', context=_context)
            SValue = self.SValue(_context)
            return SValue;
        else: # in ['"if"', '";"', 'TSTRING', 'STRING']
            return None

    def OptFP(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptFP', [])
        _token = self._peek('"fp"', '"nofp"', '";"', 'TSTRING', 'STRING', context=_context)
        if _token == '"fp"':
            self._scan('"fp"', context=_context)
            return optfp_warn(1)
        elif _token == '"nofp"':
            self._scan('"nofp"', context=_context)
            return optfp_warn(0)
        else: # in ['";"', 'TSTRING', 'STRING']
            return 1

    def Value(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'Value', [])
        _token = self._peek('"yes"', '"no"', '"true"', '"false"', '"TRUE"', '"FALSE"', 'NAME', 'FPNUMBER', 'NUMBER', context=_context)
        if _token == '"yes"':
            self._scan('"yes"', context=_context)
            return 1
        elif _token == '"no"':
            self._scan('"no"', context=_context)
            return 0
        elif _token == '"true"':
            self._scan('"true"', context=_context)
            return 1
        elif _token == '"false"':
            self._scan('"false"', context=_context)
            return 0
        elif _token == '"TRUE"':
            self._scan('"TRUE"', context=_context)
            return 1
        elif _token == '"FALSE"':
            self._scan('"FALSE"', context=_context)
            return 0
        elif _token == 'NAME':
            NAME = self._scan('NAME', context=_context)
            return NAME
        elif _token == 'FPNUMBER':
            FPNUMBER = self._scan('FPNUMBER', context=_context)
            return float(FPNUMBER.rstrip("f"))
        else: # == 'NUMBER'
            NUMBER = self._scan('NUMBER', context=_context)
            return int(NUMBER,0)

    def SValue(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'SValue', [])
        _token = self._peek('"yes"', '"no"', '"true"', '"false"', '"TRUE"', '"FALSE"', 'NAME', 'FPNUMBER', 'NUMBER', context=_context)
        if _token == '"yes"':
            self._scan('"yes"', context=_context)
            return "yes"
        elif _token == '"no"':
            self._scan('"no"', context=_context)
            return "no"
        elif _token == '"true"':
            self._scan('"true"', context=_context)
            return "true"
        elif _token == '"false"':
            self._scan('"false"', context=_context)
            return "false"
        elif _token == '"TRUE"':
            self._scan('"TRUE"', context=_context)
            return "TRUE"
        elif _token == '"FALSE"':
            self._scan('"FALSE"', context=_context)
            return "FALSE"
        elif _token == 'NAME':
            NAME = self._scan('NAME', context=_context)
            return NAME
        elif _token == 'FPNUMBER':
            FPNUMBER = self._scan('FPNUMBER', context=_context)
            return FPNUMBER
        else: # == 'NUMBER'
            NUMBER = self._scan('NUMBER', context=_context)
            return NUMBER

    def OptValue(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptValue', [])
        _token = self._peek('"yes"', '"no"', '"true"', '"false"', '"TRUE"', '"FALSE"', 'NAME', 'FPNUMBER', 'NUMBER', 'TSTRING', 'STRING', '";"', context=_context)
        if _token not in ['TSTRING', 'STRING', '";"']:
            Value = self.Value(_context)
            return Value
        elif _token == 'TSTRING':
            TSTRING = self._scan('TSTRING', context=_context)
            return eval(TSTRING)
        elif _token == 'STRING':
            STRING = self._scan('STRING', context=_context)
            return eval(STRING)
        else: # == '";"'
            return 1

    def OptSValue(self, _parent=None):
        _context = self.Context(_parent, self._scanner, 'OptSValue', [])
        _token = self._peek('"yes"', '"no"', '"true"', '"false"', '"TRUE"', '"FALSE"', 'NAME', 'FPNUMBER', 'NUMBER', context=_context)
        SValue = self.SValue(_context)
        return SValue


def parse(rule, text):
    P = Hal(HalScanner(text))
    return runtime.wrap_error_reporter(P, rule)

# End -- grammar generated by Yapps



import os, sys, tempfile, shutil, getopt, time
import subprocess
BASE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
BINDIR = os.getenv('USER_MODULE_DIR', None) or os.path.join(BASE, "bin")
sys.path.insert(0, os.path.join(BASE, "lib", "python"))

MAX_USERSPACE_NAMES = 16 # for userspace (loadusr) components
# NOTE: names are assigned dynamically for realtime components (loadrt)

# Components that use 'personality' features statically allocate
# memory based on MAX_PERSONALITIES (RTAPI_MP_ARRAY_INT)
# The number can be set with the cmdline option -P|--personalities
# Smaller values may be useful since the index of the personality
# exported is computed modulo MAX_PERSONALITIES
MAX_PERSONALITIES = 64

mp_decl_map = {'int': 'RTAPI_MP_INT', 'dummy': None}

# These are symbols that comp puts in the global namespace of the C file it
# creates.  The user is thus not allowed to add any symbols with these
# names.  That includes not only global variables and functions, but also
# HAL pins & parameters, because comp adds #defines with the names of HAL
# pins & params.
reserved_names = [ 'comp_id', 'fperiod', 'rtapi_app_main', 'rtapi_app_exit', 'extra_setup', 'extra_cleanup' ]

def _parse(rule, text, filename=None):
    global P, S
    S = HalScanner(text, filename=filename)
    P = Hal(S)
    return runtime.wrap_error_reporter(P, rule)

def parse(filename):
    initialize()
    f = open(filename).read()
    if '\r' in f:
        if require_unix_line_endings:
            raise SystemExit("%s:0: Error: File contains DOS-style or Mac-style line endings." % filename)
        else:
            print("%s:0: Warning: File contains DOS-style or Mac-style line endings." % filename, file=sys.stderr)
        f = open(filename, "rU").read()
    a, b = f.split("\n;;\n", 1)
    p = _parse('File', a + "\n\n", filename)
    if not p: raise SystemExit(1)
    if require_license:
        if not finddoc('license'):
            raise SystemExit("%s:0: License not specified" % filename)

    if not "period" in options:
        options["period"] = 1    # Option period defaults to on

    return a, b

dirmap = {'r': 'HAL_RO', 'rw': 'HAL_RW', 'in': 'HAL_IN', 'out': 'HAL_OUT', 'io': 'HAL_IO' }
typemap = {'signed': 's32', 'unsigned': 'u32'}
deprmap = {'s32': 'signed', 'u32': 'unsigned'}
deprecated = ['s32', 'u32']

def initialize():
    global functions, params, pins, comp_name, names, docs, variables
    global modparams, includes

    functions = []; params = []; pins = []; options = {}; variables = []
    modparams = []; docs = []; includes = [];
    comp_name = None

    names = {}

def Warn(msg, *args):
    if args:
        msg = msg % args
    print("%s:%d: Warning: %s" % (S.filename, S.line, msg), file=sys.stderr)

def Error(msg, *args):
    if args:
        msg = msg % args
    raise runtime.SyntaxError(S.get_pos(), msg, None)

def optfp_warn(state):
    s = "nofp" if 0 == state else "fp"
    Warn("'%s' is no longer supported and will be removed in a future version. "
         "All threads are now floating point capable. "
         "Please remove '%s' from the function declaration." % (s, s))
    return 1

def comp(name, doc):
    docs.append(('component', name, doc))
    global comp_name
    if comp_name:
        Error("Duplicate specification of component name")
    comp_name = name;

def description(doc):
    docs.append(('descr', doc));

def examples(doc):
    docs.append(('examples', doc));

def license_(doc):
    docs.append(('license', doc));

def author(doc):
    docs.append(('author', doc));

def see_also(doc):
    docs.append(('see_also', doc));

def notes(doc):
    docs.append(('notes', doc));

def type2type(type_):
    # When we start warning about s32/u32 this is where the warning goes
    return typemap.get(type_, type_)

def checkarray(name, array):
    hashes = len(re.findall("#+", name))
    if array:
        if hashes == 0: Error("Array name contains no #: %r" % name)
        if hashes > 1: Error("Array name contains more than one block of #: %r" % name)
    else:
        if hashes > 0: Error("Non-array name contains #: %r" % name)

def check_name_ok(name):
    if name in reserved_names:
        Error("Variable name %s is reserved" % name)
    if name in names:
        Error("Duplicate item name %s" % name)

def pin(name, type_, array, dir_, doc, value, personality):
    checkarray(name, array)
    type_ = type2type(type_)
    check_name_ok(name)
    docs.append(('pin', name, type_, array, dir_, doc, value, personality))
    names[name] = None
    pins.append((name, type_, array, dir_, value, personality))

def param(name, type_, array, dir_, doc, value, personality):
    checkarray(name, array)
    type_ = type2type(type_)
    check_name_ok(name)
    docs.append(('param', name, type_, array, dir_, doc, value, personality))
    names[name] = None
    params.append((name, type_, array, dir_, value, personality))

def function(name, fp, doc):
    check_name_ok(name)
    docs.append(('funct', name, fp, doc))
    names[name] = None
    functions.append((name, fp))

def option(name, value):
    if name in options:
        Error("Duplicate option name %s" % name)
    options[name] = value

def variable(type_, name, array, default):
    check_name_ok(name)
    names[name] = None
    variables.append((type_, name, array, default))

def modparam(type_, name, default, doc):
    check_name_ok(name)
    names[name] = None
    modparams.append((type_, name, default, doc))

def include(value):
    includes.append((value))

def removeprefix(s,p):
    if s.startswith(p): return s[len(p):]
    return s

def to_hal(name):
    name = re.sub("#+", lambda m: "%%0%dd" % len(m.group(0)), name)
    return name.replace("_", "-").rstrip("-").rstrip(".")
def to_c(name):
    name = re.sub("[-._]*#+", "", name)
    name = name.replace("#", "").replace(".", "_").replace("-", "_")
    return re.sub("_+", "_", name)

def prologue(f):
    print("/* Autogenerated by %s on %s -- do not edit */" % (
        sys.argv[0], time.asctime()), file=f)
    print("""\
#include <rtapi.h>
#ifdef RTAPI
#include <rtapi_app.h>
#endif
#include <rtapi_string.h>
#include <rtapi_errno.h>
#include <rtapi_math64.h>
#include <hal.h>

static int comp_id;
""", file=f)
    for name in includes:
        print("#include %s" % name, file=f)

    names = {}

    def q(s):
        s = s.replace("\\", "\\\\")
        s = s.replace("\"", "\\\"")
        s = s.replace("\r", "\\r")
        s = s.replace("\n", "\\n")
        s = s.replace("\t", "\\t")
        s = s.replace("\v", "\\v")
        return '"%s"' % s

    print("#ifdef MODULE_INFO", file=f)
    licens = None
    for v in docs:
        if not v: continue
        v = ":".join(map(str, v))
        print("MODULE_INFO(linuxcnc, %s);" % q(v), file=f)
        licens = finddoc('license')
    if licens and licens[1]:
        print("MODULE_LICENSE(\"%s\");" % licens[1].split("\n")[0], file=f)
    print("#endif // MODULE_INFO", file=f)
    print("", file=f)


    has_data = options.get("data")

    has_array = False
    has_personality = False
    for name, type_, array, dir_, value, personality in pins:
        if array: has_array = True
        if isinstance(array, tuple): has_personality = True
        if personality: has_personality = True
    for name, type_, array, dir_, value, personality in params:
        if array: has_array = True
        if isinstance(array, tuple): has_personality = True
        if personality: has_personality = True
    for type_, name, default, doc in modparams:
        decl = mp_decl_map[type_]
        if decl:
            print("%s %s" % (type_, name), end=' ', file=f)
            if default: print("= %s;" % default, file=f)
            else: print(";", file=f)
            print("%s(%s, %s);" % (decl, name, q(doc)), file=f)

    print("", file=f)
    print("struct __comp_state {", file=f)
    print("    struct __comp_state *_next;", file=f)
    if has_personality:
        print("    int _personality;", file=f)

    for name, type_, array, dir_, value, personality in pins:
        if array:
            if isinstance(array, tuple): array = array[0]
            print("    hal_%s_t *%s_p[%s];" % (type_, to_c(name), array), file=f)
        else:
            print("    hal_%s_t *%s_p;" % (type_, to_c(name)), file=f)
        names[name] = 1

    for name, type_, array, dir_, value, personality in params:
        if array:
            if isinstance(array, tuple): array = array[0]
            print("    hal_%s_t %s_p[%s];" % (type_, to_c(name), array), file=f)
        else:
            print("    hal_%s_t %s_p;" % (type_, to_c(name)), file=f)
        names[name] = 1

    for type_, name, array, value in variables:
        if array:
            print("    %s %s_p[%d];" % (type_, name, array), file=f)
        else:
            print("    %s %s_p;" % (type_, name), file=f)
    if has_data:
        print("    void *_data;", file=f)

    print("};", file=f)

    if options.get("userspace"):
        print("#include <stdlib.h>", file=f)

    print("struct __comp_state *__comp_first_inst=0, *__comp_last_inst=0;", file=f)

    print("", file=f)
    for name, fp in functions:
        if name in names:
            Error("Duplicate item name: %s" % name)
        if options.get("period"):
            print("static void %s(struct __comp_state *__comp_inst, long period);" % to_c(name), file=f)
        else:
            print("static void __no_period_%s(struct __comp_state *__comp_inst);" % to_c(name), file=f)
            # Define a tail-call function that the compiler can eliminate. It
            # hides the period parameter from the user function. Worst case it
            # becomes a jump instruction.
            print("static void %s(struct __comp_state *__comp_inst, long period)" % to_c(name), file=f)
            print("{ (void)period; __no_period_%s(__comp_inst); }" % to_c(name), file=f)
        names[name] = 1

    print("static int __comp_get_data_size(void);", file=f)
    if options.get("extra_setup"):
        print("static int extra_setup(struct __comp_state *__comp_inst, char *prefix, long extra_arg);", file=f)
    if options.get("extra_cleanup"):
        print("static void extra_cleanup(void);", file=f)

    if not options.get("no_convenience_defines"):
        print("#undef TRUE", file=f)
        print("#define TRUE (1)", file=f)
        print("#undef FALSE", file=f)
        print("#define FALSE (0)", file=f)
        print("#undef true", file=f)
        print("#define true (1)", file=f)
        print("#undef false", file=f)
        print("#define false (0)", file=f)

    print("", file=f)
    if has_personality:
        print("static int export(char *prefix, long extra_arg, long personality) {", file=f)
    else:
        print("static int export(char *prefix, long extra_arg) {", file=f)
    print("    (void)extra_arg;", file=f)
    if len(functions) > 0:
        print("    char buf[HAL_NAME_LEN + 1];", file=f)
    print("    int r = 0;", file=f)
    if has_array:
        print("    int j = 0;", file=f)
    print("    int sz = sizeof(struct __comp_state) + __comp_get_data_size();", file=f)
    print("    struct __comp_state *inst = hal_malloc(sz);", file=f)
    print("    memset(inst, 0, sz);", file=f)
    if has_data:
        print("    inst->_data = (char*)inst + sizeof(struct __comp_state);", file=f)
    if has_personality:
        print("    inst->_personality = personality;", file=f)
    if options.get("extra_setup"):
        print("    r = extra_setup(inst, prefix, extra_arg);", file=f)
        print("    if(r != 0) return r;", file=f)
        # the extra_setup() function may have changed the personality
        if has_personality:
            print("    personality = inst->_personality;", file=f)
    for name, type_, array, dir_, value, personality in pins:
        if personality:
            print("if(%s) {" % personality, file=f)
        if array:
            if isinstance(array, tuple):
                lim, cnt = array
                print("    if((%s) > (%s)) {" % (cnt, lim), file=f)
                print('        rtapi_print_msg(RTAPI_MSG_ERR,' \
                                '"Pin %s: Requested size %%d exceeds max size %%d\\n",'
                                '(int)%s, (int)%s);' % (name, cnt, lim), file=f)
                print("        return -ENOSPC;", file=f)
                print("    }", file=f)
            else: cnt = array
            print("    for(j=0; j < (%s); j++) {" % cnt, file=f)
            print("        r = hal_pin_%s_newf(%s, &(inst->%s_p[j]), comp_id," % (
                type_, dirmap[dir_], to_c(name)), file=f)
            print("            \"%%s%s\", prefix, j);" % to_hal("." + name), file=f)
            print("        if(r != 0) return r;", file=f)
            if value is not None:
                print("    *(inst->%s_p[j]) = %s;" % (to_c(name), value), file=f)
            print("    }", file=f)
        else:
            print("    r = hal_pin_%s_newf(%s, &(inst->%s_p), comp_id," % (
                type_, dirmap[dir_], to_c(name)), file=f)
            print("        \"%%s%s\", prefix);" % to_hal("." + name), file=f)
            print("    if(r != 0) return r;", file=f)
            if value is not None:
                print("    *(inst->%s_p) = %s;" % (to_c(name), value), file=f)
        if personality:
            print("}", file=f)

    for name, type_, array, dir_, value, personality in params:
        if personality:
            print("if(%s) {" % personality, file=f)
        if array:
            if isinstance(array, tuple):
                lim, cnt = array
                print("    if((%s) > (%s)) {" % (cnt, lim), file=f)
                print('        rtapi_print_msg(RTAPI_MSG_ERR,' \
                                '"Parameter %s: Requested size %%d exceeds max size %%d\\n",'
                                '(int)%s, (int)%s);' % (name, cnt, lim), file=f)
                print("        return -ENOSPC;", file=f)
                print("    }", file=f)
            else: cnt = array
            print("    for(j=0; j < (%s); j++) {" % cnt, file=f)
            print("        r = hal_param_%s_newf(%s, &(inst->%s_p[j]), comp_id," % (
                type_, dirmap[dir_], to_c(name)), file=f)
            print("            \"%%s%s\", prefix, j);" % to_hal("." + name), file=f)
            print("        if(r != 0) return r;", file=f)
            if value is not None:
                print("    inst->%s_p[j] = %s;" % (to_c(name), value), file=f)
            print("    }", file=f)
        else:
            print("    r = hal_param_%s_newf(%s, &(inst->%s_p), comp_id," % (
                type_, dirmap[dir_], to_c(name)), file=f)
            print("        \"%%s%s\", prefix);" % to_hal("." + name), file=f)
            if value is not None:
                print("    inst->%s_p = %s;" % (to_c(name), value), file=f)
            print("    if(r != 0) return r;", file=f)
        if personality:
            print("}", file=f)

    for type_, name, array, value in variables:
        if value is None: continue
        if array:
            print("    for(j=0; j < %s; j++) {" % array, file=f)
            print("        inst->%s_p[j] = %s;" % (name, value), file=f)
            print("    }", file=f)
        else:
            print("    inst->%s_p = %s;" % (name, value), file=f)

    for name, fp in functions:
        print("    rtapi_snprintf(buf, sizeof(buf), \"%%s%s\", prefix);"\
            % to_hal("." + name), file=f)
        print("    r = hal_export_funct(buf, (void(*)(void *inst, long))%s, inst, %s, 0, comp_id);" % (
            to_c(name), int(fp)), file=f)
        print("    if(r != 0) return r;", file=f)
    print("    if(__comp_last_inst) __comp_last_inst->_next = inst;", file=f)
    print("    __comp_last_inst = inst;", file=f)
    print("    if(!__comp_first_inst) __comp_first_inst = inst;", file=f)
    print("    return 0;", file=f)
    print("}", file=f)

    if options.get("count_function"):
        print("static int get_count(void);", file=f)

    if options.get("rtapi_app", 1):
        if options.get("constructable") and not options.get("singleton"):
            print("static int export_1(char *prefix, char *argstr) {", file=f)
            print("    int arg = simple_strtol(argstr, NULL, 0);", file=f)
            print("    return export(prefix, arg);", file=f)
            print("}"   , file=f)
        if not options.get("singleton") and not options.get("count_function") :
            print("static int default_count=%s, count=0;" \
                % options.get("default_count", 1), file=f)
            if options.get("userspace"):
                print("char *names[%d] = {0,};"%(MAX_USERSPACE_NAMES), file=f)
            else:
                print("RTAPI_MP_INT(count, \"number of %s\");" % comp_name, file=f)
                print("char *names = \"\"; // comma separated names", file=f)
                print("RTAPI_MP_STRING(names, \"names of %s\");" % comp_name, file=f)
        if has_personality:
            init1 = str(int(options.get('default_personality', 0)))
            init = ",".join([init1] * MAX_PERSONALITIES)
            print("static int personality[%d] = {%s};" %(MAX_PERSONALITIES,init), file=f)
            print("RTAPI_MP_ARRAY_INT(personality, %d, \"personality of each %s\");" %(MAX_PERSONALITIES,comp_name), file=f)

            # Return personality value.
            # If requested index exceeds MAX_PERSONALITIES, use modulo indexing and give message
            print("""
            static int p_value(char* cname, char *name, int idx) {
                int ans = personality[idx%%%d];
                if (idx >= %d) {
            """%(MAX_PERSONALITIES,MAX_PERSONALITIES), file=f)
            print("""
                    if (name==NULL) {
                        rtapi_print_msg(RTAPI_MSG_ERR,"%s: instance %d assigned personality=%d(=%#0x)\\n",
                                        cname, idx, ans, ans);
                    } else {
                        rtapi_print_msg(RTAPI_MSG_ERR,"%s: name %s assigned personality=%d(=%#0x)\\n",
                                        cname, name, ans, ans);
                    }
                }
                return ans;
            }
            """, file=f)

        print("int rtapi_app_main(void) {", file=f)
        print("    int r = 0;", file=f)
        if not options.get("singleton"):
            print("    int i;", file=f)
        if options.get("count_function"):
            print("    int count = get_count();", file=f)

        print("    comp_id = hal_init(\"%s\");" % comp_name, file=f)
        print("    if(comp_id < 0) return comp_id;", file=f)

        if options.get("singleton"):
            if has_personality:
                print("    r = export(\"%s\", 0, personality[0]);" % \
                        to_hal(removeprefix(comp_name, "hal_")), file=f)
            else:
                print("    r = export(\"%s\", 0);" % \
                        to_hal(removeprefix(comp_name, "hal_")), file=f)
        elif options.get("count_function"):
            print("    for(i=0; i<count; i++) {", file=f)
            print("        char buf[HAL_NAME_LEN + 1];", file=f)
            print("        rtapi_snprintf(buf, sizeof(buf), " \
                                        "\"%s.%%d\", i);" % \
                    to_hal(removeprefix(comp_name, "hal_")), file=f)
            if has_personality:
                print("        r = export(buf, i, p_value(\"%s\", buf, i) );"%comp_name, file=f)
            else:
                print("        r = export(buf, i);", file=f)
            print("    }", file=f)
        else:
            print("    if(count && names[0]) {", file=f)
            print("        rtapi_print_msg(RTAPI_MSG_ERR," \
                            "\"count= and names= are mutually exclusive\\n\");", file=f)
            print("        return -EINVAL;", file=f)
            print("    }", file=f)
            print("    if(!count && !names[0]) count = default_count;", file=f)
            print("    if(count) {", file=f)
            print("        for(i=0; i<count; i++) {", file=f)
            print("            char buf[HAL_NAME_LEN + 1];", file=f)
            print("            rtapi_snprintf(buf, sizeof(buf), " \
                                        "\"%s.%%d\", i);" % \
                    to_hal(removeprefix(comp_name, "hal_")), file=f)
            if has_personality:
                print("            r = export(buf, i, p_value(\"%s\", buf, i) );"%comp_name, file=f)
            else:
                print("            r = export(buf, i);", file=f)
            print("            if(r != 0) break;", file=f)
            print("       }", file=f)
            print("    } else {", file=f)
            if options.get("userspace"):
                print("        int max_names = sizeof(names)/sizeof(names[0]);", file=f)
                print("        for(i=0; (i < max_names) && names[i]; i++) {", file=f)
                print("            if (strlen(names[i]) < 1) {", file=f)
                print("                rtapi_print_msg(RTAPI_MSG_ERR, \"names[%d] is invalid (empty string)\\n\", i);", file=f)
                print("                r = -EINVAL;", file=f)
                print("                break;", file=f)
                print("            }", file=f)
                if has_personality:
                    print("            r = export(names[i], i, p_value(\"%s\", names[i], i) );"%comp_name, file=f)
                else:
                    print("            r = export(names[i], i);", file=f)
                print("            if(r != 0) break;", file=f)
                print("       }", file=f)
                print("    }", file=f)
            else:
                print("        size_t i, j;", file=f)
                print("        int idx;", file=f)
                print("        char buf[HAL_NAME_LEN+1];", file=f)
                print("        const size_t length = strlen(names);", file=f)
                print("        for (i = j = idx = 0; i <= length; i++) {", file=f)
                print("            const char c = buf[j] = names[i];", file=f)
                print("            if ((c == ',') || (c == '\\0')) {", file=f)
                print("                buf[j] = '\\0';", file=f)
                if has_personality:
                    print("                r = export(buf, idx, p_value(\"%s\", buf, idx) );"%comp_name, file=f)
                else:
                    print("                r = export(buf, idx);", file=f)
                print("                if(r != 0) {break;}", file=f)
                print("                idx++;", file=f)
                print("                j = 0;", file=f)
                print("            } else {", file=f)
                print("                if (++j == (sizeof(buf) / sizeof(buf[0]))) {", file=f)
                print("                    buf[j - 1] = '\\0';", file=f)
                print("                    rtapi_print_msg(RTAPI_MSG_ERR,\"names: \\\"%s\\\" too long\\n\", buf);", file=f)
                print("                    r = -EINVAL;", file=f)
                print("                    break;", file=f)
                print("                }", file=f)
                print("            }", file=f)
                print("        }", file=f)
                print("    }", file=f)

        if options.get("constructable") and not options.get("singleton"):
            print("    hal_set_constructor(comp_id, export_1);", file=f)
        print("    if(r) {", file=f)
        if options.get("extra_cleanup"):
            print("    extra_cleanup();", file=f)
        print("        hal_exit(comp_id);", file=f)
        print("    } else {", file=f)
        print("        hal_ready(comp_id);", file=f)
        print("    }", file=f)
        print("    return r;", file=f)
        print("}", file=f)

        print("", file=f)
        print("void rtapi_app_exit(void) {", file=f)
        if options.get("extra_cleanup"):
            print("    extra_cleanup();", file=f)
        print("    hal_exit(comp_id);", file=f)
        print("}", file=f)

    if options.get("userspace"):
        print("static void user_mainloop(void);", file=f)
        if options.get("userinit"):
            print("static void userinit(int argc, char **argv);", file=f)

        if not options.get("singleton"):
            print("""
int __comp_parse_count(int *argc, char **argv) {
    int i;
    for (i = 0; i < *argc; i ++) {
        if (strncmp(argv[i], "count=", 6) == 0) {
            errno = 0;
            count = strtoul(&argv[i][6], NULL, 0);
            for (; i+1 < *argc; i ++) {
                argv[i] = argv[i+1];
            }
            argv[i] = NULL;
            (*argc)--;
            if (errno == 0) {
                return 1;
            }
        }
    }
    return 0;
}
""", file=f)
            print("""
int __comp_parse_names(int *argc, char **argv) {
    int i;
    for (i = 0; i < *argc; i ++) {
        if (strncmp(argv[i], "names=", 6) == 0) {
            char *p = &argv[i][6];
            int j;
            for (; i+1 < *argc; i ++) {
                argv[i] = argv[i+1];
            }
            argv[i] = NULL;
            (*argc)--;
            for (j = 0; j < %d; j ++) {
                names[j] = strtok(p, ",");
                p = NULL;
                if (names[j] == NULL) {
                    return 1;
                }
            }
            return 1;
        }
    }
    return 0;
}
"""%MAX_USERSPACE_NAMES, file=f)
        print("int argc=0; char **argv=0;", file=f)
        print("int main(int argc_, char **argv_) {"    , file=f)
        print("    argc = argc_; argv = argv_;", file=f)
        if not options.get("singleton"):
            print("    int found_count, found_names;", file=f)
            print("    found_count = __comp_parse_count(&argc, argv);", file=f)
            print("    found_names = __comp_parse_names(&argc, argv);", file=f)
            print("    if (found_count && found_names) {", file=f)
            print("        rtapi_print_msg(RTAPI_MSG_ERR, \"count= and names= are mutually exclusive\\n\");", file=f)
            print("        return 1;", file=f)
            print("    }", file=f)
        if options.get("userinit", 0):
            print("    userinit(argc, argv);", file=f)
        print("", file=f)
        print("    if(rtapi_app_main() < 0) return 1;", file=f)
        print("    user_mainloop();", file=f)
        print("    rtapi_app_exit();", file=f)
        print("    return 0;", file=f)
        print("}", file=f)

    print("", file=f)
    if not options.get("no_convenience_defines"):
        print("#undef FUNCTION", file=f)
        if options.get("period"):
            print("#define FUNCTION(name) static void name(struct __comp_state *__comp_inst, long period)", file=f)
        else:
            print("#define FUNCTION(name) static void __no_period_##name(struct __comp_state *__comp_inst)", file=f)
        print("#undef EXTRA_SETUP", file=f)
        print("#define EXTRA_SETUP() static int extra_setup(struct __comp_state *__comp_inst, char *prefix, long extra_arg)", file=f)
        print("#undef EXTRA_CLEANUP", file=f)
        print("#define EXTRA_CLEANUP() static void extra_cleanup(void)", file=f)
        if options.get("period"):
            print("#undef fperiod", file=f)
            print("#define fperiod (period * 1e-9)", file=f)
        for name, type_, array, dir_, value, personality in pins:
            print("#undef %s" % to_c(name), file=f)
            print("#undef %s_ptr" % to_c(name), file=f)
            if array:
                print("#define %s_ptr(i) (__comp_inst->%s_p[i])" % (to_c(name), to_c(name)), file=f)
                if dir_ == 'in':
                    print("#define %s(i) (0+*(__comp_inst->%s_p[i]))" % (to_c(name), to_c(name)), file=f)
                else:
                    print("#define %s(i) (*(__comp_inst->%s_p[i]))" % (to_c(name), to_c(name)), file=f)
            else:
                print("#define %s_ptr (__comp_inst->%s_p)" % (to_c(name), to_c(name)), file=f)
                if dir_ == 'in':
                    print("#define %s (0+*__comp_inst->%s_p)" % (to_c(name), to_c(name)), file=f)
                else:
                    print("#define %s (*__comp_inst->%s_p)" % (to_c(name), to_c(name)), file=f)
        for name, type_, array, dir_, value, personality in params:
            print("#undef %s" % to_c(name), file=f)
            if array:
                print("#define %s(i) (__comp_inst->%s_p[i])" % (to_c(name), to_c(name)), file=f)
            else:
                print("#define %s (__comp_inst->%s_p)" % (to_c(name), to_c(name)), file=f)

        for type_, name, array, value in variables:
            name = name.replace("*", "")
            print("#undef %s" % name, file=f)
            print("#define %s (__comp_inst->%s_p)" % (name, name), file=f)

        if has_data:
            print("#undef data", file=f)
            print("#define data (*(%s*)(__comp_inst->_data))" % options['data'], file=f)
        if has_personality:
            print("#undef personality", file=f)
            print("#define personality (__comp_inst->_personality)", file=f)

        if options.get("userspace"):
            print("#undef FOR_ALL_INSTS", file=f)
            if options.get("singleton"):
                print("#define __comp_inst __comp_first_inst", file=f)
                print("#define FOR_ALL_INSTS()", file=f)
            else:
                print("#define FOR_ALL_INSTS() struct __comp_state *__comp_inst; for(__comp_inst = __comp_first_inst; __comp_inst; __comp_inst = __comp_inst->_next)", file=f)
    print("", file=f)
    print("", file=f)

def epilogue(f):
    data = options.get('data')
    print("", file=f)
    if data:
        print("static int __comp_get_data_size(void) { return sizeof(%s); }" % data, file=f)
    else:
        print("static int __comp_get_data_size(void) { return 0; }", file=f)

INSTALL, COMPILE, PREPROCESS, DOCUMENT, INSTALLDOC, VIEWDOC, MODINC = range(7)
modename = ("install", "compile", "preprocess", "document", "installdoc", "viewdoc", "print-modinc")

modinc = None
def find_modinc():
    global modinc
    if modinc: return modinc
    d = os.path.abspath(os.path.dirname(os.path.dirname(sys.argv[0])))
    for e in ['src', 'etc/linuxcnc', '/etc/linuxcnc', 'share/linuxcnc']:
        e = os.path.join(d, e, 'Makefile.modinc')
        if os.path.exists(e):
            modinc = e
            return e
    raise SystemExit("Unable to locate Makefile.modinc")

def build_usr(tempdir, filename, mode, origfilename):
    binname = os.path.basename(os.path.splitext(filename)[0])

    makefile = os.path.join(tempdir, "Makefile")
    f = open(makefile, "w")
    print("%s: %s" % (binname, filename), file=f)
    print("\t$(CC) -I%s -I$(EMC2_HOME)/include -I/usr/include/linuxcnc -URTAPI -U__MODULE__ -DULAPI -O2 %s -o $@ $< -Wl,-rpath,$(LIBDIR) -L$(LIBDIR) -llinuxcnchal %s" % (

        os.path.abspath(os.path.dirname(origfilename)),
        options.get("extra_compile_args", ""),
        options.get("extra_link_args", "")), file=f)
    print("include %s" % find_modinc(), file=f)
    f.close()
    result = os.system("cd %s && make -S %s" % (tempdir, binname))
    if result != 0:
        raise SystemExit(os.WEXITSTATUS(result) or 1)
    output = os.path.join(tempdir, binname)
    if mode == INSTALL:
        shutil.copy(output, os.path.join(BINDIR, binname))
    elif mode == COMPILE:
        shutil.copy(output, os.path.join(os.path.dirname(origfilename),binname))

def build_rt(tempdir, filename, mode, origfilename):
    objname = os.path.basename(os.path.splitext(filename)[0] + ".o")
    makefile = os.path.join(tempdir, "Makefile")
    f = open(makefile, "w")
    print("obj-m += %s" % objname, file=f)
    print("include %s" % find_modinc(), file=f)
    print("EXTRA_CFLAGS += -I%s" % os.path.abspath(os.path.dirname(origfilename)), file=f)
    print("EXTRA_CFLAGS += -I%s" % os.path.abspath('.'), file=f)
    f.close()
    if mode == INSTALL:
        target = "modules install"
    else:
        target = "modules"
    result = os.system("cd %s && make -S %s" % (tempdir, target))
    if result != 0:
        raise SystemExit(os.WEXITSTATUS(result) or 1)
    if mode == COMPILE:
        for extension in ".ko", ".so", ".o":
            kobjname = os.path.splitext(filename)[0] + extension
            if os.path.exists(kobjname):
                shutil.copy(kobjname, os.path.basename(kobjname))
                break
        else:
            raise SystemExit("Unable to copy module from temporary directory")

def finddoc(section=None, name=None):
    for item in docs:
        if ((section == None or section == item[0]) and
                (name == None or name == item[1])): return item
    return None

def finddocs(section=None, name=None):
    for item in docs:
        if ((section == None or section == item[0]) and
                (name == None or name == item[1])):
                    yield item

######

def to_hal_man(s):
    fn = s
    s = s.replace("_", "-")
    c = comp_name.replace("_", "-")
    if options.get("singleton"):
        s = "**%s**.**%s**" % (c, s)
    elif "_" == fn:
        s = "**%s**.__N__" % (c)
    else:
        s = "**%s**.__N__.**%s**" % (c, s)
    s = s.rstrip("-").rstrip(".")
    s = re.sub("#+", lambda m: "__" + "M" * len(m.group(0)) + "__", s)
    return s

# Generate an adoc file from the embedded declarations, defines and
# documentation.
def document(filename, outfilebase):
    a, b = parse(filename)
    if outfilebase is None:
        outfilename = os.path.splitext(filename)[0] + ".1.adoc" if options.get("userspace") else ".9.adoc"
    else:
        outfilename = outfilebase
        outfilename += ".1.adoc" if options.get("userspace") else ".9.adoc"

    f = open(outfilename, "w")

    has_personality = False
    for name, type_, array, dir_, value, personality in pins:
        if personality: has_personality = True
        if isinstance(array, tuple): has_personality = True
    for name, type_, array, dir_, value, personality in params:
        if personality: has_personality = True
        if isinstance(array, tuple): has_personality = True

    print("""//
// *******************************************************************
//
// This file was extracted from %s using halcompile.g.
// Modify the source file.
//
// *******************************************************************
//
""" % filename, file=f)

    # = manpage(section)
    print("= {}({})".format(comp_name.upper(), "1" if options.get("userspace") else "9"), file=f)

    print("\n== NAME\n", file=f)
    doc = finddoc('component')
    if doc and doc[2]:
        if '\n' in doc[2]:
            firstline, rest = doc[2].split('\n', 1)
        else:
            firstline = doc[2]
            rest = ''
        print("%s - %s" % (doc[1], firstline), file=f)
    else:
        rest = ''
        # A valid NAME section is formatted like "compname - brief description"
        # Missing the brief description would fail conversion
        print("%s - ." % doc[1], file=f)

    print("\n== SYNOPSIS\n", file=f)
    if options.get("userspace"):
        print("*%s*" % comp_name, file=f)
    else:
        if rest:
            print(rest, file=f)
        else:
            if options.get("homemod"):
                print("Custom Homing module loaded with *[EMCMOT]HOMEMOD=_%s_*"%comp_name, file=f)
            elif options.get("tpmod"):
                print("Custom Trajectory Planning module loaded with *[TRAJ]TPMOD=_%s_*"%comp_name, file=f)
            elif options.get("singleton") or options.get("count_function"):
                if has_personality:
                    print("*loadrt %s* *personality*=_P_" % comp_name, end='', file=f)
                else:
                    print("*loadrt %s*" % comp_name, end='', file=f)
            else:
                if has_personality:
                    print("*loadrt %s* [*count*=_N_|*names*=_name1_[,_name2_...]] [*personality*=_P1_[,_P2_...]]" % comp_name, end='', file=f)
                else:
                    print("*loadrt %s* [*count*=_N_|*names*=_name1_[,_name2_...]]" % comp_name, end='', file=f)
            for type_, name, default, doc in modparams:
                if type_ == "dummy":
                    print(" [*%s*=_value_[,_value_...]]" % name, end='', file=f)
                else:
                    print(" [*%s*=_N_]" % name, end='', file=f)
            print("", file=f)

            hasparamdoc = False
            for type_, name, default, doc in modparams:
                if doc: hasparamdoc = True

            if hasparamdoc:
                for type_, name, default, doc in modparams:
                    print("\n*%s*" % name, end='', file=f)
                    if default:
                        print(" [default: %s]::" % default, file=f)
                    else:
                        print("::", file=f)
                    print(doc, file=f)

        if options.get("constructable") and not options.get("singleton"):
            print("\n*newinst %s* _name_" % comp_name, file=f)

    doc = finddoc('descr')
    if doc and doc[1]:
        print("\n== DESCRIPTION\n", file=f)
        print("%s" % doc[1], file=f)

    if functions:
        print("\n== FUNCTIONS\n", file=f)
        for _, name, fp, doc in finddocs('funct'):
            print("%s" % to_hal_man(name), end='', file=f)
            if fp:
                print(" (requires a floating-point thread)::", file=f)
            else:
                print("::", file=f)
            if doc:
                print(doc, file=f)
            else:
                # Need something to follow ::
                print("// missing description\n", file=f)

    print("\n== PINS\n", file=f)
    for _, name, type_, array, dir_, doc, value, personality in finddocs('pin'):
        print("%s" % to_hal_man(name), end=' ', file=f)
        print(type_, dir_, end=' ', file=f)
        if array:
            sz = name.count("#")
            if isinstance(array, tuple):
                print(" (%s=%0*d..%s)" % ("M" * sz, sz, 0, array[1]), end=' ', file=f)
            else:
                print(" (%s=%0*d..%0*d)" % ("M" * sz, sz, 0, sz, array-1), end=' ', file=f)
        if personality:
            print(" [if %s]" % personality, end=' ', file=f)
        if value:
            print("(default: _%s_)::" % value, file=f)
        else:
            print("::", file=f)
        if doc:
            print(doc, file=f)
        else:
            # Need something to follow ::
            print("// missing description\n", file=f)

    if params:
        print("\n== PARAMETERS\n", file=f)
        for _, name, type_, array, dir_, doc, value, personality in finddocs('param'):
            print("%s" % to_hal_man(name), end=' ', file=f)
            print(type_, dir_, end=' ', file=f)
            if array:
                sz = name.count("#")
                if isinstance(array, tuple):
                    print(" (%s=%0*d..%s)" % ("M" * sz, sz, 0, array[1]), end=' ', file=f)
                else:
                    print(" (%s=%0*d..%0*d)" % ("M" * sz, sz, 0, sz, array-1), end=' ', file=f)
            if personality:
                print(" [if %s]" % personality, end=' ', file=f)
            if value:
                print("(default: _%s_)::" % value, file=f)
            else:
                print("::", file=f)
            if doc:
                print(doc, file=f)
            else:
                # Need something to follow ::
                print("// missing description\n", file=f)

    doc = finddoc('examples')
    if doc and doc[1]:
        print("\n== EXAMPLES\n", file=f)
        print("%s" % doc[1], file=f)

    doc = finddoc('see_also')
    if doc and doc[1]:
        print("\n== SEE ALSO\n", file=f)
        print("%s" % doc[1], file=f)

    doc = finddoc('notes')
    if doc and doc[1]:
        print("\n== NOTES\n", file=f)
        print("%s" % doc[1], file=f)

    doc = finddoc('author')
    if doc and doc[1]:
        print("\n== AUTHOR\n", file=f)
        print("%s" % doc[1], file=f)

    doc = finddoc('license')
    if doc and doc[1]:
        print("\n== LICENSE\n", file=f)
        print("%s" % doc[1], file=f)

    f.close()
    return outfilename
######

def make_manpage(fdir, fname):
    # Conversion to manpage format required
    try:
        # Try asciidoctor
        args = ["asciidoctor",
                "--doctype=manpage",
                "--backend=manpage",
                "--destination-dir=" + fdir,
                "-a", "mansource=LinuxCNC",
                "-a", "manmanual=LinuxCNC Documentation",
                fname]
        subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    except:
        try:
            # Alternative, try a2x
            #opts += ["--format=manpage", "--xsltproc-opts=--nonet"]
            args = ["a2x",
                    "--doctype=manpage",
                    "--format=manpage",
                    "--destination-dir=" + fdir,
                    "--xsltproc-opts=--nonet",
                    "-a", "mansource=LinuxCNC",
                    "-a", "manmanual=LinuxCNC Documentation",
                    fname]
            subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except:
            raise SystemExit("Error: Missing asciidoctor or a2x for adoc-to-manpage conversion or use --adoc for asciidoc file only")

    # We should have a file with the extension removed
    manfile = os.path.join(fdir, os.path.basename(fname).removesuffix(".adoc"))
    if not os.access(manfile, os.R_OK):
        raise SystemExit("Error: Manpage '%s' not generated" % manfile)
    return manfile
######

def process(filename, mode, outfilename):
    tempdir = tempfile.mkdtemp()
    try:
        if outfilename is None:
            if mode == PREPROCESS:
                outfilename = os.path.splitext(filename)[0] + ".c"
            else:
                outfilename = os.path.join(tempdir,
                    os.path.splitext(os.path.basename(filename))[0] + ".c")

        a, b = parse(filename)
        base_name = os.path.splitext(os.path.basename(outfilename))[0]
        if comp_name != base_name:
            raise SystemExit("Component name (%s) does not match filename (%s)" % (comp_name, base_name))

        f = open(outfilename, "w")

        if options.get("homemod"): options["singleton"] = 1
        if options.get("tpmod"):   options["singleton"] = 1
        if options.get("no_convenience_defines"): options["period"] = 1

        if options.get("userinit") and not options.get("userspace"):
            print("Warning: comp '%s' sets 'userinit' without 'userspace', ignoring" % filename, file=sys.stderr)

        if options.get("userspace"):
            if functions:
                raise SystemExit("Userspace components may not have functions")
        if not pins:
            raise SystemExit("Component must have at least one pin")
        prologue(f)
        lineno = a.count("\n") + 3

        if options.get("userspace"):
            if functions:
                raise SystemExit("May not specify functions with a userspace component.")
            f.write("#line %d \"%s\"\n" % (lineno, filename))
            f.write(b)
        else:
            if not functions or "FUNCTION" in b:
                f.write("#line %d \"%s\"\n" % (lineno, filename))
                f.write(b)
            elif len(functions) == 1:
                f.write("FUNCTION(%s) {\n" % functions[0][0])
                f.write("#line %d \"%s\"\n" % (lineno, filename))
                f.write(b)
                f.write("}\n")
            else:
                raise SystemExit("Must use FUNCTION() when more than one function is defined")
        epilogue(f)
        f.close()

        if mode != PREPROCESS:
            if options.get("userspace"):
                build_usr(tempdir, outfilename, mode, filename)
            else:
                build_rt(tempdir, outfilename, mode, filename)

    finally:
        shutil.rmtree(tempdir)

def usage(exitval=0):
    print("""%(name)s: Build, compile, and install LinuxCNC HAL components

Usage:
           %(name)s [--compile|--preprocess|--document|--adoc|--view-doc] compfile...
    [sudo] %(name)s [--install|--install-doc] compfile...
           %(name)s --compile --userspace cfile...
    [sudo] %(name)s --install --userspace cfile...
    [sudo] %(name)s --install --userspace pyfile...
           %(name)s --print-modinc

Option to set maximum 'personalities' items:
    --personalities=integer_value   (default is %(dflt)d)

Options to add compile and link flags (only for userspace, only for .c files)
    --extra-compile-args="-I/usr/include/..."
    --extra-link-args="-l..."

Do not use [sudo] for RIP installation.

""" % {'name': os.path.basename(sys.argv[0]),'dflt':MAX_PERSONALITIES})
    raise SystemExit(exitval)

def main():
    global require_license
    global MAX_USERSPACE_NAMES
    global MAX_PERSONALITIES
    require_license = True
    global require_unix_line_endings
    require_unix_line_endings = False
    mode = PREPROCESS
    adoc = False
    keepadoc = None
    outfile = None
    userspace = False
    global options
    options = {}
    try:
        opts, args = getopt.getopt(sys.argv[1:], "UluijJcpdak:o:h?P:",
                           ['unix', 'install', 'compile', 'preprocess', 'outfile=',
                            'document', 'adoc', 'keep-adoc=', 'help', 'userspace', 'install-doc',
                            'view-doc', 'require-license', 'print-modinc',
                            'personalities=', "extra-compile-args=",
                            'extra-link-args='])
    except getopt.GetoptError:
        usage(1)
    for k, v in opts:
        if k in ("-U", "--unix"):
            require_unix_line_endings = True
        if k in ("-u", "--userspace"):
            userspace = True
        if k in ("-i", "--install"):
            mode = INSTALL
        if k in ("-c", "--compile"):
            mode = COMPILE
        if k in ("-p", "--preprocess"):
            mode = PREPROCESS
        if k in ("-d", "--document"):
            mode = DOCUMENT
        if k in ("-a", "--adoc"):
            mode = DOCUMENT
            adoc = True
        if k in ("-k", "--keep-adoc"):
            if len(args) != 1:
                raise SystemExit("Cannot specify -k with multiple input files")
            keepadoc = v
        if k in ("-j", "--install-doc"):
            mode = INSTALLDOC
        if k in ("-J", "--view-doc"):
            mode = VIEWDOC
        if k in ("--print-modinc",):
            mode = MODINC
        if k in ("-l", "--require-license"):
            require_license = True
        if k in ("-o", "--outfile"):
            if len(args) != 1:
                raise SystemExit("Cannot specify -o with multiple input files")
            outfile = v
        if k in ("-P", "--personalities"):
            try:
                MAX_PERSONALITIES = int(v)
                print("MAX_PERSONALITIES=%d"%(MAX_PERSONALITIES))
            except Exception as detail:
                raise SystemExit("Bad value for -P (--personalities)=",v,"\n",detail)
        if k in ("--extra-compile-args"):
            option("extra_compile_args", v)
        if k in ("--extra-link-args"):
            option("extra_link_args", v)
        if k in ("-?", "-h", "--help"):
            usage(0)

    if outfile and mode != PREPROCESS and mode != DOCUMENT:
        raise SystemExit("Can only specify -o when preprocessing or documenting")

    if mode == MODINC:
        if args:
            raise SystemExit(
                "Can not specify input files when using --print-modinc")
        print(find_modinc())
        return 0

    for f in args:
        try:
            basename = os.path.basename(os.path.splitext(f)[0])
            if f.endswith(".comp") and mode == DOCUMENT:
                tempdir = tempfile.mkdtemp()
                try:
                    docfile = document(f, os.path.join(tempdir, basename))
                    if adoc:    # Adoc is created
                        if None == outfile:
                            outfile = os.path.join(os.path.dirname(f), os.path.basename(docfile))
                        shutil.move(docfile, outfile)
                    else:       # Else we do manpage conversion
                        manfile = make_manpage(tempdir, docfile)
                        if None == outfile:
                            outfile = os.path.join(os.path.dirname(f), os.path.basename(manfile))
                        shutil.move(manfile, outfile)
                        if keepadoc:
                            shutil.move(docfile, keepadoc)
                finally:
                    shutil.rmtree(tempdir)
            elif f.endswith(".comp") and mode == VIEWDOC:
                tempdir = tempfile.mkdtemp()
                try:
                    docfile = document(f, os.path.join(tempdir, basename))
                    manfile = make_manpage(tempdir, docfile)
                    os.spawnvp(os.P_WAIT, "man", ["man", manfile])
                finally:
                    shutil.rmtree(tempdir)
            elif f.endswith(".comp") and mode == INSTALLDOC:
                tempdir = tempfile.mkdtemp()
                print("INSTALLDOC", outfile)
                try:
                    docfile = document(f, os.path.join(tempdir, basename))
                    manfile = make_manpage(tempdir, docfile)
                    section = "1" if options.get("userspace") else "9"
                    manpath = os.path.join(BASE, "share/man/man" + section)
                    sharepath = manpath
                    if not os.path.isdir(manpath):
                        manpath = os.path.join(BASE, "docs/man/man" + section)
                        if not os.path.isdir(manpath):
                            raise SystemExit("Error: directory '%s' (nor alternative '%s') found" % (sharepath, manpath))
                    outfile = os.path.join(manpath, basename + "." + section)
                    shutil.move(manfile, outfile)
                finally:
                    shutil.rmtree(tempdir)
            elif f.endswith(".comp"):
                process(f, mode, outfile)
            elif f.endswith(".py") and mode == INSTALL:
                lines = open(f).readlines()
                if lines[0].startswith("#!"): del lines[0]
                lines.insert(0, "#!%s\n" % sys.executable)
                outfile = os.path.join(BINDIR, basename)
                try: os.unlink(outfile)
                except os.error: pass
                open(outfile, "w").writelines(lines)
                os.chmod(outfile, 0o555)
            elif f.endswith(".c") and mode != PREPROCESS:
                initialize()
                tempdir = tempfile.mkdtemp()
                try:
                    shutil.copy(f, tempdir)
                    if userspace:
                        build_usr(tempdir, os.path.join(tempdir, os.path.basename(f)), mode, f)
                    else:
                        build_rt(tempdir, os.path.join(tempdir, os.path.basename(f)), mode, f)
                finally:
                    shutil.rmtree(tempdir)
            else:
                raise SystemExit("Unrecognized file type for mode %s: %r" % (modename[mode], f))
        except Exception as e:
            try:
                if outfile is not None: os.unlink(outfile)
            except: # os.error:
                pass
            raise
if __name__ == '__main__':
    main()

# vim:sw=4:sts=4:et:syn=python
