Basic: switch to python preprocessor.
[jackhill/mal.git] / basic / basicpp.py
1 #!/usr/bin/env python
2
3 from __future__ import print_function
4 import argparse
5 import re
6 import sys
7
8 def debug(*args, **kwargs):
9 print(*args, file=sys.stderr, **kwargs)
10
11 def parse_args():
12 parser = argparse.ArgumentParser(description='Preprocess Basic code.')
13 parser.add_argument('infile', type=str,
14 help='the Basic file to preprocess')
15 parser.add_argument('--keep-rems', action='store_true', default=False,
16 help='The type of REMs to keep (0 (none) -> 4 (all)')
17 parser.add_argument('--keep-blank-lines', action='store_true', default=False,
18 help='Keep blank lines from the original file')
19 parser.add_argument('--keep-indent', action='store_true', default=False,
20 help='Keep line identing')
21 parser.add_argument('--number-lines', action='store_true', default=False,
22 help='Number the lines')
23 parser.add_argument('--keep-labels', action='store_true', default=False,
24 help='Keep string labels instead of replacing with line numbers')
25
26 return parser.parse_args()
27
28 # pull in include files
29 def resolve_includes(orig_lines, keep_rems=0):
30 included = {}
31 lines = []
32 for line in orig_lines:
33 m = re.match(r"^ *REM \$INCLUDE: '([^']*)' *$", line)
34 if m and m.group(1) not in included:
35 f = m.group(1)
36 if f not in included:
37 ilines = [l.rstrip() for l in open(f).readlines()]
38 if keep_rems: lines.append("REM vvv BEGIN '%s' vvv" % f)
39 lines.extend(ilines)
40 if keep_rems: lines.append("REM ^^^ END '%s' ^^^" % f)
41 else:
42 debug("Ignoring already included file: %s" % f)
43 else:
44 lines.append(line)
45 return lines
46
47 def drop_blank_lines(orig_lines):
48 lines = []
49 for line in orig_lines:
50 if re.match(r"^\w*$", line): continue
51 lines.append(line)
52 return lines
53
54
55 def drop_rems(orig_lines):
56 lines = []
57 for line in orig_lines:
58 if re.match(r"^ *REM", line):
59 continue
60 m = re.match(r"^(.*): *REM .*$", line)
61 if m:
62 lines.append(m.group(1))
63 else:
64 lines.append(line)
65 return lines
66
67 def remove_indent(orig_lines):
68 lines = []
69 for line in orig_lines:
70 m = re.match(r"^ *([^ ].*)$", line)
71 lines.append(m.group(1))
72 return lines
73
74 def number_lines(orig_lines, keep_labels=True):
75 # number lines
76 lnum=1
77 labels = {}
78 lines = []
79 for line in orig_lines:
80 if not keep_labels:
81 m = re.match(r"^ *([^ ]*): *$", line)
82 if m:
83 labels[m.groups(1)] = lnum
84 continue
85 lines.append("%s %s" % (lnum, line))
86 lnum += 1
87
88 if not keep_labels:
89 text = "\n".join(lines)
90 # search for and replace GOTO/GOSUBs
91 for label, lnum in labels.items():
92 text = re.sub(r"(THEN) %s\b" % label, r"THEN %s" % lnum, text)
93 text = re.sub(r"(ON [^:]* GOTO [^:]*) %s\b" % label, r"\1 %s" % lnum, text)
94 text = re.sub(r"(ON [^:]* GOSUB [^:]*) %s\b" % label, r"\2 %s" % lnum, text)
95 text = re.sub(r"(GOSUB) %s\b" % label, r"\1 %s" % lnum, text)
96 text = re.sub(r"(GOTO) %s\b" % label, r"\1 %s" % lnum, text)
97 return text.split("\n")
98 else:
99 return lines
100
101 if __name__ == '__main__':
102 args = parse_args()
103
104 debug("Preprocessing basic file '"+args.infile+"'")
105
106 # read in lines
107 lines = [l.rstrip() for l in open(args.infile).readlines()]
108 debug("Number of original lines: %s" % len(lines))
109
110 # pull in include files
111 lines = resolve_includes(lines, keep_rems=args.keep_rems)
112 debug("Number of lines after includes: %s" % len(lines))
113
114 # drop blank lines
115 if not args.keep_blank_lines:
116 lines = drop_blank_lines(lines)
117 debug("Number of lines after dropping blank lines: %s" % len(lines))
118
119 # keep/drop REMs
120 if not args.keep_rems:
121 lines = drop_rems(lines)
122 debug("Number of lines after dropping REMs: %s" % len(lines))
123
124 # keep/remove the indenting
125 if not args.keep_indent:
126 lines = remove_indent(lines)
127
128 # number lines
129 if args.number_lines:
130 lines = number_lines(lines, keep_labels=args.keep_labels)
131
132 print("\n".join(lines))