1#!/usr/bin/env python3
2"""
3
4Auxiliary functions for f2py2e.
5
6Copyright 1999,2000 Pearu Peterson all rights reserved,
7Pearu Peterson <pearu@ioc.ee>
8Permission to use, modify, and distribute this software is given under the
9terms of the NumPy (BSD style) LICENSE.
10
11
12NO WARRANTY IS EXPRESSED OR IMPLIED.  USE AT YOUR OWN RISK.
13$Date: 2005/07/24 19:01:55 $
14Pearu Peterson
15
16"""
17import pprint
18import sys
19import types
20from functools import reduce
21
22from . import __version__
23from . import cfuncs
24
25__all__ = [
26    'applyrules', 'debugcapi', 'dictappend', 'errmess', 'gentitle',
27    'getargs2', 'getcallprotoargument', 'getcallstatement',
28    'getfortranname', 'getpymethoddef', 'getrestdoc', 'getusercode',
29    'getusercode1', 'hasbody', 'hascallstatement', 'hascommon',
30    'hasexternals', 'hasinitvalue', 'hasnote', 'hasresultnote',
31    'isallocatable', 'isarray', 'isarrayofstrings', 'iscomplex',
32    'iscomplexarray', 'iscomplexfunction', 'iscomplexfunction_warn',
33    'isdouble', 'isdummyroutine', 'isexternal', 'isfunction',
34    'isfunction_wrap', 'isint1array', 'isinteger', 'isintent_aux',
35    'isintent_c', 'isintent_callback', 'isintent_copy', 'isintent_dict',
36    'isintent_hide', 'isintent_in', 'isintent_inout', 'isintent_inplace',
37    'isintent_nothide', 'isintent_out', 'isintent_overwrite', 'islogical',
38    'islogicalfunction', 'islong_complex', 'islong_double',
39    'islong_doublefunction', 'islong_long', 'islong_longfunction',
40    'ismodule', 'ismoduleroutine', 'isoptional', 'isprivate', 'isrequired',
41    'isroutine', 'isscalar', 'issigned_long_longarray', 'isstring',
42    'isstringarray', 'isstringfunction', 'issubroutine',
43    'issubroutine_wrap', 'isthreadsafe', 'isunsigned', 'isunsigned_char',
44    'isunsigned_chararray', 'isunsigned_long_long',
45    'isunsigned_long_longarray', 'isunsigned_short',
46    'isunsigned_shortarray', 'l_and', 'l_not', 'l_or', 'outmess',
47    'replace', 'show', 'stripcomma', 'throw_error',
48]
49
50
51f2py_version = __version__.version
52
53
54errmess = sys.stderr.write
55show = pprint.pprint
56
57options = {}
58debugoptions = []
59wrapfuncs = 1
60
61
62def outmess(t):
63    if options.get('verbose', 1):
64        sys.stdout.write(t)
65
66
67def debugcapi(var):
68    return 'capi' in debugoptions
69
70
71def _isstring(var):
72    return 'typespec' in var and var['typespec'] == 'character' and \
73           not isexternal(var)
74
75
76def isstring(var):
77    return _isstring(var) and not isarray(var)
78
79
80def ischaracter(var):
81    return isstring(var) and 'charselector' not in var
82
83
84def isstringarray(var):
85    return isarray(var) and _isstring(var)
86
87
88def isarrayofstrings(var):
89    # leaving out '*' for now so that `character*(*) a(m)` and `character
90    # a(m,*)` are treated differently. Luckily `character**` is illegal.
91    return isstringarray(var) and var['dimension'][-1] == '(*)'
92
93
94def isarray(var):
95    return 'dimension' in var and not isexternal(var)
96
97
98def isscalar(var):
99    return not (isarray(var) or isstring(var) or isexternal(var))
100
101
102def iscomplex(var):
103    return isscalar(var) and \
104           var.get('typespec') in ['complex', 'double complex']
105
106
107def islogical(var):
108    return isscalar(var) and var.get('typespec') == 'logical'
109
110
111def isinteger(var):
112    return isscalar(var) and var.get('typespec') == 'integer'
113
114
115def isreal(var):
116    return isscalar(var) and var.get('typespec') == 'real'
117
118
119def get_kind(var):
120    try:
121        return var['kindselector']['*']
122    except KeyError:
123        try:
124            return var['kindselector']['kind']
125        except KeyError:
126            pass
127
128
129def islong_long(var):
130    if not isscalar(var):
131        return 0
132    if var.get('typespec') not in ['integer', 'logical']:
133        return 0
134    return get_kind(var) == '8'
135
136
137def isunsigned_char(var):
138    if not isscalar(var):
139        return 0
140    if var.get('typespec') != 'integer':
141        return 0
142    return get_kind(var) == '-1'
143
144
145def isunsigned_short(var):
146    if not isscalar(var):
147        return 0
148    if var.get('typespec') != 'integer':
149        return 0
150    return get_kind(var) == '-2'
151
152
153def isunsigned(var):
154    if not isscalar(var):
155        return 0
156    if var.get('typespec') != 'integer':
157        return 0
158    return get_kind(var) == '-4'
159
160
161def isunsigned_long_long(var):
162    if not isscalar(var):
163        return 0
164    if var.get('typespec') != 'integer':
165        return 0
166    return get_kind(var) == '-8'
167
168
169def isdouble(var):
170    if not isscalar(var):
171        return 0
172    if not var.get('typespec') == 'real':
173        return 0
174    return get_kind(var) == '8'
175
176
177def islong_double(var):
178    if not isscalar(var):
179        return 0
180    if not var.get('typespec') == 'real':
181        return 0
182    return get_kind(var) == '16'
183
184
185def islong_complex(var):
186    if not iscomplex(var):
187        return 0
188    return get_kind(var) == '32'
189
190
191def iscomplexarray(var):
192    return isarray(var) and \
193           var.get('typespec') in ['complex', 'double complex']
194
195
196def isint1array(var):
197    return isarray(var) and var.get('typespec') == 'integer' \
198        and get_kind(var) == '1'
199
200
201def isunsigned_chararray(var):
202    return isarray(var) and var.get('typespec') in ['integer', 'logical']\
203        and get_kind(var) == '-1'
204
205
206def isunsigned_shortarray(var):
207    return isarray(var) and var.get('typespec') in ['integer', 'logical']\
208        and get_kind(var) == '-2'
209
210
211def isunsignedarray(var):
212    return isarray(var) and var.get('typespec') in ['integer', 'logical']\
213        and get_kind(var) == '-4'
214
215
216def isunsigned_long_longarray(var):
217    return isarray(var) and var.get('typespec') in ['integer', 'logical']\
218        and get_kind(var) == '-8'
219
220
221def issigned_chararray(var):
222    return isarray(var) and var.get('typespec') in ['integer', 'logical']\
223        and get_kind(var) == '1'
224
225
226def issigned_shortarray(var):
227    return isarray(var) and var.get('typespec') in ['integer', 'logical']\
228        and get_kind(var) == '2'
229
230
231def issigned_array(var):
232    return isarray(var) and var.get('typespec') in ['integer', 'logical']\
233        and get_kind(var) == '4'
234
235
236def issigned_long_longarray(var):
237    return isarray(var) and var.get('typespec') in ['integer', 'logical']\
238        and get_kind(var) == '8'
239
240
241def isallocatable(var):
242    return 'attrspec' in var and 'allocatable' in var['attrspec']
243
244
245def ismutable(var):
246    return not ('dimension' not in var or isstring(var))
247
248
249def ismoduleroutine(rout):
250    return 'modulename' in rout
251
252
253def ismodule(rout):
254    return 'block' in rout and 'module' == rout['block']
255
256
257def isfunction(rout):
258    return 'block' in rout and 'function' == rout['block']
259
260
261def isfunction_wrap(rout):
262    if isintent_c(rout):
263        return 0
264    return wrapfuncs and isfunction(rout) and (not isexternal(rout))
265
266
267def issubroutine(rout):
268    return 'block' in rout and 'subroutine' == rout['block']
269
270
271def issubroutine_wrap(rout):
272    if isintent_c(rout):
273        return 0
274    return issubroutine(rout) and hasassumedshape(rout)
275
276
277def hasassumedshape(rout):
278    if rout.get('hasassumedshape'):
279        return True
280    for a in rout['args']:
281        for d in rout['vars'].get(a, {}).get('dimension', []):
282            if d == ':':
283                rout['hasassumedshape'] = True
284                return True
285    return False
286
287
288def requiresf90wrapper(rout):
289    return ismoduleroutine(rout) or hasassumedshape(rout)
290
291
292def isroutine(rout):
293    return isfunction(rout) or issubroutine(rout)
294
295
296def islogicalfunction(rout):
297    if not isfunction(rout):
298        return 0
299    if 'result' in rout:
300        a = rout['result']
301    else:
302        a = rout['name']
303    if a in rout['vars']:
304        return islogical(rout['vars'][a])
305    return 0
306
307
308def islong_longfunction(rout):
309    if not isfunction(rout):
310        return 0
311    if 'result' in rout:
312        a = rout['result']
313    else:
314        a = rout['name']
315    if a in rout['vars']:
316        return islong_long(rout['vars'][a])
317    return 0
318
319
320def islong_doublefunction(rout):
321    if not isfunction(rout):
322        return 0
323    if 'result' in rout:
324        a = rout['result']
325    else:
326        a = rout['name']
327    if a in rout['vars']:
328        return islong_double(rout['vars'][a])
329    return 0
330
331
332def iscomplexfunction(rout):
333    if not isfunction(rout):
334        return 0
335    if 'result' in rout:
336        a = rout['result']
337    else:
338        a = rout['name']
339    if a in rout['vars']:
340        return iscomplex(rout['vars'][a])
341    return 0
342
343
344def iscomplexfunction_warn(rout):
345    if iscomplexfunction(rout):
346        outmess("""\
347    **************************************************************
348        Warning: code with a function returning complex value
349        may not work correctly with your Fortran compiler.
350        Run the following test before using it in your applications:
351        $(f2py install dir)/test-site/{b/runme_scalar,e/runme}
352        When using GNU gcc/g77 compilers, codes should work correctly.
353    **************************************************************\n""")
354        return 1
355    return 0
356
357
358def isstringfunction(rout):
359    if not isfunction(rout):
360        return 0
361    if 'result' in rout:
362        a = rout['result']
363    else:
364        a = rout['name']
365    if a in rout['vars']:
366        return isstring(rout['vars'][a])
367    return 0
368
369
370def hasexternals(rout):
371    return 'externals' in rout and rout['externals']
372
373
374def isthreadsafe(rout):
375    return 'f2pyenhancements' in rout and \
376           'threadsafe' in rout['f2pyenhancements']
377
378
379def hasvariables(rout):
380    return 'vars' in rout and rout['vars']
381
382
383def isoptional(var):
384    return ('attrspec' in var and 'optional' in var['attrspec'] and
385            'required' not in var['attrspec']) and isintent_nothide(var)
386
387
388def isexternal(var):
389    return 'attrspec' in var and 'external' in var['attrspec']
390
391
392def isrequired(var):
393    return not isoptional(var) and isintent_nothide(var)
394
395
396def isintent_in(var):
397    if 'intent' not in var:
398        return 1
399    if 'hide' in var['intent']:
400        return 0
401    if 'inplace' in var['intent']:
402        return 0
403    if 'in' in var['intent']:
404        return 1
405    if 'out' in var['intent']:
406        return 0
407    if 'inout' in var['intent']:
408        return 0
409    if 'outin' in var['intent']:
410        return 0
411    return 1
412
413
414def isintent_inout(var):
415    return ('intent' in var and ('inout' in var['intent'] or
416            'outin' in var['intent']) and 'in' not in var['intent'] and
417            'hide' not in var['intent'] and 'inplace' not in var['intent'])
418
419
420def isintent_out(var):
421    return 'out' in var.get('intent', [])
422
423
424def isintent_hide(var):
425    return ('intent' in var and ('hide' in var['intent'] or
426            ('out' in var['intent'] and 'in' not in var['intent'] and
427                (not l_or(isintent_inout, isintent_inplace)(var)))))
428
429def isintent_nothide(var):
430    return not isintent_hide(var)
431
432
433def isintent_c(var):
434    return 'c' in var.get('intent', [])
435
436
437def isintent_cache(var):
438    return 'cache' in var.get('intent', [])
439
440
441def isintent_copy(var):
442    return 'copy' in var.get('intent', [])
443
444
445def isintent_overwrite(var):
446    return 'overwrite' in var.get('intent', [])
447
448
449def isintent_callback(var):
450    return 'callback' in var.get('intent', [])
451
452
453def isintent_inplace(var):
454    return 'inplace' in var.get('intent', [])
455
456
457def isintent_aux(var):
458    return 'aux' in var.get('intent', [])
459
460
461def isintent_aligned4(var):
462    return 'aligned4' in var.get('intent', [])
463
464
465def isintent_aligned8(var):
466    return 'aligned8' in var.get('intent', [])
467
468
469def isintent_aligned16(var):
470    return 'aligned16' in var.get('intent', [])
471
472isintent_dict = {isintent_in: 'INTENT_IN', isintent_inout: 'INTENT_INOUT',
473                 isintent_out: 'INTENT_OUT', isintent_hide: 'INTENT_HIDE',
474                 isintent_cache: 'INTENT_CACHE',
475                 isintent_c: 'INTENT_C', isoptional: 'OPTIONAL',
476                 isintent_inplace: 'INTENT_INPLACE',
477                 isintent_aligned4: 'INTENT_ALIGNED4',
478                 isintent_aligned8: 'INTENT_ALIGNED8',
479                 isintent_aligned16: 'INTENT_ALIGNED16',
480                 }
481
482
483def isprivate(var):
484    return 'attrspec' in var and 'private' in var['attrspec']
485
486
487def hasinitvalue(var):
488    return '=' in var
489
490
491def hasinitvalueasstring(var):
492    if not hasinitvalue(var):
493        return 0
494    return var['='][0] in ['"', "'"]
495
496
497def hasnote(var):
498    return 'note' in var
499
500
501def hasresultnote(rout):
502    if not isfunction(rout):
503        return 0
504    if 'result' in rout:
505        a = rout['result']
506    else:
507        a = rout['name']
508    if a in rout['vars']:
509        return hasnote(rout['vars'][a])
510    return 0
511
512
513def hascommon(rout):
514    return 'common' in rout
515
516
517def containscommon(rout):
518    if hascommon(rout):
519        return 1
520    if hasbody(rout):
521        for b in rout['body']:
522            if containscommon(b):
523                return 1
524    return 0
525
526
527def containsmodule(block):
528    if ismodule(block):
529        return 1
530    if not hasbody(block):
531        return 0
532    for b in block['body']:
533        if containsmodule(b):
534            return 1
535    return 0
536
537
538def hasbody(rout):
539    return 'body' in rout
540
541
542def hascallstatement(rout):
543    return getcallstatement(rout) is not None
544
545
546def istrue(var):
547    return 1
548
549
550def isfalse(var):
551    return 0
552
553
554class F2PYError(Exception):
555    pass
556
557
558class throw_error:
559
560    def __init__(self, mess):
561        self.mess = mess
562
563    def __call__(self, var):
564        mess = '\n\n  var = %s\n  Message: %s\n' % (var, self.mess)
565        raise F2PYError(mess)
566
567
568def l_and(*f):
569    l, l2 = 'lambda v', []
570    for i in range(len(f)):
571        l = '%s,f%d=f[%d]' % (l, i, i)
572        l2.append('f%d(v)' % (i))
573    return eval('%s:%s' % (l, ' and '.join(l2)))
574
575
576def l_or(*f):
577    l, l2 = 'lambda v', []
578    for i in range(len(f)):
579        l = '%s,f%d=f[%d]' % (l, i, i)
580        l2.append('f%d(v)' % (i))
581    return eval('%s:%s' % (l, ' or '.join(l2)))
582
583
584def l_not(f):
585    return eval('lambda v,f=f:not f(v)')
586
587
588def isdummyroutine(rout):
589    try:
590        return rout['f2pyenhancements']['fortranname'] == ''
591    except KeyError:
592        return 0
593
594
595def getfortranname(rout):
596    try:
597        name = rout['f2pyenhancements']['fortranname']
598        if name == '':
599            raise KeyError
600        if not name:
601            errmess('Failed to use fortranname from %s\n' %
602                    (rout['f2pyenhancements']))
603            raise KeyError
604    except KeyError:
605        name = rout['name']
606    return name
607
608
609def getmultilineblock(rout, blockname, comment=1, counter=0):
610    try:
611        r = rout['f2pyenhancements'].get(blockname)
612    except KeyError:
613        return
614    if not r:
615        return
616    if counter > 0 and isinstance(r, str):
617        return
618    if isinstance(r, list):
619        if counter >= len(r):
620            return
621        r = r[counter]
622    if r[:3] == "'''":
623        if comment:
624            r = '\t/* start ' + blockname + \
625                ' multiline (' + repr(counter) + ') */\n' + r[3:]
626        else:
627            r = r[3:]
628        if r[-3:] == "'''":
629            if comment:
630                r = r[:-3] + '\n\t/* end multiline (' + repr(counter) + ')*/'
631            else:
632                r = r[:-3]
633        else:
634            errmess("%s multiline block should end with `'''`: %s\n"
635                    % (blockname, repr(r)))
636    return r
637
638
639def getcallstatement(rout):
640    return getmultilineblock(rout, 'callstatement')
641
642
643def getcallprotoargument(rout, cb_map={}):
644    r = getmultilineblock(rout, 'callprotoargument', comment=0)
645    if r:
646        return r
647    if hascallstatement(rout):
648        outmess(
649            'warning: callstatement is defined without callprotoargument\n')
650        return
651    from .capi_maps import getctype
652    arg_types, arg_types2 = [], []
653    if l_and(isstringfunction, l_not(isfunction_wrap))(rout):
654        arg_types.extend(['char*', 'size_t'])
655    for n in rout['args']:
656        var = rout['vars'][n]
657        if isintent_callback(var):
658            continue
659        if n in cb_map:
660            ctype = cb_map[n] + '_typedef'
661        else:
662            ctype = getctype(var)
663            if l_and(isintent_c, l_or(isscalar, iscomplex))(var):
664                pass
665            elif isstring(var):
666                pass
667            else:
668                ctype = ctype + '*'
669            if isstring(var) or isarrayofstrings(var):
670                arg_types2.append('size_t')
671        arg_types.append(ctype)
672
673    proto_args = ','.join(arg_types + arg_types2)
674    if not proto_args:
675        proto_args = 'void'
676    return proto_args
677
678
679def getusercode(rout):
680    return getmultilineblock(rout, 'usercode')
681
682
683def getusercode1(rout):
684    return getmultilineblock(rout, 'usercode', counter=1)
685
686
687def getpymethoddef(rout):
688    return getmultilineblock(rout, 'pymethoddef')
689
690
691def getargs(rout):
692    sortargs, args = [], []
693    if 'args' in rout:
694        args = rout['args']
695        if 'sortvars' in rout:
696            for a in rout['sortvars']:
697                if a in args:
698                    sortargs.append(a)
699            for a in args:
700                if a not in sortargs:
701                    sortargs.append(a)
702        else:
703            sortargs = rout['args']
704    return args, sortargs
705
706
707def getargs2(rout):
708    sortargs, args = [], rout.get('args', [])
709    auxvars = [a for a in rout['vars'].keys() if isintent_aux(rout['vars'][a])
710               and a not in args]
711    args = auxvars + args
712    if 'sortvars' in rout:
713        for a in rout['sortvars']:
714            if a in args:
715                sortargs.append(a)
716        for a in args:
717            if a not in sortargs:
718                sortargs.append(a)
719    else:
720        sortargs = auxvars + rout['args']
721    return args, sortargs
722
723
724def getrestdoc(rout):
725    if 'f2pymultilines' not in rout:
726        return None
727    k = None
728    if rout['block'] == 'python module':
729        k = rout['block'], rout['name']
730    return rout['f2pymultilines'].get(k, None)
731
732
733def gentitle(name):
734    l = (80 - len(name) - 6) // 2
735    return '/*%s %s %s*/' % (l * '*', name, l * '*')
736
737
738def flatlist(l):
739    if isinstance(l, list):
740        return reduce(lambda x, y, f=flatlist: x + f(y), l, [])
741    return [l]
742
743
744def stripcomma(s):
745    if s and s[-1] == ',':
746        return s[:-1]
747    return s
748
749
750def replace(str, d, defaultsep=''):
751    if isinstance(d, list):
752        return [replace(str, _m, defaultsep) for _m in d]
753    if isinstance(str, list):
754        return [replace(_m, d, defaultsep) for _m in str]
755    for k in 2 * list(d.keys()):
756        if k == 'separatorsfor':
757            continue
758        if 'separatorsfor' in d and k in d['separatorsfor']:
759            sep = d['separatorsfor'][k]
760        else:
761            sep = defaultsep
762        if isinstance(d[k], list):
763            str = str.replace('#%s#' % (k), sep.join(flatlist(d[k])))
764        else:
765            str = str.replace('#%s#' % (k), d[k])
766    return str
767
768
769def dictappend(rd, ar):
770    if isinstance(ar, list):
771        for a in ar:
772            rd = dictappend(rd, a)
773        return rd
774    for k in ar.keys():
775        if k[0] == '_':
776            continue
777        if k in rd:
778            if isinstance(rd[k], str):
779                rd[k] = [rd[k]]
780            if isinstance(rd[k], list):
781                if isinstance(ar[k], list):
782                    rd[k] = rd[k] + ar[k]
783                else:
784                    rd[k].append(ar[k])
785            elif isinstance(rd[k], dict):
786                if isinstance(ar[k], dict):
787                    if k == 'separatorsfor':
788                        for k1 in ar[k].keys():
789                            if k1 not in rd[k]:
790                                rd[k][k1] = ar[k][k1]
791                    else:
792                        rd[k] = dictappend(rd[k], ar[k])
793        else:
794            rd[k] = ar[k]
795    return rd
796
797
798def applyrules(rules, d, var={}):
799    ret = {}
800    if isinstance(rules, list):
801        for r in rules:
802            rr = applyrules(r, d, var)
803            ret = dictappend(ret, rr)
804            if '_break' in rr:
805                break
806        return ret
807    if '_check' in rules and (not rules['_check'](var)):
808        return ret
809    if 'need' in rules:
810        res = applyrules({'needs': rules['need']}, d, var)
811        if 'needs' in res:
812            cfuncs.append_needs(res['needs'])
813
814    for k in rules.keys():
815        if k == 'separatorsfor':
816            ret[k] = rules[k]
817            continue
818        if isinstance(rules[k], str):
819            ret[k] = replace(rules[k], d)
820        elif isinstance(rules[k], list):
821            ret[k] = []
822            for i in rules[k]:
823                ar = applyrules({k: i}, d, var)
824                if k in ar:
825                    ret[k].append(ar[k])
826        elif k[0] == '_':
827            continue
828        elif isinstance(rules[k], dict):
829            ret[k] = []
830            for k1 in rules[k].keys():
831                if isinstance(k1, types.FunctionType) and k1(var):
832                    if isinstance(rules[k][k1], list):
833                        for i in rules[k][k1]:
834                            if isinstance(i, dict):
835                                res = applyrules({'supertext': i}, d, var)
836                                if 'supertext' in res:
837                                    i = res['supertext']
838                                else:
839                                    i = ''
840                            ret[k].append(replace(i, d))
841                    else:
842                        i = rules[k][k1]
843                        if isinstance(i, dict):
844                            res = applyrules({'supertext': i}, d)
845                            if 'supertext' in res:
846                                i = res['supertext']
847                            else:
848                                i = ''
849                        ret[k].append(replace(i, d))
850        else:
851            errmess('applyrules: ignoring rule %s.\n' % repr(rules[k]))
852        if isinstance(ret[k], list):
853            if len(ret[k]) == 1:
854                ret[k] = ret[k][0]
855            if ret[k] == []:
856                del ret[k]
857    return ret
858