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 'function': ('^end\s*$', 0)}
181 single_indents = {'^else\s*$':-2,
182 '^else\s*if.+then\s*$':-2}
183 number_re = re.compile('^(?P<num>\d+)\s+(?P<rest>.*)')
184 line_cont_char = '$'
185 comment_char = 'c'
186 downcase = False
187 line_length = 71
188 max_split = 10
189 split_characters = "+-*/,) "
190 comment_split_characters = " "
191
192
193 __indent = 0
194 __keyword_list = []
195 __comment_pattern = re.compile(r"^(\s*#|c$|(c\s+([^=]|$))|cf2py)", re.IGNORECASE)
196
198 """Write a fortran line, with correct indent and line splits"""
199
200
201 assert(isinstance(line, str) and line.find('\n') == -1)
202
203
204 res_lines = []
205
206
207 if not line.lstrip():
208 res_lines.append("\n")
209 return res_lines
210
211
212 if self.__comment_pattern.search(line):
213
214 res_lines = self.write_comment_line(line.lstrip()[1:])
215 return res_lines
216
217 else:
218
219
220
221 myline = line.lstrip()
222
223
224 num_group = self.number_re.search(myline)
225 num = ""
226 if num_group:
227 num = num_group.group('num')
228 myline = num_group.group('rest')
229
230
231
232 (myline, part, post_comment) = myline.partition("!")
233
234 if part:
235 part = " " + part
236
237 myline = myline.replace('\"', '\'')
238
239 splitline = myline.split('\'')
240 myline = ""
241 i = 0
242 while i < len(splitline):
243 if i % 2 == 1:
244
245 while splitline[i] and splitline[i][-1] == '\\':
246 splitline[i] = splitline[i] + '\'' + splitline.pop(i + 1)
247 else:
248
249 if FortranWriter.downcase:
250 splitline[i] = splitline[i].lower()
251 else:
252 splitline[i] = splitline[i].upper()
253 i = i + 1
254
255 myline = "\'".join(splitline).rstrip()
256
257
258 if self.__keyword_list and re.search(self.keyword_pairs[\
259 self.__keyword_list[-1]][0], myline.lower()):
260 key = self.__keyword_list.pop()
261 self.__indent = self.__indent - self.keyword_pairs[key][1]
262
263
264 single_indent = 0
265 for key in self.single_indents.keys():
266 if re.search(key, myline.lower()):
267 self.__indent = self.__indent + self.single_indents[key]
268 single_indent = -self.single_indents[key]
269 break
270
271
272
273 res = self.split_line(" " + num + \
274 " " * (5 + self.__indent - len(num)) + myline,
275 self.split_characters,
276 " " * 5 + self.line_cont_char + \
277 " " * (self.__indent + 1))
278
279
280 for key in self.keyword_pairs.keys():
281 if re.search(key, myline.lower()):
282 self.__keyword_list.append(key)
283 self.__indent = self.__indent + self.keyword_pairs[key][1]
284 break
285
286
287 if single_indent != None:
288 self.__indent = self.__indent + single_indent
289 single_indent = None
290
291
292 res_lines.append("\n".join(res) + part + post_comment + "\n")
293
294 return res_lines
295
326
327 - def split_line(self, line, split_characters, line_start):
328 """Split a line if it is longer than self.line_length
329 columns. Split in preferential order according to
330 split_characters, and start each new line with line_start."""
331
332 res_lines = [line]
333
334 while len(res_lines[-1]) > self.line_length:
335 split_at = self.line_length
336 for character in split_characters:
337 index = res_lines[-1][(self.line_length - self.max_split): \
338 self.line_length].rfind(character)
339 if index >= 0:
340 split_at = self.line_length - self.max_split + index
341 break
342 newline = res_lines[-1][split_at:]
343 nquotes = self.count_number_of_quotes(newline)
344 res_lines.append(line_start +
345 ('//\''+res_lines[-1][(split_at-1):] if nquotes%2==1 else
346 ''+res_lines[-1][split_at:]))
347 res_lines[-2] = (res_lines[-2][:(split_at-1)]+'\'' if nquotes%2==1 \
348 else res_lines[-2][:split_at])
349 return res_lines
350
352 """ Count the number of real quotes (not escaped ones) in a line. """
353
354 splitline = line.split('\'')
355 i = 0
356 while i < len(splitline):
357 if i % 2 == 1:
358
359 while splitline[i] and splitline[i][-1] == '\\':
360 splitline[i] = splitline[i] + '\'' + splitline.pop(i + 1)
361 i = i + 1
362 return len(splitline)-1
363
364
365
366
368 """Routines for writing C++ lines. Keeps track of brackets,
369 spaces, indentation and splitting of long lines"""
370
372 """Exception raised if an error occurs in the definition
373 or the execution of a CPPWriter."""
374 pass
375
376
377 standard_indent = 2
378 line_cont_indent = 4
379
380 indent_par_keywords = {'^if': standard_indent,
381 '^else if': standard_indent,
382 '^for': standard_indent,
383 '^while': standard_indent,
384 '^switch': standard_indent}
385 indent_single_keywords = {'^else': standard_indent}
386 indent_content_keywords = {'^class': standard_indent,
387 '^namespace': 0}
388 cont_indent_keywords = {'^case': standard_indent,
389 '^default': standard_indent,
390 '^public': standard_indent,
391 '^private': standard_indent,
392 '^protected': standard_indent}
393
394 spacing_patterns = [('\s*\"\s*}', '\"'),
395 ('\s*,\s*', ', '),
396 ('\s*-\s*', ' - '),
397 ('([{(,=])\s*-\s*', '\g<1> -'),
398 ('(return)\s*-\s*', '\g<1> -'),
399 ('\s*\+\s*', ' + '),
400 ('([{(,=])\s*\+\s*', '\g<1> +'),
401 ('\(\s*', '('),
402 ('\s*\)', ')'),
403 ('\{\s*', '{'),
404 ('\s*\}', '}'),
405 ('\s*=\s*', ' = '),
406 ('\s*>\s*', ' > '),
407 ('\s*<\s*', ' < '),
408 ('\s*!\s*', ' !'),
409 ('\s*/\s*', '/'),
410 ('\s*\*\s*', ' * '),
411 ('\s*-\s+-\s*', '-- '),
412 ('\s*\+\s+\+\s*', '++ '),
413 ('\s*-\s+=\s*', ' -= '),
414 ('\s*\+\s+=\s*', ' += '),
415 ('\s*\*\s+=\s*', ' *= '),
416 ('\s*/=\s*', ' /= '),
417 ('\s*>\s+>\s*', ' >> '),
418 ('<\s*double\s*>>\s*', '<double> > '),
419 ('\s*<\s+<\s*', ' << '),
420 ('\s*-\s+>\s*', '->'),
421 ('\s*=\s+=\s*', ' == '),
422 ('\s*!\s+=\s*', ' != '),
423 ('\s*>\s+=\s*', ' >= '),
424 ('\s*<\s+=\s*', ' <= '),
425 ('\s*&&\s*', ' && '),
426 ('\s*\|\|\s*', ' || '),
427 ('\s*{\s*}', ' {}'),
428 ('\s*;\s*', '; '),
429 (';\s*\}', ';}'),
430 (';\s*$}', ';'),
431 ('\s*<\s*([a-zA-Z0-9]+?)\s*>', '<\g<1>>'),
432 ('^#include\s*<\s*(.*?)\s*>', '#include <\g<1>>'),
433 ('(\d+\.{0,1}\d*|\.\d+)\s*[eE]\s*([+-]{0,1})\s*(\d+)',
434 '\g<1>e\g<2>\g<3>'),
435 ('\s+',' ')]
436 spacing_re = dict([(key[0], re.compile(key[0])) for key in \
437 spacing_patterns])
438
439 init_array_pattern = re.compile(r"=\s*\{.*\}")
440 short_clause_pattern = re.compile(r"\{.*\}")
441
442 comment_char = '//'
443 comment_pattern = re.compile(r"^(\s*#\s+|\s*//)")
444 start_comment_pattern = re.compile(r"^(\s*/\*)")
445 end_comment_pattern = re.compile(r"(\s*\*/)$")
446
447 quote_chars = re.compile(r"[^\\][\"\']|^[\"\']")
448 no_space_comment_patterns = re.compile(r"--|\*\*|==|\+\+")
449 line_length = 80
450 max_split = 40
451 split_characters = " "
452 comment_split_characters = " "
453
454
455 __indent = 0
456 __keyword_list = collections.deque()
457 __comment_ongoing = False
458
460 """Write a C++ line, with correct indent, spacing and line splits"""
461
462
463 assert(isinstance(line, str) and line.find('\n') == -1)
464
465 res_lines = []
466
467
468 if self.comment_pattern.search(line) or \
469 self.start_comment_pattern.search(line) or \
470 self.__comment_ongoing:
471
472 res_lines = self.write_comment_line(line.lstrip())
473 return res_lines
474
475
476
477
478 myline = line.lstrip()
479
480
481 if not myline:
482 return ["\n"]
483
484
485 if myline[0] == "{":
486
487 indent = self.__indent
488 key = ""
489 if self.__keyword_list:
490 key = self.__keyword_list[-1]
491 if key in self.indent_par_keywords:
492 indent = indent - self.indent_par_keywords[key]
493 elif key in self.indent_single_keywords:
494 indent = indent - self.indent_single_keywords[key]
495 elif key in self.indent_content_keywords:
496 indent = indent - self.indent_content_keywords[key]
497 else:
498
499 self.__indent = self.__indent + self.standard_indent
500
501 res_lines.append(" " * indent + "{" + "\n")
502
503 self.__keyword_list.append("{")
504 myline = myline[1:].lstrip()
505 if myline:
506
507 res_lines.extend(self.write_line(myline))
508 return res_lines
509
510
511 if myline[0] == "}":
512
513 if not self.__keyword_list:
514 raise self.CPPWriterError(\
515 'Non-matching } in C++ output: ' \
516 + myline)
517
518 if self.__keyword_list[-1] in self.cont_indent_keywords.keys():
519 key = self.__keyword_list.pop()
520 self.__indent = self.__indent - self.cont_indent_keywords[key]
521
522 if not self.__keyword_list.pop() == "{":
523 raise self.CPPWriterError(\
524 'Non-matching } in C++ output: ' \
525 + ",".join(self.__keyword_list) + myline)
526
527 key = ""
528 if self.__keyword_list:
529 key = self.__keyword_list[-1]
530 if key in self.indent_par_keywords:
531 self.__indent = self.__indent - \
532 self.indent_par_keywords[key]
533 self.__keyword_list.pop()
534 elif key in self.indent_single_keywords:
535 self.__indent = self.__indent - \
536 self.indent_single_keywords[key]
537 self.__keyword_list.pop()
538 elif key in self.indent_content_keywords:
539 self.__indent = self.__indent - \
540 self.indent_content_keywords[key]
541 self.__keyword_list.pop()
542 else:
543
544 self.__indent = self.__indent - self.standard_indent
545
546
547 breakline_index = 1
548 if len(myline) > 1:
549 if myline[1] in [";", ","]:
550 breakline_index = 2
551 elif myline[1:].lstrip()[:2] == "//":
552 if myline.endswith('\n'):
553 breakline_index = len(myline) - 1
554 else:
555 breakline_index = len(myline)
556 res_lines.append("\n".join(self.split_line(\
557 myline[:breakline_index],
558 self.split_characters)) + "\n")
559 if len(myline) > breakline_index and myline[breakline_index] =='\n':
560 breakline_index +=1
561 myline = myline[breakline_index:].lstrip()
562
563 if myline:
564
565 res_lines.extend(self.write_line(myline))
566 return res_lines
567
568
569 for key in self.indent_par_keywords.keys():
570 if re.search(key, myline):
571
572 parenstack = collections.deque()
573 for i, ch in enumerate(myline[len(key)-1:]):
574 if ch == '(':
575 parenstack.append(ch)
576 elif ch == ')':
577 try:
578 parenstack.pop()
579 except IndexError:
580
581 raise self.CPPWriterError(\
582 'Non-matching parenthesis in C++ output' \
583 + myline)
584 if not parenstack:
585
586 break
587 endparen_index = len(key) + i
588
589 res_lines.append("\n".join(self.split_line(\
590 myline[:endparen_index], \
591 self.split_characters)) + \
592 "\n")
593 myline = myline[endparen_index:].lstrip()
594
595 self.__keyword_list.append(key)
596 self.__indent = self.__indent + \
597 self.indent_par_keywords[key]
598 if myline:
599
600 res_lines.extend(self.write_line(myline))
601
602 return res_lines
603
604
605 for key in self.indent_single_keywords.keys():
606 if re.search(key, myline):
607 end_index = len(key) - 1
608
609 res_lines.append(" " * self.__indent + myline[:end_index] + \
610 "\n")
611 myline = myline[end_index:].lstrip()
612
613 self.__keyword_list.append(key)
614 self.__indent = self.__indent + \
615 self.indent_single_keywords[key]
616 if myline:
617
618 res_lines.extend(self.write_line(myline))
619
620 return res_lines
621
622
623 for key in self.indent_content_keywords.keys():
624 if re.search(key, myline):
625
626 if "{" in myline:
627 end_index = myline.index("{")
628 else:
629 end_index = len(myline)
630 res_lines.append("\n".join(self.split_line(\
631 myline[:end_index], \
632 self.split_characters)) + \
633 "\n")
634 myline = myline[end_index:].lstrip()
635
636 self.__keyword_list.append(key)
637 self.__indent = self.__indent + \
638 self.indent_content_keywords[key]
639 if myline:
640
641 res_lines.extend(self.write_line(myline))
642
643 return res_lines
644
645
646 for key in self.cont_indent_keywords.keys():
647 if re.search(key, myline):
648
649 if self.__keyword_list[-1] in self.cont_indent_keywords.keys():
650 self.__indent = self.__indent - \
651 self.cont_indent_keywords[\
652 self.__keyword_list.pop()]
653
654 res_lines.append("\n".join(self.split_line(myline, \
655 self.split_characters)) + \
656 "\n")
657
658 self.__keyword_list.append(key)
659 self.__indent = self.__indent + \
660 self.cont_indent_keywords[key]
661
662 return res_lines
663
664
665 if self.init_array_pattern.search(myline):
666 res_lines.append("\n".join(self.split_line(\
667 myline,
668 self.split_characters)) + \
669 "\n")
670 return res_lines
671
672
673 if self.short_clause_pattern.search(myline):
674 lines = self.split_line(myline,
675 self.split_characters)
676 if len(lines) == 1:
677 res_lines.append("\n".join(lines) + "\n")
678 return res_lines
679
680
681 if "{" in myline:
682 end_index = myline.index("{")
683 res_lines.append("\n".join(self.split_line(\
684 myline[:end_index], \
685 self.split_characters)) + \
686 "\n")
687 myline = myline[end_index:].lstrip()
688 if myline:
689
690 res_lines.extend(self.write_line(myline))
691 return res_lines
692
693
694 if "}" in myline:
695 end_index = myline.index("}")
696 res_lines.append("\n".join(self.split_line(\
697 myline[:end_index], \
698 self.split_characters)) + \
699 "\n")
700 myline = myline[end_index:].lstrip()
701 if myline:
702
703 res_lines.extend(self.write_line(myline))
704 return res_lines
705
706
707 res_lines.append("\n".join(self.split_line(myline, \
708 self.split_characters)) + "\n")
709
710
711 if self.__keyword_list:
712 if self.__keyword_list[-1] in self.indent_par_keywords:
713 self.__indent = self.__indent - \
714 self.indent_par_keywords[self.__keyword_list.pop()]
715 elif self.__keyword_list[-1] in self.indent_single_keywords:
716 self.__indent = self.__indent - \
717 self.indent_single_keywords[self.__keyword_list.pop()]
718 elif self.__keyword_list[-1] in self.indent_content_keywords:
719 self.__indent = self.__indent - \
720 self.indent_content_keywords[self.__keyword_list.pop()]
721
722 return res_lines
723
756
758 """Split a line if it is longer than self.line_length
759 columns. Split in preferential order according to
760 split_characters. Also fix spacing for line."""
761
762
763 comment = ""
764 if line.find(self.comment_char) > -1:
765 line, dum, comment = line.partition(self.comment_char)
766
767
768 quotes = self.quote_chars.finditer(line)
769
770 start_pos = 0
771 line_quotes = []
772 line_no_quotes = []
773 for i, quote in enumerate(quotes):
774 if i % 2 == 0:
775
776 line_no_quotes.append(line[start_pos:quote.start()])
777 start_pos = quote.start()
778 else:
779
780 line_quotes.append(line[start_pos:quote.end()])
781 start_pos = quote.end()
782
783 line_no_quotes.append(line[start_pos:])
784
785
786 line.rstrip()
787 for i, no_quote in enumerate(line_no_quotes):
788 for key in self.spacing_patterns:
789 no_quote = self.spacing_re[key[0]].sub(key[1], no_quote)
790 line_no_quotes[i] = no_quote
791
792
793 line = line_no_quotes[0]
794 for i in range(len(line_quotes)):
795 line += line_quotes[i]
796 if len(line_no_quotes) > i + 1:
797 line += line_no_quotes[i+1]
798
799
800 res_lines = [" " * self.__indent + line]
801
802 while len(res_lines[-1]) > self.line_length:
803 long_line = res_lines[-1]
804 split_at = -1
805 for character in split_characters:
806 index = long_line[(self.line_length - self.max_split): \
807 self.line_length].rfind(character)
808 if index >= 0:
809 split_at = self.line_length - self.max_split + index + 1
810 break
811
812
813 if split_at == -1:
814 split_at = len(long_line)
815 for character in split_characters:
816 split = long_line[self.line_length].find(character)
817 if split > 0:
818 split_at = min(split, split_at)
819 if split_at == len(long_line):
820 break
821
822
823 quotes = self.quote_chars.findall(long_line[:split_at])
824 if quotes and len(quotes) % 2 == 1:
825 quote_match = self.quote_chars.search(long_line[split_at:])
826 if not quote_match:
827 raise self.CPPWriterError(\
828 "Error: Unmatched quote in line " + long_line)
829 split_at = quote_match.end() + split_at + 1
830 split_match = re.search(self.split_characters,
831 long_line[split_at:])
832 if split_match:
833 split_at = split_at + split_match.start()
834 else:
835 split_at = len(long_line) + 1
836
837
838 if long_line[split_at:].lstrip():
839
840 res_lines[-1] = long_line[:split_at].rstrip()
841 res_lines.append(" " * \
842 (self.__indent + self.line_cont_indent) + \
843 long_line[split_at:].strip())
844 else:
845 break
846
847 if comment:
848 res_lines[-1] += " " + self.comment_char + comment
849
850 return res_lines
851
880
886
888
892
894 """Extends the regular file.writeline() function to write out
895 nicely formatted code"""
896
897 self.write(lines)
898