1582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu"""Source List Parser
2582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
3582b5d869c0f05814d4d567636a743d3fdddf431Chia-I WuThe syntax of a source list file is a very small subset of GNU Make.  These
4582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wufeatures are supported
5582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
64c15a77f27204620ed35d97d75e521ca982e1cc5José Fonseca operators: =, +=, :=
7582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu line continuation
8582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu non-nested variable expansion
9582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu comment
10582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
11582b5d869c0f05814d4d567636a743d3fdddf431Chia-I WuThe goal is to allow Makefile's and SConscript's to share source listing.
12582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu"""
13582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
14582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wuclass SourceListParser(object):
15582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu    def __init__(self):
16ea8dcfc90d5abbf699cd64be4dccd1e69fe82d75José Fonseca        self.symbol_table = {}
17582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        self._reset()
18582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
19582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu    def _reset(self, filename=None):
20582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        self.filename = filename
21582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
22582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        self.line_no = 1
23582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        self.line_cont = ''
24582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
25582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu    def _error(self, msg):
26582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        raise RuntimeError('%s:%d: %s' % (self.filename, self.line_no, msg))
27582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
28582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu    def _next_dereference(self, val, cur):
29582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        """Locate the next $(...) in value."""
30582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        deref_pos = val.find('$', cur)
31582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        if deref_pos < 0:
32582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            return (-1, -1)
33582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        elif val[deref_pos + 1] != '(':
34582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            self._error('non-variable dereference')
35582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
36582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        deref_end = val.find(')', deref_pos + 2)
37582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        if deref_end < 0:
38582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            self._error('unterminated variable dereference')
39582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
40582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        return (deref_pos, deref_end + 1)
41582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
42582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu    def _expand_value(self, val):
43582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        """Perform variable expansion."""
44582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        expanded = ''
45582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        cur = 0
46582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        while True:
47582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            deref_pos, deref_end = self._next_dereference(val, cur)
48582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            if deref_pos < 0:
49582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                expanded += val[cur:]
50582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                break
51582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
52582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            sym = val[(deref_pos + 2):(deref_end - 1)]
53582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            expanded += val[cur:deref_pos] + self.symbol_table[sym]
54582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            cur = deref_end
55582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
56582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        return expanded
57582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
58582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu    def _parse_definition(self, line):
59582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        """Parse a variable definition line."""
60582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        op_pos = line.find('=')
61582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        op_end = op_pos + 1
62582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        if op_pos < 0:
63582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            self._error('not a variable definition')
64582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
654c15a77f27204620ed35d97d75e521ca982e1cc5José Fonseca        if op_pos > 0:
66ea606ee7b49d130fdedd5a707e79fc9e37ba280cJosé Fonseca            if line[op_pos - 1] in [':', '+', '?']:
674c15a77f27204620ed35d97d75e521ca982e1cc5José Fonseca                op_pos -= 1
68582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        else:
694c15a77f27204620ed35d97d75e521ca982e1cc5José Fonseca            self._error('only =, :=, and += are supported')
70582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
71582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        # set op, sym, and val
72582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        op = line[op_pos:op_end]
73582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        sym = line[:op_pos].strip()
74582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        val = self._expand_value(line[op_end:].lstrip())
75582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
764c15a77f27204620ed35d97d75e521ca982e1cc5José Fonseca        if op in ('=', ':='):
77582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            self.symbol_table[sym] = val
78582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        elif op == '+=':
79582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            self.symbol_table[sym] += ' ' + val
80ea606ee7b49d130fdedd5a707e79fc9e37ba280cJosé Fonseca        elif op == '?=':
81ea606ee7b49d130fdedd5a707e79fc9e37ba280cJosé Fonseca            if sym not in self.symbol_table:
82ea606ee7b49d130fdedd5a707e79fc9e37ba280cJosé Fonseca                self.symbol_table[sym] = val
83582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
84582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu    def _parse_line(self, line):
85582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        """Parse a source list line."""
86582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        # more lines to come
87582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        if line and line[-1] == '\\':
88582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            # spaces around "\\\n" are replaced by a single space
89582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            if self.line_cont:
90582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                self.line_cont += line[:-1].strip() + ' '
91582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            else:
92582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                self.line_cont = line[:-1].rstrip() + ' '
93582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            return 0
94582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
95582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        # combine with previous lines
96582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        if self.line_cont:
97582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            line = self.line_cont + line.lstrip()
98582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            self.line_cont = ''
99582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
100582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        if line:
101582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            begins_with_tab = (line[0] == '\t')
102582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
103582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            line = line.lstrip()
104582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            if line[0] != '#':
105582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                if begins_with_tab:
106582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                    self._error('recipe line not supported')
107582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                else:
108582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                    self._parse_definition(line)
109582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
110582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        return 1
111582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
112582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu    def parse(self, filename):
113582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        """Parse a source list file."""
114582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        if self.filename != filename:
115582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            fp = open(filename)
116582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            lines = fp.read().splitlines()
117582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            fp.close()
118582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
119582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            try:
120582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                self._reset(filename)
121582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                for line in lines:
122582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                    self.line_no += self._parse_line(line)
123582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu            except:
124582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                self._reset()
125582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu                raise
126582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu
127582b5d869c0f05814d4d567636a743d3fdddf431Chia-I Wu        return self.symbol_table
128ea8dcfc90d5abbf699cd64be4dccd1e69fe82d75José Fonseca
129ea8dcfc90d5abbf699cd64be4dccd1e69fe82d75José Fonseca    def add_symbol(self, name, value):
130ea8dcfc90d5abbf699cd64be4dccd1e69fe82d75José Fonseca        self.symbol_table[name] = value
131