source_list.py 3.68 KB
Newer Older
1 2 3 4 5
"""Source List Parser

The syntax of a source list file is a very small subset of GNU Make.  These
features are supported

6
 operators: =, +=, :=
7 8 9 10 11 12 13 14 15
 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):
16
        self.symbol_table = {}
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
        self._reset()

    def _reset(self, filename=None):
        self.filename = filename

        self.line_no = 1
        self.line_cont = ''

    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')

65
        if op_pos > 0:
Jose Fonseca's avatar
Jose Fonseca committed
66
            if line[op_pos - 1] in [':', '+', '?']:
67
                op_pos -= 1
68
        else:
69
            self._error('only =, :=, and += are supported')
70 71 72 73 74 75

        # set op, sym, and val
        op = line[op_pos:op_end]
        sym = line[:op_pos].strip()
        val = self._expand_value(line[op_end:].lstrip())

76
        if op in ('=', ':='):
77 78 79
            self.symbol_table[sym] = val
        elif op == '+=':
            self.symbol_table[sym] += ' ' + val
Jose Fonseca's avatar
Jose Fonseca committed
80 81 82
        elif op == '?=':
            if sym not in self.symbol_table:
                self.symbol_table[sym] = val
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 124 125 126 127

    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
128 129 130

    def add_symbol(self, name, value):
        self.symbol_table[name] = value