waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

d_scan.py (5056B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2016-2018 (ita)
      4 
      5 """
      6 Provide a scanner for finding dependencies on d files
      7 """
      8 
      9 import re
     10 from waflib import Utils
     11 
     12 def filter_comments(filename):
     13 	"""
     14 	:param filename: d file name
     15 	:type filename: string
     16 	:rtype: list
     17 	:return: a list of characters
     18 	"""
     19 	txt = Utils.readf(filename)
     20 	i = 0
     21 	buf = []
     22 	max = len(txt)
     23 	begin = 0
     24 	while i < max:
     25 		c = txt[i]
     26 		if c == '"' or c == "'":  # skip a string or character literal
     27 			buf.append(txt[begin:i])
     28 			delim = c
     29 			i += 1
     30 			while i < max:
     31 				c = txt[i]
     32 				if c == delim:
     33 					break
     34 				elif c == '\\':  # skip the character following backslash
     35 					i += 1
     36 				i += 1
     37 			i += 1
     38 			begin = i
     39 		elif c == '/':  # try to replace a comment with whitespace
     40 			buf.append(txt[begin:i])
     41 			i += 1
     42 			if i == max:
     43 				break
     44 			c = txt[i]
     45 			if c == '+':  # eat nesting /+ +/ comment
     46 				i += 1
     47 				nesting = 1
     48 				c = None
     49 				while i < max:
     50 					prev = c
     51 					c = txt[i]
     52 					if prev == '/' and c == '+':
     53 						nesting += 1
     54 						c = None
     55 					elif prev == '+' and c == '/':
     56 						nesting -= 1
     57 						if nesting == 0:
     58 							break
     59 						c = None
     60 					i += 1
     61 			elif c == '*':  # eat /* */ comment
     62 				i += 1
     63 				c = None
     64 				while i < max:
     65 					prev = c
     66 					c = txt[i]
     67 					if prev == '*' and c == '/':
     68 						break
     69 					i += 1
     70 			elif c == '/':  # eat // comment
     71 				i += 1
     72 				while i < max and txt[i] != '\n':
     73 					i += 1
     74 			else:  # no comment
     75 				begin = i - 1
     76 				continue
     77 			i += 1
     78 			begin = i
     79 			buf.append(' ')
     80 		else:
     81 			i += 1
     82 	buf.append(txt[begin:])
     83 	return buf
     84 
     85 class d_parser(object):
     86 	"""
     87 	Parser for d files
     88 	"""
     89 	def __init__(self, env, incpaths):
     90 		#self.code = ''
     91 		#self.module = ''
     92 		#self.imports = []
     93 
     94 		self.allnames = []
     95 
     96 		self.re_module = re.compile(r"module\s+([^;]+)")
     97 		self.re_import = re.compile(r"import\s+([^;]+)")
     98 		self.re_import_bindings = re.compile("([^:]+):(.*)")
     99 		self.re_import_alias = re.compile("[^=]+=(.+)")
    100 
    101 		self.env = env
    102 
    103 		self.nodes = []
    104 		self.names = []
    105 
    106 		self.incpaths = incpaths
    107 
    108 	def tryfind(self, filename):
    109 		"""
    110 		Search file a file matching an module/import directive
    111 
    112 		:param filename: file to read
    113 		:type filename: string
    114 		"""
    115 		found = 0
    116 		for n in self.incpaths:
    117 			found = n.find_resource(filename.replace('.', '/') + '.d')
    118 			if found:
    119 				self.nodes.append(found)
    120 				self.waiting.append(found)
    121 				break
    122 		if not found:
    123 			if not filename in self.names:
    124 				self.names.append(filename)
    125 
    126 	def get_strings(self, code):
    127 		"""
    128 		:param code: d code to parse
    129 		:type code: string
    130 		:return: the modules that the code uses
    131 		:rtype: a list of match objects
    132 		"""
    133 		#self.imports = []
    134 		self.module = ''
    135 		lst = []
    136 
    137 		# get the module name (if present)
    138 
    139 		mod_name = self.re_module.search(code)
    140 		if mod_name:
    141 			self.module = re.sub(r'\s+', '', mod_name.group(1)) # strip all whitespaces
    142 
    143 		# go through the code, have a look at all import occurrences
    144 
    145 		# first, lets look at anything beginning with "import" and ending with ";"
    146 		import_iterator = self.re_import.finditer(code)
    147 		if import_iterator:
    148 			for import_match in import_iterator:
    149 				import_match_str = re.sub(r'\s+', '', import_match.group(1)) # strip all whitespaces
    150 
    151 				# does this end with an import bindings declaration?
    152 				# (import bindings always terminate the list of imports)
    153 				bindings_match = self.re_import_bindings.match(import_match_str)
    154 				if bindings_match:
    155 					import_match_str = bindings_match.group(1)
    156 					# if so, extract the part before the ":" (since the module declaration(s) is/are located there)
    157 
    158 				# split the matching string into a bunch of strings, separated by a comma
    159 				matches = import_match_str.split(',')
    160 
    161 				for match in matches:
    162 					alias_match = self.re_import_alias.match(match)
    163 					if alias_match:
    164 						# is this an alias declaration? (alias = module name) if so, extract the module name
    165 						match = alias_match.group(1)
    166 
    167 					lst.append(match)
    168 		return lst
    169 
    170 	def start(self, node):
    171 		"""
    172 		The parsing starts here
    173 
    174 		:param node: input file
    175 		:type node: :py:class:`waflib.Node.Node`
    176 		"""
    177 		self.waiting = [node]
    178 		# while the stack is not empty, add the dependencies
    179 		while self.waiting:
    180 			nd = self.waiting.pop(0)
    181 			self.iter(nd)
    182 
    183 	def iter(self, node):
    184 		"""
    185 		Find all the modules that a file depends on, uses :py:meth:`waflib.Tools.d_scan.d_parser.tryfind` to process dependent files
    186 
    187 		:param node: input file
    188 		:type node: :py:class:`waflib.Node.Node`
    189 		"""
    190 		path = node.abspath() # obtain the absolute path
    191 		code = "".join(filter_comments(path)) # read the file and filter the comments
    192 		names = self.get_strings(code) # obtain the import strings
    193 		for x in names:
    194 			# optimization
    195 			if x in self.allnames:
    196 				continue
    197 			self.allnames.append(x)
    198 
    199 			# for each name, see if it is like a node or not
    200 			self.tryfind(x)
    201 
    202 def scan(self):
    203 	"look for .d/.di used by a d file"
    204 	env = self.env
    205 	gruik = d_parser(env, self.generator.includes_nodes)
    206 	node = self.inputs[0]
    207 	gruik.start(node)
    208 	nodes = gruik.nodes
    209 	names = gruik.names
    210 	return (nodes, names)
    211