|  | """Source List Parser | 
|  |  | 
|  | The syntax of a source list file is a very small subset of GNU Make.  These | 
|  | features are supported | 
|  |  | 
|  | operators: =, +=, := | 
|  | line continuation | 
|  | non-nested variable expansion | 
|  | comment | 
|  |  | 
|  | The goal is to allow Makefile's and SConscript's to share source listing. | 
|  | """ | 
|  |  | 
|  | class SourceListParser(object): | 
|  | def __init__(self): | 
|  | self.symbol_table = {} | 
|  | self._reset() | 
|  |  | 
|  | def _reset(self, filename=None): | 
|  | self.filename = filename | 
|  |  | 
|  | self.line_no = 1 | 
|  | self.line_cont = '' | 
|  |  | 
|  | def _error(self, msg): | 
|  | raise RuntimeError('%s:%d: %s' % (self.filename, self.line_no, msg)) | 
|  |  | 
|  | def _next_dereference(self, val, cur): | 
|  | """Locate the next $(...) in value.""" | 
|  | deref_pos = val.find('$', cur) | 
|  | if deref_pos < 0: | 
|  | return (-1, -1) | 
|  | elif val[deref_pos + 1] != '(': | 
|  | self._error('non-variable dereference') | 
|  |  | 
|  | deref_end = val.find(')', deref_pos + 2) | 
|  | if deref_end < 0: | 
|  | self._error('unterminated variable dereference') | 
|  |  | 
|  | return (deref_pos, deref_end + 1) | 
|  |  | 
|  | def _expand_value(self, val): | 
|  | """Perform variable expansion.""" | 
|  | expanded = '' | 
|  | cur = 0 | 
|  | while True: | 
|  | deref_pos, deref_end = self._next_dereference(val, cur) | 
|  | if deref_pos < 0: | 
|  | expanded += val[cur:] | 
|  | break | 
|  |  | 
|  | sym = val[(deref_pos + 2):(deref_end - 1)] | 
|  | expanded += val[cur:deref_pos] + self.symbol_table[sym] | 
|  | cur = deref_end | 
|  |  | 
|  | return expanded | 
|  |  | 
|  | def _parse_definition(self, line): | 
|  | """Parse a variable definition line.""" | 
|  | op_pos = line.find('=') | 
|  | op_end = op_pos + 1 | 
|  | if op_pos < 0: | 
|  | self._error('not a variable definition') | 
|  |  | 
|  | if op_pos > 0: | 
|  | if line[op_pos - 1] in [':', '+', '?']: | 
|  | op_pos -= 1 | 
|  | else: | 
|  | self._error('only =, :=, and += are supported') | 
|  |  | 
|  | # set op, sym, and val | 
|  | op = line[op_pos:op_end] | 
|  | sym = line[:op_pos].strip() | 
|  | val = self._expand_value(line[op_end:].lstrip()) | 
|  |  | 
|  | if op in ('=', ':='): | 
|  | self.symbol_table[sym] = val | 
|  | elif op == '+=': | 
|  | self.symbol_table[sym] += ' ' + val | 
|  | elif op == '?=': | 
|  | if sym not in self.symbol_table: | 
|  | self.symbol_table[sym] = val | 
|  |  | 
|  | def _parse_line(self, line): | 
|  | """Parse a source list line.""" | 
|  | # more lines to come | 
|  | if line and line[-1] == '\\': | 
|  | # spaces around "\\\n" are replaced by a single space | 
|  | if self.line_cont: | 
|  | self.line_cont += line[:-1].strip() + ' ' | 
|  | else: | 
|  | self.line_cont = line[:-1].rstrip() + ' ' | 
|  | return 0 | 
|  |  | 
|  | # combine with previous lines | 
|  | if self.line_cont: | 
|  | line = self.line_cont + line.lstrip() | 
|  | self.line_cont = '' | 
|  |  | 
|  | if line: | 
|  | begins_with_tab = (line[0] == '\t') | 
|  |  | 
|  | line = line.lstrip() | 
|  | if line[0] != '#': | 
|  | if begins_with_tab: | 
|  | self._error('recipe line not supported') | 
|  | else: | 
|  | self._parse_definition(line) | 
|  |  | 
|  | return 1 | 
|  |  | 
|  | def parse(self, filename): | 
|  | """Parse a source list file.""" | 
|  | if self.filename != filename: | 
|  | fp = open(filename) | 
|  | lines = fp.read().splitlines() | 
|  | fp.close() | 
|  |  | 
|  | try: | 
|  | self._reset(filename) | 
|  | for line in lines: | 
|  | self.line_no += self._parse_line(line) | 
|  | except: | 
|  | self._reset() | 
|  | raise | 
|  |  | 
|  | return self.symbol_table | 
|  |  | 
|  | def add_symbol(self, name, value): | 
|  | self.symbol_table[name] = value |