mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-01-25 22:00:14 +00:00
468 lines
16 KiB
Python
468 lines
16 KiB
Python
from sys import argv, stderr, float_info
|
|
import sys
|
|
from upstream.parsePolyglot import parseYAML
|
|
from os import walk
|
|
from os.path import join
|
|
from re import sub, match, split, DOTALL
|
|
from collections import namedtuple
|
|
import ast
|
|
|
|
verbosity = 1
|
|
|
|
try:
|
|
NameConstant = ast.NameConstant
|
|
except:
|
|
NameConstant = lambda a: a
|
|
|
|
class Discard(Exception):
|
|
pass
|
|
|
|
class Unhandled(Exception):
|
|
pass
|
|
|
|
failed = False
|
|
|
|
Ctx = namedtuple('Ctx', ['vars', 'context', 'type'])
|
|
|
|
def convert(python, prec, file, type):
|
|
try:
|
|
expr = ast.parse(python, filename=file, mode='eval').body
|
|
cxx = to_cxx(expr, prec, Ctx(vars=[], type=type, context=None))
|
|
return sub('" \\+ "', '', cxx)
|
|
except (Unhandled, AssertionError):
|
|
print("While translating: " + python, file=stderr)
|
|
raise
|
|
except SyntaxError as e:
|
|
raise Unhandled("syntax error: " + str(e) + ": " + repr(python))
|
|
|
|
def py_str(py):
|
|
def maybe_unstr(s):
|
|
if '(' in s:
|
|
return s
|
|
else:
|
|
return repr(s)
|
|
if type(py) is dict:
|
|
return '{' + ', '.join([repr(k) + ': ' + maybe_str(py[k]) for k in py]) + '}'
|
|
if not isinstance(py, "".__class__):
|
|
return repr(py)
|
|
return py
|
|
|
|
def rename(id):
|
|
return {
|
|
'R::default': 'R::default_',
|
|
'default': 'default_',
|
|
'R::do': 'R::do_',
|
|
'do': 'do_',
|
|
'union': 'union_',
|
|
'False': 'false',
|
|
'True': 'true',
|
|
'xrange': 'R::range',
|
|
'None': 'R::Nil()',
|
|
'null': 'R::Nil()',
|
|
'delete': 'delete_',
|
|
'float': 'double',
|
|
'int_cmp': 'int',
|
|
'float_cmp': 'double',
|
|
'range': 'R::range',
|
|
'list': '',
|
|
'R::union': 'R::union_'
|
|
}.get(id, id)
|
|
|
|
def to_cxx_str(expr):
|
|
if type(expr) is ast.Str:
|
|
return string(expr.s)
|
|
if type(expr) is ast.Num:
|
|
return string(str(expr.n))
|
|
if 'frozenset' in ast.dump(expr):
|
|
raise Discard("frozenset not supported")
|
|
if type(expr) is ast.Name:
|
|
raise Discard("dict with non-string key")
|
|
raise Unhandled("not string expr: " + ast.dump(expr))
|
|
|
|
def is_null(expr):
|
|
return (type(expr) is ast.Name and expr.id in ['None', 'null']
|
|
or type(expr) is NameConstant and expr.value == None)
|
|
|
|
def is_bool(expr):
|
|
return (type(expr) is ast.Name and expr.id in ['true', 'false', 'True', 'False']
|
|
or type(expr) is NameConstant and expr.value in [True, False])
|
|
|
|
def to_cxx_expr(expr, prec, ctx):
|
|
if ctx.type == 'query':
|
|
if type(expr) in [ast.Str, ast.Num] or is_null(expr) or is_bool(expr):
|
|
return "R::expr(" + to_cxx(expr, 17, ctx) + ")"
|
|
return to_cxx(expr, prec, ctx)
|
|
|
|
def to_cxx(expr, prec, ctx, parentType=None):
|
|
context = ctx.context
|
|
ctx = Ctx(vars=ctx.vars, type=ctx.type, context=None)
|
|
try:
|
|
t = type(expr)
|
|
if t == ast.Num:
|
|
if abs(expr.n) > 4503599627370496:
|
|
f = repr(expr.n)
|
|
if "e" in f:
|
|
return f
|
|
else:
|
|
return f + ".0"
|
|
else:
|
|
return repr(expr.n)
|
|
elif t == ast.Call:
|
|
#assert not expr.kwargs
|
|
#assert not expr.starargs
|
|
return to_cxx(expr.func, 2, ctx_set(ctx, context='function')) + to_args(expr.func, expr.args, expr.keywords, ctx)
|
|
elif t == ast.Attribute:
|
|
if type(expr.value) is ast.Name:
|
|
if expr.value.id == 'r':
|
|
if expr.attr == 'error' and context != 'function':
|
|
return "R::error()"
|
|
if expr.attr == 'binary':
|
|
if ctx.type == 'query':
|
|
return 'R::binary'
|
|
else:
|
|
return 'R::Binary'
|
|
return rename("R::" + expr.attr)
|
|
elif expr.value.id == 'datetime':
|
|
if expr.attr == 'fromtimestamp':
|
|
return "R::Time"
|
|
elif expr.attr == 'now':
|
|
return "R::Time::now"
|
|
if expr.attr == 'RqlTzinfo':
|
|
return 'R::Time::parse_utc_offset'
|
|
if expr.attr in ['encode', 'close']:
|
|
raise Discard(expr.attr + " not supported")
|
|
return to_cxx_expr(expr.value, 2, ctx) + "." + rename(expr.attr)
|
|
elif t == ast.Name:
|
|
if expr.id in ['frozenset']:
|
|
raise Discard("frozenset not supported")
|
|
elif expr.id in ctx.vars:
|
|
if ctx.type == 'query':
|
|
return parens(prec, 3, "*" + expr.id)
|
|
else:
|
|
return expr.id
|
|
elif (expr.id == 'range' or expr.id == 'xrange') and ctx.type != 'query':
|
|
return 'array_range'
|
|
elif expr.id == 'nil' and ctx.type == 'query':
|
|
return 'R::expr(nil)'
|
|
return rename(expr.id)
|
|
elif t == NameConstant:
|
|
if expr.value == True:
|
|
return "true"
|
|
elif expr.value == False:
|
|
return "false"
|
|
elif expr.value == None:
|
|
return "R::Nil()"
|
|
else:
|
|
raise Unhandled("constant: " + repr(expr.value))
|
|
elif t == ast.Subscript:
|
|
st = type(expr.slice)
|
|
if st == ast.Index:
|
|
return to_cxx(expr.value, 2, ctx) + "[" + to_cxx(expr.slice.value, 17, ctx) + "]"
|
|
if st == ast.Slice:
|
|
assert not expr.slice.step
|
|
if not expr.slice.upper:
|
|
return to_cxx(expr.value, 2, ctx) + ".slice(" + to_cxx(expr.slice.lower, 17, ctx) + ")"
|
|
if not expr.slice.lower:
|
|
return to_cxx(expr.value, 2, ctx) + ".limit(" + to_cxx(expr.slice.upper, 17, ctx) + ")"
|
|
return to_cxx(expr.value, 2, ctx) + ".slice(" + to_cxx(expr.slice.lower, 17, ctx) + ", " + to_cxx(expr.slice.upper, 17, ctx) + ")"
|
|
else:
|
|
raise Unhandled("slice type: " + repr(st))
|
|
elif t == ast.Dict:
|
|
if ctx.type == 'query':
|
|
return "R::object(" + ', '.join([to_cxx(k, 17, ctx) + ", " + to_cxx(v, 17, ctx) for k, v in zip(expr.keys, expr.values)]) + ")"
|
|
else:
|
|
return "R::Object{" + ', '.join(["{" + to_cxx_str(k) + ", " + to_cxx(v, 17, ctx) + "}" for k, v in zip(expr.keys, expr.values)]) + "}"
|
|
elif t == ast.Str:
|
|
return string(expr.s, ctx)
|
|
elif t == ast.List:
|
|
if ctx.type == 'query':
|
|
return "R::array(" + ', '.join([to_cxx(el, 17, ctx) for el in expr.elts]) + ")"
|
|
else:
|
|
if parentType == ast.List:
|
|
return "{ R::Array{" + ', '.join([to_cxx(el, 17, ctx, t) for el in expr.elts]) + "} }"
|
|
else:
|
|
return "R::Array{" + ', '.join([to_cxx(el, 17, ctx, t) for el in expr.elts]) + "}"
|
|
elif t == ast.Lambda:
|
|
assert not expr.args.vararg
|
|
assert not expr.args.kwarg
|
|
ctx = ctx_set(ctx, vars = ctx.vars + [arg.arg for arg in expr.args.args])
|
|
return "[=](" + ', '.join(['R::Var ' + arg.arg for arg in expr.args.args]) + "){ return " + to_cxx_expr(expr.body, 17, ctx_set(ctx, type='query')) + "; }"
|
|
elif t == ast.BinOp:
|
|
if type(expr.op) is ast.Mult and type(expr.left) is ast.Str:
|
|
return "repeat(" + to_cxx(expr.left, 17, ctx) + ", " + to_cxx(expr.right, 17, ctx) + ")"
|
|
ll = type(expr.left) is ast.List or type(expr.left) is ast.ListComp
|
|
rl = type(expr.right) is ast.List or type(expr.right) is ast.ListComp
|
|
op, op_prec = convert_op(expr.op)
|
|
if type(expr.op) is ast.Add and ll and rl:
|
|
return "append(" + to_cxx_expr(expr.left, op_prec, ctx) + ", " + to_cxx(expr.right, op_prec, ctx) + ")"
|
|
if op_prec:
|
|
return parens(prec, op_prec, to_cxx_expr(expr.left, op_prec, ctx) + " " + op + " " + to_cxx(expr.right, op_prec, ctx))
|
|
else:
|
|
return op + "(" + to_cxx(expr.left, 17, ctx) + ", " + to_cxx(expr.right, 17, ctx) + ")"
|
|
elif t == ast.ListComp:
|
|
assert len(expr.generators) == 1
|
|
assert type(expr.generators[0]) == ast.comprehension
|
|
assert type(expr.generators[0].target) == ast.Name
|
|
assert expr.generators[0].ifs == []
|
|
seq = to_cxx(expr.generators[0].iter, 2, ctx)
|
|
if ctx.type == 'query':
|
|
var = expr.generators[0].target.id
|
|
body = to_cxx(expr.elt, 17, ctx_set(ctx, vars = ctx.vars + [var]))
|
|
return seq + ".map([=](R::Var " + var + "){ return " + body + "; })"
|
|
else:
|
|
var = expr.generators[0].target.id
|
|
body = to_cxx(expr.elt, 17, ctx_set(ctx, vars = ctx.vars + [var]))
|
|
# assume int
|
|
return "array_map([=](int " + var + "){ return " + body + "; }, " + seq + ")"
|
|
elif t == ast.Compare:
|
|
assert len(expr.ops) == 1
|
|
assert len(expr.comparators) == 1
|
|
op, op_prec = convert_op(expr.ops[0])
|
|
return parens(prec, op_prec, to_cxx_expr(expr.left, op_prec, ctx) + op + to_cxx(expr.comparators[0], op_prec, ctx))
|
|
elif t == ast.UnaryOp:
|
|
op, op_prec = convert_op(expr.op)
|
|
return parens(prec, op_prec, op + to_cxx(expr.operand, op_prec, ctx))
|
|
elif t == ast.Bytes:
|
|
return string(expr.s, ctx)
|
|
elif t == ast.Tuple:
|
|
if ctx.type == 'query':
|
|
return "R::array(" + ', '.join([to_cxx(el, 17, ctx) for el in expr.elts]) + ")"
|
|
else:
|
|
return "R::Array{" + ', '.join([to_cxx(el, 17, ctx) for el in expr.elts]) + "}"
|
|
else:
|
|
raise Unhandled('ast type: ' + repr(t) + ', fields: ' + str(expr._fields))
|
|
except Unhandled:
|
|
print("While translating: " + ast.dump(expr), file=stderr)
|
|
raise
|
|
|
|
def ctx_set(ctx, context=None, vars=None, type=None):
|
|
if context is None:
|
|
context = ctx.context
|
|
if vars is None:
|
|
vars = ctx.vars
|
|
if type is None:
|
|
type = ctx.type
|
|
return Ctx(vars=vars, type=type, context=context)
|
|
|
|
def convert_op(op):
|
|
t = type(op)
|
|
if t == ast.Add:
|
|
return '+', 6
|
|
if t == ast.Sub:
|
|
return '-', 6
|
|
if t == ast.Mod:
|
|
return '%', 5
|
|
if t == ast.Mult:
|
|
return '*', 5
|
|
if t == ast.Div:
|
|
return '/', 5
|
|
if t == ast.Pow:
|
|
return 'pow', 0
|
|
if t == ast.Eq:
|
|
return '==', 9
|
|
if t == ast.NotEq:
|
|
return '!=', 9
|
|
if t == ast.Lt:
|
|
return '<', 8
|
|
if t == ast.Gt:
|
|
return '>', 8
|
|
if t == ast.GtE:
|
|
return '>=', 8
|
|
if t == ast.LtE:
|
|
return '<=', 8
|
|
if t == ast.USub:
|
|
return '-', 3
|
|
if t == ast.BitAnd:
|
|
return '&&', 13
|
|
if t == ast.BitOr:
|
|
return '||', 14
|
|
if t == ast.Invert:
|
|
return '!', 3
|
|
else:
|
|
raise Unhandled('op type: ' + repr(t))
|
|
|
|
def to_args(func, args, optargs, ctx):
|
|
it = func
|
|
while type(it) is ast.Attribute:
|
|
it = it.value
|
|
if type(it) is ast.Call:
|
|
ctx = ctx_set(ctx, type='query')
|
|
break
|
|
if type(it) is ast.Name and it.id == 'r':
|
|
ctx = ctx_set(ctx, type='query')
|
|
ret = "("
|
|
ret = ret + ', '.join([to_cxx(arg, 17, ctx) for arg in args])
|
|
o = list(optargs)
|
|
if o:
|
|
out = []
|
|
for f in o:
|
|
out.append("{" + string(f.arg) + ", R::expr(" + to_cxx(f.value, 17, ctx) + ")}")
|
|
if args:
|
|
ret = ret + ", "
|
|
ret = ret + "R::OptArgs{" + ', '.join(out) + "}"
|
|
return ret + ")"
|
|
|
|
def string(s, ctx=None):
|
|
was_hex = False
|
|
wrap = ctx and ctx.type == 'string'
|
|
if type(s) is str:
|
|
s = s.encode('utf8')
|
|
if type(s) is bytes:
|
|
def string_escape(c):
|
|
nonlocal wrap
|
|
nonlocal was_hex
|
|
if c == 0:
|
|
wrap = True
|
|
if c < 32 or c > 127 or (was_hex and chr(c) in "0123456789abcdefABCDEF"):
|
|
was_hex = True
|
|
return '\\x' + ('0' + hex(c)[2:])[-2:]
|
|
was_hex = False
|
|
if c == 34:
|
|
return '\\"'
|
|
if c == 92:
|
|
return '\\\\'
|
|
else:
|
|
return chr(c)
|
|
else:
|
|
raise Unhandled("string type: " + repr(type(s)))
|
|
e = '"' + ''.join([string_escape(c) for c in s]) + '"'
|
|
if wrap:
|
|
return "std::string(" + e + ", " + str(len(s)) + ")"
|
|
return e
|
|
|
|
def parens(prec, in_prec, cxx):
|
|
if in_prec >= prec:
|
|
return "(" + cxx + ")"
|
|
else:
|
|
return cxx
|
|
|
|
print("// auto-generated by yaml_to_cxx.py from " + argv[1])
|
|
print("#include \"testlib.h\"")
|
|
|
|
indent = 0
|
|
|
|
def p(s):
|
|
print((indent * " ") + s);
|
|
|
|
def enter(s = ""):
|
|
if s:
|
|
p(s)
|
|
global indent
|
|
indent = indent + 1
|
|
|
|
def exit(s = ""):
|
|
global indent
|
|
indent = indent - 1
|
|
if s:
|
|
p(s)
|
|
|
|
def get(o, ks, d):
|
|
try:
|
|
for k in ks:
|
|
if k in o:
|
|
return o[k]
|
|
except:
|
|
pass
|
|
return d
|
|
|
|
def python_tests(tests):
|
|
for test in tests:
|
|
runopts = get(test, ['runopts'], None)
|
|
try:
|
|
ot = py_str(get(test['ot'], ['py', 'cd'], test['ot']))
|
|
except:
|
|
try:
|
|
ot = py_str(test['py']['ot'])
|
|
except:
|
|
ot = None
|
|
if 'def' in test:
|
|
py = get(test['def'], ['py', 'cd'], test['def'])
|
|
if py and type(py) is not dict:
|
|
yield py_str(py), None, 'def', runopts
|
|
py = get(test, ['py', 'cd'], None)
|
|
if py:
|
|
if isinstance(py, "".__class__):
|
|
yield py, ot, 'query', runopts
|
|
elif type(py) is dict and 'cd' in py:
|
|
yield py_str(py['cd']), ot, 'query', runopts
|
|
else:
|
|
for t in py:
|
|
yield py_str(t), ot, 'query', runopts
|
|
|
|
def maybe_discard(py, ot):
|
|
if ot is None:
|
|
return
|
|
if match(".*Expected .* argument", ot):
|
|
raise Discard("argument checks not supported")
|
|
if match(".*argument .* must", ot):
|
|
raise Discard("argument checks not supported")
|
|
if match(".*infix bitwise", ot):
|
|
raise Discard("infix bitwise not supported")
|
|
if match(".*Object keys must be strings", ot):
|
|
raise Discard("string object keys tests not supported")
|
|
if match(".*Got .* argument", ot):
|
|
raise Discard("argument checks not supported")
|
|
if match(".*AttributeError.*", ot):
|
|
raise Discard("attribute checks not supported, will cause a compiler error")
|
|
|
|
data = parseYAML(open(argv[1]).read())
|
|
|
|
name = sub('/', '_', argv[1].split('.')[0])
|
|
|
|
enter("void %s() {" % name)
|
|
|
|
p("enter_section(\"%s: %s\");" % (name, data['desc'].replace('"', '\\"')))
|
|
|
|
if 'table_variable_name' in data:
|
|
for var in split(" |, ", data['table_variable_name']):
|
|
p("temp_table %s_table;" % var)
|
|
p("R::Term %s = %s_table.table();" % (var, var))
|
|
|
|
defined = []
|
|
for py, ot, tp, runopts in python_tests(data["tests"]):
|
|
try:
|
|
maybe_discard(py, ot)
|
|
assignment = match("\\A(\\w+) *= *([^=].*)\\Z", py, DOTALL)
|
|
if runopts:
|
|
args = ", R::optargs(" + ', '.join(['"' + k + '", ' + convert(py_str(runopts[k]), 17, name, 'value') for k in runopts]) + ")"
|
|
else:
|
|
args = ''
|
|
if assignment:
|
|
var = assignment.group(1)
|
|
if var == 'float_max':
|
|
p('auto float_max = ' + repr(float_info.max) + ";")
|
|
elif var == 'float_min':
|
|
p('auto float_min = ' + repr(float_info.min) + ";")
|
|
else:
|
|
if tp == 'def' and var not in ['bad_insert', 'trows']:
|
|
val = convert(assignment.group(2), 15, name, 'string')
|
|
post = ""
|
|
else:
|
|
val = convert(assignment.group(2), 15, name, 'query')
|
|
post = ".run(*conn" + args + ")"
|
|
if var in defined:
|
|
dvar = var
|
|
else:
|
|
defined.append(var);
|
|
dvar = "auto " + var
|
|
p("TEST_DO(" + dvar + " = (" + val + post + "));")
|
|
elif ot:
|
|
p("TEST_EQ(maybe_run(%s, *conn%s), (%s));" % (convert(py, 2, name, 'query'), args, convert(ot, 17, name, 'datum')))
|
|
else:
|
|
p("TEST_DO(maybe_run(%s, *conn%s));" % (convert(py, 2, name, 'query'), args))
|
|
except Discard as exc:
|
|
if verbosity >= 1:
|
|
print("Discarding %s (%s): %s" % (repr(py), repr(ot), str(exc)), file=stderr)
|
|
pass
|
|
except Unhandled as e:
|
|
failed = True
|
|
print(argv[1] + ": could not translate: " + str(e), file=stderr)
|
|
|
|
p("section_cleanup();")
|
|
p("exit_section();")
|
|
|
|
exit("}")
|
|
|
|
if failed:
|
|
sys.exit(1)
|