Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2@file 

3@brief One class which visits a syntax tree. 

4""" 

5 

6import ast 

7import inspect 

8from .code_exception import CodeException 

9from .node_visitor_translator import CodeNodeVisitor 

10 

11 

12class TranslateClass: 

13 

14 """ 

15 Interface for a class which translates a code 

16 written in pseudo-SQL syntax into another language. 

17 """ 

18 

19 def __init__(self, code_func): 

20 """ 

21 Constructor. 

22 

23 @param code_func code (str) or function(func) 

24 """ 

25 if isinstance(code_func, str): 

26 code = code_func 

27 else: 

28 code = inspect.getsource(code_func) 

29 self.init(code) 

30 

31 def init(self, code): 

32 """ 

33 Parses the function code and add it the class, 

34 it complements the constructor. 

35 

36 @param code function code 

37 """ 

38 node = ast.parse(code) 

39 v = CodeNodeVisitor() 

40 v.visit(node) 

41 

42 self._rows = v.Rows 

43 self._code = code 

44 

45 def __str__(self): 

46 """ 

47 Returns a string representing a tree. 

48 """ 

49 return self.to_str() 

50 

51 def to_str(self, fields=None): 

52 """ 

53 Returns a string representing a tree. 

54 

55 @param fields additional fields to add at the end of each row 

56 @return string 

57 """ 

58 if fields is None: 

59 fields = [] 

60 if len(fields) == 0: 

61 rows = ["{0}{1}: {2} - nbch {3}".format(" " * r["indent"], r["type"], r["str"], len(r.get("children", []))) 

62 for r in self._rows] 

63 else: 

64 rows = ["{0}{1}: {2} - nbch {3}".format(" " * r["indent"], r["type"], r["str"], len(r.get("children", []))) 

65 + " --- " + ",".join(["%s=%s" % 

66 (_, r.get(_, "")) for _ in fields]) 

67 for r in self._rows] 

68 

69 return "\n".join(rows) 

70 

71 def Code(self): 

72 """ 

73 Returns the code of the initial Python function 

74 into another language. 

75 

76 @return str 

77 """ 

78 # we add a field "processed" in each rows to tell it was interpreted 

79 for row in self._rows: 

80 row["processed"] = row["type"] == "Module" 

81 

82 code_rows = [] 

83 

84 for row in self._rows: 

85 if row["processed"]: 

86 continue 

87 

88 if row["type"] == "FunctionDef": 

89 res = self.interpretFunction(row) 

90 if res is not None and len(res) > 0: 

91 code_rows.extend(res) 

92 

93 for row in self._rows: 

94 if not row["processed"]: 

95 return self.RaiseCodeException( 

96 "the function was unable to interpret all the lines", 

97 code_rows=code_rows) 

98 

99 return "\n".join(code_rows) 

100 

101 def RaiseCodeException(self, message, field="processed", code_rows=None): 

102 """ 

103 Raises an exception when interpreting the code. 

104 

105 @param field field to add to the message exception 

106 @param code_rows list of rows to display 

107 

108 :raises: CodeException 

109 """ 

110 if code_rows is None: 

111 code_rows = [] 

112 if len(code_rows) > 0: 

113 if "_status" in self.__dict__ and len(self._status) > 0: 

114 code_rows = code_rows + ["", "-- STATUS --", ""] + self._status 

115 raise CodeException(message + "\n---tree:\n" 

116 + self.to_str(["processed"]) + 

117 "\n\n---so far:\n" 

118 + "\n".join(code_rows)) 

119 elif "_status" in self.__dict__: 

120 raise CodeException(message + "\n---tree:\n" 

121 + self.to_str(["processed"]) + 

122 "\n\n-- STATUS --\n" 

123 + "\n".join(self._status)) 

124 else: 

125 raise CodeException(message + "\n---tree:\n" 

126 + self.to_str(["processed"])) 

127 

128 def interpretFunction(self, obj): 

129 """ 

130 Starts the interpretation of node which begins a function. 

131 

132 @param obj obj to begin with (a function) 

133 @return list of strings 

134 """ 

135 if "children" not in obj: 

136 return self.RaiseCodeException("children key is missing") 

137 if "name" not in obj: 

138 return self.RaiseCodeException("name is missing") 

139 

140 obj["processed"] = True 

141 chil = obj["children"] 

142 code_rows = [] 

143 

144 # signature 

145 name = obj["name"] 

146 argus = [_ for _ in chil if _["type"] == "arguments"] 

147 args = [] 

148 for a in argus: 

149 a["processed"] = True 

150 for ch in a["children"]: 

151 if ch["type"] == "arg": 

152 ch["processed"] = True 

153 args.append(ch) 

154 names = [_["str"] for _ in args] 

155 

156 sign = self.Signature(name, names) 

157 if sign is not None and len(sign) > 0: 

158 code_rows.extend(sign) 

159 

160 # the rest 

161 self._status = code_rows # for debugging purpose 

162 # assi = [_ for _ in chil if _["type"] == "Assign"] 

163 for an in chil: 

164 if an["type"] == "Assign": 

165 one = self.Intruction(an) 

166 if one is not None and len(one) > 0: 

167 code_rows.extend(one) 

168 elif an["type"] == "Return": 

169 one = self.interpretReturn(an) 

170 if one is not None and len(one) > 0: 

171 code_rows.extend(one) 

172 elif an["type"] == "arguments": 

173 pass 

174 else: 

175 return self.RaiseCodeException("unexpected type: " + an["type"]) 

176 return code_rows 

177 

178 def Signature(self, name, rows): 

179 """ 

180 Build the signature of a function based on its name and its children. 

181 

182 @param name name 

183 @param rows node where type == arguments 

184 @return list of strings (code) 

185 """ 

186 return self.RaiseCodeException("not implemented") 

187 

188 def Intruction(self, rows): 

189 """ 

190 Builds an instruction of a function based on its name and its children. 

191 

192 @param rows node where type == Assign 

193 @return list of strings (code) 

194 """ 

195 rows["processed"] = True 

196 chil = rows["children"] 

197 name = [_ for _ in chil if _["type"] == "Name"] 

198 if len(name) != 1: 

199 return self.RaiseCodeException( 

200 "expecting only one row not %d" % 

201 len(chil)) 

202 call = [_ for _ in chil if _["type"] == "Call"] 

203 if len(call) != 1: 

204 return self.RaiseCodeException( 

205 "expecting only one row not %d" % 

206 len(call)) 

207 

208 name = name[0] 

209 name["processed"] = True 

210 

211 call = call[0] 

212 call["processed"] = True 

213 

214 varn = name["str"] 

215 kind = call["str"] 

216 

217 # the first attribute gives the name the table 

218 method = call["children"][0] 

219 method["processed"] = True 

220 table, meth = method["str"].split(".") 

221 if meth != kind: 

222 return self.RaiseCodeException( 

223 "cannot go further, expects: {0}=={1}".format( 

224 kind, 

225 meth)) 

226 

227 if kind == "select": 

228 top = call["children"][1:] 

229 return self.Select(varn, table, top) 

230 elif kind == "where": 

231 top = call["children"][1:] 

232 return self.Where(varn, table, top) 

233 elif kind == "groupby": 

234 top = call["children"][1:] 

235 return self.GroupBy(varn, table, top) 

236 else: 

237 return self.RaiseCodeException("not implemented for: " + kind) 

238 

239 def Select(self, name, table, rows): 

240 """ 

241 Interprets a select statement. 

242 

243 @param name name of the table which receives the results 

244 @param table name of the table it applies to 

245 @param rows rows to consider 

246 @return list of strings (code) 

247 """ 

248 return self.RaiseCodeException("not implemented") 

249 

250 def Where(self, name, table, rows): 

251 """ 

252 Interprets a select statement. 

253 

254 @param name name of the table which receives the results 

255 @param table name of the table it applies to 

256 @param rows rows to consider 

257 @return list of strings (code) 

258 """ 

259 return self.RaiseCodeException("not implemented") 

260 

261 _symbols = {"Lt": "<", "Gt": ">", "Mult": "*", } 

262 

263 def ResolveExpression(self, node, prefixAtt): 

264 """ 

265 Produces an expression based on a a node and its children. 

266 

267 @param node node 

268 @param prefixAtt prefix to add before an attribute (usually _) 

269 @return a string, the used fields, the called functions 

270 """ 

271 if node["type"] == "keyword": 

272 chil = node["children"] 

273 if len(chil) == 1: 

274 node["processed"] = True 

275 return self.ResolveExpression(chil[0], prefixAtt) 

276 else: 

277 return self.RaiseCodeException( 

278 "not implemented for type: " 

279 + node["type"]) 

280 

281 elif node["type"] == "BinOp" or node["type"] == "Compare": 

282 chil = node["children"] 

283 if len(chil) == 3: 

284 node["processed"] = True 

285 ex1, fi1, fu1 = self.ResolveExpression(chil[0], prefixAtt) 

286 ex2, fi2, fu2 = self.ResolveExpression(chil[1], prefixAtt) 

287 ex3, fi3, fu3 = self.ResolveExpression(chil[2], prefixAtt) 

288 fi1.update(fi2) 

289 fi1.update(fi3) 

290 fu1.update(fu2) 

291 fu1.update(fu3) 

292 ex = "{0}{1}{2}".format(ex1, ex2, ex3) 

293 return ex, fi1, fu1 

294 else: 

295 return self.RaiseCodeException( 

296 "not implemented for type: " 

297 + node["type"]) 

298 

299 elif node["type"] in TranslateClass._symbols: 

300 node["processed"] = True 

301 return TranslateClass._symbols[node["type"]], {}, {} 

302 

303 elif node["type"] == "Attribute": 

304 node["processed"] = True 

305 return prefixAtt + node["str"], {node["str"]: node}, {} 

306 

307 elif node["type"] == "Num": 

308 node["processed"] = True 

309 return node["str"], {}, {} 

310 

311 elif node["type"] == "Call": 

312 node["processed"] = True 

313 

314 expre = [] 

315 field = {} 

316 funcs = {} 

317 

318 if node["str"] in ["Or", "And", "Not"]: 

319 for chil in node["children"]: 

320 if chil["type"] == "Attribute": 

321 if chil["str"] != node["str"]: 

322 return self.RaiseCodeException("incoherence") 

323 elif len(chil["children"]) != 1: 

324 return self.RaiseCodeException("incoherence") 

325 else: 

326 chil["processed"] = True 

327 ex, fi, fu = self.ResolveExpression( 

328 chil["children"][0], prefixAtt) 

329 expre.append("({0})".format(ex)) 

330 field.update(fi) 

331 funcs.update(funcs) 

332 expre.append(node["str"].lower()) 

333 else: 

334 ex, fi, fu = self.ResolveExpression(chil, prefixAtt) 

335 expre.append("({0})".format(ex)) 

336 field.update(fi) 

337 funcs.update(funcs) 

338 

339 elif node["str"] == "CFT": 

340 # we need to look further as CFT is a way to call a function 

341 funcName = None 

342 subexp = [] 

343 for chil in node["children"]: 

344 if chil["type"] == "Attribute": 

345 chil["processed"] = True 

346 ex, fi, fu = self.ResolveExpression(chil, prefixAtt) 

347 subexp.append(ex) 

348 field.update(fi) 

349 funcs.update(funcs) 

350 elif chil["type"] == "Name" and chil["str"] == "CFT": 

351 pass 

352 elif chil["type"] == "Name": 

353 # we call function chil["str"] 

354 funcName = chil["str"] 

355 funcs[chil["str"]] = chil["str"] 

356 else: 

357 return self.RaiseCodeException( 

358 "unexpected configuration: " 

359 + node["type"]) 

360 chil["processed"] = True 

361 expre.append("{0}({1})".format(funcName, ",".join(subexp))) 

362 

363 elif node["str"] == "len": 

364 # aggregated function 

365 funcName = None 

366 subexp = [] 

367 for chil in node["children"]: 

368 if chil["type"] == "Attribute": 

369 chil["processed"] = True 

370 ex, fi, fu = self.ResolveExpression(chil, prefixAtt) 

371 subexp.append(ex) 

372 field.update(fi) 

373 funcs.update(funcs) 

374 elif chil["type"] == "Name" and chil["str"] == "CFT": 

375 pass 

376 elif chil["type"] == "Name": 

377 # we call function chil["str"] 

378 funcName = chil["str"] 

379 funcs[chil["str"]] = chil["str"] 

380 else: 

381 return self.RaiseCodeException( 

382 "unexpected configuration: " 

383 + node["type"]) 

384 chil["processed"] = True 

385 expre.append("{0}({1})".format(funcName, ",".join(subexp))) 

386 else: 

387 return self.RaiseCodeException( 

388 "not implemented for function: " 

389 + node["str"]) 

390 return " ".join(expre), field, funcs 

391 

392 else: 

393 return self.RaiseCodeException( 

394 "not implemented for type: " 

395 + node["type"]) 

396 

397 def interpretReturn(self, obj): 

398 """ 

399 Starts the interpretation of a node which sets a return. 

400 

401 @param obj obj to begin with (a function) 

402 @return list of strings 

403 """ 

404 if "children" not in obj: 

405 return self.RaiseCodeException("children key is missing") 

406 allret = [] 

407 obj["processed"] = True 

408 for node in obj["children"]: 

409 if node["type"] == "Name": 

410 allret.append(node) 

411 else: 

412 return self.RaiseCodeException("unexpected type: " + node["type"]) 

413 return self.setReturn(allret) 

414 

415 def setReturn(self, nodes): 

416 """ 

417 Indicates all nodes containing information about returned results. 

418 

419 @param node list of nodes 

420 @return list of string 

421 """ 

422 return self.RaiseCodeException("not implemented")