OILS / pea / gen_cpp.py View on Github | oils.pub

163 lines, 95 significant
1import ast
2from ast import stmt, ClassDef, FunctionDef, Assign
3
4import typing
5from typing import Any
6
7from pea.header import (TypeSyntaxError, PyFile, Program)
8from pea import parse
9
10
11class ConstVisitor(ast.NodeVisitor):
12
13 def __init__(self, const_lookup: dict[str, int]):
14 ast.NodeVisitor.__init__(self)
15 self.const_lookup = const_lookup
16 self.str_id = 0
17
18 def visit_Constant(self, o: ast.Constant) -> None:
19 if isinstance(o.value, str):
20 self.const_lookup[o.value] = self.str_id
21 self.str_id += 1
22
23
24class ForwardDeclPass:
25 """Emit forward declarations."""
26
27 # TODO: Move this to ParsePass after comparing with mycpp.
28
29 def __init__(self, f: typing.IO[str]) -> None:
30 self.f = f
31
32 def DoPyFile(self, py_file: PyFile) -> None:
33
34 # TODO: could omit empty namespaces
35 namespace = py_file.namespace
36 self.f.write(f'namespace {namespace} {{ // forward declare\n')
37
38 for stmt in py_file.module.body:
39 match stmt:
40 case ClassDef():
41 class_name = stmt.name
42 self.f.write(f' class {class_name};\n')
43
44 self.f.write(f'}} // forward declare {namespace}\n')
45 self.f.write('\n')
46
47
48class PrototypesPass:
49 """Parse signatures and Emit function prototypes."""
50
51 def __init__(self, opts: Any, prog: Program, f: typing.IO[str]) -> None:
52 self.opts = opts
53 self.prog = prog
54 self.f = f
55
56 def DoClass(self, cls: ClassDef) -> None:
57 for stmt in cls.body:
58 match stmt:
59 case FunctionDef():
60 if stmt.type_comment:
61 sig = parse.ParseFuncType(stmt) # may raise
62
63 if self.opts.verbose:
64 print('METHOD')
65 print(ast.dump(sig, indent=' '))
66 # TODO: We need to print virtual here
67
68 self.prog.method_types[stmt] = sig # save for ImplPass
69 self.prog.stats['num_methods'] += 1
70
71 # TODO: assert that there aren't top-level statements?
72 case _:
73 pass
74
75 def DoPyFile(self, py_file: PyFile) -> None:
76 for stmt in py_file.module.body:
77 match stmt:
78 case FunctionDef():
79 if stmt.type_comment:
80 sig = parse.ParseFuncType(stmt) # may raise
81
82 if self.opts.verbose:
83 print('FUNC')
84 print(ast.dump(sig, indent=' '))
85
86 self.prog.func_types[stmt] = sig # save for ImplPass
87
88 self.prog.stats['num_funcs'] += 1
89
90 case ClassDef():
91 self.DoClass(stmt)
92 self.prog.stats['num_classes'] += 1
93
94 case _:
95 # Import, Assign, etc.
96 #print(stmt)
97
98 # TODO: omit __name__ == '__main__' etc.
99 # if __name__ == '__main__'
100 pass
101
102
103class ImplPass:
104 """Emit function and method bodies.
105
106 Algorithm:
107 collect local variables first
108 """
109
110 def __init__(self, prog: Program, f: typing.IO[str]) -> None:
111 self.prog = prog
112 self.f = f
113
114 # TODO: needs to be fully recursive, so you get bodies of loops, etc.
115 def DoBlock(self, stmts: list[stmt], indent: int = 0) -> None:
116 """e.g. body of function, method, etc."""
117
118 #print('STMTS %s' % stmts)
119
120 ind_str = ' ' * indent
121
122 for stmt in stmts:
123 match stmt:
124 case Assign():
125 #print('%s* Assign' % ind_str)
126 #print(ast.dump(stmt, indent=' '))
127
128 if stmt.type_comment:
129 # This parses with the func_type production in the grammar
130 try:
131 typ = ast.parse(stmt.type_comment)
132 except SyntaxError as e:
133 # New syntax error
134 raise TypeSyntaxError(stmt.lineno,
135 stmt.type_comment)
136
137 self.prog.assign_types[stmt] = typ
138
139 #print('%s TYPE: Assign' % ind_str)
140 #print(ast.dump(typ, indent=' '))
141
142 self.prog.stats['num_assign'] += 1
143
144 case _:
145 pass
146
147 def DoClass(self, cls: ClassDef) -> None:
148 for stmt in cls.body:
149 match stmt:
150 case FunctionDef():
151 self.DoBlock(stmt.body, indent=1)
152
153 case _:
154 pass
155
156 def DoPyFile(self, py_file: PyFile) -> None:
157 for stmt in py_file.module.body:
158 match stmt:
159 case ClassDef():
160 self.DoClass(stmt)
161
162 case FunctionDef():
163 self.DoBlock(stmt.body, indent=1)