Skip to content

Commit

Permalink
Merge pull request #317 from atopile/revert-314-mawildoer/negative-as…
Browse files Browse the repository at this point in the history
…sertions

Revert "Fix negative unaries"
  • Loading branch information
mawildoer committed Apr 26, 2024
2 parents f8a4692 + c46168b commit 54d4faf
Show file tree
Hide file tree
Showing 8 changed files with 641 additions and 432 deletions.
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ dev = [
"ruff",
"black",
"debugpy",
"antlr4-tools",
]

[project.scripts]
Expand Down
4 changes: 1 addition & 3 deletions src/atopile/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,7 @@ def __eq__(self, other: object) -> bool:
# NOTE: realistically this is only useful for testing
if isinstance(other, RangedValue):
return self.min_qty == other.min_qty and self.max_qty == other.max_qty

# NOTE: this doesn't work for farenheit or kelvin, but everything else is okay
if self.min_val == self.max_val == other and (self.unit.dimensionless or other == 0):
if self.min_val == self.max_val == other and self.unit.dimensionless:
return True
return False

Expand Down
124 changes: 81 additions & 43 deletions src/atopile/front_end.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,9 @@ def __init__(
val_b: Number | pint.Quantity,
unit: Optional[pint.Unit] = None,
src_ctx: Optional[ParserRuleContext] = None,
pretty_unit: Optional[str] = None,
):
self.src_ctx = src_ctx
super().__init__(val_a, val_b, unit, pretty_unit)
super().__init__(val_a, val_b, unit)


class Expression(expressions.Expression):
Expand Down Expand Up @@ -380,23 +379,19 @@ def visitBoolean_(self, ctx: ap.Boolean_Context) -> bool:

def visitLiteral_physical(self, ctx: ap.Literal_physicalContext) -> RangedValue:
"""Yield a physical value from a physical context."""
if ctx.quantity():
return self.visitQuantity(ctx.quantity())
if ctx.implicit_quantity():
return self.visitImplicit_quantity(ctx.implicit_quantity())
if ctx.bilateral_quantity():
return self.visitBilateral_quantity(ctx.bilateral_quantity())
if ctx.bound_quantity():
return self.visitBound_quantity(ctx.bound_quantity())

raise ValueError # this should be protected because it shouldn't be parseable

def visitQuantity(self, ctx: ap.QuantityContext) -> RangedValue:
def visitImplicit_quantity(self, ctx: ap.Implicit_quantityContext) -> RangedValue:
"""Yield a physical value from an implicit quantity context."""
value = float(ctx.NUMBER().getText())

# Ignore the positive unary operator
if ctx.MINUS():
value = -value

if ctx.name():
unit = _get_unit_from_ctx(ctx.name())
else:
Expand All @@ -411,72 +406,107 @@ def visitQuantity(self, ctx: ap.QuantityContext) -> RangedValue:

def visitBilateral_quantity(self, ctx: ap.Bilateral_quantityContext) -> RangedValue:
"""Yield a physical value from a bilateral quantity context."""
nominal_quantity = self.visitQuantity(ctx.quantity())
nominal = float(ctx.bilateral_nominal().NUMBER().getText())

if ctx.bilateral_nominal().name():
unit = _get_unit_from_ctx(ctx.bilateral_nominal().name())
else:
unit = pint.Unit("")

tol_ctx: ap.Bilateral_toleranceContext = ctx.bilateral_tolerance()
tol_num = float(tol_ctx.NUMBER().getText())

if tol_ctx.PERCENT():
tol_divider = 100
# FIXME: hardcoding this seems wrong, but the parser/lexer wasn't picking up on it
elif tol_ctx.name() and tol_ctx.name().getText() == "ppm":
tol_divider = 1e6
else:
tol_divider = None

if tol_divider:
if nominal_quantity == 0:
if nominal == 0:
raise errors.AtoError.from_ctx(
tol_ctx,
"Can't calculate tolerance percentage of a nominal value of zero",
)

# In this case, life's a little easier, and we can simply multiply the nominal
tol = tol_num / tol_divider
return nominal_quantity * RangedValue(-tol, tol)
return RangedValue(
src_ctx=ctx,
val_a=nominal * (1 - tol_num / tol_divider),
val_b=nominal * (1 + tol_num / tol_divider),
unit=unit,
)

if tol_ctx.name():
# In this case there's a named unit on the tolerance itself
# We need to make sure it's dimensionally compatible with the nominal
tol_quantity = RangedValue(-tol_num, tol_num, _get_unit_from_ctx(tol_ctx.name()), tol_ctx)
tolerance_unit = _get_unit_from_ctx(tol_ctx.name())
try:
return nominal_quantity + tol_quantity
tolerance = (tol_num * tolerance_unit).to(unit).magnitude
except pint.DimensionalityError as ex:
raise errors.AtoTypeError.from_ctx(
tol_ctx.name(),
f"Tolerance unit '{tol_quantity.unit}' is not dimensionally"
f" compatible with nominal unit '{nominal_quantity.unit}'",
f"Tolerance unit '{tolerance_unit}' is not dimensionally"
f" compatible with nominal unit '{unit}'",
) from ex

return RangedValue(
src_ctx=ctx,
val_a=nominal - tolerance,
val_b=nominal + tolerance,
unit=unit,
)

# If there's no unit or percent, then we have a simple tolerance in the same units
# as the nominal
return RangedValue(
src_ctx=ctx,
val_a=nominal_quantity.min_val - tol_num,
val_b=nominal_quantity.max_val + tol_num,
unit=nominal_quantity.unit,
val_a=nominal - tol_num,
val_b=nominal + tol_num,
unit=unit,
)

def visitBound_quantity(self, ctx: ap.Bound_quantityContext) -> RangedValue:
"""Yield a physical value from a bound quantity context."""

start = self.visitQuantity(ctx.quantity(0))
assert start.tolerance == 0
end = self.visitQuantity(ctx.quantity(1))
assert end.tolerance == 0
def _parse_end(
ctx: ap.Quantity_endContext,
) -> tuple[float, Optional[pint.Unit]]:
value = float(ctx.NUMBER().getText())
if ctx.name():
unit = _get_unit_from_ctx(ctx.name())
else:
unit = None
return value, unit

try:
return RangedValue(
src_ctx=ctx,
val_a=start.min_qty,
val_b=end.min_qty,
pretty_unit=start.pretty_unit,
)
except pint.DimensionalityError as ex:
raise errors.AtoTypeError.from_ctx(
ctx,
f"Tolerance unit '{end.unit}' is not dimensionally"
f" compatible with nominal unit '{start.unit}'",
) from ex
start_val, start_unit = _parse_end(ctx.quantity_end(0))
end_val, end_unit = _parse_end(ctx.quantity_end(1))

if start_unit is None and end_unit is None:
unit = pint.Unit("")
elif start_unit and end_unit:
unit = start_unit
try:
end_val = (end_val * end_unit).to(start_unit).magnitude
except pint.DimensionalityError as ex:
raise errors.AtoTypeError.from_ctx(
ctx,
f"Tolerance unit '{end_unit}' is not dimensionally"
f" compatible with nominal unit '{start_unit}'",
) from ex
elif start_unit:
unit = start_unit
else:
unit = end_unit

return RangedValue(
src_ctx=ctx,
val_a=start_val,
val_b=end_val,
unit=unit,
)


class HandleStmtsFunctional(AtopileParserVisitor):
Expand Down Expand Up @@ -595,27 +625,35 @@ def visitTerm(self, ctx: ap.TermContext) -> expressions.NumericishTypes:
return expressions.defer_operation_factory(
self.visit(ctx.term()),
operator.mul,
self.visit(ctx.power()),
self.visit(ctx.factor()),
)

if ctx.DIV():
return expressions.defer_operation_factory(
self.visit(ctx.term()),
operator.truediv,
self.visit(ctx.power()),
self.visit(ctx.factor()),
)

return self.visit(ctx.factor())

def visitFactor(self, ctx: ap.FactorContext) -> expressions.NumericishTypes:
# Ignore the unary plus operator

if ctx.MINUS():
return operator.neg(self.visit(ctx.power()))

return self.visit(ctx.power())

def visitPower(self, ctx: ap.PowerContext) -> expressions.NumericishTypes:
if ctx.POWER():
if ctx.factor():
return expressions.defer_operation_factory(
self.visit(ctx.atom(0)),
self.visit(ctx.atom()),
operator.pow,
self.visit(ctx.atom(1)),
self.visit(ctx.factor()),
)

return self.visit(ctx.atom(0))
return self.visit(ctx.atom())

def visitAtom(self, ctx: ap.AtomContext) -> expressions.NumericishTypes:
if ctx.arithmetic_group():
Expand Down
21 changes: 14 additions & 7 deletions src/atopile/parser/AtopileParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,17 @@ arithmetic_expression
;

term
: term ('*' | '/') power
| power
: term ('*' | '/') factor
| factor
;

factor
: '+' factor
| '-' factor
| power;

power
: atom ('**' atom)?
: atom ('**' factor)?
;


Expand All @@ -107,11 +112,13 @@ arithmetic_group
literal_physical
: bound_quantity
| bilateral_quantity
| quantity;
| implicit_quantity;

bound_quantity: quantity 'to' quantity;
bilateral_quantity: quantity PLUS_OR_MINUS bilateral_tolerance;
quantity: ('+' | '-')? NUMBER name?;
bound_quantity: quantity_end 'to' quantity_end;
quantity_end: NUMBER name?;
bilateral_quantity: bilateral_nominal PLUS_OR_MINUS bilateral_tolerance;
implicit_quantity: NUMBER name?;
bilateral_nominal: NUMBER name?;
bilateral_tolerance: NUMBER ('%' | name)?;

name_or_attr: attr | name;
Expand Down
7 changes: 5 additions & 2 deletions src/atopile/parser/AtopileParser.interp

Large diffs are not rendered by default.

Loading

0 comments on commit 54d4faf

Please sign in to comment.