1 """Source List Parser 2 3 The syntax of a source list file is a very small subset of GNU Make. These 4 features are supported 5 6 operators: =, +=, := 7 line continuation 8 non-nested variable expansion 9 comment 10 11 The goal is to allow Makefile's and SConscript's to share source listing. 12 """ 13 14 class SourceListParser(object): 15 def __init__(self): 16 self.symbol_table = {} 17 self._reset() 18 19 def _reset(self, filename=None): 20 self.filename = filename 21 22 self.line_no = 1 23 self.line_cont = '' 24 25 def _error(self, msg): 26 raise RuntimeError('%s:%d: %s' % (self.filename, self.line_no, msg)) 27 28 def _next_dereference(self, val, cur): 29 """Locate the next $(...) in value.""" 30 deref_pos = val.find('$', cur) 31 if deref_pos < 0: 32 return (-1, -1) 33 elif val[deref_pos + 1] != '(': 34 self._error('non-variable dereference') 35 36 deref_end = val.find(')', deref_pos + 2) 37 if deref_end < 0: 38 self._error('unterminated variable dereference') 39 40 return (deref_pos, deref_end + 1) 41 42 def _expand_value(self, val): 43 """Perform variable expansion.""" 44 expanded = '' 45 cur = 0 46 while True: 47 deref_pos, deref_end = self._next_dereference(val, cur) 48 if deref_pos < 0: 49 expanded += val[cur:] 50 break 51 52 sym = val[(deref_pos + 2):(deref_end - 1)] 53 expanded += val[cur:deref_pos] + self.symbol_table[sym] 54 cur = deref_end 55 56 return expanded 57 58 def _parse_definition(self, line): 59 """Parse a variable definition line.""" 60 op_pos = line.find('=') 61 op_end = op_pos + 1 62 if op_pos < 0: 63 self._error('not a variable definition') 64 65 if op_pos > 0: 66 if line[op_pos - 1] in [':', '+', '?']: 67 op_pos -= 1 68 else: 69 self._error('only =, :=, and += are supported') 70 71 # set op, sym, and val 72 op = line[op_pos:op_end] 73 sym = line[:op_pos].strip() 74 val = self._expand_value(line[op_end:].lstrip()) 75 76 if op in ('=', ':='): 77 self.symbol_table[sym] = val 78 elif op == '+=': 79 self.symbol_table[sym] += ' ' + val 80 elif op == '?=': 81 if sym not in self.symbol_table: 82 self.symbol_table[sym] = val 83 84 def _parse_line(self, line): 85 """Parse a source list line.""" 86 # more lines to come 87 if line and line[-1] == '\\': 88 # spaces around "\\\n" are replaced by a single space 89 if self.line_cont: 90 self.line_cont += line[:-1].strip() + ' ' 91 else: 92 self.line_cont = line[:-1].rstrip() + ' ' 93 return 0 94 95 # combine with previous lines 96 if self.line_cont: 97 line = self.line_cont + line.lstrip() 98 self.line_cont = '' 99 100 if line: 101 begins_with_tab = (line[0] == '\t') 102 103 line = line.lstrip() 104 if line[0] != '#': 105 if begins_with_tab: 106 self._error('recipe line not supported') 107 else: 108 self._parse_definition(line) 109 110 return 1 111 112 def parse(self, filename): 113 """Parse a source list file.""" 114 if self.filename != filename: 115 fp = open(filename) 116 lines = fp.read().splitlines() 117 fp.close() 118 119 try: 120 self._reset(filename) 121 for line in lines: 122 self.line_no += self._parse_line(line) 123 except: 124 self._reset() 125 raise 126 127 return self.symbol_table 128 129 def add_symbol(self, name, value): 130 self.symbol_table[name] = value 131