1#! /usr/bin/env python
2# encoding: utf-8
3# DC 2008
4# Thomas Nagy 2016-2018 (ita)
5
6import re
7
8INC_REGEX = r"""(?:^|['">]\s*;)\s*(?:|#\s*)INCLUDE\s+(?:\w+_)?[<"'](.+?)(?=["'>])"""
9USE_REGEX = r"""(?:^|;)\s*USE(?:\s+|(?:(?:\s*,\s*(?:NON_)?INTRINSIC)?\s*::))\s*(\w+)"""
10MOD_REGEX = r"""(?:^|;)\s*MODULE(?!\s+(?:PROCEDURE|SUBROUTINE|FUNCTION))\s+(\w+)"""
11SMD_REGEX = r"""(?:^|;)\s*SUBMODULE\s*\(([\w:]+)\)\s*(\w+)"""
12
13re_inc = re.compile(INC_REGEX, re.I)
14re_use = re.compile(USE_REGEX, re.I)
15re_mod = re.compile(MOD_REGEX, re.I)
16re_smd = re.compile(SMD_REGEX, re.I)
17
18class fortran_parser(object):
19	"""
20	This parser returns:
21
22	* the nodes corresponding to the module names to produce
23	* the nodes corresponding to the include files used
24	* the module names used by the fortran files
25	"""
26	def __init__(self, incpaths):
27		self.seen = []
28		"""Files already parsed"""
29
30		self.nodes = []
31		"""List of :py:class:`waflib.Node.Node` representing the dependencies to return"""
32
33		self.names = []
34		"""List of module names to return"""
35
36		self.incpaths = incpaths
37		"""List of :py:class:`waflib.Node.Node` representing the include paths"""
38
39	def find_deps(self, node):
40		"""
41		Parses a Fortran file to obtain the dependencies used/provided
42
43		:param node: fortran file to read
44		:type node: :py:class:`waflib.Node.Node`
45		:return: lists representing the includes, the modules used, and the modules created by a fortran file
46		:rtype: tuple of list of strings
47		"""
48		txt = node.read()
49		incs = []
50		uses = []
51		mods = []
52		for line in txt.splitlines():
53			# line by line regexp search? optimize?
54			m = re_inc.search(line)
55			if m:
56				incs.append(m.group(1))
57			m = re_use.search(line)
58			if m:
59				uses.append(m.group(1))
60			m = re_mod.search(line)
61			if m:
62				mods.append(m.group(1))
63			m = re_smd.search(line)
64			if m:
65				uses.append(m.group(1))
66				mods.append('{0}:{1}'.format(m.group(1),m.group(2)))
67		return (incs, uses, mods)
68
69	def start(self, node):
70		"""
71		Start parsing. Use the stack ``self.waiting`` to hold nodes to iterate on
72
73		:param node: fortran file
74		:type node: :py:class:`waflib.Node.Node`
75		"""
76		self.waiting = [node]
77		while self.waiting:
78			nd = self.waiting.pop(0)
79			self.iter(nd)
80
81	def iter(self, node):
82		"""
83		Processes a single file during dependency parsing. Extracts files used
84		modules used and modules provided.
85		"""
86		incs, uses, mods = self.find_deps(node)
87		for x in incs:
88			if x in self.seen:
89				continue
90			self.seen.append(x)
91			self.tryfind_header(x)
92
93		for x in uses:
94			name = "USE@%s" % x
95			if not name in self.names:
96				self.names.append(name)
97
98		for x in mods:
99			name = "MOD@%s" % x
100			if not name in self.names:
101				self.names.append(name)
102
103	def tryfind_header(self, filename):
104		"""
105		Adds an include file to the list of nodes to process
106
107		:param filename: file name
108		:type filename: string
109		"""
110		found = None
111		for n in self.incpaths:
112			found = n.find_resource(filename)
113			if found:
114				self.nodes.append(found)
115				self.waiting.append(found)
116				break
117		if not found:
118			if not filename in self.names:
119				self.names.append(filename)
120
121