1########################################################################
2# Copyright (c) 2000, BeOpen.com.
3# Copyright (c) 1995-2000, Corporation for National Research Initiatives.
4# Copyright (c) 1990-1995, Stichting Mathematisch Centrum.
5# All rights reserved.
6#
7# See the file "Misc/COPYRIGHT" for information on usage and
8# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
9########################################################################
10
11# Python script to parse cstubs file for gl and generate C stubs.
12# usage: python cgen.py <cstubs >glmodule.c
13#
14# NOTE: You  must first make a python binary without the "GL" option
15#       before you can run this, when building Python for the first time.
16#       See comments in the Makefile.
17#
18# XXX BUG return arrays generate wrong code
19# XXX need to change error returns into gotos to free mallocked arrays
20from warnings import warnpy3k
21warnpy3k("the cgen module has been removed in Python 3.0", stacklevel=2)
22del warnpy3k
23
24
25import string
26import sys
27
28
29# Function to print to stderr
30#
31def err(*args):
32    savestdout = sys.stdout
33    try:
34        sys.stdout = sys.stderr
35        for i in args:
36            print i,
37        print
38    finally:
39        sys.stdout = savestdout
40
41
42# The set of digits that form a number
43#
44digits = '0123456789'
45
46
47# Function to extract a string of digits from the front of the string.
48# Returns the leading string of digits and the remaining string.
49# If no number is found, returns '' and the original string.
50#
51def getnum(s):
52    n = ''
53    while s and s[0] in digits:
54        n = n + s[0]
55        s = s[1:]
56    return n, s
57
58
59# Function to check if a string is a number
60#
61def isnum(s):
62    if not s: return False
63    for c in s:
64        if not c in digits: return False
65    return True
66
67
68# Allowed function return types
69#
70return_types = ['void', 'short', 'long']
71
72
73# Allowed function argument types
74#
75arg_types = ['char', 'string', 'short', 'u_short', 'float', 'long', 'double']
76
77
78# Need to classify arguments as follows
79#       simple input variable
80#       simple output variable
81#       input array
82#       output array
83#       input giving size of some array
84#
85# Array dimensions can be specified as follows
86#       constant
87#       argN
88#       constant * argN
89#       retval
90#       constant * retval
91#
92# The dimensions given as constants * something are really
93# arrays of points where points are 2- 3- or 4-tuples
94#
95# We have to consider three lists:
96#       python input arguments
97#       C stub arguments (in & out)
98#       python output arguments (really return values)
99#
100# There is a mapping from python input arguments to the input arguments
101# of the C stub, and a further mapping from C stub arguments to the
102# python return values
103
104
105# Exception raised by checkarg() and generate()
106#
107arg_error = 'bad arg'
108
109
110# Function to check one argument.
111# Arguments: the type and the arg "name" (really mode plus subscript).
112# Raises arg_error if something's wrong.
113# Return type, mode, factor, rest of subscript; factor and rest may be empty.
114#
115def checkarg(type, arg):
116    #
117    # Turn "char *x" into "string x".
118    #
119    if type == 'char' and arg[0] == '*':
120        type = 'string'
121        arg = arg[1:]
122    #
123    # Check that the type is supported.
124    #
125    if type not in arg_types:
126        raise arg_error, ('bad type', type)
127    if type[:2] == 'u_':
128        type = 'unsigned ' + type[2:]
129    #
130    # Split it in the mode (first character) and the rest.
131    #
132    mode, rest = arg[:1], arg[1:]
133    #
134    # The mode must be 's' for send (= input) or 'r' for return argument.
135    #
136    if mode not in ('r', 's'):
137        raise arg_error, ('bad arg mode', mode)
138    #
139    # Is it a simple argument: if so, we are done.
140    #
141    if not rest:
142        return type, mode, '', ''
143    #
144    # Not a simple argument; must be an array.
145    # The 'rest' must be a subscript enclosed in [ and ].
146    # The subscript must be one of the following forms,
147    # otherwise we don't handle it (where N is a number):
148    #       N
149    #       argN
150    #       retval
151    #       N*argN
152    #       N*retval
153    #
154    if rest[:1] <> '[' or rest[-1:] <> ']':
155        raise arg_error, ('subscript expected', rest)
156    sub = rest[1:-1]
157    #
158    # Is there a leading number?
159    #
160    num, sub = getnum(sub)
161    if num:
162        # There is a leading number
163        if not sub:
164            # The subscript is just a number
165            return type, mode, num, ''
166        if sub[:1] == '*':
167            # There is a factor prefix
168            sub = sub[1:]
169        else:
170            raise arg_error, ('\'*\' expected', sub)
171    if sub == 'retval':
172        # size is retval -- must be a reply argument
173        if mode <> 'r':
174            raise arg_error, ('non-r mode with [retval]', mode)
175    elif not isnum(sub) and (sub[:3] <> 'arg' or not isnum(sub[3:])):
176        raise arg_error, ('bad subscript', sub)
177    #
178    return type, mode, num, sub
179
180
181# List of functions for which we have generated stubs
182#
183functions = []
184
185
186# Generate the stub for the given function, using the database of argument
187# information build by successive calls to checkarg()
188#
189def generate(type, func, database):
190    #
191    # Check that we can handle this case:
192    # no variable size reply arrays yet
193    #
194    n_in_args = 0
195    n_out_args = 0
196    #
197    for a_type, a_mode, a_factor, a_sub in database:
198        if a_mode == 's':
199            n_in_args = n_in_args + 1
200        elif a_mode == 'r':
201            n_out_args = n_out_args + 1
202        else:
203            # Can't happen
204            raise arg_error, ('bad a_mode', a_mode)
205        if (a_mode == 'r' and a_sub) or a_sub == 'retval':
206            err('Function', func, 'too complicated:',
207                a_type, a_mode, a_factor, a_sub)
208            print '/* XXX Too complicated to generate code for */'
209            return
210    #
211    functions.append(func)
212    #
213    # Stub header
214    #
215    print
216    print 'static PyObject *'
217    print 'gl_' + func + '(self, args)'
218    print '\tPyObject *self;'
219    print '\tPyObject *args;'
220    print '{'
221    #
222    # Declare return value if any
223    #
224    if type <> 'void':
225        print '\t' + type, 'retval;'
226    #
227    # Declare arguments
228    #
229    for i in range(len(database)):
230        a_type, a_mode, a_factor, a_sub = database[i]
231        print '\t' + a_type,
232        brac = ket = ''
233        if a_sub and not isnum(a_sub):
234            if a_factor:
235                brac = '('
236                ket = ')'
237            print brac + '*',
238        print 'arg' + repr(i+1) + ket,
239        if a_sub and isnum(a_sub):
240            print '[', a_sub, ']',
241        if a_factor:
242            print '[', a_factor, ']',
243        print ';'
244    #
245    # Find input arguments derived from array sizes
246    #
247    for i in range(len(database)):
248        a_type, a_mode, a_factor, a_sub = database[i]
249        if a_mode == 's' and a_sub[:3] == 'arg' and isnum(a_sub[3:]):
250            # Sending a variable-length array
251            n = eval(a_sub[3:])
252            if 1 <= n <= len(database):
253                b_type, b_mode, b_factor, b_sub = database[n-1]
254                if b_mode == 's':
255                    database[n-1] = b_type, 'i', a_factor, repr(i)
256                    n_in_args = n_in_args - 1
257    #
258    # Assign argument positions in the Python argument list
259    #
260    in_pos = []
261    i_in = 0
262    for i in range(len(database)):
263        a_type, a_mode, a_factor, a_sub = database[i]
264        if a_mode == 's':
265            in_pos.append(i_in)
266            i_in = i_in + 1
267        else:
268            in_pos.append(-1)
269    #
270    # Get input arguments
271    #
272    for i in range(len(database)):
273        a_type, a_mode, a_factor, a_sub = database[i]
274        if a_type[:9] == 'unsigned ':
275            xtype = a_type[9:]
276        else:
277            xtype = a_type
278        if a_mode == 'i':
279            #
280            # Implicit argument;
281            # a_factor is divisor if present,
282            # a_sub indicates which arg (`database index`)
283            #
284            j = eval(a_sub)
285            print '\tif',
286            print '(!geti' + xtype + 'arraysize(args,',
287            print repr(n_in_args) + ',',
288            print repr(in_pos[j]) + ',',
289            if xtype <> a_type:
290                print '('+xtype+' *)',
291            print '&arg' + repr(i+1) + '))'
292            print '\t\treturn NULL;'
293            if a_factor:
294                print '\targ' + repr(i+1),
295                print '= arg' + repr(i+1),
296                print '/', a_factor + ';'
297        elif a_mode == 's':
298            if a_sub and not isnum(a_sub):
299                # Allocate memory for varsize array
300                print '\tif ((arg' + repr(i+1), '=',
301                if a_factor:
302                    print '('+a_type+'(*)['+a_factor+'])',
303                print 'PyMem_NEW(' + a_type, ',',
304                if a_factor:
305                    print a_factor, '*',
306                print a_sub, ')) == NULL)'
307                print '\t\treturn PyErr_NoMemory();'
308            print '\tif',
309            if a_factor or a_sub: # Get a fixed-size array array
310                print '(!geti' + xtype + 'array(args,',
311                print repr(n_in_args) + ',',
312                print repr(in_pos[i]) + ',',
313                if a_factor: print a_factor,
314                if a_factor and a_sub: print '*',
315                if a_sub: print a_sub,
316                print ',',
317                if (a_sub and a_factor) or xtype <> a_type:
318                    print '('+xtype+' *)',
319                print 'arg' + repr(i+1) + '))'
320            else: # Get a simple variable
321                print '(!geti' + xtype + 'arg(args,',
322                print repr(n_in_args) + ',',
323                print repr(in_pos[i]) + ',',
324                if xtype <> a_type:
325                    print '('+xtype+' *)',
326                print '&arg' + repr(i+1) + '))'
327            print '\t\treturn NULL;'
328    #
329    # Begin of function call
330    #
331    if type <> 'void':
332        print '\tretval =', func + '(',
333    else:
334        print '\t' + func + '(',
335    #
336    # Argument list
337    #
338    for i in range(len(database)):
339        if i > 0: print ',',
340        a_type, a_mode, a_factor, a_sub = database[i]
341        if a_mode == 'r' and not a_factor:
342            print '&',
343        print 'arg' + repr(i+1),
344    #
345    # End of function call
346    #
347    print ');'
348    #
349    # Free varsize arrays
350    #
351    for i in range(len(database)):
352        a_type, a_mode, a_factor, a_sub = database[i]
353        if a_mode == 's' and a_sub and not isnum(a_sub):
354            print '\tPyMem_DEL(arg' + repr(i+1) + ');'
355    #
356    # Return
357    #
358    if n_out_args:
359        #
360        # Multiple return values -- construct a tuple
361        #
362        if type <> 'void':
363            n_out_args = n_out_args + 1
364        if n_out_args == 1:
365            for i in range(len(database)):
366                a_type, a_mode, a_factor, a_sub = database[i]
367                if a_mode == 'r':
368                    break
369            else:
370                raise arg_error, 'expected r arg not found'
371            print '\treturn',
372            print mkobject(a_type, 'arg' + repr(i+1)) + ';'
373        else:
374            print '\t{ PyObject *v = PyTuple_New(',
375            print n_out_args, ');'
376            print '\t  if (v == NULL) return NULL;'
377            i_out = 0
378            if type <> 'void':
379                print '\t  PyTuple_SetItem(v,',
380                print repr(i_out) + ',',
381                print mkobject(type, 'retval') + ');'
382                i_out = i_out + 1
383            for i in range(len(database)):
384                a_type, a_mode, a_factor, a_sub = database[i]
385                if a_mode == 'r':
386                    print '\t  PyTuple_SetItem(v,',
387                    print repr(i_out) + ',',
388                    s = mkobject(a_type, 'arg' + repr(i+1))
389                    print s + ');'
390                    i_out = i_out + 1
391            print '\t  return v;'
392            print '\t}'
393    else:
394        #
395        # Simple function return
396        # Return None or return value
397        #
398        if type == 'void':
399            print '\tPy_INCREF(Py_None);'
400            print '\treturn Py_None;'
401        else:
402            print '\treturn', mkobject(type, 'retval') + ';'
403    #
404    # Stub body closing brace
405    #
406    print '}'
407
408
409# Subroutine to return a function call to mknew<type>object(<arg>)
410#
411def mkobject(type, arg):
412    if type[:9] == 'unsigned ':
413        type = type[9:]
414        return 'mknew' + type + 'object((' + type + ') ' + arg + ')'
415    return 'mknew' + type + 'object(' + arg + ')'
416
417
418defined_archs = []
419
420# usage: cgen [ -Dmach ... ] [ file ]
421for arg in sys.argv[1:]:
422    if arg[:2] == '-D':
423        defined_archs.append(arg[2:])
424    else:
425        # Open optional file argument
426        sys.stdin = open(arg, 'r')
427
428
429# Input line number
430lno = 0
431
432
433# Input is divided in two parts, separated by a line containing '%%'.
434#       <part1>         -- literally copied to stdout
435#       <part2>         -- stub definitions
436
437# Variable indicating the current input part.
438#
439part = 1
440
441# Main loop over the input
442#
443while 1:
444    try:
445        line = raw_input()
446    except EOFError:
447        break
448    #
449    lno = lno+1
450    words = string.split(line)
451    #
452    if part == 1:
453        #
454        # In part 1, copy everything literally
455        # except look for a line of just '%%'
456        #
457        if words == ['%%']:
458            part = part + 1
459        else:
460            #
461            # Look for names of manually written
462            # stubs: a single percent followed by the name
463            # of the function in Python.
464            # The stub name is derived by prefixing 'gl_'.
465            #
466            if words and words[0][0] == '%':
467                func = words[0][1:]
468                if (not func) and words[1:]:
469                    func = words[1]
470                if func:
471                    functions.append(func)
472            else:
473                print line
474        continue
475    if not words:
476        continue                # skip empty line
477    elif words[0] == 'if':
478        # if XXX rest
479        # if !XXX rest
480        if words[1][0] == '!':
481            if words[1][1:] in defined_archs:
482                continue
483        elif words[1] not in defined_archs:
484            continue
485        words = words[2:]
486    if words[0] == '#include':
487        print line
488    elif words[0][:1] == '#':
489        pass                    # ignore comment
490    elif words[0] not in return_types:
491        err('Line', lno, ': bad return type :', words[0])
492    elif len(words) < 2:
493        err('Line', lno, ': no funcname :', line)
494    else:
495        if len(words) % 2 <> 0:
496            err('Line', lno, ': odd argument list :', words[2:])
497        else:
498            database = []
499            try:
500                for i in range(2, len(words), 2):
501                    x = checkarg(words[i], words[i+1])
502                    database.append(x)
503                print
504                print '/*',
505                for w in words: print w,
506                print '*/'
507                generate(words[0], words[1], database)
508            except arg_error, msg:
509                err('Line', lno, ':', msg)
510
511
512print
513print 'static struct PyMethodDef gl_methods[] = {'
514for func in functions:
515    print '\t{"' + func + '", gl_' + func + '},'
516print '\t{NULL, NULL} /* Sentinel */'
517print '};'
518print
519print 'void'
520print 'initgl()'
521print '{'
522print '\t(void) Py_InitModule("gl", gl_methods);'
523print '}'
524