Home | History | Annotate | Download | only in scons
      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 
      6  operators: =, +=, :=
      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):
     16         self.symbol_table = {}
     17         self._reset()
     18 
     19     def _reset(self, filename=None):
     20         self.filename = filename
     21 
     22         self.line_no = 1
     23         self.line_cont = ''
     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 
     65         if op_pos > 0:
     66             if line[op_pos - 1] in [':', '+', '?']:
     67                 op_pos -= 1
     68         else:
     69             self._error('only =, :=, and += are supported')
     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 
     76         if op in ('=', ':='):
     77             self.symbol_table[sym] = val
     78         elif op == '+=':
     79             self.symbol_table[sym] += ' ' + val
     80         elif op == '?=':
     81             if sym not in self.symbol_table:
     82                 self.symbol_table[sym] = val
     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
    128 
    129     def add_symbol(self, name, value):
    130         self.symbol_table[name] = value
    131