Let's start by considering python3.8.5's grammar, in this case I'm interested to figure out how to transpile python Comparisons to c.
For the sake of simplicity, let's assume we're dealing with a very little python trivial subset and we just want to transpile trivial Compare expressions:
expr = Compare(expr left, cmpop* ops, expr* comparators)
If I'm not mistaken, in python an expression such as a<b<c
is converted into something like a<b && b<c
where b is only evaluated once... so I guess in c you should do something like bool v0=a<b; bool v1=v0<c
in order to prevent b being evaluated more than once in case the first clause is true.
Unfortunately I don't know how to put that into code, so far this is what I've got:
import ast
import shutil
import textwrap
from subprocess import PIPE
from subprocess import Popen
class Visitor(ast.NodeVisitor):
def visit(self, node):
ret = super().visit(node)
if ret is None:
raise Exception("Unsupported node")
return ret
def visit_Expr(self, node):
return f"{self.visit(node.value)};"
def visit_Eq(self, node):
return "=="
def visit_Lt(self, node):
return "<"
def visit_LtE(self, node):
return "<="
def visit_Load(self, node):
return "//load"
def visit_Name(self, node):
return f"{node.id}"
def visit_Compare(self, node):
left = self.visit(node.left)
ops = [self.visit(x) for x in node.ops]
comparators = [self.visit(x) for x in node.comparators]
if len(ops) == 1 and len(comparators) == 1:
return f"({left} {ops[0]} {comparators[0]})"
else:
lhs = ",".join([f"'{v}'" for v in ops])
rhs = ",".join([f"{v}" for v in comparators])
return f"cmp<{lhs}>({rhs})"
def visit_Call(self, node):
func = self.visit(node.func)
args = [self.visit(x) for x in node.args]
# keywords = [self.visit(x) for x in node.keywords]
return f"{func}({','.join(args)})"
def visit_Module(self, node):
return f"{''.join([self.visit(x) for x in node.body])}"
def visit_Num(self, node):
return node.n
if __name__ == "__main__":
out = Visitor().visit(
ast.parse(
textwrap.dedent(
"""
1 == 1<3
1 == (1<3)
1 == (0 < foo(0 <= bar() < 3, baz())) < (4 < 5)
foo(0 <= bar() < 3, baz())
"""
)
)
)
if shutil.which("clang-format"):
cmd = "clang-format -style webkit -offset 0 -length {} -assume-filename None"
p = Popen(
cmd.format(len(out)), stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=True
)
out = p.communicate(input=out.encode("utf-8"))[0].decode("utf-8")
print(out)
else:
print(out)
As you can see, the output will be some sort of non compilable c output:
cmp<'==', '<'>(1, 3);
(1 == (1 < 3));
cmp<'==', '<'>((0 < foo(cmp<'<=', '<'>(bar(), 3), baz())), (4 < 5));
foo(cmp<'<=', '<'>(bar(), 3), baz());
Question, what'd be the algorithm (a python working example would be ideal here but just some general pseudocode that allowed me to improve the provided snippet would be also fine) that'd allowed me to convert python Compare expressions to c?
int i1 = 1; int i2 = 1; int i3 = 3; if(i1 == i2 && i2 < i3)
. If you make the compiler even smarter, it might be able to tell that it doesn't matter if the 1 is evaluated twice, so it can change it toif(1 == 1 && 1 < 3)
– Appreciablereturn f"cmp<{lhs}>({rhs})"
? That's not what C code looks like. – Amatorycmp('==', '<'>(1, 1, 3);
for1 == 1 < 3
), but I think it makes a lot more sense to just generate code in the form that user253751 suggested. – Amatory