1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Classes to write good-looking output in different languages:
17 Fortran, C++, etc."""
18
19
20 import re
21 import collections
22
24 """Generic Writer class. All writers should inherit from this class."""
25
26 supported_preprocessor_commands = ['if']
27 preprocessor_command_re=re.compile(
28 "\s*(?P<command>%s)\s*\(\s*(?P<body>.*)\s*\)\s*{\s*"\
29 %('|'.join(supported_preprocessor_commands)))
30 preprocessor_endif_re=re.compile(\
31 "\s*}\s*(?P<endif>else)?\s*(\((?P<body>.*)\))?\s*(?P<new_block>{)?\s*")
32
34 """Exception raised if an error occurs in the definition
35 or the execution of a Writer."""
36
37 pass
38
40 """Exception raised if an error occurs in the handling of the
41 preprocessor tags '##' in the template file."""
42 pass
43
45 """Initialize file to write to"""
46
47 return file.__init__(self, name, opt)
48
50 """Write a line with proper indent and splitting of long lines
51 for the language in question."""
52
53 pass
54
60
82
84 """Extends the regular file.writeline() function to write out
85 nicely formatted code. When defining a context, then the lines
86 will be preprocessed to apply possible conditional statements on the
87 content of the template depending on the contextual variables specified."""
88
89 splitlines = []
90 if isinstance(lines, list):
91 for line in lines:
92 if not isinstance(line, str):
93 raise self.FileWriterError("%s not string" % repr(line))
94 splitlines.extend(line.split('\n'))
95 elif isinstance(lines, str):
96 splitlines.extend(lines.split('\n'))
97 else:
98 raise self.FileWriterError("%s not string" % repr(lines))
99
100 if len(context)>0:
101 splitlines = self.preprocess_template(splitlines,context=context)
102
103 for line in splitlines:
104 res_lines = self.write_line(line)
105 for line_to_write in res_lines:
106 self.write(line_to_write)
107
109 """ This class takes care of applying the pre-processing statements
110 starting with ## in the template .inc files, using the contextual
111 variables specified in the dictionary 'context' given in input with
112 the variable names given as keys and their respective value as values."""
113
114 template_lines = []
115 if isinstance(input_lines, list):
116 for line in input_lines:
117 if not isinstance(line, str):
118 raise self.FileWriterError("%s not string" % repr(input_lines))
119 template_lines.extend(line.split('\n'))
120 elif isinstance(input_lines, str):
121 template_lines.extend(input_lines.split('\n'))
122 else:
123 raise self.FileWriterError("%s not string" % repr(input_lines))
124
125
126 for contextual_variable, value in context.items():
127 exec('%s=%s'%(str(contextual_variable),repr(value)))
128
129 res = []
130
131 if_stack = []
132 for i, line in enumerate(template_lines):
133 if not line.startswith('##'):
134 if all(if_stack):
135 res.append(line)
136 continue
137 preproc_command = self.preprocessor_command_re.match(line[2:])
138
139 if preproc_command is None:
140 preproc_endif = self.preprocessor_endif_re.match(line[2:])
141 if len(if_stack)==0 or preproc_endif is None:
142 raise self.FilePreProcessingError, 'Incorrect '+\
143 'preprocessing command %s at line %d.'%(line,i)
144 if preproc_endif.group('new_block') is None:
145 if_stack.pop()
146 elif preproc_endif.group('endif')=='else':
147 if_stack[-1]=(not if_stack[-1])
148
149 elif preproc_command.group('command')=='if':
150 try:
151 if_stack.append(eval(preproc_command.group('body'))==True)
152 except Exception, e:
153 raise self.FilePreProcessingError, 'Could not evaluate'+\
154 "python expression '%s' given the context %s provided."%\
155 (preproc_command.group('body'),str(context))+\
156 "\nLine %d of file %s."%(i,self.name)
157
158 if len(if_stack)>0:
159 raise self.FilePreProcessingError, 'Some conditional statements are'+\
160 ' not properly terminated.'
161 return res
162
163
164
165
167 """Routines for writing fortran lines. Keeps track of indentation
168 and splitting of long lines"""
169
171 """Exception raised if an error occurs in the definition
172 or the execution of a FortranWriter."""
173 pass
174
175
176 keyword_pairs = {'^if.+then\s*$': ('^endif', 2),
177 '^type(?!\s*\()\s*.+\s*$': ('^endtype', 2),
178 '^do(?!\s+\d+)\s+': ('^enddo\s*$', 2),
179 '^subroutine': ('^end\s*$', 0),
180 '^module': ('^end\s*$', 0),
181 'function': ('^end\s*$', 0)}
182 single_indents = {'^else\s*$':-2,
183 '^else\s*if.+then\s*$':-2}
184 number_re = re.compile('^(?P<num>\d+)\s+(?P<rest>.*)')
185 line_cont_char = '$'
186 comment_char = 'c'
187 downcase = False
188 line_length = 71
189 max_split = 20
190 split_characters = "+-*/,) "
191 comment_split_characters = " "
192
193
194 __indent = 0
195 __keyword_list = []
196 __comment_pattern = re.compile(r"^(\s*#|c$|(c\s+([^=]|$))|cf2py|c\-\-|c\*\*)", re.IGNORECASE)
197
199 """Write a fortran line, with correct indent and line splits"""
200
201
202 assert(isinstance(line, str) and line.find('\n') == -1)
203
204
205 res_lines = []
206
207
208 if not line.lstrip():
209 res_lines.append("\n")
210 return res_lines
211
212
213 if self.__comment_pattern.search(line):
214
215 res_lines = self.write_comment_line(line.lstrip()[1:])
216 return res_lines
217
218 else:
219
220
221
222 myline = line.lstrip()
223
224
225 num_group = self.number_re.search(myline)
226 num = ""
227 if num_group:
228 num = num_group.group('num')
229 myline = num_group.group('rest')
230
231
232
233 (myline, part, post_comment) = myline.partition("!")
234
235 if part:
236 part = " " + part
237
238 myline = myline.replace('\"', '\'')
239
240 splitline = myline.split('\'')
241 myline = ""
242 i = 0
243 while i < len(splitline):
244 if i % 2 == 1:
245
246 while splitline[i] and splitline[i][-1] == '\\':
247 splitline[i] = splitline[i] + '\'' + splitline.pop(i + 1)
248 else:
249
250 if FortranWriter.downcase:
251 splitline[i] = splitline[i].lower()
252 else:
253 splitline[i] = splitline[i].upper()
254 i = i + 1
255
256 myline = "\'".join(splitline).rstrip()
257
258
259 if self.__keyword_list and re.search(self.keyword_pairs[\
260 self.__keyword_list[-1]][0], myline.lower()):
261 key = self.__keyword_list.pop()
262 self.__indent = self.__indent - self.keyword_pairs[key][1]
263
264
265 single_indent = 0
266 for key in self.single_indents.keys():
267 if re.search(key, myline.lower()):
268 self.__indent = self.__indent + self.single_indents[key]
269 single_indent = -self.single_indents[key]
270 break
271
272
273
274 res = self.split_line(" " + num + \
275 " " * (5 + self.__indent - len(num)) + myline,
276 self.split_characters,
277 " " * 5 + self.line_cont_char + \
278 " " * (self.__indent + 1))
279
280
281 for key in self.keyword_pairs.keys():
282 if re.search(key, myline.lower()):
283 self.__keyword_list.append(key)
284 self.__indent = self.__indent + self.keyword_pairs[key][1]
285 break
286
287
288 if single_indent != None:
289 self.__indent = self.__indent + single_indent
290 single_indent = None
291
292
293 res_lines.append("\n".join(res) + part + post_comment + "\n")
294
295 return res_lines
296
328
329 - def split_line(self, line, split_characters, line_start):
330 """Split a line if it is longer than self.line_length
331 columns. Split in preferential order according to
332 split_characters, and start each new line with line_start."""
333
334 res_lines = [line]
335
336 while len(res_lines[-1]) > self.line_length:
337 split_at = 0
338 for character in split_characters:
339 index = res_lines[-1][(self.line_length - self.max_split): \
340 self.line_length].rfind(character)
341 if index >= 0:
342 split_at_tmp = self.line_length - self.max_split + index
343 if split_at_tmp > split_at:
344 split_at = split_at_tmp
345 if split_at == 0:
346 split_at = self.line_length
347
348 newline = res_lines[-1][split_at:]
349 nquotes = self.count_number_of_quotes(newline)
350
351
352
353 offset = 0
354 if nquotes%2==1:
355 if res_lines[-1][(split_at-1)] == '\'':
356 offset = 1
357 nquotes -=1
358 res_lines.append(line_start +(res_lines[-1][(split_at-offset):]))
359 else:
360 res_lines.append(line_start +('//\''+res_lines[-1][(split_at-offset):]))
361
362 elif res_lines[-1][(split_at)] in self.split_characters:
363 if res_lines[-1][(split_at)] in ')':
364
365 offset = -1
366
367
368 res_lines.append(line_start +res_lines[-1][(split_at-offset):])
369 elif line_start.startswith(('c','C')) or res_lines[-1][(split_at)] in split_characters:
370 res_lines.append(line_start +res_lines[-1][(split_at):])
371 else:
372 l_start = line_start.rstrip()
373 res_lines.append(l_start +res_lines[-1][(split_at):])
374
375 res_lines[-2] = (res_lines[-2][:(split_at-offset)]+'\'' if nquotes%2==1 \
376 else res_lines[-2][:split_at-offset])
377 return res_lines
378
380 """ Count the number of real quotes (not escaped ones) in a line. """
381
382 splitline = line.split('\'')
383 i = 0
384 while i < len(splitline):
385 if i % 2 == 1:
386
387 while splitline[i] and splitline[i][-1] == '\\':
388 splitline[i] = splitline[i] + '\'' + splitline.pop(i + 1)
389 i = i + 1
390 return len(splitline)-1
391
392
393
394
396 """Routines for writing C++ lines. Keeps track of brackets,
397 spaces, indentation and splitting of long lines"""
398
400 """Exception raised if an error occurs in the definition
401 or the execution of a CPPWriter."""
402 pass
403
404
405 standard_indent = 2
406 line_cont_indent = 4
407
408 indent_par_keywords = {'^if': standard_indent,
409 '^else if': standard_indent,
410 '^for': standard_indent,
411 '^while': standard_indent,
412 '^switch': standard_indent}
413 indent_single_keywords = {'^else': standard_indent}
414 indent_content_keywords = {'^class': standard_indent,
415 '^namespace': 0}
416 cont_indent_keywords = {'^case': standard_indent,
417 '^default': standard_indent,
418 '^public': standard_indent,
419 '^private': standard_indent,
420 '^protected': standard_indent}
421
422 spacing_patterns = [('\s*\"\s*}', '\"'),
423 ('\s*,\s*', ', '),
424 ('\s*-\s*', ' - '),
425 ('([{(,=])\s*-\s*', '\g<1> -'),
426 ('(return)\s*-\s*', '\g<1> -'),
427 ('\s*\+\s*', ' + '),
428 ('([{(,=])\s*\+\s*', '\g<1> +'),
429 ('\(\s*', '('),
430 ('\s*\)', ')'),
431 ('\{\s*', '{'),
432 ('\s*\}', '}'),
433 ('\s*=\s*', ' = '),
434 ('\s*>\s*', ' > '),
435 ('\s*<\s*', ' < '),
436 ('\s*!\s*', ' !'),
437 ('\s*/\s*', '/'),
438 ('\s*\*\s*', ' * '),
439 ('\s*-\s+-\s*', '-- '),
440 ('\s*\+\s+\+\s*', '++ '),
441 ('\s*-\s+=\s*', ' -= '),
442 ('\s*\+\s+=\s*', ' += '),
443 ('\s*\*\s+=\s*', ' *= '),
444 ('\s*/=\s*', ' /= '),
445 ('\s*>\s+>\s*', ' >> '),
446 ('<\s*double\s*>>\s*', '<double> > '),
447 ('\s*<\s+<\s*', ' << '),
448 ('\s*-\s+>\s*', '->'),
449 ('\s*=\s+=\s*', ' == '),
450 ('\s*!\s+=\s*', ' != '),
451 ('\s*>\s+=\s*', ' >= '),
452 ('\s*<\s+=\s*', ' <= '),
453 ('\s*&&\s*', ' && '),
454 ('\s*\|\|\s*', ' || '),
455 ('\s*{\s*}', ' {}'),
456 ('\s*;\s*', '; '),
457 (';\s*\}', ';}'),
458 (';\s*$}', ';'),
459 ('\s*<\s*([a-zA-Z0-9]+?)\s*>', '<\g<1>>'),
460 ('^#include\s*<\s*(.*?)\s*>', '#include <\g<1>>'),
461 ('(\d+\.{0,1}\d*|\.\d+)\s*[eE]\s*([+-]{0,1})\s*(\d+)',
462 '\g<1>e\g<2>\g<3>'),
463 ('\s+',' ')]
464 spacing_re = dict([(key[0], re.compile(key[0])) for key in \
465 spacing_patterns])
466
467 init_array_pattern = re.compile(r"=\s*\{.*\}")
468 short_clause_pattern = re.compile(r"\{.*\}")
469
470 comment_char = '//'
471 comment_pattern = re.compile(r"^(\s*#\s+|\s*//)")
472 start_comment_pattern = re.compile(r"^(\s*/\*)")
473 end_comment_pattern = re.compile(r"(\s*\*/)$")
474
475 quote_chars = re.compile(r"[^\\][\"\']|^[\"\']")
476 no_space_comment_patterns = re.compile(r"--|\*\*|==|\+\+")
477 line_length = 80
478 max_split = 40
479 split_characters = " "
480 comment_split_characters = " "
481
482
483 __indent = 0
484 __keyword_list = collections.deque()
485 __comment_ongoing = False
486
488 """Write a C++ line, with correct indent, spacing and line splits"""
489
490
491 assert(isinstance(line, str) and line.find('\n') == -1)
492
493 res_lines = []
494
495
496 if self.comment_pattern.search(line) or \
497 self.start_comment_pattern.search(line) or \
498 self.__comment_ongoing:
499
500 res_lines = self.write_comment_line(line.lstrip())
501 return res_lines
502
503
504
505
506 myline = line.lstrip()
507
508
509 if not myline:
510 return ["\n"]
511
512
513 if myline[0] == "{":
514
515 indent = self.__indent
516 key = ""
517 if self.__keyword_list:
518 key = self.__keyword_list[-1]
519 if key in self.indent_par_keywords:
520 indent = indent - self.indent_par_keywords[key]
521 elif key in self.indent_single_keywords:
522 indent = indent - self.indent_single_keywords[key]
523 elif key in self.indent_content_keywords:
524 indent = indent - self.indent_content_keywords[key]
525 else:
526
527 self.__indent = self.__indent + self.standard_indent
528
529 res_lines.append(" " * indent + "{" + "\n")
530
531 self.__keyword_list.append("{")
532 myline = myline[1:].lstrip()
533 if myline:
534
535 res_lines.extend(self.write_line(myline))
536 return res_lines
537
538
539 if myline[0] == "}":
540
541 if not self.__keyword_list:
542 raise self.CPPWriterError(\
543 'Non-matching } in C++ output: ' \
544 + myline)
545
546 if self.__keyword_list[-1] in self.cont_indent_keywords.keys():
547 key = self.__keyword_list.pop()
548 self.__indent = self.__indent - self.cont_indent_keywords[key]
549
550 if not self.__keyword_list.pop() == "{":
551 raise self.CPPWriterError(\
552 'Non-matching } in C++ output: ' \
553 + ",".join(self.__keyword_list) + myline)
554
555 key = ""
556 if self.__keyword_list:
557 key = self.__keyword_list[-1]
558 if key in self.indent_par_keywords:
559 self.__indent = self.__indent - \
560 self.indent_par_keywords[key]
561 self.__keyword_list.pop()
562 elif key in self.indent_single_keywords:
563 self.__indent = self.__indent - \
564 self.indent_single_keywords[key]
565 self.__keyword_list.pop()
566 elif key in self.indent_content_keywords:
567 self.__indent = self.__indent - \
568 self.indent_content_keywords[key]
569 self.__keyword_list.pop()
570 else:
571
572 self.__indent = self.__indent - self.standard_indent
573
574
575 breakline_index = 1
576 if len(myline) > 1:
577 if myline[1] in [";", ","]:
578 breakline_index = 2
579 elif myline[1:].lstrip()[:2] == "//":
580 if myline.endswith('\n'):
581 breakline_index = len(myline) - 1
582 else:
583 breakline_index = len(myline)
584 res_lines.append("\n".join(self.split_line(\
585 myline[:breakline_index],
586 self.split_characters)) + "\n")
587 if len(myline) > breakline_index and myline[breakline_index] =='\n':
588 breakline_index +=1
589 myline = myline[breakline_index:].lstrip()
590
591 if myline:
592
593 res_lines.extend(self.write_line(myline))
594 return res_lines
595
596
597 for key in self.indent_par_keywords.keys():
598 if re.search(key, myline):
599
600 parenstack = collections.deque()
601 for i, ch in enumerate(myline[len(key)-1:]):
602 if ch == '(':
603 parenstack.append(ch)
604 elif ch == ')':
605 try:
606 parenstack.pop()
607 except IndexError:
608
609 raise self.CPPWriterError(\
610 'Non-matching parenthesis in C++ output' \
611 + myline)
612 if not parenstack:
613
614 break
615 endparen_index = len(key) + i
616
617 res_lines.append("\n".join(self.split_line(\
618 myline[:endparen_index], \
619 self.split_characters)) + \
620 "\n")
621 myline = myline[endparen_index:].lstrip()
622
623 self.__keyword_list.append(key)
624 self.__indent = self.__indent + \
625 self.indent_par_keywords[key]
626 if myline:
627
628 res_lines.extend(self.write_line(myline))
629
630 return res_lines
631
632
633 for key in self.indent_single_keywords.keys():
634 if re.search(key, myline):
635 end_index = len(key) - 1
636
637 res_lines.append(" " * self.__indent + myline[:end_index] + \
638 "\n")
639 myline = myline[end_index:].lstrip()
640
641 self.__keyword_list.append(key)
642 self.__indent = self.__indent + \
643 self.indent_single_keywords[key]
644 if myline:
645
646 res_lines.extend(self.write_line(myline))
647
648 return res_lines
649
650
651 for key in self.indent_content_keywords.keys():
652 if re.search(key, myline):
653
654 if "{" in myline:
655 end_index = myline.index("{")
656 else:
657 end_index = len(myline)
658 res_lines.append("\n".join(self.split_line(\
659 myline[:end_index], \
660 self.split_characters)) + \
661 "\n")
662 myline = myline[end_index:].lstrip()
663
664 self.__keyword_list.append(key)
665 self.__indent = self.__indent + \
666 self.indent_content_keywords[key]
667 if myline:
668
669 res_lines.extend(self.write_line(myline))
670
671 return res_lines
672
673
674 for key in self.cont_indent_keywords.keys():
675 if re.search(key, myline):
676
677 if self.__keyword_list[-1] in self.cont_indent_keywords.keys():
678 self.__indent = self.__indent - \
679 self.cont_indent_keywords[\
680 self.__keyword_list.pop()]
681
682 res_lines.append("\n".join(self.split_line(myline, \
683 self.split_characters)) + \
684 "\n")
685
686 self.__keyword_list.append(key)
687 self.__indent = self.__indent + \
688 self.cont_indent_keywords[key]
689
690 return res_lines
691
692
693 if self.init_array_pattern.search(myline):
694 res_lines.append("\n".join(self.split_line(\
695 myline,
696 self.split_characters)) + \
697 "\n")
698 return res_lines
699
700
701 if self.short_clause_pattern.search(myline):
702 lines = self.split_line(myline,
703 self.split_characters)
704 if len(lines) == 1:
705 res_lines.append("\n".join(lines) + "\n")
706 return res_lines
707
708
709 if "{" in myline:
710 end_index = myline.index("{")
711 res_lines.append("\n".join(self.split_line(\
712 myline[:end_index], \
713 self.split_characters)) + \
714 "\n")
715 myline = myline[end_index:].lstrip()
716 if myline:
717
718 res_lines.extend(self.write_line(myline))
719 return res_lines
720
721
722 if "}" in myline:
723 end_index = myline.index("}")
724 res_lines.append("\n".join(self.split_line(\
725 myline[:end_index], \
726 self.split_characters)) + \
727 "\n")
728 myline = myline[end_index:].lstrip()
729 if myline:
730
731 res_lines.extend(self.write_line(myline))
732 return res_lines
733
734
735 res_lines.append("\n".join(self.split_line(myline, \
736 self.split_characters)) + "\n")
737
738
739 if self.__keyword_list:
740 if self.__keyword_list[-1] in self.indent_par_keywords:
741 self.__indent = self.__indent - \
742 self.indent_par_keywords[self.__keyword_list.pop()]
743 elif self.__keyword_list[-1] in self.indent_single_keywords:
744 self.__indent = self.__indent - \
745 self.indent_single_keywords[self.__keyword_list.pop()]
746 elif self.__keyword_list[-1] in self.indent_content_keywords:
747 self.__indent = self.__indent - \
748 self.indent_content_keywords[self.__keyword_list.pop()]
749
750 return res_lines
751
784
786 """Split a line if it is longer than self.line_length
787 columns. Split in preferential order according to
788 split_characters. Also fix spacing for line."""
789
790
791 comment = ""
792 if line.find(self.comment_char) > -1:
793 line, dum, comment = line.partition(self.comment_char)
794
795
796 quotes = self.quote_chars.finditer(line)
797
798 start_pos = 0
799 line_quotes = []
800 line_no_quotes = []
801 for i, quote in enumerate(quotes):
802 if i % 2 == 0:
803
804 line_no_quotes.append(line[start_pos:quote.start()])
805 start_pos = quote.start()
806 else:
807
808 line_quotes.append(line[start_pos:quote.end()])
809 start_pos = quote.end()
810
811 line_no_quotes.append(line[start_pos:])
812
813
814 line.rstrip()
815 for i, no_quote in enumerate(line_no_quotes):
816 for key in self.spacing_patterns:
817 no_quote = self.spacing_re[key[0]].sub(key[1], no_quote)
818 line_no_quotes[i] = no_quote
819
820
821 line = line_no_quotes[0]
822 for i in range(len(line_quotes)):
823 line += line_quotes[i]
824 if len(line_no_quotes) > i + 1:
825 line += line_no_quotes[i+1]
826
827
828 res_lines = [" " * self.__indent + line]
829
830 while len(res_lines[-1]) > self.line_length:
831 long_line = res_lines[-1]
832 split_at = -1
833 for character in split_characters:
834 index = long_line[(self.line_length - self.max_split): \
835 self.line_length].rfind(character)
836 if index >= 0:
837 split_at = self.line_length - self.max_split + index + 1
838 break
839
840
841 if split_at == -1:
842 split_at = len(long_line)
843 for character in split_characters:
844 split = long_line[self.line_length].find(character)
845 if split > 0:
846 split_at = min(split, split_at)
847 if split_at == len(long_line):
848 break
849
850
851 quotes = self.quote_chars.findall(long_line[:split_at])
852 if quotes and len(quotes) % 2 == 1:
853 quote_match = self.quote_chars.search(long_line[split_at:])
854 if not quote_match:
855 raise self.CPPWriterError(\
856 "Error: Unmatched quote in line " + long_line)
857 split_at = quote_match.end() + split_at + 1
858 split_match = re.search(self.split_characters,
859 long_line[split_at:])
860 if split_match:
861 split_at = split_at + split_match.start()
862 else:
863 split_at = len(long_line) + 1
864
865
866 if long_line[split_at:].lstrip():
867
868 res_lines[-1] = long_line[:split_at].rstrip()
869 res_lines.append(" " * \
870 (self.__indent + self.line_cont_indent) + \
871 long_line[split_at:].strip())
872 else:
873 break
874
875 if comment:
876 res_lines[-1] += " " + self.comment_char + comment
877
878 return res_lines
879
908
914
916
920
922 """Extends the regular file.writeline() function to write out
923 nicely formatted code"""
924
925 self.write(lines)
926