blob: e16d1f9b6d2b6a31dc387323a88fcf6df94ea2a4 [file] [log] [blame]
Chia-I Wu582b5d82011-08-18 17:12:29 +08001"""Source List Parser
2
3The syntax of a source list file is a very small subset of GNU Make. These
4features are supported
5
José Fonseca4c15a772012-04-29 21:44:05 +01006 operators: =, +=, :=
Chia-I Wu582b5d82011-08-18 17:12:29 +08007 line continuation
8 non-nested variable expansion
9 comment
10
11The goal is to allow Makefile's and SConscript's to share source listing.
12"""
13
14class SourceListParser(object):
15 def __init__(self):
José Fonsecaea8dcfc2012-08-14 12:18:45 +010016 self.symbol_table = {}
Chia-I Wu582b5d82011-08-18 17:12:29 +080017 self._reset()
18
19 def _reset(self, filename=None):
20 self.filename = filename
21
22 self.line_no = 1
23 self.line_cont = ''
Chia-I Wu582b5d82011-08-18 17:12:29 +080024
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
José Fonseca4c15a772012-04-29 21:44:05 +010065 if op_pos > 0:
José Fonsecaea606ee2012-06-11 19:38:07 +010066 if line[op_pos - 1] in [':', '+', '?']:
José Fonseca4c15a772012-04-29 21:44:05 +010067 op_pos -= 1
Chia-I Wu582b5d82011-08-18 17:12:29 +080068 else:
José Fonseca4c15a772012-04-29 21:44:05 +010069 self._error('only =, :=, and += are supported')
Chia-I Wu582b5d82011-08-18 17:12:29 +080070
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
José Fonseca4c15a772012-04-29 21:44:05 +010076 if op in ('=', ':='):
Chia-I Wu582b5d82011-08-18 17:12:29 +080077 self.symbol_table[sym] = val
78 elif op == '+=':
79 self.symbol_table[sym] += ' ' + val
José Fonsecaea606ee2012-06-11 19:38:07 +010080 elif op == '?=':
81 if sym not in self.symbol_table:
82 self.symbol_table[sym] = val
Chia-I Wu582b5d82011-08-18 17:12:29 +080083
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
José Fonsecaea8dcfc2012-08-14 12:18:45 +0100128
129 def add_symbol(self, name, value):
130 self.symbol_table[name] = value