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)", 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
327
328 - def split_line(self, line, split_characters, line_start):
329 """Split a line if it is longer than self.line_length
330 columns. Split in preferential order according to
331 split_characters, and start each new line with line_start."""
332
333 res_lines = [line]
334
335 while len(res_lines[-1]) > self.line_length:
336 split_at = 0
337 for character in split_characters:
338 index = res_lines[-1][(self.line_length - self.max_split): \
339 self.line_length].rfind(character)
340 if index >= 0:
341 split_at_tmp = self.line_length - self.max_split + index
342 if split_at_tmp > split_at:
343 split_at = split_at_tmp
344 if split_at == 0:
345 split_at = self.line_length
346
347 newline = res_lines[-1][split_at:]
348 nquotes = self.count_number_of_quotes(newline)
349
350
351
352 offset = 0
353 if nquotes%2==1:
354 if res_lines[-1][(split_at-1)] == '\'':
355 offset = 1
356 nquotes -=1
357 res_lines.append(line_start +(res_lines[-1][(split_at-offset):]))
358 else:
359 res_lines.append(line_start +('//\''+res_lines[-1][(split_at-offset):]))
360
361 elif res_lines[-1][(split_at)] in self.split_characters:
362 if res_lines[-1][(split_at)] in ')':
363
364 offset = -1
365
366
367 res_lines.append(line_start +res_lines[-1][(split_at-offset):])
368 elif line_start.startswith(('c','C')) or res_lines[-1][(split_at)] in split_characters:
369 res_lines.append(line_start +res_lines[-1][(split_at):])
370 else:
371 l_start = line_start.rstrip()
372 res_lines.append(l_start +res_lines[-1][(split_at):])
373
374 res_lines[-2] = (res_lines[-2][:(split_at-offset)]+'\'' if nquotes%2==1 \
375 else res_lines[-2][:split_at-offset])
376 return res_lines
377
379 """ Count the number of real quotes (not escaped ones) in a line. """
380
381 splitline = line.split('\'')
382 i = 0
383 while i < len(splitline):
384 if i % 2 == 1:
385
386 while splitline[i] and splitline[i][-1] == '\\':
387 splitline[i] = splitline[i] + '\'' + splitline.pop(i + 1)
388 i = i + 1
389 return len(splitline)-1
390
391
392
393
395 """Routines for writing C++ lines. Keeps track of brackets,
396 spaces, indentation and splitting of long lines"""
397
399 """Exception raised if an error occurs in the definition
400 or the execution of a CPPWriter."""
401 pass
402
403
404 standard_indent = 2
405 line_cont_indent = 4
406
407 indent_par_keywords = {'^if': standard_indent,
408 '^else if': standard_indent,
409 '^for': standard_indent,
410 '^while': standard_indent,
411 '^switch': standard_indent}
412 indent_single_keywords = {'^else': standard_indent}
413 indent_content_keywords = {'^class': standard_indent,
414 '^namespace': 0}
415 cont_indent_keywords = {'^case': standard_indent,
416 '^default': standard_indent,
417 '^public': standard_indent,
418 '^private': standard_indent,
419 '^protected': standard_indent}
420
421 spacing_patterns = [('\s*\"\s*}', '\"'),
422 ('\s*,\s*', ', '),
423 ('\s*-\s*', ' - '),
424 ('([{(,=])\s*-\s*', '\g<1> -'),
425 ('(return)\s*-\s*', '\g<1> -'),
426 ('\s*\+\s*', ' + '),
427 ('([{(,=])\s*\+\s*', '\g<1> +'),
428 ('\(\s*', '('),
429 ('\s*\)', ')'),
430 ('\{\s*', '{'),
431 ('\s*\}', '}'),
432 ('\s*=\s*', ' = '),
433 ('\s*>\s*', ' > '),
434 ('\s*<\s*', ' < '),
435 ('\s*!\s*', ' !'),
436 ('\s*/\s*', '/'),
437 ('\s*\*\s*', ' * '),
438 ('\s*-\s+-\s*', '-- '),
439 ('\s*\+\s+\+\s*', '++ '),
440 ('\s*-\s+=\s*', ' -= '),
441 ('\s*\+\s+=\s*', ' += '),
442 ('\s*\*\s+=\s*', ' *= '),
443 ('\s*/=\s*', ' /= '),
444 ('\s*>\s+>\s*', ' >> '),
445 ('<\s*double\s*>>\s*', '<double> > '),
446 ('\s*<\s+<\s*', ' << '),
447 ('\s*-\s+>\s*', '->'),
448 ('\s*=\s+=\s*', ' == '),
449 ('\s*!\s+=\s*', ' != '),
450 ('\s*>\s+=\s*', ' >= '),
451 ('\s*<\s+=\s*', ' <= '),
452 ('\s*&&\s*', ' && '),
453 ('\s*\|\|\s*', ' || '),
454 ('\s*{\s*}', ' {}'),
455 ('\s*;\s*', '; '),
456 (';\s*\}', ';}'),
457 (';\s*$}', ';'),
458 ('\s*<\s*([a-zA-Z0-9]+?)\s*>', '<\g<1>>'),
459 ('^#include\s*<\s*(.*?)\s*>', '#include <\g<1>>'),
460 ('(\d+\.{0,1}\d*|\.\d+)\s*[eE]\s*([+-]{0,1})\s*(\d+)',
461 '\g<1>e\g<2>\g<3>'),
462 ('\s+',' ')]
463 spacing_re = dict([(key[0], re.compile(key[0])) for key in \
464 spacing_patterns])
465
466 init_array_pattern = re.compile(r"=\s*\{.*\}")
467 short_clause_pattern = re.compile(r"\{.*\}")
468
469 comment_char = '//'
470 comment_pattern = re.compile(r"^(\s*#\s+|\s*//)")
471 start_comment_pattern = re.compile(r"^(\s*/\*)")
472 end_comment_pattern = re.compile(r"(\s*\*/)$")
473
474 quote_chars = re.compile(r"[^\\][\"\']|^[\"\']")
475 no_space_comment_patterns = re.compile(r"--|\*\*|==|\+\+")
476 line_length = 80
477 max_split = 40
478 split_characters = " "
479 comment_split_characters = " "
480
481
482 __indent = 0
483 __keyword_list = collections.deque()
484 __comment_ongoing = False
485
487 """Write a C++ line, with correct indent, spacing and line splits"""
488
489
490 assert(isinstance(line, str) and line.find('\n') == -1)
491
492 res_lines = []
493
494
495 if self.comment_pattern.search(line) or \
496 self.start_comment_pattern.search(line) or \
497 self.__comment_ongoing:
498
499 res_lines = self.write_comment_line(line.lstrip())
500 return res_lines
501
502
503
504
505 myline = line.lstrip()
506
507
508 if not myline:
509 return ["\n"]
510
511
512 if myline[0] == "{":
513
514 indent = self.__indent
515 key = ""
516 if self.__keyword_list:
517 key = self.__keyword_list[-1]
518 if key in self.indent_par_keywords:
519 indent = indent - self.indent_par_keywords[key]
520 elif key in self.indent_single_keywords:
521 indent = indent - self.indent_single_keywords[key]
522 elif key in self.indent_content_keywords:
523 indent = indent - self.indent_content_keywords[key]
524 else:
525
526 self.__indent = self.__indent + self.standard_indent
527
528 res_lines.append(" " * indent + "{" + "\n")
529
530 self.__keyword_list.append("{")
531 myline = myline[1:].lstrip()
532 if myline:
533
534 res_lines.extend(self.write_line(myline))
535 return res_lines
536
537
538 if myline[0] == "}":
539
540 if not self.__keyword_list:
541 raise self.CPPWriterError(\
542 'Non-matching } in C++ output: ' \
543 + myline)
544
545 if self.__keyword_list[-1] in self.cont_indent_keywords.keys():
546 key = self.__keyword_list.pop()
547 self.__indent = self.__indent - self.cont_indent_keywords[key]
548
549 if not self.__keyword_list.pop() == "{":
550 raise self.CPPWriterError(\
551 'Non-matching } in C++ output: ' \
552 + ",".join(self.__keyword_list) + myline)
553
554 key = ""
555 if self.__keyword_list:
556 key = self.__keyword_list[-1]
557 if key in self.indent_par_keywords:
558 self.__indent = self.__indent - \
559 self.indent_par_keywords[key]
560 self.__keyword_list.pop()
561 elif key in self.indent_single_keywords:
562 self.__indent = self.__indent - \
563 self.indent_single_keywords[key]
564 self.__keyword_list.pop()
565 elif key in self.indent_content_keywords:
566 self.__indent = self.__indent - \
567 self.indent_content_keywords[key]
568 self.__keyword_list.pop()
569 else:
570
571 self.__indent = self.__indent - self.standard_indent
572
573
574 breakline_index = 1
575 if len(myline) > 1:
576 if myline[1] in [";", ","]:
577 breakline_index = 2
578 elif myline[1:].lstrip()[:2] == "//":
579 if myline.endswith('\n'):
580 breakline_index = len(myline) - 1
581 else:
582 breakline_index = len(myline)
583 res_lines.append("\n".join(self.split_line(\
584 myline[:breakline_index],
585 self.split_characters)) + "\n")
586 if len(myline) > breakline_index and myline[breakline_index] =='\n':
587 breakline_index +=1
588 myline = myline[breakline_index:].lstrip()
589
590 if myline:
591
592 res_lines.extend(self.write_line(myline))
593 return res_lines
594
595
596 for key in self.indent_par_keywords.keys():
597 if re.search(key, myline):
598
599 parenstack = collections.deque()
600 for i, ch in enumerate(myline[len(key)-1:]):
601 if ch == '(':
602 parenstack.append(ch)
603 elif ch == ')':
604 try:
605 parenstack.pop()
606 except IndexError:
607
608 raise self.CPPWriterError(\
609 'Non-matching parenthesis in C++ output' \
610 + myline)
611 if not parenstack:
612
613 break
614 endparen_index = len(key) + i
615
616 res_lines.append("\n".join(self.split_line(\
617 myline[:endparen_index], \
618 self.split_characters)) + \
619 "\n")
620 myline = myline[endparen_index:].lstrip()
621
622 self.__keyword_list.append(key)
623 self.__indent = self.__indent + \
624 self.indent_par_keywords[key]
625 if myline:
626
627 res_lines.extend(self.write_line(myline))
628
629 return res_lines
630
631
632 for key in self.indent_single_keywords.keys():
633 if re.search(key, myline):
634 end_index = len(key) - 1
635
636 res_lines.append(" " * self.__indent + myline[:end_index] + \
637 "\n")
638 myline = myline[end_index:].lstrip()
639
640 self.__keyword_list.append(key)
641 self.__indent = self.__indent + \
642 self.indent_single_keywords[key]
643 if myline:
644
645 res_lines.extend(self.write_line(myline))
646
647 return res_lines
648
649
650 for key in self.indent_content_keywords.keys():
651 if re.search(key, myline):
652
653 if "{" in myline:
654 end_index = myline.index("{")
655 else:
656 end_index = len(myline)
657 res_lines.append("\n".join(self.split_line(\
658 myline[:end_index], \
659 self.split_characters)) + \
660 "\n")
661 myline = myline[end_index:].lstrip()
662
663 self.__keyword_list.append(key)
664 self.__indent = self.__indent + \
665 self.indent_content_keywords[key]
666 if myline:
667
668 res_lines.extend(self.write_line(myline))
669
670 return res_lines
671
672
673 for key in self.cont_indent_keywords.keys():
674 if re.search(key, myline):
675
676 if self.__keyword_list[-1] in self.cont_indent_keywords.keys():
677 self.__indent = self.__indent - \
678 self.cont_indent_keywords[\
679 self.__keyword_list.pop()]
680
681 res_lines.append("\n".join(self.split_line(myline, \
682 self.split_characters)) + \
683 "\n")
684
685 self.__keyword_list.append(key)
686 self.__indent = self.__indent + \
687 self.cont_indent_keywords[key]
688
689 return res_lines
690
691
692 if self.init_array_pattern.search(myline):
693 res_lines.append("\n".join(self.split_line(\
694 myline,
695 self.split_characters)) + \
696 "\n")
697 return res_lines
698
699
700 if self.short_clause_pattern.search(myline):
701 lines = self.split_line(myline,
702 self.split_characters)
703 if len(lines) == 1:
704 res_lines.append("\n".join(lines) + "\n")
705 return res_lines
706
707
708 if "{" in myline:
709 end_index = myline.index("{")
710 res_lines.append("\n".join(self.split_line(\
711 myline[:end_index], \
712 self.split_characters)) + \
713 "\n")
714 myline = myline[end_index:].lstrip()
715 if myline:
716
717 res_lines.extend(self.write_line(myline))
718 return res_lines
719
720
721 if "}" in myline:
722 end_index = myline.index("}")
723 res_lines.append("\n".join(self.split_line(\
724 myline[:end_index], \
725 self.split_characters)) + \
726 "\n")
727 myline = myline[end_index:].lstrip()
728 if myline:
729
730 res_lines.extend(self.write_line(myline))
731 return res_lines
732
733
734 res_lines.append("\n".join(self.split_line(myline, \
735 self.split_characters)) + "\n")
736
737
738 if self.__keyword_list:
739 if self.__keyword_list[-1] in self.indent_par_keywords:
740 self.__indent = self.__indent - \
741 self.indent_par_keywords[self.__keyword_list.pop()]
742 elif self.__keyword_list[-1] in self.indent_single_keywords:
743 self.__indent = self.__indent - \
744 self.indent_single_keywords[self.__keyword_list.pop()]
745 elif self.__keyword_list[-1] in self.indent_content_keywords:
746 self.__indent = self.__indent - \
747 self.indent_content_keywords[self.__keyword_list.pop()]
748
749 return res_lines
750
783
785 """Split a line if it is longer than self.line_length
786 columns. Split in preferential order according to
787 split_characters. Also fix spacing for line."""
788
789
790 comment = ""
791 if line.find(self.comment_char) > -1:
792 line, dum, comment = line.partition(self.comment_char)
793
794
795 quotes = self.quote_chars.finditer(line)
796
797 start_pos = 0
798 line_quotes = []
799 line_no_quotes = []
800 for i, quote in enumerate(quotes):
801 if i % 2 == 0:
802
803 line_no_quotes.append(line[start_pos:quote.start()])
804 start_pos = quote.start()
805 else:
806
807 line_quotes.append(line[start_pos:quote.end()])
808 start_pos = quote.end()
809
810 line_no_quotes.append(line[start_pos:])
811
812
813 line.rstrip()
814 for i, no_quote in enumerate(line_no_quotes):
815 for key in self.spacing_patterns:
816 no_quote = self.spacing_re[key[0]].sub(key[1], no_quote)
817 line_no_quotes[i] = no_quote
818
819
820 line = line_no_quotes[0]
821 for i in range(len(line_quotes)):
822 line += line_quotes[i]
823 if len(line_no_quotes) > i + 1:
824 line += line_no_quotes[i+1]
825
826
827 res_lines = [" " * self.__indent + line]
828
829 while len(res_lines[-1]) > self.line_length:
830 long_line = res_lines[-1]
831 split_at = -1
832 for character in split_characters:
833 index = long_line[(self.line_length - self.max_split): \
834 self.line_length].rfind(character)
835 if index >= 0:
836 split_at = self.line_length - self.max_split + index + 1
837 break
838
839
840 if split_at == -1:
841 split_at = len(long_line)
842 for character in split_characters:
843 split = long_line[self.line_length].find(character)
844 if split > 0:
845 split_at = min(split, split_at)
846 if split_at == len(long_line):
847 break
848
849
850 quotes = self.quote_chars.findall(long_line[:split_at])
851 if quotes and len(quotes) % 2 == 1:
852 quote_match = self.quote_chars.search(long_line[split_at:])
853 if not quote_match:
854 raise self.CPPWriterError(\
855 "Error: Unmatched quote in line " + long_line)
856 split_at = quote_match.end() + split_at + 1
857 split_match = re.search(self.split_characters,
858 long_line[split_at:])
859 if split_match:
860 split_at = split_at + split_match.start()
861 else:
862 split_at = len(long_line) + 1
863
864
865 if long_line[split_at:].lstrip():
866
867 res_lines[-1] = long_line[:split_at].rstrip()
868 res_lines.append(" " * \
869 (self.__indent + self.line_cont_indent) + \
870 long_line[split_at:].strip())
871 else:
872 break
873
874 if comment:
875 res_lines[-1] += " " + self.comment_char + comment
876
877 return res_lines
878
907
913
915
919
921 """Extends the regular file.writeline() function to write out
922 nicely formatted code"""
923
924 self.write(lines)
925