diff options
Diffstat (limited to 'scons/source_list.py')
-rw-r--r-- | scons/source_list.py | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/scons/source_list.py b/scons/source_list.py new file mode 100644 index 0000000000..fbd3ef7dc1 --- /dev/null +++ b/scons/source_list.py @@ -0,0 +1,123 @@ +"""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._reset() + + def _reset(self, filename=None): + self.filename = filename + + self.line_no = 1 + self.line_cont = '' + self.symbol_table = {} + + 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 and 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 == ':=': + self.symbol_table[sym] = val + elif op == '+=': + 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 |