1#!/usr/bin/python2.5
2#
3# Copyright 2009 Olivier Gillet.
4#
5# Author: Olivier Gillet (ol.gillet@gmail.com)
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15# You should have received a copy of the GNU General Public License
16# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17#
18# -----------------------------------------------------------------------------
19#
20# Generates .cc and .h files for string, lookup tables, etc.
21
22"""Compiles python string tables/arrays into .cc and .h files."""
23
24import os
25import string
26import sys
27
28
29class ResourceEntry(object):
30
31  def __init__(self, index, key, value, dupe_of, table):
32    self._index = index
33    self._key = key
34    self._value = value
35    self._dupe_of = self._key if dupe_of is None else dupe_of
36    self._table = table
37
38  @property
39  def variable_name(self):
40    return '%s_%s' % (self._table.prefix.lower(), self._dupe_of)
41
42  @property
43  def declaration(self):
44    c_type = self._table.c_type
45    name = self.variable_name
46    return 'const %(c_type)s %(name)s[] PROGMEM' % locals()
47
48  def Declare(self, f):
49    if self._dupe_of == self._key:
50      # Dupes are not declared.
51      f.write('extern %s;\n' % self.declaration)
52
53  def DeclareAlias(self, f):
54    prefix = self._table.prefix
55    key = self._key.upper()
56    index = self._index
57    if self._table.python_type == str:
58      comment = '  // %s' % self._value
59      size = None
60    else:
61      comment = ''
62      size = len(self._value)
63    f.write('#define %(prefix)s_%(key)s %(index)d%(comment)s\n' % locals())
64    if not size is None:
65      f.write('#define %(prefix)s_%(key)s_SIZE %(size)d\n' % locals())
66
67  def Compile(self, f):
68    # Do not create declaration for dupes.
69    if self._dupe_of != self._key:
70      return
71
72    declaration = self.declaration
73    if self._table.python_type == str:
74      value = self._value
75      f.write('static %(declaration)s = "%(value)s";\n' % locals())
76    else:
77      f.write('%(declaration)s = {\n' % locals())
78      n_elements = len(self._value)
79      for i in xrange(0, n_elements, 8):
80        f.write('  ');
81        f.write(', '.join(
82            '%6d' % self._value[j] for j in xrange(i, min(n_elements, i + 8))))
83        f.write(',\n');
84      f.write('};\n')
85
86
87class ResourceTable(object):
88
89  def __init__(self, resource_tuple):
90    self.name = resource_tuple[1]
91    self.prefix = resource_tuple[2]
92    self.c_type = resource_tuple[3]
93    self.python_type = resource_tuple[4]
94    self.ram_based_table = resource_tuple[5]
95    self.entries = []
96    self._ComputeIdentifierRewriteTable()
97    keys = set()
98    values = {}
99    for index, entry in enumerate(resource_tuple[0]):
100      if self.python_type == str:
101        # There is no name/value for string entries
102        key, value = entry, entry.strip()
103      else:
104        key, value = entry
105
106      # Add a prefix to avoid key duplicates.
107      key = self._MakeIdentifier(key)
108      while key in keys:
109        key = '_%s' % key
110      keys.add(key)
111      hashable_value = tuple(value)
112      self.entries.append(ResourceEntry(index, key, value,
113          values.get(hashable_value, None), self))
114      if not hashable_value in values:
115        values[hashable_value] = key
116
117  def _ComputeIdentifierRewriteTable(self):
118    in_chr = ''.join(map(chr, range(256)))
119    out_chr = [ord('_')] * 256
120    # Tolerated characters.
121    for i in string.uppercase + string.lowercase + string.digits:
122      out_chr[ord(i)] = ord(i.lower())
123
124    # Rewritten characters.
125    in_rewritten = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09~*+=><^"|'
126    out_rewritten = '0123456789TPSeglxpv'
127    for rewrite in zip(in_rewritten, out_rewritten):
128      out_chr[ord(rewrite[0])] = ord(rewrite[1])
129
130    table = string.maketrans(in_chr, ''.join(map(chr, out_chr)))
131    bad_chars = '\t\n\r-:()[]"\',;'
132    self._MakeIdentifier = lambda s:s.translate(table, bad_chars)
133
134  def DeclareEntries(self, f):
135    if self.python_type != str:
136      for entry in self.entries:
137        entry.Declare(f)
138
139  def DeclareAliases(self, f):
140    for entry in self.entries:
141      entry.DeclareAlias(f)
142
143  def Compile(self, f):
144    # Write a declaration for each entry.
145    for entry in self.entries:
146      entry.Compile(f)
147
148    # Write the resource pointer table.
149    modifier = 'PROGMEM ' if not self.ram_based_table else ''
150    c_type = self.c_type
151    name = self.name
152    f.write(
153        '\n\n%(modifier)sconst %(c_type)s* const %(name)s_table[] = {\n' % locals())
154    for entry in self.entries:
155      f.write('  %s,\n' % entry.variable_name)
156    f.write('};\n\n')
157
158
159class ResourceLibrary(object):
160
161  def __init__(self, root):
162    self._tables = []
163    self._root = root
164    # Create resource table objects for all resources.
165    for resource_tuple in root.resources:
166      # Split a multiline string into a list of strings
167      if resource_tuple[-2] == str:
168        resource_tuple = list(resource_tuple)
169        resource_tuple[0] = [x for x in resource_tuple[0].split('\n') if x]
170        resource_tuple = tuple(resource_tuple)
171      self._tables.append(ResourceTable(resource_tuple))
172
173  @property
174  def max_num_entries(self):
175    max_num_entries = 0
176    for table in self._tables:
177      max_num_entries = max(max_num_entries, len(table.entries))
178    return max_num_entries
179
180  def _OpenNamespace(self, f):
181    if self._root.namespace:
182      f.write('\nnamespace %s {\n\n' % self._root.namespace)
183
184  def _CloseNamespace(self, f):
185    if self._root.namespace:
186      f.write('\n}  // namespace %s\n' % self._root.namespace)
187
188  def _DeclareTables(self, f):
189    for table in self._tables:
190      f.write('extern const %s* const %s_table[];\n\n' % (table.c_type, table.name))
191
192  def _DeclareEntries(self, f):
193    for table in self._tables:
194      table.DeclareEntries(f)
195
196  def _DeclareAliases(self, f):
197    for table in self._tables:
198      table.DeclareAliases(f)
199
200  def _CompileTables(self, f):
201    for table in self._tables:
202      table.Compile(f)
203
204  def GenerateHeader(self):
205    root = self._root
206    f = file(os.path.join(root.target, 'resources.h'), 'wb')
207    # Write header and header guard
208    header_guard = root.target.replace(os.path.sep, '_').upper()
209    header_guard = '%s_RESOURCES_H_' % header_guard
210    f.write(root.header + '\n\n')
211    f.write('#ifndef %s\n' % header_guard)
212    f.write('#define %s\n\n' % header_guard)
213    f.write(root.includes + '\n\n')
214    if root.create_specialized_manager:
215      f.write('#include "avrlib/resources_manager.h"\n')
216    self._OpenNamespace(f)
217    f.write('typedef %s ResourceId;\n\n' % \
218        root.types[self.max_num_entries > 255])
219    self._DeclareTables(f)
220    self._DeclareEntries(f)
221    self._DeclareAliases(f)
222    if root.create_specialized_manager:
223      f.write('typedef avrlib::ResourcesManager<\n')
224      f.write('    ResourceId,\n')
225      f.write('    avrlib::ResourcesTables<\n')
226      f.write('        %s_table,\n' % root.resources[0][1])
227      f.write('        %s_table> > ResourcesManager; \n' % root.resources[1][1])
228    self._CloseNamespace(f)
229    f.write('\n#endif  // %s\n' % (header_guard))
230    f.close()
231
232  def GenerateCc(self):
233    root = self._root
234    file_name = os.path.join(self._root.target, 'resources.cc')
235    f = file(file_name, 'wb')
236    f.write(self._root.header + '\n\n')
237    f.write('#include "%s"\n' % file_name.replace('.cc', '.h'))
238    self._OpenNamespace(f)
239    self._CompileTables(f)
240    self._CloseNamespace(f)
241    f.close()
242
243
244def Compile(path):
245  # A hacky way of loading the py file passed as an argument as a module +
246  # a descent along the module path.
247  base_name = os.path.splitext(path)[0]
248  sys.path += [os.path.abspath('.')]
249  resource_module = __import__(base_name.replace('/', '.'))
250  for part in base_name.split('/')[1:]:
251    resource_module = getattr(resource_module, part)
252
253  library = ResourceLibrary(resource_module)
254  library.GenerateHeader()
255  library.GenerateCc()
256
257
258def main(argv):
259  for i in xrange(1, len(argv)):
260    Compile(argv[i])
261
262
263if __name__ == '__main__':
264  main(sys.argv)
265