loaders: JPG: Fix bussy loop on corrupted file.
[gfxprim.git] / gen / bin / cct.py
blob29030ba6d376fd929d1ac1ac747363a4b093339e
1 #!/usr/bin/env python
3 # Distributed under GPLv2.1 or any later
5 # Copyright (C) 2014 Tomas Gavenciak <gavento@ucw.cz>
6 # Copyright (C) 2014 Cyril Hrubis <metan@ucw.cz>
9 import re
10 import getopt
11 from sys import argv, exit
12 from os import path, remove, system
14 def perror(filename, line, lineno, row, error):
15 print('%s:%i:%i: error: %s\n' % (filename, lineno, row, error))
16 print(line)
17 print(' ' * row + '^\n')
18 exit(1)
20 # parse {{ expression }} blocks, escape special chars
21 def transform_verbatim(filename, line, lineno, startrow=0):
22 tokens = re.split('({{|}})', line)
23 code = '"'
24 row = 0
25 in_code = False
26 for token in tokens:
27 if token == '{{':
28 if in_code:
29 perror(filename, line, lineno, row + startrow, 'Unexpected {{')
30 else:
31 in_code = True
32 code = code + '" + str('
33 elif token == '}}':
34 if in_code:
35 in_code = False
36 code = code + ') + "'
37 else:
38 perror(filename, line, lineno, row + startrow, 'Unexpected }}')
39 else:
40 # escape \ and " but only in verbatim mode
41 if not in_code:
42 token = token.replace("\\", "\\\\").replace('"', '\\"')
43 code = code + token
45 row += len(token)
47 if in_code:
48 perror(filename, line, lineno, row + startrow, 'Unterminated {{')
50 return code + '"'
52 def transform(filename, lines, include_dirs, startindent, indent_depth):
53 out = []
54 lastindent = 0
55 lineno = 0
57 for l in lines:
58 lineno += 1
59 l = l.rstrip('\n')
61 if l == '@':
62 continue;
64 if re.match('\s*@\s.*', l):
65 padd = l[:len(l) - len(l.lstrip())]
66 l = l.lstrip()
67 # lines with '@ end' ends intent block by setting new indent
68 if re.match('@\s*end\s*', l):
69 lastindent = len(l[2:]) - len(l[2:].lstrip())
70 elif re.match('@\s*include.*', l):
71 include_filename = re.sub('@\s*include\s*', '', l)
72 include_path = ''
74 if not include_filename:
75 perror(filename, l, lineno, len(l), 'Expected filename')
77 for dirname in include_dirs:
78 if path.isfile(dirname + '/' + include_filename):
79 include_path = dirname + '/' + include_filename
80 break
82 if not include_path:
83 perror(filename, l, lineno, len(l) - len(include_filename),
84 "Failed to locate '%s' in %s" %
85 (include_filename, include_dirs))
87 try:
88 infile = open(include_path, 'r')
89 except Exception as err:
90 perror(filename, l, lineno, len(l) - len(include_filename), str(err))
92 out = out + transform(include_filename, infile.readlines(),
93 include_dirs, lastindent + startindent,
94 indent_depth)
96 infile.close()
97 else:
98 code = re.sub('\t', ' ', l[2:]).rstrip()
99 # full-line comments do not change last indent
100 if code and not re.match('^[ ]*#', code):
101 if code.endswith(':'):
102 lastindent = len(code) - len(code.lstrip()) + indent_depth
103 if (padd):
104 out.append(' ' * startindent + 'cct.set_padd("%s")' % padd)
105 out.append(' ' * startindent + code)
106 if (padd):
107 out.append(' ' * startindent + 'cct.set_padd("")')
108 # special handling for {@ call() @}
109 elif re.match('.*{@.*@}.*', l):
110 tokens = re.split('{@|@}', l)
111 if len(tokens) > 3:
112 row = len((tokens[0] + tokens[1] + tokens[2]).replace('\t', ' '))
113 perror(filename, l, lineno, row + 4,
114 "Only one {@ call() @} per line is allowed")
115 prefix = transform_verbatim(filename, tokens[0], lineno)
116 startrow = len((tokens[0] + tokens[1]).replace('\t', ' ')) + 2
117 suffix = transform_verbatim(filename, tokens[2], lineno, startrow)
118 out.append(' ' * (lastindent + startindent) + 'cct.set_prefix(' + prefix + ')')
119 out.append(' ' * (lastindent + startindent) + 'cct.set_suffix(' + suffix + ')')
120 out.append(' ' * (lastindent + startindent) + tokens[1].strip())
121 out.append(' ' * (lastindent + startindent) + 'cct.reset()')
122 else:
123 code = transform_verbatim(filename, l, lineno)
124 out.append(' ' * (lastindent + startindent) + 'cct.write(' + code + ')')
126 return out
128 header = [
129 "#!/usr/bin/env python",
130 "#",
131 "# Generated file do _not_ edit by hand!",
132 "#",
133 "from sys import exit",
134 "from os import remove, path",
136 "class cct:",
137 " def __init__(self, outfile_path, filename):",
138 " self.first = True",
139 " self.filename = filename",
140 " self.outfile_path = outfile_path",
141 " self.suffix = []",
142 " self.prefix = []",
143 " try:",
144 " self.outfile = open(outfile_path, 'w')",
145 " except Exception as err:",
146 " self.error('Failed to open file: ' + outfile_path + ' : ' + str(err))",
148 " def error_cleanup(self):",
149 " self.outfile.close()",
150 " remove(self.outfile_path)",
152 " def error(self, string):",
153 " self.error_cleanup()",
154 " print('cct: error: ' + string)",
155 " exit(1)",
157 " def write(self, line):",
158 " if self.first:",
159 " if 'cct_header' in globals():",
160 " self.first = False",
161 " cct_header(path.basename(self.outfile_path), self.filename)",
162 " if not line and not ''.join(self.suffix):",
163 " prefix = ''.join(self.prefix).rstrip()",
164 " else:",
165 " prefix = ''.join(self.prefix)",
166 " self.outfile.write(prefix + line + ''.join(self.suffix) + '\\n')",
168 " def set_prefix(self, prefix):",
169 " self.prefix.append(prefix)",
171 " def set_suffix(self, suffix):",
172 " self.suffix.append(suffix)",
174 " def reset(self):",
175 " self.suffix.pop()",
176 " self.prefix.pop()",
178 " def close(self):",
179 " if 'cct_footer' in globals():",
180 " cct_footer(path.basename(self.outfile_path), self.filename)",
182 " try:",
183 " self.outfile.close()",
184 " except Exception as err:",
185 " self.error('Failed to write ' + self.outfile_path + ' : ' + str(err))",
189 footer = [
190 "except Exception as err:",
191 " cct.error_cleanup()",
192 " raise",
193 "cct.close()",
196 def generate(filename, lines, include_dirs, indent_depth, outfile):
197 out = header
198 out.append("cct = cct('%s', '%s')" % (outfile, filename))
199 out.append("")
200 out.append("try:")
201 res = transform(filename, lines, include_dirs, indent_depth, indent_depth)
202 out = out + res + footer
203 return '\n'.join(out)
205 def error(error):
206 print(error)
207 exit(1)
209 def usage():
210 print('Usage:\ncct [-Idir] [-v] [-o outfile] file.c.t\n')
211 print('-E\n\tStops at first phase, leaves python script')
212 print('-i\n\tSets indenntation depth, default is 4')
213 print('-I\n\tAdds include path(s)')
214 print('-o\n\tSets output file')
215 print('-v\n\tSets verbose mode')
216 print('-h | --help\n\tPrints this help.')
218 def write_script(script_name, t):
219 try:
220 result = open(script_name, 'w')
221 except Exception as err:
222 error('Failed to open file: ' + script_name + ' : ' + str(err))
224 result.write(t)
226 try:
227 result.close()
228 except Exception as err:
229 error('Failed to close file: ' + script_name + ' : ' + str(err))
231 def main():
232 try:
233 opts, args = getopt.getopt(argv[1:], 'Eho:i:I:v', ['help'])
234 except getopt.GetoptError as err:
235 print(str(err))
236 usage()
237 exit(1)
239 include_dirs = ['.']
240 verbose = False
241 outfile = ''
242 execute = True
243 indent_depth = 4
245 for opt, arg in opts:
246 if opt in ('-h', '--help'):
247 usage()
248 exit(0)
249 elif opt == '-i':
250 indent_depth = int(arg)
251 elif opt == '-I':
252 include_dirs.append(arg)
253 elif opt == '-v':
254 verbose = True
255 elif opt == '-o':
256 outfile = arg
257 elif opt == '-E':
258 execute = False
260 if len(args) != 1:
261 error('No input files.')
263 if not outfile:
264 if not args[0].endswith('.t'):
265 error('No outfile set and template does not end with .t')
267 outfile = args[0][:-2]
269 if verbose:
270 print("Settings\n--------")
271 print("Include Dirs: %s" % include_dirs)
272 print("Template File: %s" % args[0])
273 print("Output File: %s" % outfile)
274 print("Indentation Depth: %i" % indent_depth)
275 print("")
277 with open(args[0], 'rt') as f:
278 t = generate(args[0], f.readlines(), include_dirs, indent_depth, outfile)
280 script_name = outfile + '.py'
282 if execute:
283 try:
284 glob = {}
285 exec(t, glob)
286 except Exception:
287 # Something failed fallback to writing file
288 # and executing it because the error trace
289 # from exec() tends to be less informative
290 write_script(script_name, t)
291 system('python ' + script_name)
292 remove(script_name)
293 exit(1)
294 else:
295 write_script(script_name, t)
297 if __name__ == '__main__':
298 main()