yaml_to_cxx.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. from sys import argv, stderr, float_info
  2. import sys
  3. from upstream.parsePolyglot import parseYAML
  4. from os import walk
  5. from os.path import join
  6. from re import sub, match, split, DOTALL
  7. from collections import namedtuple
  8. import ast
  9. verbosity = 1
  10. try:
  11. NameConstant = ast.NameConstant
  12. except:
  13. NameConstant = lambda a: a
  14. class Discard(Exception):
  15. pass
  16. class Unhandled(Exception):
  17. pass
  18. failed = False
  19. Ctx = namedtuple('Ctx', ['vars', 'context', 'type'])
  20. def convert(python, prec, file, type):
  21. try:
  22. expr = ast.parse(python, filename=file, mode='eval').body
  23. cxx = to_cxx(expr, prec, Ctx(vars=[], type=type, context=None))
  24. return sub('" \\+ "', '', cxx)
  25. except (Unhandled, AssertionError):
  26. print("While translating: " + python, file=stderr)
  27. raise
  28. except SyntaxError as e:
  29. raise Unhandled("syntax error: " + str(e) + ": " + repr(python))
  30. def py_str(py):
  31. def maybe_unstr(s):
  32. if '(' in s:
  33. return s
  34. else:
  35. return repr(s)
  36. if type(py) is dict:
  37. return '{' + ', '.join([repr(k) + ': ' + maybe_str(py[k]) for k in py]) + '}'
  38. if not isinstance(py, "".__class__):
  39. return repr(py)
  40. return py
  41. def rename(id):
  42. return {
  43. 'R::default': 'R::default_',
  44. 'default': 'default_',
  45. 'R::do': 'R::do_',
  46. 'do': 'do_',
  47. 'union': 'union_',
  48. 'False': 'false',
  49. 'True': 'true',
  50. 'xrange': 'R::range',
  51. 'None': 'R::Nil()',
  52. 'null': 'R::Nil()',
  53. 'delete': 'delete_',
  54. 'float': 'double',
  55. 'int_cmp': 'int',
  56. 'float_cmp': 'double',
  57. 'range': 'R::range',
  58. 'list': '',
  59. 'R::union': 'R::union_'
  60. }.get(id, id)
  61. def to_cxx_str(expr):
  62. if type(expr) is ast.Str:
  63. return string(expr.s)
  64. if type(expr) is ast.Num:
  65. return string(str(expr.n))
  66. if 'frozenset' in ast.dump(expr):
  67. raise Discard("frozenset not supported")
  68. if type(expr) is ast.Name:
  69. raise Discard("dict with non-string key")
  70. raise Unhandled("not string expr: " + ast.dump(expr))
  71. def is_null(expr):
  72. return (type(expr) is ast.Name and expr.id in ['None', 'null']
  73. or type(expr) is NameConstant and expr.value == None)
  74. def is_bool(expr):
  75. return (type(expr) is ast.Name and expr.id in ['true', 'false', 'True', 'False']
  76. or type(expr) is NameConstant and expr.value in [True, False])
  77. def to_cxx_expr(expr, prec, ctx):
  78. if ctx.type == 'query':
  79. if type(expr) in [ast.Str, ast.Num] or is_null(expr) or is_bool(expr):
  80. return "R::expr(" + to_cxx(expr, 17, ctx) + ")"
  81. return to_cxx(expr, prec, ctx)
  82. def to_cxx(expr, prec, ctx, parentType=None):
  83. context = ctx.context
  84. ctx = Ctx(vars=ctx.vars, type=ctx.type, context=None)
  85. try:
  86. t = type(expr)
  87. if t == ast.Num:
  88. if abs(expr.n) > 4503599627370496:
  89. f = repr(expr.n)
  90. if "e" in f:
  91. return f
  92. else:
  93. return f + ".0"
  94. else:
  95. return repr(expr.n)
  96. elif t == ast.Call:
  97. #assert not expr.kwargs
  98. #assert not expr.starargs
  99. return to_cxx(expr.func, 2, ctx_set(ctx, context='function')) + to_args(expr.func, expr.args, expr.keywords, ctx)
  100. elif t == ast.Attribute:
  101. if type(expr.value) is ast.Name:
  102. if expr.value.id == 'r':
  103. if expr.attr == 'error' and context != 'function':
  104. return "R::error()"
  105. if expr.attr == 'binary':
  106. if ctx.type == 'query':
  107. return 'R::binary'
  108. else:
  109. return 'R::Binary'
  110. return rename("R::" + expr.attr)
  111. elif expr.value.id == 'datetime':
  112. if expr.attr == 'fromtimestamp':
  113. return "R::Time"
  114. elif expr.attr == 'now':
  115. return "R::Time::now"
  116. if expr.attr == 'RqlTzinfo':
  117. return 'R::Time::parse_utc_offset'
  118. if expr.attr in ['encode', 'close']:
  119. raise Discard(expr.attr + " not supported")
  120. return to_cxx_expr(expr.value, 2, ctx) + "." + rename(expr.attr)
  121. elif t == ast.Name:
  122. if expr.id in ['frozenset']:
  123. raise Discard("frozenset not supported")
  124. elif expr.id in ctx.vars:
  125. if ctx.type == 'query':
  126. return parens(prec, 3, "*" + expr.id)
  127. else:
  128. return expr.id
  129. elif (expr.id == 'range' or expr.id == 'xrange') and ctx.type != 'query':
  130. return 'array_range'
  131. elif expr.id == 'nil' and ctx.type == 'query':
  132. return 'R::expr(nil)'
  133. return rename(expr.id)
  134. elif t == NameConstant:
  135. if expr.value == True:
  136. return "true"
  137. elif expr.value == False:
  138. return "false"
  139. elif expr.value == None:
  140. return "R::Nil()"
  141. else:
  142. raise Unhandled("constant: " + repr(expr.value))
  143. elif t == ast.Subscript:
  144. st = type(expr.slice)
  145. if st == ast.Index:
  146. return to_cxx(expr.value, 2, ctx) + "[" + to_cxx(expr.slice.value, 17, ctx) + "]"
  147. if st == ast.Slice:
  148. assert not expr.slice.step
  149. if not expr.slice.upper:
  150. return to_cxx(expr.value, 2, ctx) + ".slice(" + to_cxx(expr.slice.lower, 17, ctx) + ")"
  151. if not expr.slice.lower:
  152. return to_cxx(expr.value, 2, ctx) + ".limit(" + to_cxx(expr.slice.upper, 17, ctx) + ")"
  153. return to_cxx(expr.value, 2, ctx) + ".slice(" + to_cxx(expr.slice.lower, 17, ctx) + ", " + to_cxx(expr.slice.upper, 17, ctx) + ")"
  154. else:
  155. raise Unhandled("slice type: " + repr(st))
  156. elif t == ast.Dict:
  157. if ctx.type == 'query':
  158. return "R::object(" + ', '.join([to_cxx(k, 17, ctx) + ", " + to_cxx(v, 17, ctx) for k, v in zip(expr.keys, expr.values)]) + ")"
  159. else:
  160. return "R::Object{" + ', '.join(["{" + to_cxx_str(k) + ", " + to_cxx(v, 17, ctx) + "}" for k, v in zip(expr.keys, expr.values)]) + "}"
  161. elif t == ast.Str:
  162. return string(expr.s, ctx)
  163. elif t == ast.List:
  164. if ctx.type == 'query':
  165. return "R::array(" + ', '.join([to_cxx(el, 17, ctx) for el in expr.elts]) + ")"
  166. else:
  167. if parentType == ast.List:
  168. return "{ R::Array{" + ', '.join([to_cxx(el, 17, ctx, t) for el in expr.elts]) + "} }"
  169. else:
  170. return "R::Array{" + ', '.join([to_cxx(el, 17, ctx, t) for el in expr.elts]) + "}"
  171. elif t == ast.Lambda:
  172. assert not expr.args.vararg
  173. assert not expr.args.kwarg
  174. ctx = ctx_set(ctx, vars = ctx.vars + [arg.arg for arg in expr.args.args])
  175. return "[=](" + ', '.join(['R::Var ' + arg.arg for arg in expr.args.args]) + "){ return " + to_cxx_expr(expr.body, 17, ctx_set(ctx, type='query')) + "; }"
  176. elif t == ast.BinOp:
  177. if type(expr.op) is ast.Mult and type(expr.left) is ast.Str:
  178. return "repeat(" + to_cxx(expr.left, 17, ctx) + ", " + to_cxx(expr.right, 17, ctx) + ")"
  179. ll = type(expr.left) is ast.List or type(expr.left) is ast.ListComp
  180. rl = type(expr.right) is ast.List or type(expr.right) is ast.ListComp
  181. op, op_prec = convert_op(expr.op)
  182. if type(expr.op) is ast.Add and ll and rl:
  183. return "append(" + to_cxx_expr(expr.left, op_prec, ctx) + ", " + to_cxx(expr.right, op_prec, ctx) + ")"
  184. if op_prec:
  185. return parens(prec, op_prec, to_cxx_expr(expr.left, op_prec, ctx) + " " + op + " " + to_cxx(expr.right, op_prec, ctx))
  186. else:
  187. return op + "(" + to_cxx(expr.left, 17, ctx) + ", " + to_cxx(expr.right, 17, ctx) + ")"
  188. elif t == ast.ListComp:
  189. assert len(expr.generators) == 1
  190. assert type(expr.generators[0]) == ast.comprehension
  191. assert type(expr.generators[0].target) == ast.Name
  192. assert expr.generators[0].ifs == []
  193. seq = to_cxx(expr.generators[0].iter, 2, ctx)
  194. if ctx.type == 'query':
  195. var = expr.generators[0].target.id
  196. body = to_cxx(expr.elt, 17, ctx_set(ctx, vars = ctx.vars + [var]))
  197. return seq + ".map([=](R::Var " + var + "){ return " + body + "; })"
  198. else:
  199. var = expr.generators[0].target.id
  200. body = to_cxx(expr.elt, 17, ctx_set(ctx, vars = ctx.vars + [var]))
  201. # assume int
  202. return "array_map([=](int " + var + "){ return " + body + "; }, " + seq + ")"
  203. elif t == ast.Compare:
  204. assert len(expr.ops) == 1
  205. assert len(expr.comparators) == 1
  206. op, op_prec = convert_op(expr.ops[0])
  207. return parens(prec, op_prec, to_cxx_expr(expr.left, op_prec, ctx) + op + to_cxx(expr.comparators[0], op_prec, ctx))
  208. elif t == ast.UnaryOp:
  209. op, op_prec = convert_op(expr.op)
  210. return parens(prec, op_prec, op + to_cxx(expr.operand, op_prec, ctx))
  211. elif t == ast.Bytes:
  212. return string(expr.s, ctx)
  213. elif t == ast.Tuple:
  214. if ctx.type == 'query':
  215. return "R::array(" + ', '.join([to_cxx(el, 17, ctx) for el in expr.elts]) + ")"
  216. else:
  217. return "R::Array{" + ', '.join([to_cxx(el, 17, ctx) for el in expr.elts]) + "}"
  218. else:
  219. raise Unhandled('ast type: ' + repr(t) + ', fields: ' + str(expr._fields))
  220. except Unhandled:
  221. print("While translating: " + ast.dump(expr), file=stderr)
  222. raise
  223. def ctx_set(ctx, context=None, vars=None, type=None):
  224. if context is None:
  225. context = ctx.context
  226. if vars is None:
  227. vars = ctx.vars
  228. if type is None:
  229. type = ctx.type
  230. return Ctx(vars=vars, type=type, context=context)
  231. def convert_op(op):
  232. t = type(op)
  233. if t == ast.Add:
  234. return '+', 6
  235. if t == ast.Sub:
  236. return '-', 6
  237. if t == ast.Mod:
  238. return '%', 5
  239. if t == ast.Mult:
  240. return '*', 5
  241. if t == ast.Div:
  242. return '/', 5
  243. if t == ast.Pow:
  244. return 'pow', 0
  245. if t == ast.Eq:
  246. return '==', 9
  247. if t == ast.NotEq:
  248. return '!=', 9
  249. if t == ast.Lt:
  250. return '<', 8
  251. if t == ast.Gt:
  252. return '>', 8
  253. if t == ast.GtE:
  254. return '>=', 8
  255. if t == ast.LtE:
  256. return '<=', 8
  257. if t == ast.USub:
  258. return '-', 3
  259. if t == ast.BitAnd:
  260. return '&&', 13
  261. if t == ast.BitOr:
  262. return '||', 14
  263. if t == ast.Invert:
  264. return '!', 3
  265. else:
  266. raise Unhandled('op type: ' + repr(t))
  267. def to_args(func, args, optargs, ctx):
  268. it = func
  269. while type(it) is ast.Attribute:
  270. it = it.value
  271. if type(it) is ast.Call:
  272. ctx = ctx_set(ctx, type='query')
  273. break
  274. if type(it) is ast.Name and it.id == 'r':
  275. ctx = ctx_set(ctx, type='query')
  276. ret = "("
  277. ret = ret + ', '.join([to_cxx(arg, 17, ctx) for arg in args])
  278. o = list(optargs)
  279. if o:
  280. out = []
  281. for f in o:
  282. out.append("{" + string(f.arg) + ", R::expr(" + to_cxx(f.value, 17, ctx) + ")}")
  283. if args:
  284. ret = ret + ", "
  285. ret = ret + "R::OptArgs{" + ', '.join(out) + "}"
  286. return ret + ")"
  287. def string(s, ctx=None):
  288. was_hex = False
  289. wrap = ctx and ctx.type == 'string'
  290. if type(s) is str:
  291. s = s.encode('utf8')
  292. if type(s) is bytes:
  293. def string_escape(c):
  294. nonlocal wrap
  295. nonlocal was_hex
  296. if c == 0:
  297. wrap = True
  298. if c < 32 or c > 127 or (was_hex and chr(c) in "0123456789abcdefABCDEF"):
  299. was_hex = True
  300. return '\\x' + ('0' + hex(c)[2:])[-2:]
  301. was_hex = False
  302. if c == 34:
  303. return '\\"'
  304. if c == 92:
  305. return '\\\\'
  306. else:
  307. return chr(c)
  308. else:
  309. raise Unhandled("string type: " + repr(type(s)))
  310. e = '"' + ''.join([string_escape(c) for c in s]) + '"'
  311. if wrap:
  312. return "std::string(" + e + ", " + str(len(s)) + ")"
  313. return e
  314. def parens(prec, in_prec, cxx):
  315. if in_prec >= prec:
  316. return "(" + cxx + ")"
  317. else:
  318. return cxx
  319. print("// auto-generated by yaml_to_cxx.py from " + argv[1])
  320. print("#include \"testlib.h\"")
  321. indent = 0
  322. def p(s):
  323. print((indent * " ") + s);
  324. def enter(s = ""):
  325. if s:
  326. p(s)
  327. global indent
  328. indent = indent + 1
  329. def exit(s = ""):
  330. global indent
  331. indent = indent - 1
  332. if s:
  333. p(s)
  334. def get(o, ks, d):
  335. try:
  336. for k in ks:
  337. if k in o:
  338. return o[k]
  339. except:
  340. pass
  341. return d
  342. def python_tests(tests):
  343. for test in tests:
  344. runopts = get(test, ['runopts'], None)
  345. try:
  346. ot = py_str(get(test['ot'], ['py', 'cd'], test['ot']))
  347. except:
  348. try:
  349. ot = py_str(test['py']['ot'])
  350. except:
  351. ot = None
  352. if 'def' in test:
  353. py = get(test['def'], ['py', 'cd'], test['def'])
  354. if py and type(py) is not dict:
  355. yield py_str(py), None, 'def', runopts
  356. py = get(test, ['py', 'cd'], None)
  357. if py:
  358. if isinstance(py, "".__class__):
  359. yield py, ot, 'query', runopts
  360. elif type(py) is dict and 'cd' in py:
  361. yield py_str(py['cd']), ot, 'query', runopts
  362. else:
  363. for t in py:
  364. yield py_str(t), ot, 'query', runopts
  365. def maybe_discard(py, ot):
  366. if ot is None:
  367. return
  368. if match(".*Expected .* argument", ot):
  369. raise Discard("argument checks not supported")
  370. if match(".*argument .* must", ot):
  371. raise Discard("argument checks not supported")
  372. if match(".*infix bitwise", ot):
  373. raise Discard("infix bitwise not supported")
  374. if match(".*Object keys must be strings", ot):
  375. raise Discard("string object keys tests not supported")
  376. if match(".*Got .* argument", ot):
  377. raise Discard("argument checks not supported")
  378. if match(".*AttributeError.*", ot):
  379. raise Discard("attribute checks not supported, will cause a compiler error")
  380. data = parseYAML(open(argv[1]).read())
  381. name = sub('/', '_', argv[1].split('.')[0])
  382. enter("void %s() {" % name)
  383. p("enter_section(\"%s: %s\");" % (name, data['desc'].replace('"', '\\"')))
  384. if 'table_variable_name' in data:
  385. for var in split(" |, ", data['table_variable_name']):
  386. p("temp_table %s_table;" % var)
  387. p("R::Term %s = %s_table.table();" % (var, var))
  388. defined = []
  389. for py, ot, tp, runopts in python_tests(data["tests"]):
  390. try:
  391. maybe_discard(py, ot)
  392. assignment = match("\\A(\\w+) *= *([^=].*)\\Z", py, DOTALL)
  393. if runopts:
  394. args = ", R::optargs(" + ', '.join(['"' + k + '", ' + convert(py_str(runopts[k]), 17, name, 'value') for k in runopts]) + ")"
  395. else:
  396. args = ''
  397. if assignment:
  398. var = assignment.group(1)
  399. if var == 'float_max':
  400. p('auto float_max = ' + repr(float_info.max) + ";")
  401. elif var == 'float_min':
  402. p('auto float_min = ' + repr(float_info.min) + ";")
  403. else:
  404. if tp == 'def' and var not in ['bad_insert', 'trows']:
  405. val = convert(assignment.group(2), 15, name, 'string')
  406. post = ""
  407. else:
  408. val = convert(assignment.group(2), 15, name, 'query')
  409. post = ".run(*conn" + args + ")"
  410. if var in defined:
  411. dvar = var
  412. else:
  413. defined.append(var);
  414. dvar = "auto " + var
  415. p("TEST_DO(" + dvar + " = (" + val + post + "));")
  416. elif ot:
  417. p("TEST_EQ(maybe_run(%s, *conn%s), (%s));" % (convert(py, 2, name, 'query'), args, convert(ot, 17, name, 'datum')))
  418. else:
  419. p("TEST_DO(maybe_run(%s, *conn%s));" % (convert(py, 2, name, 'query'), args))
  420. except Discard as exc:
  421. if verbosity >= 1:
  422. print("Discarding %s (%s): %s" % (repr(py), repr(ot), str(exc)), file=stderr)
  423. pass
  424. except Unhandled as e:
  425. failed = True
  426. print(argv[1] + ": could not translate: " + str(e), file=stderr)
  427. p("section_cleanup();")
  428. p("exit_section();")
  429. exit("}")
  430. if failed:
  431. sys.exit(1)