Created
July 5, 2015 06:51
-
-
Save AstraLuma/f02f7cd5f720247b24e6 to your computer and use it in GitHub Desktop.
Compile a python expression to forward polish notation (antimony style)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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