-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathfparser2.py
More file actions
6250 lines (5533 loc) · 292 KB
/
Copy pathfparser2.py
File metadata and controls
6250 lines (5533 loc) · 292 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# BSD 3-Clause License
#
# Copyright (c) 2017-2026, Science and Technology Facilities Council.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
# Authors: R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab
# J. Henrichs, Bureau of Meteorology
# I. Kavcic, Met Office
# Modified: A. B. G. Chalk, STFC Daresbury Lab
# M. Naylor, University of Cambridge, UK
# -----------------------------------------------------------------------------
''' This module provides the fparser2 to PSyIR front-end, it follows a
Visitor Pattern to traverse relevant fparser2 nodes and contains the logic
to transform each node into the equivalent PSyIR representation.'''
from collections import OrderedDict
from dataclasses import dataclass, field
import re
import os
import sys
from typing import Iterable, Optional, Tuple, Union
from fparser.common.readfortran import (
FortranStringReader, FortranFileReader, FortranReaderBase)
from fparser.two import C99Preprocessor, Fortran2003, utils
from fparser.two.parser import ParserFactory
from fparser.two.utils import walk, BlockBase, StmtBase, Base
from fparser.common.sourceinfo import FortranFormat
from fparser.two.symbol_table import SYMBOL_TABLES
from fparser.two.utils import FortranSyntaxError, NoMatchError
from psyclone.configuration import Config
from psyclone.errors import InternalError, GenerationError
from psyclone.psyir.commentable_mixin import CommentableMixin
from psyclone.psyir.nodes import (
ArrayConstructor,
ArrayMember, ACCRoutineDirective, ArrayOfStructuresReference,
ArrayReference, Assignment, BinaryOperation, Call, CodeBlock, Container,
DataNode, Directive, FileContainer, IfBlock, IntrinsicCall, Literal, Loop,
Member, Node, OMPDeclareTargetDirective, Range, Reference, Return,
Routine, Schedule, StructureReference, UnaryOperation, WhileLoop,
Fparser2CodeBlock, ScopingNode, UnknownDirective)
from psyclone.psyir.nodes.array_mixin import ArrayMixin
from psyclone.psyir.symbols import (
ArgumentInterface, ArrayType, AutomaticInterface, ScalarType,
CommonBlockInterface, ContainerSymbol, DataSymbol, DataTypeSymbol,
DefaultModuleInterface, GenericInterfaceSymbol, ImportInterface,
NoType, RoutineSymbol, StaticInterface,
StructureType, Symbol, SymbolError, UnknownInterface,
UnresolvedInterface, UnresolvedType, UnsupportedFortranType,
UnsupportedType, SymbolTable)
# fparser dynamically generates classes which confuses pylint membership checks
# pylint: disable=maybe-no-member
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
# pylint: disable=too-many-lines
#: The list of Fortran intrinsic functions that we know about (and can
#: therefore distinguish from array accesses). These are taken from
#: fparser.
FORTRAN_INTRINSICS = Fortran2003.Intrinsic_Name.function_names
#: Mapping from Fortran data types to PSyIR types
TYPE_MAP_FROM_FORTRAN = {"integer": ScalarType.Intrinsic.INTEGER,
"character": ScalarType.Intrinsic.CHARACTER,
"logical": ScalarType.Intrinsic.BOOLEAN,
"real": ScalarType.Intrinsic.REAL,
"double precision": ScalarType.Intrinsic.REAL}
#: Mapping from Fortran access specifiers to PSyIR visibilities
VISIBILITY_MAP_FROM_FORTRAN = {"public": Symbol.Visibility.PUBLIC,
"private": Symbol.Visibility.PRIVATE}
#: Mapping from fparser2 Fortran Literal types to PSyIR types
CONSTANT_TYPE_MAP = {
Fortran2003.Real_Literal_Constant: ScalarType.Intrinsic.REAL,
Fortran2003.Logical_Literal_Constant: ScalarType.Intrinsic.BOOLEAN,
Fortran2003.Char_Literal_Constant: ScalarType.Intrinsic.CHARACTER,
Fortran2003.Int_Literal_Constant: ScalarType.Intrinsic.INTEGER}
#: Mapping from Fortran intent to PSyIR access type
INTENT_MAPPING = {"in": ArgumentInterface.Access.READ,
"out": ArgumentInterface.Access.WRITE,
"inout": ArgumentInterface.Access.READWRITE}
#: Those routine prefix specifications that we support.
SUPPORTED_ROUTINE_PREFIXES = ["ELEMENTAL", "PURE", "IMPURE"]
def _first_type_match(nodelist, typekind):
'''Returns the first instance of the specified type in the given
node list.
:param list nodelist: list of fparser2 nodes.
:param type typekind: the fparser2 Type we are searching for.
:returns: the first instance of the specified type.
:rtype: instance of typekind
:raises ValueError: if the list does not contain an object of type \
typekind.
'''
for node in nodelist:
if isinstance(node, typekind):
return node
raise ValueError # Type not found
def _find_or_create_unresolved_symbol(location, name, scope_limit=None,
**kargs) -> Symbol:
'''Returns the symbol with the given 'name' from a symbol table
associated with the 'location' node or one of its ancestors. If a
symbol is found then the type of the existing symbol is compared
with the specified 'symbol_type' parameter (passed as part of
'**kargs'). If it is not already an instance of this type, then
the symbol is specialised (in place).
If the symbol is not found then a new Symbol with the specified
visibility but of unresolved interface is created and inserted in the
most local SymbolTable that has a Routine or Container node as
parent.
The scope_limit variable further limits the symbol table search so
that the search through ancestor nodes stops when the scope_limit
node is reached i.e. ancestors of the scope_limit node are not
searched.
:param location: PSyIR node from which to operate.
:type location: :py:class:`psyclone.psyir.nodes.Node`
:param str name: the name of the symbol.
:param scope_limit: optional Node which limits the symbol
search space to the symbol tables of the nodes within the
given scope. If it is None (the default), the whole
scope (all symbol tables in ancestor nodes) is searched
otherwise ancestors of the scope_limit node are not
searched.
:type scope_limit: :py:class:`psyclone.psyir.nodes.Node` or
`NoneType`
:param kargs: arguments to pass on when either specialising an
existing symbol or creating a new one.
:type kargs: unwrapped dict
:returns: the matching symbol.
:raises TypeError: if the supplied scope_limit is not a Node.
:raises ValueError: if the supplied scope_limit node is not an
ancestor of the supplied node.
'''
if not isinstance(location, Node):
raise TypeError(
f"The location argument '{location}' provided to "
f"_find_or_create_unresolved_symbol() is not of type `Node`.")
if scope_limit is not None:
# Validate the supplied scope_limit
if not isinstance(scope_limit, Node):
raise TypeError(
f"The scope_limit argument '{scope_limit}' provided to "
f"_find_or_create_unresolved_symbol() is not of type `Node`.")
# Check that the scope_limit Node is an ancestor of this
# Reference Node and raise an exception if not.
mynode = location.parent
while mynode is not None:
if mynode is scope_limit:
# The scope_limit node is an ancestor of the supplied node.
break
mynode = mynode.parent
else:
# The scope_limit node is not an ancestor of the
# supplied node so raise an exception.
raise ValueError(
f"The scope_limit node '{scope_limit}' provided to "
f"_find_or_create_unresolved_symbol() is not an ancestor of "
f"this node '{location}'.")
# In Fortran, no import can clash with the name of a parent Routine.
# Therefore, if the name we've been given corresponds to the name of the
# enclosing Routine (or its RESULT if it is a function) then it *must*
# refer to that and cannot be brought in by an import.
parent_scope = location.ancestor(Routine)
if parent_scope:
if (parent_scope.return_symbol and
parent_scope.return_symbol.name.lower() == name.lower()):
# The PSyIR canonicalises functions such that they always have
# a RESULT clause. As such, according to the Fortran standard, any
# reference to the name specified in the RESULT clause is to the
# DataSymbol.
return parent_scope.return_symbol
if parent_scope.name.lower() == name.lower():
return parent_scope.symbol
table = location.scope.symbol_table
while table:
# By default, `lookup` looks in all ancestor scopes. However, we need
# to check for wildcard imports as we work our way up.
sym = table.lookup(name, scope_limit=table.node,
otherwise=None)
if sym:
if "symbol_type" in kargs:
expected_type = kargs.pop("symbol_type")
if not isinstance(sym, expected_type):
# The caller specified a sub-class so we need to
# specialise the existing symbol.
sym.specialise(expected_type, **kargs)
return sym
if table.wildcard_imports(scope_limit=table.node):
# There's a wildcard import into this scope so we stop
# searching and create an unresolved symbol (below). This is
# permitted to shadow a declaration in an outer scope because
# it may be a different entity (coming from the import).
# TODO #2915 - it may be that we've already resolved all symbols
# from this import but currently we have no way of recording that.
kargs["shadowing"] = True
break
table = table.parent_symbol_table(scope_limit)
# Find the closest ancestor symbol table attached to a Routine or
# Container node. We don't want to add to a Schedule node as in
# some situations PSyclone assumes symbols are declared within
# Routine or Container symbol tables due to its Fortran provenance
# (but should probably not!). We also have cases when the whole
# tree has not been built so the symbol table is not connected to
# a node.
symbol_table = location.scope.symbol_table
while (
symbol_table and symbol_table.node
and not isinstance(symbol_table.node, (Routine, Container))
and symbol_table.parent_symbol_table() is not None
):
symbol_table = symbol_table.parent_symbol_table()
# All requested Nodes have been checked but there has been no
# match. Add it to the symbol table as an unresolved symbol in any
# case as, for example, it might be declared later, or the
# declaration may be hidden (perhaps in a codeblock), or it may be
# imported with a wildcard import.
return symbol_table.new_symbol(
name, interface=UnresolvedInterface(), **kargs)
def _refine_symbols_with_usage_location(
location: Node,
execution_part: Fortran2003.Execution_Part
):
''' Refine the symbol information that we obtained from parsing
the declarations sections by knowledge inferred by looking at the
usage location of the symbols in the execution_part
:param location: scope to enhance information for.
:param execution_part: fparser nodes to analyse for symbol usage.
'''
# The top-reference of an assignment lhs is guaranteed to be a DataSymbol
# This is not true for statement functions, but fparser and psyclone
# currently don't support them.
for assignment in walk(execution_part, Fortran2003.Assignment_Stmt):
if isinstance(assignment.items[0], Fortran2003.Part_Ref):
name = assignment.items[0].items[0].string.lower()
_find_or_create_unresolved_symbol(
location, name,
symbol_type=DataSymbol,
datatype=UnresolvedType())
# Get a set of all names used directly as children of Expressions
direct_refnames_in_exprs = {
x.string.lower() for x in walk(execution_part, Fortran2003.Name)
if isinstance(x.parent, (Fortran2003.BinaryOpBase,
Fortran2003.UnaryOpBase,
Fortran2003.Actual_Arg_Spec_List))
}
# Traverse all part_ref, in fparser these are <name>(<list>) expressions,
# and specialise their names as DataSymbols if some of the following
# conditions is found:
for part_ref in walk(execution_part, Fortran2003.Part_Ref):
name = part_ref.items[0].string.lower()
if isinstance(part_ref.parent, Fortran2003.Data_Ref):
# If it's part of an accessor "a%b(:)", we don't continue, as 'b'
# is not something that we have a symbol for and cannot specialise
continue
for child in part_ref.items:
if isinstance(child, Fortran2003.Section_Subscript_List):
# If any of its direct children is a triplet "<lb>:<up>:<step>"
# we know its a DataSymbol, for now of UnresolvedType, as we
# don't know enough to infer the whole shape.
if any(isinstance(subchild, Fortran2003.Subscript_Triplet)
for subchild in child.items):
_find_or_create_unresolved_symbol(
location, name,
symbol_type=DataSymbol,
datatype=UnresolvedType())
if name in direct_refnames_in_exprs:
# If any other expression has the same reference name without
# parenthesis, e.g.: a + a(3), we know a is an array and not a
# function call, as the latter have mandatory parenthesis.
_find_or_create_unresolved_symbol(
location, name,
symbol_type=DataSymbol,
datatype=UnresolvedType())
def _find_or_create_psyclone_internal_cmp(node):
'''
Utility routine to return a symbol of the generic psyclone comparison
interface. If the interface does not exist in the scope it first adds
the necessary code to the parent module.
:param node: location where the comparison interface is needed.
:type node: :py:class:`psyclone.psyir.nodes.Node`
:returns: the comparison interface symbol.
:rtype: :py:class:`psyclone.psyir.symbols.Symbol`
:raises NotImplementedError: if there is no ancestor module container
on which to add the interface code into.
'''
try:
return node.scope.symbol_table.lookup_with_tag("psyclone_internal_cmp")
except KeyError:
container = node.ancestor(Container)
if container and not isinstance(container, FileContainer):
# pylint: disable=import-outside-toplevel
from psyclone.psyir.frontend.fortran import FortranReader
# Giving them the same name in different modules causes issues with
# the nvidia compiler when offloading, so we use the module name as
# prefix
root = container.name + "_psyclone_internal_cmp"
name_interface = node.scope.symbol_table.next_available_name(
root)
name_f_int = node.scope.symbol_table.next_available_name(
root + "_int")
name_f_logical = node.scope.symbol_table.next_available_name(
root + "_logical")
name_f_char = node.scope.symbol_table.next_available_name(
root + "_char")
fortran_reader = FortranReader()
dummymod = fortran_reader.psyir_from_source(f'''
module dummy
implicit none
interface {name_interface}
procedure {name_f_int}
procedure {name_f_logical}
procedure {name_f_char}
end interface {name_interface}
private {name_interface}
private {name_f_int}, {name_f_logical}, {name_f_char}
contains
logical pure function {name_f_int}(op1, op2)
integer, intent(in) :: op1, op2
{name_f_int} = op1.eq.op2
end function
logical pure function {name_f_logical}(op1, op2)
logical, intent(in) :: op1, op2
{name_f_logical} = op1.eqv.op2
end function
logical pure function {name_f_char}(op1, op2)
character(*), intent(in) :: op1, op2
{name_f_char} = op1.eq.op2
end function
end module dummy
''').children[0] # We skip the top FileContainer
# Add the new functions and interface to the ancestor container
container.children.extend(dummymod.pop_all_children())
# The routine symbols fail to be removed from dummymod when calling
# pop_all_children as they're referenced by the interface. We can't
# merge the symbol tables together since it results in duplicated
# symbols, so instead we just need to fix the name interface
# manually.
sym = dummymod.symbol_table.lookup(name_interface)
routine_symbol1 = container.symbol_table.lookup(name_f_int)
routine_symbol1.visibitity = Symbol.Visibility.PRIVATE
routine_symbol2 = container.symbol_table.lookup(name_f_logical)
routine_symbol2.visibitity = Symbol.Visibility.PRIVATE
routine_symbol3 = container.symbol_table.lookup(name_f_char)
routine_symbol3.visibitity = Symbol.Visibility.PRIVATE
symbol = GenericInterfaceSymbol(
sym.name,
[(routine_symbol1, sym.routines[0].from_container),
(routine_symbol2, sym.routines[1].from_container),
(routine_symbol3, sym.routines[2].from_container)],
visibility=Symbol.Visibility.PRIVATE
)
container.symbol_table.add(symbol)
symbol = container.symbol_table.lookup(name_interface)
# Add the appropriate tag to find it regardless of the name
container.symbol_table.tags_dict['psyclone_internal_cmp'] = symbol
return symbol
raise NotImplementedError(
"Could not find the generic comparison interface and the scope does "
"not have an ancestor container in which to add it.")
def _copy_full_base_reference(node):
'''
Given the supplied node, creates a new node with the same access
apart from the final array access. Such a node is then suitable for use
as an argument to either e.g. LBOUND or UBOUND.
e.g. if `node` is an ArrayMember representing the inner access in
'grid%data(:)' then this routine will return a PSyIR node for
'grid%data'.
:param node: the array access. In the case of a structure, this \
must be the inner-most part of the access.
:type node: :py:class:`psyclone.psyir.nodes.Reference` or \
:py:class:`psyclone.psyir.nodes.Member`
:returns: the PSyIR for a suitable argument to either LBOUND or \
UBOUND applied to the supplied `node`.
:rtype: :py:class:`psyclone.psyir.nodes.Node`
:raises InternalError: if the supplied node is not an instance of \
either Reference or Member.
'''
if isinstance(node, Reference):
return Reference(node.symbol)
if isinstance(node, Member):
# We have to take care with derived types:
# grid(1)%data(:...) becomes
# grid(1)%data(lbound(grid(1)%data,1):...)
# N.B. the argument to lbound becomes a Member access rather
# than an ArrayMember access.
parent_ref = node.ancestor(Reference, include_self=True)
# We have to find the location of the supplied node in the
# StructureReference.
inner = parent_ref
depth = 0
while hasattr(inner, "member") and inner is not node:
depth += 1
inner = inner.member
# Now we take a copy of the full reference and then modify it so
# that the copy of 'node' is replaced by a Member().
arg = parent_ref.copy()
# We use the depth computed for the original reference in order
# to find the copy of 'node'.
inner = arg
for _ in range(depth-1):
inner = inner.member
# Change the innermost access to be a Member.
inner.children[0] = Member(node.name, inner)
return arg
raise InternalError(
f"The supplied node must be an instance of either Reference "
f"or Member but got '{type(node).__name__}'.")
def _kind_find_or_create(name, symbol_table):
'''
Utility method that returns a Symbol representing the named KIND
parameter. If the supplied Symbol Table (or one of its ancestors)
does not contain an appropriate entry then one is created. If it does
contain a matching entry then it must be either a Symbol or a
DataSymbol.
If it is a DataSymbol then it must have a datatype of 'Integer',
'Unresolved' or 'Unsupported'. If it is Unresolved then the fact
that we now know that this Symbol represents a KIND parameter means we
can change the datatype to be 'integer' and mark it as constant.
If the existing symbol is a generic Symbol then it is replaced with
a new DataSymbol of type 'integer'.
:param str name: the name of the variable holding the KIND value.
:param symbol_table: the Symbol Table associated with the code being \
processed.
:type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
:returns: the Symbol representing the KIND parameter.
:rtype: :py:class:`psyclone.psyir.symbols.DataSymbol`
:raises TypeError: if the symbol table already contains an entry for \
`name` but it is not an instance of Symbol or DataSymbol.
:raises TypeError: if the symbol table already contains a DataSymbol \
for `name` and its datatype is not 'Integer' or 'Unresolved'.
'''
lower_name = name.lower()
try:
kind_symbol = symbol_table.lookup(lower_name)
# pylint: disable=unidiomatic-typecheck
if type(kind_symbol) is Symbol:
# There is an existing entry but it's only a generic Symbol
# so we need to specialise it to a DataSymbol of integer type.
kind_symbol.specialise(DataSymbol, datatype=default_integer_type(),
is_constant=True)
elif isinstance(kind_symbol, DataSymbol):
if not (isinstance(kind_symbol.datatype,
(UnsupportedType, UnresolvedType)) or
(isinstance(kind_symbol.datatype, ScalarType) and
kind_symbol.datatype.intrinsic ==
ScalarType.Intrinsic.INTEGER)):
raise TypeError(
f"SymbolTable already contains a DataSymbol for variable "
f"'{lower_name}' used as a kind parameter but it is not a "
f"'Unresolved', 'Unsupported' or 'scalar Integer' type.")
# A KIND parameter must be of type integer so set it here if it
# was previously 'Unresolved'. We don't know what precision this is
# so set it to the default.
if isinstance(kind_symbol.datatype, UnresolvedType):
kind_symbol.datatype = default_integer_type()
kind_symbol.is_constant = True
else:
raise TypeError(
f"A symbol representing a kind parameter must be an instance "
f"of either a Symbol or a DataSymbol. However, found an entry "
f"of type '{type(kind_symbol).__name__}' for variable "
f"'{lower_name}'.")
except KeyError:
# The SymbolTable does not contain an entry for this kind parameter
# so look to see if it is imported and if not create one.
kind_symbol = _find_or_create_unresolved_symbol(
symbol_table.node, lower_name,
symbol_type=DataSymbol,
datatype=default_integer_type(),
visibility=symbol_table.default_visibility,
is_constant=True)
return kind_symbol
def default_precision(_):
'''Returns the default precision specified by the front end. This is
currently always set to undefined irrespective of the datatype but
could be read from a config file in the future. The unused
argument provides the name of the datatype. This name will allow a
future implementation of this method to choose different default
precisions for different datatypes if required.
There are alternative options for setting a default precision,
such as:
1) The back-end sets the default precision in a similar manner
to this routine.
2) A PSyIR transformation is used to set default precision.
This routine is primarily here as a placeholder and could be
replaced by an alternative solution, see issue #748.
:returns: the default precision for the supplied datatype name.
:rtype: :py:class:`psyclone.psyir.symbols.scalartype.Precision`
'''
return ScalarType.Precision.UNDEFINED
def default_integer_type():
'''Returns the default integer datatype specified by the front end.
:returns: the default integer datatype.
:rtype: :py:class:`psyclone.psyir.symbols.ScalarType`
'''
return ScalarType(ScalarType.Intrinsic.INTEGER,
default_precision(ScalarType.Intrinsic.INTEGER))
def default_real_type():
'''Returns the default real datatype specified by the front end.
:returns: the default real datatype.
:rtype: :py:class:`psyclone.psyir.symbols.ScalarType`
'''
return ScalarType(ScalarType.Intrinsic.REAL,
default_precision(ScalarType.Intrinsic.REAL))
def get_literal_precision(fparser2_node, psyir_literal_parent):
'''Takes a Fortran2003 literal node as input and returns the appropriate
PSyIR precision type for that node. Adds a UnresolvedType DataSymbol in
the SymbolTable if the precision is given by an undefined symbol.
:param fparser2_node: the fparser2 literal node.
:type fparser2_node: :py:class:`Fortran2003.Real_Literal_Constant` or \
:py:class:`Fortran2003.Logical_Literal_Constant` or \
:py:class:`Fortran2003.Char_Literal_Constant` or \
:py:class:`Fortran2003.Int_Literal_Constant`
:param psyir_literal_parent: the PSyIR node that will be the \
parent of the PSyIR literal node that will be created from the \
fparser2 node information.
:type psyir_literal_parent: :py:class:`psyclone.psyir.nodes.Node`
:returns: the PSyIR Precision of this literal value.
:rtype: :py:class:`psyclone.psyir.symbols.DataSymbol`, int or \
:py:class:`psyclone.psyir.symbols.ScalarType.Precision`
:raises InternalError: if the arguments are of the wrong type.
:raises InternalError: if there's no symbol table associated with \
`psyir_literal_parent` or one of its ancestors.
'''
if not isinstance(fparser2_node,
(Fortran2003.Real_Literal_Constant,
Fortran2003.Logical_Literal_Constant,
Fortran2003.Char_Literal_Constant,
Fortran2003.Int_Literal_Constant)):
raise InternalError(
f"Unsupported literal type '{type(fparser2_node).__name__}' found "
f"in get_literal_precision.")
if not isinstance(psyir_literal_parent, Node):
raise InternalError(
f"Expecting argument psyir_literal_parent to be a PSyIR Node but "
f"found '{type(psyir_literal_parent).__name__}' in "
f"get_literal_precision.")
precision_name = fparser2_node.items[1]
if not precision_name:
# Precision may still be specified by the exponent in a real literal
if isinstance(fparser2_node, Fortran2003.Real_Literal_Constant):
precision_value = fparser2_node.items[0]
if "d" in precision_value.lower():
return ScalarType.Precision.DOUBLE
if "e" in precision_value.lower():
return ScalarType.Precision.SINGLE
# Return the default precision
try:
data_name = CONSTANT_TYPE_MAP[type(fparser2_node)]
except KeyError as err:
raise NotImplementedError(
f"Could not process {type(fparser2_node).__name__}. Only "
f"'real', 'integer', 'logical' and 'character' intrinsic "
f"types are supported.") from err
return default_precision(data_name)
try:
# Precision is specified as an integer
return int(precision_name)
except ValueError:
# Precision is not an integer so should be a kind symbol
# PSyIR stores names as lower case.
precision_name = precision_name.lower()
# Find the closest symbol table
try:
symbol_table = psyir_literal_parent.scope.symbol_table
except SymbolError as err:
# No symbol table found. This should never happen in
# normal usage but could occur if a test constructs a
# PSyIR without a Schedule.
raise InternalError(
f"Failed to find a symbol table to which to add the kind "
f"symbol '{precision_name}'.") from err
return Reference(_kind_find_or_create(precision_name, symbol_table))
def _process_routine_symbols(module_ast, container, visibility_map):
'''
Examines the supplied fparser2 parse tree for a module and creates
RoutineSymbols for every routine (function or subroutine) that it
contains.
:param module_ast: fparser2 parse tree.
:type module_ast: :py:class:`fparser.two.Fortran2003.Base`
:param container: the PSyIR node in which to add the empty Routine nodes.
:type container: :py:class:`psyclone.psyir.nodes.Container`
:param visibility_map: dict of symbol names with explicit visibilities.
:type visibility_map: Dict[str, \
:py:class:`psyclone.psyir.symbols.Symbol.Visibility`]
'''
# If we are in a FileContainer, then the input here will be the Subroutine
# or Function we are interested in.
routines = []
if isinstance(module_ast, (Fortran2003.Subroutine_Subprogram,
Fortran2003.Function_Subprogram)):
routines = [module_ast]
else:
# Otherwise we have a module, so we search for the Subroutines and
# Functions that are children of the module (to avoid finding
# Subroutines or Functions contained within sub-scopes of this
# Module).
routine_parent = [x for x in module_ast.children if isinstance(x,
Fortran2003.Module_Subprogram_Part)]
if len(routine_parent) == 1:
routines = [x for x in routine_parent[0].children if isinstance(
x,
(Fortran2003.Subroutine_Subprogram,
Fortran2003.Function_Subprogram))]
# A subroutine has no type but a function does. However, we don't know what
# it is at this stage so we give all functions a UnresolvedType.
# TODO #1314 extend the frontend to ensure that the type of a Routine's
# return_symbol matches the type of the associated RoutineSymbol.
type_map = {Fortran2003.Subroutine_Subprogram: NoType,
Fortran2003.Function_Subprogram: UnresolvedType}
for routine in routines:
# Fortran routines are of unknown purity by default.
is_pure = None
# By default, Fortran routines are not elemental.
is_elemental = False
# Name of the routine.
stmt = walk(routine, (Fortran2003.Subroutine_Stmt,
Fortran2003.Function_Stmt))[0]
name = str(stmt.children[1])
# Type to give the RoutineSymbol.
sym_type = type_map[type(routine)]()
# Visibility of the symbol.
vis = visibility_map.get(name.lower(),
container.symbol_table.default_visibility)
# Check any prefixes on the routine declaration.
prefix = stmt.children[0]
if prefix:
for child in prefix.children:
if isinstance(child, Fortran2003.Prefix_Spec):
if child.string == "PURE":
is_pure = True
elif child.string == "IMPURE":
is_pure = False
elif child.string == "ELEMENTAL":
is_elemental = True
rsymbol = RoutineSymbol(name, sym_type, visibility=vis,
is_pure=is_pure, is_elemental=is_elemental,
interface=DefaultModuleInterface())
routine_obj = Routine(rsymbol, is_program=False)
container.addchild(routine_obj)
def _process_access_spec(attr):
'''
Converts from an fparser2 Access_Spec node to a PSyIR visibility.
:param attr: the fparser2 AST node to process.
:type attr: :py:class:`fparser.two.Fortran2003.Access_Spec`
:return: the PSyIR visibility corresponding to the access spec.
:rtype: :py:class:`psyclone.psyir.Symbol.Visibility`
:raises InternalError: if an invalid access specification is found.
'''
try:
return VISIBILITY_MAP_FROM_FORTRAN[attr.string.lower()]
except KeyError as err:
raise InternalError(f"Unexpected Access Spec attribute "
f"'{attr}'.") from err
def _create_struct_reference(parent, base_ref, base_symbol, members,
indices):
'''
Utility to create a StructureReference or ArrayOfStructuresReference. Any
PSyIR nodes in the supplied lists of members and indices are copied
when making the new node.
:param parent: Parent node of the PSyIR node we are constructing.
:type parent: :py:class:`psyclone.psyir.nodes.Node`
:param type base_ref: the type of Reference to create.
:param base_symbol: the Symbol that the reference is to.
:type base_symbol: :py:class:`psyclone.psyir.symbols.Symbol`
:param members: the component(s) of the structure that are being accessed.\
Any components that are array references must provide the name of the \
array and a list of DataNodes describing which part of it is accessed.
:type members: list of str or 2-tuples containing (str, \
list of nodes describing array access)
:param indices: a list of Nodes describing the array indices for \
the base reference (if any).
:type indices: list of :py:class:`psyclone.psyir.nodes.Node`
:raises InternalError: if any element in the `members` list is not a \
str or tuple or if `indices` are supplied for a StructureReference \
or *not* supplied for an ArrayOfStructuresReference.
:raises NotImplementedError: if `base_ref` is not a StructureReference or \
an ArrayOfStructuresReference.
'''
# Ensure we create a copy of any References within the list of
# members making up this structure access.
new_members = []
for member in members:
if isinstance(member, str):
new_members.append(member)
elif isinstance(member, tuple):
# Second member of the tuple is a list of index expressions
new_members.append((member[0], [kid.copy() for kid in member[1]]))
else:
raise InternalError(
f"List of members must contain only strings or tuples "
f"but found entry of type '{type(member).__name__}'")
if base_ref is StructureReference:
if indices:
raise InternalError(
f"Creating a StructureReference but array indices have been "
f"supplied ({indices}) which makes no sense.")
return base_ref.create(base_symbol, new_members, parent=parent)
if base_ref is ArrayOfStructuresReference:
if not indices:
raise InternalError(
"Cannot create an ArrayOfStructuresReference without one or "
"more index expressions but the 'indices' argument is empty.")
return base_ref.create(base_symbol, [idx.copy() for idx in indices],
new_members, parent=parent)
raise NotImplementedError(
f"Cannot create structure reference for type '{base_ref}' - expected "
f"either StructureReference or ArrayOfStructuresReference.")
def _get_arg_names(node_list):
'''Utility function that given an fparser2 argument list returns two
separate lists, one with the arguments themselves and another with
the argument names.
:param node_list: a list of fparser2 argument nodes which could \
be positional or named.
:type node_list: List[:py:class:`fparser.two.utils.Base`]
:returns: a list of fparser2 arguments with any name \
information and a separate list of named argument names.
:rtype: Tuple[List[:py:class:`fparser.two.utils.Base`], \
List[Union[str, None]]
'''
arg_names = []
arg_nodes = []
for node in node_list:
# We get names from what fparser consider Arg_Spec (functions)
# or Component_Spec (derived type)
if isinstance(node, (Fortran2003.Actual_Arg_Spec,
Fortran2003.Component_Spec)):
arg_names.append(node.children[0].string)
arg_nodes.append(node.children[1])
else:
arg_names.append(None)
arg_nodes.append(node)
return arg_nodes, arg_names
class Fparser2Reader():
'''
Class to encapsulate the functionality for processing the fparser2 AST and
convert the nodes to PSyIR.
:param ignore_directives: Whether directives should be ignored or not
(default True). Only has an effect if comments were not ignored when
creating the fparser2 AST.
:param last_comments_as_codeblocks: Whether the last comments in the a
given block (e.g. subroutine, do, if-then body, etc.) should be kept as
CodeBlocks or lost (default False). Only has an effect if comments
were not ignored when creating the fparser2 AST.
:param resolve_modules: Whether to resolve modules while parsing a file,
for more precise control it also accepts a list of module names.
Defaults to False.
:param ignore_comments: whether to let the parser ignore comments.
:param free_form: whether to parse using Fortran free_form syntax.
:param ignore_directives: whether to ignore directives while parsing.
:param conditional_openmp: whether to parse conditional OpenMP statements.
:raises TypeError: if the constructor argument is not of the expected type.
'''
_parser = None
unary_operators = OrderedDict([
('+', UnaryOperation.Operator.PLUS),
('-', UnaryOperation.Operator.MINUS),
('.not.', UnaryOperation.Operator.NOT)])
binary_operators = OrderedDict([
('+', BinaryOperation.Operator.ADD),
('-', BinaryOperation.Operator.SUB),
('*', BinaryOperation.Operator.MUL),
('/', BinaryOperation.Operator.DIV),
('**', BinaryOperation.Operator.POW),
('==', BinaryOperation.Operator.EQ),
('.eq.', BinaryOperation.Operator.EQ),
('.eqv.', BinaryOperation.Operator.EQV),
('/=', BinaryOperation.Operator.NE),
('.ne.', BinaryOperation.Operator.NE),
('.neqv.', BinaryOperation.Operator.NEQV),
('<=', BinaryOperation.Operator.LE),
('.le.', BinaryOperation.Operator.LE),
('<', BinaryOperation.Operator.LT),
('.lt.', BinaryOperation.Operator.LT),
('>=', BinaryOperation.Operator.GE),
('.ge.', BinaryOperation.Operator.GE),
('>', BinaryOperation.Operator.GT),
('.gt.', BinaryOperation.Operator.GT),
('.and.', BinaryOperation.Operator.AND),
('.or.', BinaryOperation.Operator.OR)])
@dataclass
class SelectTypeInfo:
"""Class for storing required information from an fparser2
Select_Type_Construct.
:param guard_type: the guard types used by 'type is' and 'class is'
select-type clauses e.g. 'REAL', 'REAL(KIND = 4), or 'mytype'
in 'type_is(REAL)' 'type_is(REAL(KIND = 4)' and 'class
is(mytype)' respectively. These are stored as a list of
str, ordered as found within the select-type
construct's 'type is', 'class is' and 'class default'
clauses with None indicating the 'class default' clause.
:param guard_type_name: a string representation of the guard types used
by 'type is' and 'class is' select-type clauses e.g. 'REAL',
'REAL(KIND = 4)', or 'mytype' are stored as
'REAL', 'REAL_4' and 'mytype' respectively. These are
designed to be used as base variable names in
the code. These are ordered as they are found in the
the select type construct 'type is, 'class is'
and 'class default' clauses with None representing the
'class default'.
:param intrinsic_type_name: the base intrinsic string name for the
particular clause or None if there is no intrinsic type. e.g.
'type is(REAL*4)' becomes 'REAL' and 'type is(mytype)' becomes
None. These are ordered as they occur in the select-type
construct's clauses.
:param clause_type: the name of the clause in the select-type construct
i.e. one of 'type is', 'class is' and 'class default'. These are
ordered as they occur within the select-type construct.
:param stmts: a list of fparser2 statements holding the content of each
of the select-type construct 'type is, 'class is' and
'class default' clauses. These are ordered as they occur within the
select-type construct.
:param selector: the name of the select-type construct selector e.g.
'selector' in 'select type(selector)'.
:param num_clauses: the number of 'type is', 'class is' and
'class default' clauses in the select type construct.
:param default_idx: index of the 'default' clause as it appears within
the select-type construct's 'type is, 'class is' and
'class default' clauses, or -1 if no default clause is found.
"""
guard_type: list[Optional[str]] = field(default_factory=list)
guard_type_name: list[Optional[str]] = field(default_factory=list)
intrinsic_type_name: list[Optional[str]] = field(default_factory=list)
clause_type: list[str] = field(default_factory=list)
stmts: list[list[StmtBase]] = field(default_factory=list)
selector: str = ""
num_clauses: int = -1
default_idx: int = -1
def __init__(
self,
ignore_directives: bool = True,
last_comments_as_codeblocks: bool = False,
resolve_modules: Union[bool, list[str]] = False,
ignore_comments: bool = False,
free_form: bool = False,
conditional_openmp: bool = False,
):
self._ignore_comments = ignore_comments
self._free_form = free_form
self._conditional_openmp = conditional_openmp
if isinstance(resolve_modules, bool):
self._resolve_all_modules = resolve_modules
self._modules_to_resolve = []
elif (isinstance(resolve_modules, Iterable) and
all(isinstance(x, str) for x in resolve_modules)):
self._resolve_all_modules = False
self._modules_to_resolve = [n.lower() for n in resolve_modules]
else:
raise TypeError(
f"The 'resolve_modules' argument must be a boolean or an "
f"Iterable[str] but found '{resolve_modules}'.")
# Map of fparser2 node types to handlers (which are class methods)
self.handlers = {
Fortran2003.Allocate_Stmt: self._allocate_handler,
Fortran2003.Allocate_Shape_Spec: self._allocate_shape_spec_handler,
Fortran2003.Assignment_Stmt: self._assignment_handler,
Fortran2003.Array_Constructor: self._array_constructor_handler,
Fortran2003.Structure_Constructor: self._call_handler,
Fortran2003.Data_Pointer_Object: self._structure_accessor_handler,
Fortran2003.Data_Ref: self._structure_accessor_handler,
Fortran2003.Pointer_Assignment_Stmt: self._assignment_handler,