1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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
|