All pastes #439799 Raw Edit

tomp

public text v1 · immutable
·published 2007-04-14 15:20 UTC
rendered paste body
#!/usr/bin/python
#    This is 'comp', 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import os, sys, tempfile, shutil, getopt, time
BASE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
sys.path.insert(0, os.path.join(BASE, "lib", "python"))

%%
parser Hal:
    ignore: "//.*"
    ignore: "/[*](.|\n)*?[*]/"
    ignore: "[ \t\r\n]+"

    token END: ";;"
    token PARAMDIRECTION: "rw|r"
    token PINDIRECTION: "in|out|io"
    token TYPE: "float|bit|signed|unsigned|u32|s32"
    token NAME: "[a-zA-Z_][a-zA-Z0-9_]*"
    token FPNUMBER: "-?([0-9]*\.[0-9]+|[0-9]+\.?)([Ee][+-]?[0-9]+)?f?"
    token NUMBER: "-?[0-9]+|0x[0-9a-fA-F]+"
    token STRING: '"(\\.|[^\\"])*"'
    token TSTRING: '"""(\\.|\\\n|[^\\"]|"(?!"")|\n)*"""'

    rule File: Declaration* "$" {{ return True }}
    rule Declaration:
        "component" NAME OptString";" {{ comp(NAME, OptString); }}
      | "pin" PINDIRECTION TYPE NAME OptAssign OptString ";"  {{ pin(NAME, TYPE, PINDIRECTION, OptString, OptAssign) }}
      | "param" PARAMDIRECTION TYPE NAME OptAssign OptString ";" {{ param(NAME, TYPE, PARAMDIRECTION, OptString, OptAssign) }}
      | "function" NAME OptFP OptString ";"       {{ function(NAME, OptFP, OptString) }}
      | "option" NAME OptValue ";"   {{ option(NAME, OptValue) }}
      | "see_also" String ";"   {{ see_also(String) }}
      | "description" String ";"   {{ description(String) }}

    rule String: TSTRING {{ return eval(TSTRING) }} 
            | STRING {{ return eval(STRING) }}
 
    rule OptString: TSTRING {{ return eval(TSTRING) }} 
            | STRING {{ return eval(STRING) }}
            | {{ return '' }}
    rule OptAssign: "=" Value {{ return Value; }}
                | {{ return None }}
    rule OptFP: "fp" {{ return 1 }} | "nofp" {{ return 0 }} | {{ return 1 }}
    rule Value: "yes" {{ return 1 }} | "no" {{ return 0 }}  
                | "true" {{ return 1 }} | "false" {{ return 0 }}  
                | "TRUE" {{ return 1 }} | "FALSE" {{ return 0 }}  
                | NAME {{ return NAME }}
                | FPNUMBER {{ return float(FPNUMBER.rstrip("f")) }}
                | NUMBER {{ return int(NUMBER,0) }}
    rule OptValue: Value {{ return Value }}
                | {{ return 1 }}
%%

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

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, options, comp_name, names, docs

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

    names = {}

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

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

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 see_also(doc):
    docs.append(('see_also', doc));

def type2type(type):
    # When we start warning about s32/u32 this is where the warning goes
    return typemap.get(type, type)
    
def pin(name, type, dir, doc, value):
    type = type2type(type)
    if name in names:
        Error("Duplicate item name %s" % name)
    docs.append(('pin', name, type, dir, doc, value))
    names[name] = None
    pins.append((name, type, dir, value))

def param(name, type, dir, doc, value):
    type = type2type(type)
    if name in names:
        Error("Duplicate item name %s" % name)
    docs.append(('param', name, type, dir, doc, value))
    names[name] = None
    params.append((name, type, dir, value))

def function(name, fp, doc):
    if name in names:
        raise runtime.SyntaxError, "Duplicate item name %s" % name
    docs.append(('funct', name, fp, doc))
    names[name] = None
    functions.append((name, fp))

def option(name, value):
    if name in options:
        raise runtime.SyntaxError, "Duplicate option name %s" % name
    options[name] = value

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

def to_hal(name):
    return name.replace("_", "-").rstrip("-").rstrip(".")

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

static int comp_id;
"""
    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 >>f, "#ifdef MODULE_INFO"
    for v in docs:
        if not v: continue
        v = ":".join(map(str, v))
        print >>f, "MODULE_INFO(emc2, %s);" % q(v)
    print >>f, "#endif // __MODULE_INFO"
    print >>f

    has_data = options.get("data")

    print >>f
    print >>f, "struct state {"
    print >>f, "    struct state *_next;"
    for name, type, dir, value in pins:
        if names.has_key(name):
            raise RuntimeError, "Duplicate item name: %s" % name
        print >>f, "    hal_%s_t *%s;" % (type, name)
        names[name] = 1

    for name, type, dir, value in params:
        if names.has_key(name):
            raise RuntimeError, "Duplicate item name: %s" % name
        print >>f, "    hal_%s_t %s;" % (type, name)
        names[name] = 1

    if has_data:
        print >>f, "    void *_data;"

    print >>f, "};"

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

    print >>f, "struct state *inst=0;"
    print >>f, "struct state *first_inst=0;"
    
    print >>f
    for name, fp in functions:
        if names.has_key(name):
            raise RuntimeError, "Duplicate item name: %s" % name
        print >>f, "static void %s(struct state *inst, long period);" % name
        names[name] = 1

    print >>f, "static int get_data_size(void);"
    if options.get("extra_setup"):
        print >>f, "static int extra_setup(struct state *inst, long extra_arg);"
    if options.get("extra_cleanup"):
        print >>f, "static void extra_cleanup(void);"

    print >>f
    print >>f, "static int export(char *prefix, long extra_arg) {"
    print >>f, "    char buf[HAL_NAME_LEN + 2];"
    print >>f, "    int r = 0;"
    print >>f, "    int sz = sizeof(struct state) + get_data_size();"
    print >>f, "    struct state *inst = hal_malloc(sz);"
    print >>f, "    memset(inst, 0, sz);"
    if options.get("extra_setup"):
	print >>f, "    r = extra_setup(inst, extra_arg);"
	print >>f, "    if(r != 0) return r;"

    for name, type, dir, value in pins:
        print >>f, "    r = hal_pin_%s_newf(%s, &(inst->%s), comp_id," % (
            type, dirmap[dir], name)
        print >>f, "        \"%%s%s\", prefix);" % to_hal("." + name)
        print >>f, "    if(r != 0) return r;"
        if value is not None:
            print >>f, "    *(inst->%s) = %s;" % (name, value)

    for name, type, dir, value in params:
        print >>f, "    r = hal_param_%s_newf(%s, &(inst->%s), comp_id," % (
            type, dirmap[dir], name)
        print >>f, "        \"%%s%s\", prefix);" % to_hal("." + name)
        if value is not None:
            print >>f, "    inst->%s = %s;" % (name, value)
        print >>f, "    if(r != 0) return r;"

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

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

    if options.get("rtapi_app", 1):
        if options.get("constructable") and not options.get("singleton"):
            print >>f, "static int export_1(char *prefix, char *argstr) {"
            print >>f, "    int arg = simple_strtol(argstr, NULL, 0);"
            print >>f, "    return export(prefix, arg);"
            print >>f, "}"   
        if not options.get("singleton") and not options.get("count_function") :
            print >>f, "static int count = %s;" \
                % options.get("default_count", 1)
            if not options.get("userspace"):
                print >>f, "RTAPI_MP_INT(count, \"number of %s\");" % comp_name

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

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

        if options.get("singleton"):
            print >>f, "    r = export(\"%s\", 0);" % \
                    to_hal(removeprefix(comp_name, "hal_"))
        else:
            print >>f, "    for(i=0; i<count; i++) {"
            print >>f, "        char buf[HAL_NAME_LEN + 2];"
            print >>f, "        rtapi_snprintf(buf, HAL_NAME_LEN, " \
                                        "\"%s.%%d\", i);" % \
                    to_hal(removeprefix(comp_name, "hal_"))
            print >>f, "        r = export(buf, i);"
            print >>f, "        if(r != 0) break;"
            print >>f, "    }"
        if options.get("constructable") and not options.get("singleton"):
            print >>f, "    hal_set_constructor(comp_id, export_1);"
        print >>f, "    if(r) {"
	if options.get("extra_cleanup"):
            print >>f, "    extra_cleanup();"
        print >>f, "        hal_exit(comp_id);"
        print >>f, "    } else {"
        print >>f, "        hal_ready(comp_id);"
        print >>f, "    }"
        print >>f, "    return r;";
        print >>f, "}"

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

    if options.get("userspace"):
        print >>f, "static void user_mainloop(void);"
        if options.get("userinit"):
            print >>f, "static void user_init(int argc, char **argv);"
        print >>f, "int argc=0; char **argv=0;"
        print >>f, "int main(int argc_, char **argv_) {"    
        print >>f, "    argc = argc_; argv = argv;"
        print >>f 
        if options.get("userinit", 0):
            print >>f, "    userinit(argc, argv)";
        print >>f 
        print >>f, "    if(rtapi_app_main() < 0) return 1;"
        print >>f, "    user_mainloop();"
        print >>f, "    rtapi_app_exit();"
        print >>f, "    return 0;"
        print >>f, "}"

    print >>f
    if not options.get("no_convenience_defines"):
        print >>f, "#define FUNCTION(name) static void name(struct state *inst, long period)"
        print >>f, "#define EXTRA_SETUP() static int extra_setup(struct state *inst, long extra_arg)"
        print >>f, "#define EXTRA_CLEANUP() static void extra_cleanup(void)"
        print >>f, "#define fperiod (period * 1e-9)"
        for name, type, dir, value in pins:
            if dir == 'in':
                print >>f, "#define %s (0+*inst->%s)" % (name, name)
            else:
                print >>f, "#define %s (*inst->%s)" % (name, name)
        for name, type, dir, value in params:
            print >>f, "#define %s (inst->%s)" % (name, name)
        
        if has_data:
            print >>f, "#define data (*(%s*)&(inst->_data))" % options['data']

        if options.get("userspace"):
            print >>f, "#define FOR_ALL_INSTS() for(inst = first_inst; inst; inst = inst->_next)"    
    print >>f
    print >>f

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

INSTALL, COMPILE, PREPROCESS, DOCUMENT = range(4)

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/emc2', '/etc/emc2']:
        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 >>f, "%s: %s" % (binname, filename)
    print >>f, "\t$(CC) $(EXTRA_CFLAGS) -URTAPI -DULAPI -O2 %s -o $@ $< -Wl,-rpath,$(LIBDIR) -L$(LIBDIR) -lemchal %s" % (
        options.get("extra_compile_args", ""),
        options.get("extra_link_args", ""))
    print >>f, "include %s" % find_modinc()
    f.close()
    result = os.system("cd %s; make -S %s" % (tempdir, binname))
    if result != 0:
        raise SystemExit, result
    output = os.path.join(tempdir, binname)
    if mode == INSTALL:
        shutil.copy(output, os.path.join(BASE, "bin", 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 >>f, "obj-m += %s" % objname
    print >>f, "include %s" % find_modinc()
    print >>f, "EXTRA_CFLAGS += -I%s" % os.path.abspath(os.path.dirname(origfilename))
    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, result
    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):
    if options.get("singleton"):
        s = "%s.%s" % (comp_name, s)
    else:
        s = "%s.\\fIN\\fB.%s" % (comp_name, s)
    s = s.replace("_", "-")
    s = s.rstrip("-")
    s = s.rstrip(".")
    # s = s.replace("-", "\\-")
    return s

def document(filename, outfilename):
    if outfilename is None:
        outfilename = os.path.splitext(filename)[0] + ".9"

    initialize()
    f = open(filename).read()
    a, b = f.split("\n;;\n", 1)

    p = parse('File', a, filename)
    if not p: raise SystemExit, 1

    f = open(outfilename, "w")

    print >>f, ".TH %s \"9\" \"%s\" \"EMC Documentation\" \"HAL Component\"" % (
        comp_name.upper(), time.strftime("%F"))
    print >>f, ".de TQ\n.br\n.ns\n.TP \\\\$1\n..\n"

    print >>f, ".SH NAME\n"
    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 >>f, "%s \\- %s" % (doc[1], firstline)
    else:
        rest = ''
        print >>f, "%s" % doc[1]


    print >>f, ".SH SYNOPSIS"
    if options.get("userspace"):
        print >>f, ".B %s" % comp_name
    else:
        if rest:
            print >>f, rest
        elif options.get("singleton") or options.get("count_function"):
            print >>f, ".B loadrt %s" % comp_name
        else:
            print >>f, ".B loadrt %s [count=\\fIN\\fB]" % comp_name
        if options.get("constructable") and not options.get("singleton"):
            print >>f, ".PP\n.B newinst %s \\fIname\\fB" % comp_name

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

    if not options.get("userspace"):
        print >>f, ".SH FUNCTIONS"
        for _, name, fp, doc in finddocs('funct'):
            print >>f, ".TP"
            print >>f, "\\fB%s\\fR" % to_hal_man(name),
            if fp:
                print >>f, "(uses floating-point)"
            else:
                print >>f
            print >>f, doc

    lead = ".TP"
    print >>f, ".SH PINS"
    for _, name, type, dir, doc, value in finddocs('pin'):
        print >>f, lead
        print >>f, ".B %s\\fR" % to_hal_man(name),
        print >>f, type, dir,
        if value:
            print >>f, "\\fR(default: \\fI%s\\fR)" % value
        else:
            print >>f, "\\fR"
        if doc:
            print >>f, doc
            lead = ".TP"
        else:
            lead = ".TQ"

    lead = ".TP"
    if params:
        print >>f, ".SH PARAMETERS"
        for _, name, type, dir, doc, value in finddocs('param'):
            print >>f, lead
            print >>f, ".B %s\\fR" % to_hal_man(name),
            print >>f, type, dir,
            if value:
                print >>f, "\\fR(default: \\fI%s\\fR)" % value
            else:
                print >>f, "\\fR"
            if doc:
                print >>f, doc
                lead = ".TP"
            else:
                lead = ".TQ"

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

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")

        initialize()

        f = open(filename).read()
        a, b = f.split("\n;;\n", 1)

        p = parse('File', a, filename)
        if not p: raise SystemExit, 1

        f = open(outfilename, "w")

        if options.get("userspace"):
            if functions:
                raise SystemExit, "Userspace components may not have functions"
        else:
            if not functions:
                raise SystemExit, "Realtime component must have at least one function"
        if not pins:
            raise SystemExit, "Component must have at least one pin"
        prologue(f)
        lineno = a.count("\n") + 3
        f.write("#line %d \"%s\"\n" % (lineno, filename))
        f.write(b)
        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 EMC HAL components

Usage:
    %(name)s [--install|--compile|--preprocess|--document] compfile...
    %(name)s [--install|--compile] cfile...
""" % {'name': os.path.basename(sys.argv[0])}
    raise SystemExit, exitval

def main():
    mode = PREPROCESS
    outfile = None
    try:
        opts, args = getopt.getopt(sys.argv[1:], "icpdo:h?",
                           ['install', 'compile', 'preprocess', 'outfile=',
                            'document', 'help'])
    except getopt.GetoptError:
        usage(1)

    for k, v in opts:
        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 ("-o", "--outfile"):
            if len(args) != 1:
                raise SystemExit, "Cannot specify -o with multiple input files"
            outfile = 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"

    for f in args:
        if f.endswith(".comp") and mode == DOCUMENT:
            document(f, outfile)            
        elif f.endswith(".comp"):
            process(f, mode, outfile)
        elif f.endswith(".c") and mode != PREPROCESS:
            tempdir = tempfile.mkdtemp()
            try:
                shutil.copy(f, tempdir)
                build(tempdir, os.path.join(tempdir, os.path.basename(f)), mode, f)
            finally:
                shutil.rmtree(tempdir) 
        else:
            raise SystemExit, "Unrecognized file type: %s" % f

if __name__ == '__main__':
    main()

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