gerbonara/gerbonara/aperture_macros/expression.py

212 wiersze
6.3 KiB
Python

#!/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}'