Chia-I Wu | 582b5d8 | 2011-08-18 17:12:29 +0800 | [diff] [blame] | 1 | """Source List Parser |
| 2 | |
| 3 | The syntax of a source list file is a very small subset of GNU Make. These |
| 4 | features are supported |
| 5 | |
José Fonseca | 4c15a77 | 2012-04-29 21:44:05 +0100 | [diff] [blame] | 6 | operators: =, +=, := |
Chia-I Wu | 582b5d8 | 2011-08-18 17:12:29 +0800 | [diff] [blame] | 7 | line continuation |
| 8 | non-nested variable expansion |
| 9 | comment |
| 10 | |
| 11 | The goal is to allow Makefile's and SConscript's to share source listing. |
| 12 | """ |
| 13 | |
| 14 | class SourceListParser(object): |
| 15 | def __init__(self): |
José Fonseca | ea8dcfc | 2012-08-14 12:18:45 +0100 | [diff] [blame] | 16 | self.symbol_table = {} |
Chia-I Wu | 582b5d8 | 2011-08-18 17:12:29 +0800 | [diff] [blame] | 17 | 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 Wu | 582b5d8 | 2011-08-18 17:12:29 +0800 | [diff] [blame] | 24 | |
| 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é Fonseca | 4c15a77 | 2012-04-29 21:44:05 +0100 | [diff] [blame] | 65 | if op_pos > 0: |
José Fonseca | ea606ee | 2012-06-11 19:38:07 +0100 | [diff] [blame] | 66 | if line[op_pos - 1] in [':', '+', '?']: |
José Fonseca | 4c15a77 | 2012-04-29 21:44:05 +0100 | [diff] [blame] | 67 | op_pos -= 1 |
Chia-I Wu | 582b5d8 | 2011-08-18 17:12:29 +0800 | [diff] [blame] | 68 | else: |
José Fonseca | 4c15a77 | 2012-04-29 21:44:05 +0100 | [diff] [blame] | 69 | self._error('only =, :=, and += are supported') |
Chia-I Wu | 582b5d8 | 2011-08-18 17:12:29 +0800 | [diff] [blame] | 70 | |
| 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é Fonseca | 4c15a77 | 2012-04-29 21:44:05 +0100 | [diff] [blame] | 76 | if op in ('=', ':='): |
Chia-I Wu | 582b5d8 | 2011-08-18 17:12:29 +0800 | [diff] [blame] | 77 | self.symbol_table[sym] = val |
| 78 | elif op == '+=': |
| 79 | self.symbol_table[sym] += ' ' + val |
José Fonseca | ea606ee | 2012-06-11 19:38:07 +0100 | [diff] [blame] | 80 | elif op == '?=': |
| 81 | if sym not in self.symbol_table: |
| 82 | self.symbol_table[sym] = val |
Chia-I Wu | 582b5d8 | 2011-08-18 17:12:29 +0800 | [diff] [blame] | 83 | |
| 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é Fonseca | ea8dcfc | 2012-08-14 12:18:45 +0100 | [diff] [blame] | 128 | |
| 129 | def add_symbol(self, name, value): |
| 130 | self.symbol_table[name] = value |