######################################################################## # Copyright (c) 2000, BeOpen.com. # Copyright (c) 1995-2000, Corporation for National Research Initiatives. # Copyright (c) 1990-1995, Stichting Mathematisch Centrum. # All rights reserved. # # See the file "Misc/COPYRIGHT" for information on usage and # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. ######################################################################## # Python script to parse cstubs file for gl and generate C stubs. # usage: python cgen.py glmodule.c # # NOTE: You must first make a python binary without the "GL" option # before you can run this, when building Python for the first time. # See comments in the Makefile. # # XXX BUG return arrays generate wrong code # XXX need to change error returns into gotos to free mallocked arrays import string import sys # Function to print to stderr # def err(*args): savestdout = sys.stdout try: sys.stdout = sys.stderr for i in args: print i, print finally: sys.stdout = savestdout # The set of digits that form a number # digits = '0123456789' # Function to extract a string of digits from the front of the string. # Returns the leading string of digits and the remaining string. # If no number is found, returns '' and the original string. # def getnum(s): n = '' while s and s[0] in digits: n = n + s[0] s = s[1:] return n, s # Function to check if a string is a number # def isnum(s): if not s: return False for c in s: if not c in digits: return False return True # Allowed function return types # return_types = ['void', 'short', 'long'] # Allowed function argument types # arg_types = ['char', 'string', 'short', 'u_short', 'float', 'long', 'double'] # Need to classify arguments as follows # simple input variable # simple output variable # input array # output array # input giving size of some array # # Array dimensions can be specified as follows # constant # argN # constant * argN # retval # constant * retval # # The dimensions given as constants * something are really # arrays of points where points are 2- 3- or 4-tuples # # We have to consider three lists: # python input arguments # C stub arguments (in & out) # python output arguments (really return values) # # There is a mapping from python input arguments to the input arguments # of the C stub, and a further mapping from C stub arguments to the # python return values # Exception raised by checkarg() and generate() # arg_error = 'bad arg' # Function to check one argument. # Arguments: the type and the arg "name" (really mode plus subscript). # Raises arg_error if something's wrong. # Return type, mode, factor, rest of subscript; factor and rest may be empty. # def checkarg(type, arg): # # Turn "char *x" into "string x". # if type == 'char' and arg[0] == '*': type = 'string' arg = arg[1:] # # Check that the type is supported. # if type not in arg_types: raise arg_error, ('bad type', type) if type[:2] == 'u_': type = 'unsigned ' + type[2:] # # Split it in the mode (first character) and the rest. # mode, rest = arg[:1], arg[1:] # # The mode must be 's' for send (= input) or 'r' for return argument. # if mode not in ('r', 's'): raise arg_error, ('bad arg mode', mode) # # Is it a simple argument: if so, we are done. # if not rest: return type, mode, '', '' # # Not a simple argument; must be an array. # The 'rest' must be a subscript enclosed in [ and ]. # The subscript must be one of the following forms, # otherwise we don't handle it (where N is a number): # N # argN # retval # N*argN # N*retval # if rest[:1] != '[' or rest[-1:] != ']': raise arg_error, ('subscript expected', rest) sub = rest[1:-1] # # Is there a leading number? # num, sub = getnum(sub) if num: # There is a leading number if not sub: # The subscript is just a number return type, mode, num, '' if sub[:1] == '*': # There is a factor prefix sub = sub[1:] else: raise arg_error, ('\'*\' expected', sub) if sub == 'retval': # size is retval -- must be a reply argument if mode != 'r': raise arg_error, ('non-r mode with [retval]', mode) elif not isnum(sub) and (sub[:3] != 'arg' or not isnum(sub[3:])): raise arg_error, ('bad subscript', sub) # return type, mode, num, sub # List of functions for which we have generated stubs # functions = [] # Generate the stub for the given function, using the database of argument # information build by successive calls to checkarg() # def generate(type, func, database): # # Check that we can handle this case: # no variable size reply arrays yet # n_in_args = 0 n_out_args = 0 # for a_type, a_mode, a_factor, a_sub in database: if a_mode == 's': n_in_args = n_in_args + 1 elif a_mode == 'r': n_out_args = n_out_args + 1 else: # Can't happen raise arg_error, ('bad a_mode', a_mode) if (a_mode == 'r' and a_sub) or a_sub == 'retval': err('Function', func, 'too complicated:', a_type, a_mode, a_factor, a_sub) print '/* XXX Too complicated to generate code for */' return # functions.append(func) # # Stub header # print print 'static PyObject *' print 'gl_' + func + '(self, args)' print '\tPyObject *self;' print '\tPyObject *args;' print '{' # # Declare return value if any # if type != 'void': print '\t' + type, 'retval;' # # Declare arguments # for i in range(len(database)): a_type, a_mode, a_factor, a_sub = database[i] print '\t' + a_type, brac = ket = '' if a_sub and not isnum(a_sub): if a_factor: brac = '(' ket = ')' print brac + '*', print 'arg' + repr(i+1) + ket, if a_sub and isnum(a_sub): print '[', a_sub, ']', if a_factor: print '[', a_factor, ']', print ';' # # Find input arguments derived from array sizes # for i in range(len(database)): a_type, a_mode, a_factor, a_sub = database[i] if a_mode == 's' and a_sub[:3] == 'arg' and isnum(a_sub[3:]): # Sending a variable-length array n = eval(a_sub[3:]) if 1 <= n <= len(database): b_type, b_mode, b_factor, b_sub = database[n-1] if b_mode == 's': database[n-1] = b_type, 'i', a_factor, repr(i) n_in_args = n_in_args - 1 # # Assign argument positions in the Python argument list # in_pos = [] i_in = 0 for i in range(len(database)): a_type, a_mode, a_factor, a_sub = database[i] if a_mode == 's': in_pos.append(i_in) i_in = i_in + 1 else: in_pos.append(-1) # # Get input arguments # for i in range(len(database)): a_type, a_mode, a_factor, a_sub = database[i] if a_type[:9] == 'unsigned ': xtype = a_type[9:] else: xtype = a_type if a_mode == 'i': # # Implicit argument; # a_factor is divisor if present, # a_sub indicates which arg (`database index`) # j = eval(a_sub) print '\tif', print '(!geti' + xtype + 'arraysize(args,', print repr(n_in_args) + ',', print repr(in_pos[j]) + ',', if xtype != a_type: print '('+xtype+' *)', print '&arg' + repr(i+1) + '))' print '\t\treturn NULL;' if a_factor: print '\targ' + repr(i+1), print '= arg' + repr(i+1), print '/', a_factor + ';' elif a_mode == 's': if a_sub and not isnum(a_sub): # Allocate memory for varsize array print '\tif ((arg' + repr(i+1), '=', if a_factor: print '('+a_type+'(*)['+a_factor+'])', print 'PyMem_NEW(' + a_type, ',', if a_factor: print a_factor, '*', print a_sub, ')) == NULL)' print '\t\treturn PyErr_NoMemory();' print '\tif', if a_factor or a_sub: # Get a fixed-size array array print '(!geti' + xtype + 'array(args,', print repr(n_in_args) + ',', print repr(in_pos[i]) + ',', if a_factor: print a_factor, if a_factor and a_sub: print '*', if a_sub: print a_sub, print ',', if (a_sub and a_factor) or xtype != a_type: print '('+xtype+' *)', print 'arg' + repr(i+1) + '))' else: # Get a simple variable print '(!geti' + xtype + 'arg(args,', print repr(n_in_args) + ',', print repr(in_pos[i]) + ',', if xtype != a_type: print '('+xtype+' *)', print '&arg' + repr(i+1) + '))' print '\t\treturn NULL;' # # Begin of function call # if type != 'void': print '\tretval =', func + '(', else: print '\t' + func + '(', # # Argument list # for i in range(len(database)): if i > 0: print ',', a_type, a_mode, a_factor, a_sub = database[i] if a_mode == 'r' and not a_factor: print '&', print 'arg' + repr(i+1), # # End of function call # print ');' # # Free varsize arrays # for i in range(len(database)): a_type, a_mode, a_factor, a_sub = database[i] if a_mode == 's' and a_sub and not isnum(a_sub): print '\tPyMem_DEL(arg' + repr(i+1) + ');' # # Return # if n_out_args: # # Multiple return values -- construct a tuple # if type != 'void': n_out_args = n_out_args + 1 if n_out_args == 1: for i in range(len(database)): a_type, a_mode, a_factor, a_sub = database[i] if a_mode == 'r': break else: raise arg_error, 'expected r arg not found' print '\treturn', print mkobject(a_type, 'arg' + repr(i+1)) + ';' else: print '\t{ PyObject *v = PyTuple_New(', print n_out_args, ');' print '\t if (v == NULL) return NULL;' i_out = 0 if type != 'void': print '\t PyTuple_SetItem(v,', print repr(i_out) + ',', print mkobject(type, 'retval') + ');' i_out = i_out + 1 for i in range(len(database)): a_type, a_mode, a_factor, a_sub = database[i] if a_mode == 'r': print '\t PyTuple_SetItem(v,', print repr(i_out) + ',', s = mkobject(a_type, 'arg' + repr(i+1)) print s + ');' i_out = i_out + 1 print '\t return v;' print '\t}' else: # # Simple function return # Return None or return value # if type == 'void': print '\tPy_INCREF(Py_None);' print '\treturn Py_None;' else: print '\treturn', mkobject(type, 'retval') + ';' # # Stub body closing brace # print '}' # Subroutine to return a function call to mknewobject() # def mkobject(type, arg): if type[:9] == 'unsigned ': type = type[9:] return 'mknew' + type + 'object((' + type + ') ' + arg + ')' return 'mknew' + type + 'object(' + arg + ')' defined_archs = [] # usage: cgen [ -Dmach ... ] [ file ] for arg in sys.argv[1:]: if arg[:2] == '-D': defined_archs.append(arg[2:]) else: # Open optional file argument sys.stdin = open(arg, 'r') # Input line number lno = 0 # Input is divided in two parts, separated by a line containing '%%'. # -- literally copied to stdout # -- stub definitions # Variable indicating the current input part. # part = 1 # Main loop over the input # while 1: try: line = raw_input() except EOFError: break # lno = lno+1 words = string.split(line) # if part == 1: # # In part 1, copy everything literally # except look for a line of just '%%' # if words == ['%%']: part = part + 1 else: # # Look for names of manually written # stubs: a single percent followed by the name # of the function in Python. # The stub name is derived by prefixing 'gl_'. # if words and words[0][0] == '%': func = words[0][1:] if (not func) and words[1:]: func = words[1] if func: functions.append(func) else: print line continue if not words: continue # skip empty line elif words[0] == 'if': # if XXX rest # if !XXX rest if words[1][0] == '!': if words[1][1:] in defined_archs: continue elif words[1] not in defined_archs: continue words = words[2:] if words[0] == '#include': print line elif words[0][:1] == '#': pass # ignore comment elif words[0] not in return_types: err('Line', lno, ': bad return type :', words[0]) elif len(words) < 2: err('Line', lno, ': no funcname :', line) else: if len(words) % 2 != 0: err('Line', lno, ': odd argument list :', words[2:]) else: database = [] try: for i in range(2, len(words), 2): x = checkarg(words[i], words[i+1]) database.append(x) print print '/*', for w in words: print w, print '*/' generate(words[0], words[1], database) except arg_error, msg: err('Line', lno, ':', msg) print print 'static struct PyMethodDef gl_methods[] = {' for func in functions: print '\t{"' + func + '", gl_' + func + '},' print '\t{NULL, NULL} /* Sentinel */' print '};' print print 'void' print 'initgl()' print '{' print '\t(void) Py_InitModule("gl", gl_methods);' print '}'