Coverage for kye/parser/types.py: 36%

149 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-01 16:38 -0700

1from __future__ import annotations 

2from typing import Optional, Literal, Union, TYPE_CHECKING 

3 

4if TYPE_CHECKING: 

5 from kye.parser.environment import Environment 

6 

7class Expression: 

8 """ Abstract interface for Types, Edges & Values """ 

9 name: Optional[str] 

10 env: Environment 

11 

12 def extends(self, other: Expression) -> bool: 

13 raise NotImplementedError() 

14 

15 def __getitem__(self, key: str) -> Edge: 

16 raise NotImplementedError() 

17 

18 def __contains__(self, key: str) -> bool: 

19 raise NotImplementedError() 

20 

21class Value(Expression): 

22 value: str | int | float | bool 

23 type: Type 

24 

25 def __init__(self, type: Type, value: str | int | float | bool): 

26 super().__init__() 

27 self.name = None 

28 self.type = type 

29 self.value = value 

30 

31 def extends(self, other: Expression) -> bool: 

32 if not self.type.extends(other): 

33 return False 

34 if isinstance(other, Value): 

35 return self.value == other.value 

36 return True 

37 

38 def __getitem__(self, key: str) -> Edge: 

39 return self.type[key].bind(self) 

40 

41 def __contains__(self, key: str) -> bool: 

42 return key in self.type 

43 

44 def __repr__(self): 

45 return "{}({})".format( 

46 self.type.name, 

47 repr(self.value), 

48 ) 

49 

50 

51class Type(Expression): 

52 name: str 

53 edges: dict[str, Edge] 

54 filter: Optional[Edge] 

55 

56 def __init__(self, 

57 name: str, 

58 edges: Optional[dict[str, Edge]] = None, 

59 filter: Optional[Edge] = None, 

60 env: Optional[Environment] = None, 

61 ): 

62 self.name = name 

63 self.edges = edges or {} 

64 self.filter = filter 

65 self.env = env 

66 assert self.env is None or self.env.name == name 

67 

68 def _extend_filter(self, filter: Optional[Edge]): 

69 if self.filter: 

70 if filter is not None: 

71 filter = filter['__and__'].apply(self.filter) 

72 else: 

73 filter = self.filter 

74 return filter 

75 

76 def _extend_edges(self, edges: dict[str, Edge]): 

77 edges = {**edges} 

78 for key, edge in self.edges.items(): 

79 # If over writing the edge, ensure 

80 # that it can extend the parent's edge 

81 if key in edges: 

82 assert edges[key].extends(edge) 

83 else: 

84 edges[key] = edge 

85 return edges 

86 

87 def extend(self, 

88 name: Optional[str] = None, 

89 edges: dict[str, Edge] = {}, 

90 filter: Optional[Edge] = None, 

91 env: Optional[Environment] = None, 

92 ): 

93 return Type( 

94 name=name or self.name, 

95 edges=self._extend_edges(edges), 

96 filter=self._extend_filter(filter), 

97 env=env or self.env, 

98 ) 

99 

100 def extends(self, other: Expression) -> bool: 

101 if isinstance(other, Edge): 

102 return self.extends(other.returns) 

103 if isinstance(other, Value): 

104 return self.extends(other.type) 

105 if isinstance(other, Type): 

106 for other_key, other_edge in other.edges.items(): 

107 if other_key not in self: 

108 return False 

109 if not self[other_key].extends(other_edge): 

110 return False 

111 # TODO: Also check if our filter is a subset of the other's filter? 

112 return True 

113 raise Exception('How did you get here?') 

114 

115 def select(self, value: str | float | int | bool): 

116 return Value(type=self, value=value) 

117 

118 def __getitem__(self, key: str) -> Edge: 

119 return self.edges[key] 

120 

121 def __contains__(self, key: str): 

122 return key in self.edges 

123 

124 def __repr__(self): 

125 return '{}{}'.format( 

126 self.name, 

127 '{' + ','.join(repr(edge) for edge in self.edges.values()) + '}' if len(self.edges) else '', 

128 ) 

129 

130class Model(Type): 

131 indexes: list[list[str]] 

132 

133 def __init__(self, 

134 name: str, 

135 indexes: list[list[str]], 

136 edges: dict[str, Edge] = {}, 

137 filter: Optional[Edge] = None, 

138 env: Optional[Environment] = None, 

139 ): 

140 super().__init__(name, edges, filter, env) 

141 assert len(indexes) > 0 

142 self.indexes = indexes 

143 

144 def extend(self, 

145 name: Optional[str] = None, 

146 indexes: Optional[list[list[str]]] = None, 

147 edges: dict[str, Edge] = {}, 

148 filter: Optional[Edge] = None, 

149 env: Optional[Environment] = None, 

150 ): 

151 return Model( 

152 name=name or self.name, 

153 indexes=indexes or self.indexes, 

154 edges=self._extend_edges(edges), 

155 filter=self._extend_filter(filter), 

156 env=env or self.env, 

157 ) 

158 

159 def extends(self, other: Expression) -> bool: 

160 if not super().extends(other): 

161 return False 

162 if isinstance(other, Model): 

163 indexes = {tuple(idx) for idx in self.indexes} 

164 for other_idx in other.indexes: 

165 if tuple(other_idx) not in indexes: 

166 return False 

167 return True 

168 

169 @property 

170 def index(self) -> set[str]: 

171 """ Flatten the 2d list of indexes into a set """ 

172 return {idx for idxs in self.indexes for idx in idxs} 

173 

174 def __repr__(self): 

175 non_index_edges = [ 

176 repr(self.edges[edge]) 

177 for edge in self.edges.keys() 

178 if edge not in self.index 

179 ] 

180 return '{}{}{}'.format( 

181 self.name, 

182 ''.join('(' + ','.join(repr(self.edges[edge]) for edge in idx) + ')' for idx in self.indexes), 

183 '{' + ','.join(non_index_edges) + '}' if len(non_index_edges) else '', 

184 ) 

185 

186class Edge(Expression): 

187 owner: Type 

188 name: Optional[str] 

189 returns: Type 

190 parameters: list[Type] 

191 bound: Optional[Expression] 

192 values: list[Optional[Edge]] 

193 nullable: bool 

194 multiple: bool 

195 

196 def __init__(self, 

197 owner: Type, 

198 name: Optional[str], 

199 returns: Type, 

200 parameters: list[Type] = [], 

201 values: list[Optional[Edge]] = [], 

202 bound: Optional[Expression] = None, 

203 nullable: bool = False, 

204 multiple: bool = False, 

205 ): 

206 self.owner = owner 

207 self.name = name 

208 self.returns = returns 

209 self.parameters = parameters 

210 assert bound is None or bound.extends(self.owner) 

211 self.bound = bound 

212 self.values = self._normalize_values(values) 

213 self.nullable = nullable 

214 self.multiple = multiple 

215 

216 def _copy(self, **kwargs): 

217 return Edge(**{**self.__dict__, **kwargs}) 

218 

219 def _normalize_values(self, values: list[Edge]): 

220 assert len(values) <= len(self.parameters) 

221 for i, val in enumerate(values): 

222 assert val is None or val.extends(self.parameters[i]) 

223 # fill the undefined values with null 

224 values = values + [None] * (len(self.parameters) - len(values)) 

225 return values 

226 

227 def bind(self, exp: Optional[Expression]) -> Edge: 

228 return self._copy(bound=exp) 

229 

230 def apply(self, values: list[Optional[Edge]]): 

231 return self._copy(values=values) 

232 

233 def extends(self, other: Expression) -> bool: 

234 if not self.returns.extends(other): 

235 return False 

236 if isinstance(other, Edge): 

237 if not self.nullable and other.nullable: 

238 return False 

239 if not self.multiple and other.multiple: 

240 return False 

241 # TODO: Might also want to check parameters and values? 

242 return True 

243 

244 def __getitem__(self, key: str) -> Edge: 

245 return self.returns[key].bind(self) 

246 

247 def __contains__(self, key: str) -> bool: 

248 return key in self.returns 

249 

250 def __repr__(self): 

251 return "{}{}".format( 

252 self.name, 

253 ([['' ,'+'], 

254 ['?','*']])[int(self.nullable)][int(self.multiple)] 

255 ) 

256 

257if __name__ == '__main__': 

258 boolean = Type('Boolean') 

259 number = Type('Number') 

260 number.edges['__gt__'] = Edge(owner=number, name='__gt__', parameters=[number], returns=boolean) 

261 number.edges['__lt__'] = Edge(owner=number, name='__lt__', parameters=[number], returns=boolean) 

262 string = Type('String') 

263 string.edges['length'] = Edge(owner=string, name='length', returns=number) 

264 big_string = string.extend(name='BigString') 

265 big_string.filter = big_string['length']['__gt__'].apply([number.select(5)]) 

266 print('hi')