Coverage for kye/compiler.py: 0%

175 statements  

« 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 

5 

6class Meta: 

7 kind: Literal['edge', 'type'] 

8 user_defined: bool 

9 type_ref: str 

10 

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 

15 

16class Compiler: 

17 definitions: dict[str, Types.Definition] 

18 ast: dict[str, AST.Expression] 

19 meta: dict[str, Meta] 

20 

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) 

60 

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 ] 

68 

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 ] 

76 

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 } 

83 

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 ] 

91 

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 

97 

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 

103 

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 ) 

113 

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 

117 

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 

124 

125 def read_definitions(self, ast: AST.ModuleDefinitions) -> Compiler: 

126 for child in ast.children: 

127 self.read_type(child) 

128 return self 

129 

130 def _get(self, ref: str) -> Types.Definition: 

131 if ref not in self.meta: 

132 raise KeyError(f'Unknown symbol `{ref}`') 

133 

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 

140 

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] 

151 

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 

162 

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 

167 

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 

174 

175 if ref not in self.meta: 

176 raise KeyError(f"Unknown edge `{typ.ref}.{name}`") 

177 

178 return self.get_edge(ref) 

179 

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 ) 

190 

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') 

207 

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')