summaryrefslogtreecommitdiff
path: root/gerbonara/aperture_macros/expression.py
diff options
context:
space:
mode:
Diffstat (limited to 'gerbonara/aperture_macros/expression.py')
-rw-r--r--gerbonara/aperture_macros/expression.py211
1 files changed, 211 insertions, 0 deletions
diff --git a/gerbonara/aperture_macros/expression.py b/gerbonara/aperture_macros/expression.py
new file mode 100644
index 0000000..0cf055a
--- /dev/null
+++ b/gerbonara/aperture_macros/expression.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright 2021 Jan Götte <gerbonara@jaseg.de>
+
+import operator
+import re
+import ast
+
+from ..utils import MM, Inch, MILLIMETERS_PER_INCH
+
+
+def expr(obj):
+ return obj if isinstance(obj, Expression) else ConstantExpression(obj)
+
+
+class Expression:
+ def optimized(self, variable_binding={}):
+ return self
+
+ def __str__(self):
+ return f'<{self.to_gerber()}>'
+
+ def __repr__(self):
+ return f'<E {self.to_gerber()}>'
+
+ def converted(self, unit):
+ return self
+
+ def calculate(self, variable_binding={}, unit=None):
+ expr = self.converted(unit).optimized(variable_binding)
+ if not isinstance(expr, ConstantExpression):
+ raise IndexError(f'Cannot fully resolve expression due to unresolved variables: {expr} with variables {variable_binding}')
+ return expr.value
+
+ def __add__(self, other):
+ return OperatorExpression(operator.add, self, expr(other)).optimized()
+
+ def __radd__(self, other):
+ return expr(other) + self
+
+ def __sub__(self, other):
+ return OperatorExpression(operator.sub, self, expr(other)).optimized()
+
+ def __rsub__(self, other):
+ return expr(other) - self
+
+ def __mul__(self, other):
+ return OperatorExpression(operator.mul, self, expr(other)).optimized()
+
+ def __rmul__(self, other):
+ return expr(other) * self
+
+ def __truediv__(self, other):
+ return OperatorExpression(operator.truediv, self, expr(other)).optimized()
+
+ def __rtruediv__(self, other):
+ return expr(other) / self
+
+ def __neg__(self):
+ return 0 - self
+
+ def __pos__(self):
+ return self
+
+class UnitExpression(Expression):
+ def __init__(self, expr, unit):
+ self._expr = expr
+ self.unit = unit
+
+ def to_gerber(self, unit=None):
+ return self.converted(unit).optimized().to_gerber()
+
+ def __eq__(self, other):
+ return type(other) == type(self) and \
+ self.unit == other.unit and\
+ self._expr == other._expr
+
+ def __str__(self):
+ return f'<{self._expr.to_gerber()} {self.unit}>'
+
+ def __repr__(self):
+ return f'<UE {self._expr.to_gerber()} {self.unit}>'
+
+ def converted(self, unit):
+ if self.unit is None or unit is None or self.unit == unit:
+ return self._expr
+
+ elif MM == unit:
+ return self._expr * MILLIMETERS_PER_INCH
+
+ elif Inch == unit:
+ return self._expr / MILLIMETERS_PER_INCH
+
+ else:
+ raise ValueError(f'invalid unit {unit}, must be "inch" or "mm".')
+
+ def __add__(self, other):
+ if not isinstance(other, UnitExpression):
+ raise ValueError('Unit mismatch: Can only add/subtract UnitExpression from UnitExpression, not scalar.')
+
+ if self.unit == other.unit or self.unit is None or other.unit is None:
+ return UnitExpression(self._expr + other._expr, self.unit)
+
+ if other.unit == 'mm': # -> and self.unit == 'inch'
+ return UnitExpression(self._expr + (other._expr / MILLIMETERS_PER_INCH), self.unit)
+ else: # other.unit == 'inch' and self.unit == 'mm'
+ return UnitExpression(self._expr + (other._expr * MILLIMETERS_PER_INCH), self.unit)
+
+ def __radd__(self, other):
+ # left hand side cannot have been an UnitExpression or __radd__ would not have been called
+ raise ValueError('Unit mismatch: Can only add/subtract UnitExpression from UnitExpression, not scalar.')
+
+ def __sub__(self, other):
+ return (self + (-other)).optimize()
+
+ def __rsub__(self, other):
+ # see __radd__ above
+ raise ValueError('Unit mismatch: Can only add/subtract UnitExpression from UnitExpression, not scalar.')
+
+ def __mul__(self, other):
+ return UnitExpression(self._expr * other, self.unit)
+
+ def __rmul__(self, other):
+ return UnitExpression(other * self._expr, self.unit)
+
+ def __truediv__(self, other):
+ return UnitExpression(self._expr / other, self.unit)
+
+ def __rtruediv__(self, other):
+ return UnitExpression(other / self._expr, self.unit)
+
+ def __neg__(self):
+ return UnitExpression(-self._expr, self.unit)
+
+ def __pos__(self):
+ return self
+
+
+class ConstantExpression(Expression):
+ def __init__(self, value):
+ self.value = value
+
+ def __float__(self):
+ return float(self.value)
+
+ def __eq__(self, other):
+ return type(self) == type(other) and self.value == other.value
+
+ def to_gerber(self, _unit=None):
+ return f'{self.value:.6f}'.rstrip('0').rstrip('.')
+
+
+class VariableExpression(Expression):
+ def __init__(self, number):
+ self.number = number
+
+ def optimized(self, variable_binding={}):
+ if self.number in variable_binding:
+ return ConstantExpression(variable_binding[self.number])
+ return self
+
+ def __eq__(self, other):
+ return type(self) == type(other) and \
+ self.number == other.number
+
+ def to_gerber(self, _unit=None):
+ return f'${self.number}'
+
+
+class OperatorExpression(Expression):
+ def __init__(self, op, l, r):
+ self.op = op
+ self.l = ConstantExpression(l) if isinstance(l, (int, float)) else l
+ self.r = ConstantExpression(r) if isinstance(r, (int, float)) else r
+
+ def __eq__(self, other):
+ return type(self) == type(other) and \
+ self.op == other.op and \
+ self.l == other.l and \
+ self.r == other.r
+
+ def optimized(self, variable_binding={}):
+ l = self.l.optimized(variable_binding)
+ r = self.r.optimized(variable_binding)
+
+ if self.op in (operator.add, operator.mul):
+ if id(r) < id(l):
+ l, r = r, l
+
+ if isinstance(l, ConstantExpression) and isinstance(r, ConstantExpression):
+ return ConstantExpression(self.op(float(l), float(r)))
+
+ return OperatorExpression(self.op, l, r)
+
+ def to_gerber(self, unit=None):
+ lval = self.l.to_gerber(unit)
+ rval = self.r.to_gerber(unit)
+
+ if isinstance(self.l, OperatorExpression):
+ lval = f'({lval})'
+ if isinstance(self.r, OperatorExpression):
+ rval = f'({rval})'
+
+ op = {operator.add: '+',
+ operator.sub: '-',
+ operator.mul: 'X',
+ operator.truediv: '/'} [self.op]
+
+ return f'{lval}{op}{rval}'
+