Coverage for kye/compiler.py: 0%
175 statements
« prev ^ index » next coverage.py v7.3.2, created at 2024-01-12 11:19 -0700
« prev ^ index » next coverage.py v7.3.2, created at 2024-01-12 11:19 -0700
1from __future__ import annotations
2from typing import Optional, Literal, Union
3import kye.parser.kye_ast as AST
4import kye.types as Types
6class Meta:
7 kind: Literal['edge', 'type']
8 user_defined: bool
9 type_ref: str
11 def __init__(self, kind: Literal['edge', 'type'], user_defined: bool, type_ref: str):
12 self.kind = kind
13 self.user_defined = user_defined
14 self.type_ref = type_ref
16class Compiler:
17 definitions: dict[str, Types.Definition]
18 ast: dict[str, AST.Expression]
19 meta: dict[str, Meta]
21 def __init__(self):
22 self.definitions = {}
23 self.ast = {}
24 self.meta = {}
25 Type = self.define_native_type('Type')
26 Object = self.define_native_type('Object')
27 String = self.define_native_type('String', extends=Object)
28 Number = self.define_native_type('Number', extends=Object)
29 Boolean = self.define_native_type('Boolean', extends=Object)
30 self.define_native_edge(model=Type, name='$and', args=[Type], returns=Type)
31 self.define_native_edge(model=Type, name='$or', args=[Type], returns=Type)
32 self.define_native_edge(model=Type, name='$xor', args=[Type], returns=Type)
33 self.define_native_edge(model=Type, name='$eq', args=[Object], returns=Type)
34 self.define_native_edge(model=Type, name='$ne', args=[Object], returns=Type)
35 self.define_native_edge(model=Type, name='$gt', args=[Object], returns=Type)
36 self.define_native_edge(model=Type, name='$lt', args=[Object], returns=Type)
37 self.define_native_edge(model=Type, name='$gte', args=[Object], returns=Type)
38 self.define_native_edge(model=Type, name='$lte', args=[Object], returns=Type)
39 self.define_native_edge(model=Type, name='$filter', args=[Boolean], returns=Type)
40 self.define_native_edge(model=Object, name='$eq', args=[Object], returns=Boolean)
41 self.define_native_edge(model=Object, name='$ne', args=[Object], returns=Boolean)
42 self.define_native_edge(model=Boolean, name='$and', args=[Boolean], returns=Boolean)
43 self.define_native_edge(model=Boolean, name='$or', args=[Boolean], returns=Boolean)
44 self.define_native_edge(model=Boolean, name='$xor', args=[Boolean], returns=Boolean)
45 self.define_native_edge(model=Number, name='$gt', args=[Number], returns=Boolean)
46 self.define_native_edge(model=Number, name='$lt', args=[Number], returns=Boolean)
47 self.define_native_edge(model=Number, name='$gte', args=[Number], returns=Boolean)
48 self.define_native_edge(model=Number, name='$lte', args=[Number], returns=Boolean)
49 self.define_native_edge(model=Number, name='$add', args=[Number], returns=Number)
50 self.define_native_edge(model=Number, name='$sub', args=[Number], returns=Number)
51 self.define_native_edge(model=Number, name='$mul', args=[Number], returns=Number)
52 self.define_native_edge(model=Number, name='$div', args=[Number], returns=Number)
53 self.define_native_edge(model=Number, name='$mod', args=[Number], returns=Number)
54 self.define_native_edge(model=String, name='length', returns=Number)
55 self.define_native_edge(model=String, name='$gt', args=[String], returns=Boolean)
56 self.define_native_edge(model=String, name='$lt', args=[String], returns=Boolean)
57 self.define_native_edge(model=String, name='$gte', args=[String], returns=Boolean)
58 self.define_native_edge(model=String, name='$lte', args=[String], returns=Boolean)
59 self.define_native_edge(model=String, name='$add', args=[String], returns=String)
61 @property
62 def edges(self) -> list[Types.Edge]:
63 return [
64 self.get_edge(ref)
65 for ref, meta in self.meta.items()
66 if meta.kind == 'edge' and meta.user_defined
67 ]
69 @property
70 def types(self) -> list[Types.Type]:
71 return [
72 self.get_type(ref, include_edges=True)
73 for ref, meta in self.meta.items()
74 if meta.kind == 'type' and meta.user_defined
75 ]
77 def get_models(self) -> Types.Models:
78 return {
79 type_ref: self.get_type(type_ref, include_edges=True)
80 for type_ref, meta in self.meta.items()
81 if meta.kind == 'type' and meta.user_defined
82 }
84 def get_edges(self, type_ref: str) -> list[Types.Edge]:
85 assert self.meta[type_ref].kind == 'type'
86 return [
87 self.get_edge(edge_ref)
88 for edge_ref, meta in self.meta.items()
89 if meta.kind == 'edge' and meta.type_ref == type_ref
90 ]
92 def define_native_type(self, *args, **kwargs) -> Types.Type:
93 typ = Types.Type(*args, **kwargs)
94 self.definitions[typ.ref] = typ
95 self.meta[typ.ref] = Meta(kind='type', user_defined=False, type_ref=typ.ref)
96 return typ
98 def define_native_edge(self, *args, **kwargs) -> Types.Edge:
99 edge = Types.Edge(*args, **kwargs)
100 self.definitions[edge.ref] = edge
101 self.meta[edge.ref] = Meta(kind='edge', user_defined=False, type_ref=edge.model.ref)
102 return edge
104 def _save_ast(self, type_ref: str, ref: str, ast: AST.Definition):
105 assert ref not in self.ast
106 assert ref not in self.definitions
107 self.ast[ref] = ast
108 self.meta[ref] = Meta(
109 kind='type' if isinstance(ast, AST.TypeDefinition) else 'edge',
110 user_defined=True,
111 type_ref=type_ref,
112 )
114 def read_edge(self, type_ref: str, ast: AST.EdgeDefinition) -> Compiler:
115 self._save_ast(type_ref, type_ref + '.' + ast.name, ast)
116 return self
118 def read_type(self, ast: AST.TypeDefinition) -> Compiler:
119 self._save_ast(ast.name, ast.name, ast)
120 if isinstance(ast, AST.ModelDefinition):
121 for edge_ast in ast.edges:
122 self.read_edge(ast.name, edge_ast)
123 return self
125 def read_definitions(self, ast: AST.ModuleDefinitions) -> Compiler:
126 for child in ast.children:
127 self.read_type(child)
128 return self
130 def _get(self, ref: str) -> Types.Definition:
131 if ref not in self.meta:
132 raise KeyError(f'Unknown symbol `{ref}`')
134 # Already compiled
135 if ref in self.definitions:
136 existing = self.definitions[ref]
137 if existing is None:
138 raise Exception(f'Possible circular reference for `{ref}`')
139 return existing
141 # Clear the ref from the table first,
142 # so that if the function calls itself
143 # it will get a circular reference error
144 self.definitions[ref] = None
145 if self.meta[ref].kind == 'edge':
146 self.definitions[ref] = self.compile_edge(self.ast[ref], self.get_type(self.meta[ref].type_ref))
147 elif self.meta[ref].kind == 'type':
148 self.definitions[ref] = self.compile_type(self.ast[ref])
149 assert isinstance(self.definitions[ref], Types.Definition)
150 return self.definitions[ref]
152 def get_type(self, type_ref: str, include_edges=False) -> Types.Type:
153 typ = self._get(type_ref)
154 assert isinstance(typ, Types.Type)
155 if include_edges:
156 for edge in self.get_edges(type_ref):
157 if edge.name not in typ.edges:
158 typ.edges[edge.name] = edge
159 else:
160 assert typ.edges[edge.name] == edge
161 return typ
163 def get_edge(self, edge_ref: str) -> Types.Edge:
164 edge = self._get(edge_ref)
165 assert isinstance(edge, Types.Edge)
166 return edge
168 def lookup_edge(self, typ: Types.Type, name: str) -> Types.Edge:
169 extended_type = typ
170 ref = extended_type.ref + '.' + name
171 while ref not in self.meta and extended_type.extends is not None:
172 extended_type = extended_type.extends
173 ref = extended_type.ref + '.' + name
175 if ref not in self.meta:
176 raise KeyError(f"Unknown edge `{typ.ref}.{name}`")
178 return self.get_edge(ref)
180 def compile_edge(self, ast: AST.EdgeDefinition, model: Types.Type):
181 assert isinstance(ast, AST.EdgeDefinition)
182 return Types.Edge(
183 name=ast.name,
184 model=model,
185 nullable=ast.cardinality in ('?','*'),
186 multiple=ast.cardinality in ('+','*'),
187 loc=ast.meta,
188 expr=self.compile_expression(ast.type, model),
189 )
191 def compile_type(self, ast: AST.TypeDefinition):
192 assert isinstance(ast, AST.TypeDefinition)
193 if isinstance(ast, AST.AliasDefinition):
194 return Types.Type(
195 ref=ast.name,
196 loc=ast.meta,
197 expr=self.compile_expression(ast.type, None),
198 )
199 if isinstance(ast, AST.ModelDefinition):
200 return Types.Type(
201 ref=ast.name,
202 indexes=ast.indexes,
203 loc=ast.meta,
204 extends=self.get_type('Object'),
205 )
206 raise Exception('Unknown TypeDefinition')
208 def compile_expression(self, ast: AST.Expression, typ: Optional[Types.Type]) -> Types.Expression:
209 assert isinstance(ast, AST.Expression)
210 if isinstance(ast, AST.Identifier):
211 if ast.kind == 'type':
212 return Types.TypeRefExpression(
213 type=self.get_type(ast.name),
214 loc=ast.meta,
215 returns=self.get_type('Type'),
216 )
217 if ast.kind == 'edge':
218 edge = self.lookup_edge(typ, ast.name)
219 return Types.EdgeRefExpression(
220 edge=edge,
221 loc=ast.meta,
222 )
223 elif isinstance(ast, AST.LiteralExpression):
224 if type(ast.value) is str:
225 typ = self.get_type('String')
226 elif type(ast.value) is bool:
227 typ = self.get_type('Boolean')
228 elif isinstance(ast.value, (int, float)):
229 typ = self.get_type('Number')
230 else:
231 raise Exception()
232 return Types.LiteralExpression(type=typ, value=ast.value, loc=ast.meta)
233 elif isinstance(ast, AST.Operation):
234 assert len(ast.children) >= 1
235 expr = self.compile_expression(ast.children[0], typ)
236 if ast.name == 'filter':
237 assert len(ast.children) <= 2
238 if len(ast.children) == 2:
239 assert expr.is_type()
240 filter = self.compile_expression(ast.children[1], expr.get_context())
241 expr = Types.CallExpression(
242 bound=expr,
243 args=[filter],
244 edge=self.get_edge('Type.$filter'),
245 loc=ast.meta,
246 )
247 elif ast.name == 'dot':
248 assert len(ast.children) >= 2
249 for child in ast.children[1:]:
250 expr = self.compile_expression(child, expr.get_context())
251 else:
252 for child in ast.children[1:]:
253 expr = Types.CallExpression(
254 bound=expr,
255 args=[
256 self.compile_expression(child, typ)
257 ],
258 edge=self.lookup_edge(expr.returns, '$' + ast.name),
259 loc=ast.meta,
260 )
261 return expr
262 else:
263 raise Exception('Unknown Expression')