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"""
6import ast
7import inspect
8from .code_exception import CodeException
9from .node_visitor_translator import CodeNodeVisitor
12class TranslateClass:
14 """
15 Interface for a class which translates a code
16 written in pseudo-SQL syntax into another language.
17 """
19 def __init__(self, code_func):
20 """
21 Constructor.
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)
31 def init(self, code):
32 """
33 Parses the function code and add it the class,
34 it complements the constructor.
36 @param code function code
37 """
38 node = ast.parse(code)
39 v = CodeNodeVisitor()
40 v.visit(node)
42 self._rows = v.Rows
43 self._code = code
45 def __str__(self):
46 """
47 Returns a string representing a tree.
48 """
49 return self.to_str()
51 def to_str(self, fields=None):
52 """
53 Returns a string representing a tree.
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]
69 return "\n".join(rows)
71 def Code(self):
72 """
73 Returns the code of the initial Python function
74 into another language.
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"
82 code_rows = []
84 for row in self._rows:
85 if row["processed"]:
86 continue
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)
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)
99 return "\n".join(code_rows)
101 def RaiseCodeException(self, message, field="processed", code_rows=None):
102 """
103 Raises an exception when interpreting the code.
105 @param field field to add to the message exception
106 @param code_rows list of rows to display
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"]))
128 def interpretFunction(self, obj):
129 """
130 Starts the interpretation of node which begins a function.
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")
140 obj["processed"] = True
141 chil = obj["children"]
142 code_rows = []
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]
156 sign = self.Signature(name, names)
157 if sign is not None and len(sign) > 0:
158 code_rows.extend(sign)
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
178 def Signature(self, name, rows):
179 """
180 Build the signature of a function based on its name and its children.
182 @param name name
183 @param rows node where type == arguments
184 @return list of strings (code)
185 """
186 return self.RaiseCodeException("not implemented")
188 def Intruction(self, rows):
189 """
190 Builds an instruction of a function based on its name and its children.
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))
208 name = name[0]
209 name["processed"] = True
211 call = call[0]
212 call["processed"] = True
214 varn = name["str"]
215 kind = call["str"]
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))
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)
239 def Select(self, name, table, rows):
240 """
241 Interprets a select statement.
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")
250 def Where(self, name, table, rows):
251 """
252 Interprets a select statement.
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")
261 _symbols = {"Lt": "<", "Gt": ">", "Mult": "*", }
263 def ResolveExpression(self, node, prefixAtt):
264 """
265 Produces an expression based on a a node and its children.
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"])
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"])
299 elif node["type"] in TranslateClass._symbols:
300 node["processed"] = True
301 return TranslateClass._symbols[node["type"]], {}, {}
303 elif node["type"] == "Attribute":
304 node["processed"] = True
305 return prefixAtt + node["str"], {node["str"]: node}, {}
307 elif node["type"] == "Num":
308 node["processed"] = True
309 return node["str"], {}, {}
311 elif node["type"] == "Call":
312 node["processed"] = True
314 expre = []
315 field = {}
316 funcs = {}
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)
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)))
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
392 else:
393 return self.RaiseCodeException(
394 "not implemented for type: "
395 + node["type"])
397 def interpretReturn(self, obj):
398 """
399 Starts the interpretation of a node which sets a return.
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)
415 def setReturn(self, nodes):
416 """
417 Indicates all nodes containing information about returned results.
419 @param node list of nodes
420 @return list of string
421 """
422 return self.RaiseCodeException("not implemented")