Skip to content

Instantly share code, notes, and snippets.

@AstraLuma
Created July 5, 2015 06:51
Show Gist options
  • Select an option

  • Save AstraLuma/f02f7cd5f720247b24e6 to your computer and use it in GitHub Desktop.

Select an option

Save AstraLuma/f02f7cd5f720247b24e6 to your computer and use it in GitHub Desktop.
Compile a python expression to forward polish notation (antimony style)
import ast
import io
class SubSyntaxError(SyntaxError):
"""
A syntax error in parsing or little mini language.
"""
class Equ:
_cache = {}
def __init__(self, expr, **vars):
self.python = expr
if '__noarg__' in vars:
self.vars = ...
else:
self.vars = vars
@property
def math(self):
return str(self).format(self.vars)
def __str__(self):
if self.python not in Equ._cache:
Equ._cache[self.python] = self._compile()
return Equ._cache[self.python]
def _compile(self):
tree = ast.parse(self.python, mode='exec', filename='<equation>')
buf = io.StringIO()
maker = Visitor(buf.write, ... if self.vars is ... else self.vars.keys())
maker.visit(tree)
return buf.getvalue()
class Visitor(ast.NodeVisitor):
BIFUNCS = {
'min': 'i',
'max': 'a',
}
UNIFUNCS = {
'sin': 's',
'cos': 'c',
'tan': 't',
'asin': 'S',
'acos': 'C',
'atan': 'T',
'abs': 'b',
'sqrt': 'r',
}
def __init__(self, feedee, varnames):
self.varnames = varnames
self.feedee = feedee
def visit_Num(self, node):
if isinstance(node.n, complex):
raise SubSyntaxError("Complex types not allowed")
self.feedee('f{:g}'.format(node.n))
def visit_Name(self, node):
if node.id in 'XYZ':
self.feedee(node.id)
elif self.varnames is not ... and node.id not in self.varnames:
raise SubSyntaxError("Unknown variable {}".format(node.id))
else:
self.feedee("f{"+node.id+"}")
def visit_Expr(self, node):
self.visit(node.value)
def visit_UnaryOp(self, node):
mname = "visit_" + type(node.op).__name__
if hasattr(self, mname):
getattr(self, mname)(node)
else:
self.generic_visit(node)
def visit_USub(self, node):
self.feedee('n')
self.visit(node.operand)
def visit_BinOp(self, node):
mname = "visit_" + type(node.op).__name__
if hasattr(self, mname):
getattr(self, mname)(node)
else:
self.generic_visit(node)
def visit_Add(self, node):
self.feedee('+')
self.visit(node.left)
self.visit(node.right)
def visit_Sub(self, node):
self.feedee('-')
self.visit(node.left)
self.visit(node.right)
def visit_Mult(self, node):
self.feedee('*')
self.visit(node.left)
self.visit(node.right)
def visit_Div(self, node):
self.feedee('/')
self.visit(node.left)
self.visit(node.right)
def visit_Pow(self, node):
if isinstance(node.right, ast.Num) and node.right.n == 2:
self.feedee('q')
self.visit(node.left)
else:
self.feedee('p')
self.visit(node.left)
self.visit(node.right)
def visit_Call(self, node):
if node.keywords or node.starargs or node.kwargs:
raise SubSyntaxError("Can only use explicit positional arguments")
if not isinstance(node.func, ast.Name):
raise SubSyntaxError("Can only use names for calls")
fname = node.func.id
if fname in self.UNIFUNCS:
# Single-argument functions. Super easy.
self.feedee(self.UNIFUNCS[fname])
self.visit(node.args[0])
elif fname in self.BIFUNCS:
args = node.args
if len(args) < 2:
# Seriously?
self.visit(args[0])
elif len(args) == 2:
self.feedee(self.BIFUNCS[fname])
self.visit(args[0])
self.visit(args[1])
else: # len(args) > 2
# Remap to a series of 2-calls
for a in args[:-1]:
self.feedee(self.BIFUNCS[fname])
self.visit(a)
self.visit(args[-1])
else:
raise SubSyntaxError("Unknown function {}".format(fname))
def visit_With(self, node):
# Have to vastly remap this
self.feedee('m')
maps = {}
for m in node.items:
if not isinstance(m.optional_vars, ast.Name):
raise SubSyntaxError("with must have a simple as")
if m.optional_vars.id not in 'XYZ':
raise SubSyntaxError("Can only use X, Y, and Z in with")
maps[m.optional_vars.id] = m.context_expr
for v in 'XYZ':
if v in maps:
self.visit(maps[v])
else:
self.feedee(' ')
self.visit(node.body[0])
def visit_Module(self, node):
if len(node.body) != 1:
raise SubSyntaxError("Can only give one statement")
self.visit(node.body[0])
def generic_visit(self, node):
raise SubSyntaxError("Can't use a {}".format(type(node).__name__))
if __name__ == '__main__':
import sys
e = sys.argv[1]
print(e, "->", Equ(e, __noarg__=True))
offset:
-{f()}f{o}
circle:
-r+qXqYf{r}
-r+q-Xf{x}q-Yf{y}f{r}
polygon_radius:
-f{r_}Y
triangle:
-*f{dy}g-Xf{x}g*f{dx}g-Yf{y}g
right_trangle:
aa-f{x}gX-f{y}gY-X/+*f{x}g-Yf{y}g*+f{x}gf{w}g-+f{y}gf{h}gYf{h}g
rectangle:
aa-f{xmin}gX-Xf{xmax}ga-f{ymin}gY-Yf{ymax}g
move:
-Xf{dx}
-Yf{dy}
-Zf{dz}
+Xf{dx}
+Yf{dy}
+Zf{dz}
rotate:
+*f{ca}gX*f{sa}gY
+*f{nsa}gX*f{ca}gY
+*f{nca}gX*f{nsa}gY
+*f{sa}gX*f{nca}gY
reflect_x:
-*f2f{x0}X
reflect_y:
-*f2f{y0}Y
reflect_z:
-*f2f{z0}Z
reflect_xy:
+f{x0}g-Yf{y0}g
+f{y0}g-Xf{x0}g
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment