# -*- coding:utf-8 -*-
#
# Copyright (C) 2019-2021, Saarland University
# Copyright (C) 2019-2021, Maximilian Köhl <koehl@cs.uni-saarland.de>
from __future__ import annotations
import dataclasses as d
import typing as t
import abc
import decimal
import enum
import fractions
import math
import warnings
from . import errors, operators, types
if t.TYPE_CHECKING:
from . import context, distributions
[docs]
class Expression(abc.ABC):
"""
Abstract base class for expressions.
"""
@abc.abstractmethod
def infer_type(self, scope: context.Scope) -> types.Type:
raise NotImplementedError()
def infer_target_type(self, scope: context.Scope) -> types.Type:
raise errors.InvalidTypeError(
f"expression {self} is not a valid assignment target"
)
@property
@abc.abstractmethod
def children(self) -> t.Sequence[Expression]:
"""
The direct children of the expression.
"""
raise NotImplementedError()
[docs]
def traverse(self) -> t.Iterator[Expression]:
"""
Returns an iterator over the subexpressions.
"""
yield self
for child in self.children:
yield from child.traverse()
@property
def subexpressions(self) -> t.AbstractSet[Expression]:
"""
A set of subexpressions.
"""
return frozenset(self.traverse())
@property
def is_sampling_free(self) -> bool:
"""
Returns whether the expression is *sampling-free*.
"""
return all(not isinstance(e, Sample) for e in self.traverse())
@property
def used_names(self) -> t.AbstractSet[Name]:
"""
Returns a set of *name expressions* occuring in the expression.
"""
return frozenset(child for child in self.traverse() if isinstance(child, Name))
class _Leaf(Expression):
@property
def children(self) -> t.Sequence[Expression]:
return ()
class Constant(_Leaf, abc.ABC):
pass
[docs]
@d.dataclass(frozen=True)
class BooleanConstant(Constant):
"""
A boolean constant.
Attributes
----------
boolean:
The value of the boolean constant.
"""
boolean: bool
def infer_type(self, scope: context.Scope) -> types.Type:
return types.BOOL
[docs]
class NumericConstant(Constant, abc.ABC):
"""
Abstract base class for numeric constants.
"""
@property
@abc.abstractmethod
def as_float(self) -> float:
"""
The numeric constant as a floating-point number.
Note that this may result in a loss of precision.
"""
raise NotImplementedError()
@property
@abc.abstractmethod
def as_fraction(self) -> fractions.Fraction:
"""
The numeric constant as a fraction.
Note that this may result in a loss of precision
"""
raise NotImplementedError()
TRUE: Expression = BooleanConstant(True)
FALSE: Expression = BooleanConstant(False)
[docs]
@d.dataclass(frozen=True)
class IntegerConstant(NumericConstant):
"""
An integer constant.
Attributes
----------
integer:
The value of the integer constant.
"""
integer: int
def infer_type(self, scope: context.Scope) -> types.Type:
return types.INT
@property
def as_float(self) -> float:
warnings.warn(
"converting an integer constant to a float may result in a loss of precision"
)
return float(self.integer)
@property
def as_fraction(self) -> fractions.Fraction:
return fractions.Fraction(self.integer)
_NAMED_REAL_MAP: t.Dict[str, NamedReal] = {}
[docs]
class NamedReal(enum.Enum):
"""
An enum of named reals.
Attributes
----------
symbol:
The mathematical symbol of the real.
float_value:
A floating-point approximation of the real.
"""
PI = "π", math.pi
""" The number π. """
E = "e", math.e
""" The number e. """
symbol: str
float_value: float
def __init__(self, symbol: str, float_value: float) -> None:
self.symbol = symbol
self.float_value = float_value
_NAMED_REAL_MAP[symbol] = self
Real = t.Union[NamedReal, fractions.Fraction]
[docs]
@d.dataclass(frozen=True)
class RealConstant(NumericConstant):
"""
A real constant.
Attributes
----------
real:
The real (either :class:`NamedReal` or a fraction).
"""
real: Real
def infer_type(self, scope: context.Scope) -> types.Type:
return types.REAL
@property
def as_float(self) -> float:
warnings.warn(
"converting a real constant to a float may result in a loss of precision"
)
if isinstance(self.real, NamedReal):
return self.real.float_value
return float(self.real)
@property
def as_fraction(self) -> fractions.Fraction:
if isinstance(self.real, fractions.Fraction):
return self.real
else:
warnings.warn(
"converting a named real constant do a fraction does result in a loss of precision"
)
return fractions.Fraction(self.real.float_value)
[docs]
@d.dataclass(frozen=True)
class Name(_Leaf):
"""
A name expression.
Attributes
----------
identifier:
The identifier.
"""
identifier: str
def infer_type(self, scope: context.Scope) -> types.Type:
return scope.lookup(self.identifier).typ
def infer_target_type(self, scope: context.Scope) -> types.Type:
return self.infer_type(scope)
# XXX: this class should be abstract, however, then it would not type-check
# https://github.com/python/mypy/issues/5374
[docs]
@d.dataclass(frozen=True)
class BinaryExpression(Expression):
"""
Abstract base class for binary expressions.
Attributes
----------
operator:
The binary operator (:class:`~momba.model.operators.BinaryOperator`).
left:
The left operand.
right:
The right operand.
"""
operator: operators.BinaryOperator
left: Expression
right: Expression
@property
def children(self) -> t.Sequence[Expression]:
return self.left, self.right
# XXX: this method shall be implemented by all subclasses
def infer_type(self, scope: context.Scope) -> types.Type:
raise NotImplementedError()
[docs]
class Boolean(BinaryExpression):
"""
A boolean binary expression.
Attributes
----------
operator:
The boolean operator (:class:`~momba.model.operators.BooleanOperator`).
"""
operator: operators.BooleanOperator
def infer_type(self, scope: context.Scope) -> types.Type:
left_type = scope.get_type(self.left)
if left_type != types.BOOL:
raise errors.InvalidTypeError(f"expected types.BOOL but got {left_type}")
right_type = scope.get_type(self.right)
if right_type != types.BOOL:
raise errors.InvalidTypeError(f"expected types.BOOL but got {right_type}")
return types.BOOL
_REAL_RESULT_OPERATORS = {
operators.ArithmeticBinaryOperator.REAL_DIV,
operators.ArithmeticBinaryOperator.LOG,
operators.ArithmeticBinaryOperator.POW,
}
[docs]
class ArithmeticBinary(BinaryExpression):
"""
An arithmetic binary expression.
Attributes
----------
operator:
The arithmetic operator (:class:`~momba.model.operators.ArithmeticBinaryOperator`).
"""
operator: operators.ArithmeticBinaryOperator
def infer_type(self, scope: context.Scope) -> types.Type:
left_type = scope.get_type(self.left)
right_type = scope.get_type(self.right)
if not left_type.is_numeric or not right_type.is_numeric:
raise errors.InvalidTypeError(
"operands of arithmetic expressions must have a numeric type"
)
is_int = (
types.INT.is_assignable_from(left_type)
and types.INT.is_assignable_from(right_type)
and self.operator not in _REAL_RESULT_OPERATORS
)
if is_int:
return types.INT
else:
return types.REAL
[docs]
class Equality(BinaryExpression):
"""
An equality binary expression.
Attributes
----------
operator:
The equality operator (:class:`~momba.model.operators.EqualityOperator`).
"""
operator: operators.EqualityOperator
def get_common_type(self, scope: context.Scope) -> types.Type:
left_type = scope.get_type(self.left)
right_type = scope.get_type(self.right)
if left_type.is_assignable_from(right_type):
return left_type
elif right_type.is_assignable_from(left_type):
return right_type
raise AssertionError(
"type-inference should ensure that some of the above is true"
)
def infer_type(self, scope: context.Scope) -> types.Type:
left_type = scope.get_type(self.left)
right_type = scope.get_type(self.right)
left_assignable_right = left_type.is_assignable_from(right_type)
right_assignable_left = right_type.is_assignable_from(left_type)
if left_assignable_right or right_assignable_left:
return types.BOOL
# XXX: JANI specifies that “left and right must be assignable to some common type”
# not sure whether this implementation reflects this specification
raise errors.InvalidTypeError(
"invalid combination of type for equality comparison"
)
[docs]
class Comparison(BinaryExpression):
"""
A comparison expression.
Attributes
----------
operator:
The comparison operator (:class:`~momba.model.operators.ComparisonOperator`).
"""
operator: operators.ComparisonOperator
def infer_type(self, scope: context.Scope) -> types.Type:
left_type = scope.get_type(self.left)
if not left_type.is_numeric:
raise errors.InvalidTypeError(f"expected numeric type but got {left_type}")
right_type = scope.get_type(self.right)
if not right_type.is_numeric:
raise errors.InvalidTypeError(f"expected numeric type but got {right_type}")
return types.BOOL
[docs]
@d.dataclass(frozen=True)
class Conditional(Expression):
"""
A ternary conditional expression.
Attributes
----------
condition:
The condition.
consequence:
The consequence to be evaluated if the condition is true.
alternative:
The alternative to be evaluated if the condition is false.
"""
condition: Expression
consequence: Expression
alternative: Expression
@property
def children(self) -> t.Sequence[Expression]:
return self.condition, self.consequence, self.alternative
def infer_type(self, scope: context.Scope) -> types.Type:
condition_type = scope.get_type(self.condition)
if condition_type != types.BOOL:
raise errors.InvalidTypeError(
f"expected `types.BOOL` but got `{condition_type}`"
)
consequence_type = scope.get_type(self.consequence)
alternative_type = scope.get_type(self.alternative)
if consequence_type.is_assignable_from(alternative_type):
return consequence_type
elif alternative_type.is_assignable_from(consequence_type):
return alternative_type
else:
raise errors.InvalidTypeError(
"invalid combination of consequence and alternative types"
)
# XXX: this class should be abstract, however, then it would not type-check
# https://github.com/python/mypy/issues/5374
[docs]
@d.dataclass(frozen=True)
class UnaryExpression(Expression, abc.ABC):
"""
Base class of all unary expressions.
Attributes
----------
operator:
The unary operator (:class:`~momba.model.operators.UnaryOperator`).
operand:
The operand.
"""
operator: operators.UnaryOperator
operand: Expression
@property
def children(self) -> t.Sequence[Expression]:
return (self.operand,)
# XXX: this method shall be implemented by all subclasses
def infer_type(self, scope: context.Scope) -> types.Type:
raise NotImplementedError()
[docs]
class ArithmeticUnary(UnaryExpression):
"""
An arithmetic unary expression.
Attributes
----------
operator:
The arithmetic operator (:class:`~momba.model.operators.ArithmeticUnaryOperator`).
"""
operator: operators.ArithmeticUnaryOperator
def infer_type(self, scope: context.Scope) -> types.Type:
operand_type = scope.get_type(self.operand)
if not operand_type.is_numeric:
raise errors.InvalidTypeError(
f"expected a numeric type but got {operand_type}"
)
return self.operator.infer_result_type(operand_type)
[docs]
class Not(UnaryExpression):
"""
Logical negation.
Attributes
----------
operator:
The logical negation operator (:class:`~momba.model.operators.NotOperator`).
"""
operator: operators.NotOperator
def infer_type(self, scope: context.Scope) -> types.Type:
operand_type = scope.get_type(self.operand)
if operand_type != types.BOOL:
raise errors.InvalidTypeError(
f"expected `types.BOOL` but got {operand_type}"
)
return types.BOOL
[docs]
@d.dataclass(frozen=True)
class Sample(Expression):
"""
A sample expression.
Attributes
----------
distribution:
The type of the distribution to sample from
(:class:`~momba.model.distributions.DistributionType`).
arguments:
The arguments to the distribution.
"""
distribution: distributions.DistributionType
arguments: t.Sequence[Expression]
def __post_init__(self) -> None:
if len(self.arguments) != self.distribution.arity:
raise errors.InvalidTypeError(
f"distribution {self.distribution} requires {self.distribution.arity} "
f"arguments but {len(self.arguments)} were given"
)
@property
def children(self) -> t.Sequence[Expression]:
return self.arguments
def infer_type(self, scope: context.Scope) -> types.Type:
# we already know that the arity of the parameters and arguments match
for argument, parameter_type in zip(
self.arguments, self.distribution.parameter_types
):
argument_type = scope.get_type(argument)
if not parameter_type.is_assignable_from(argument_type):
raise errors.InvalidTypeError(
f"parameter type `{parameter_type}` is not assignable "
f"from argument type `{argument_type}`"
)
return self.distribution.result_type
# requires JANI extension `nondet-selection`
[docs]
@d.dataclass(frozen=True)
class Selection(Expression):
"""
A non-deterministic selection expression.
Attributes
----------
variable:
The identifier to select over.
condition:
The condition that should be satisfied.
"""
variable: str
condition: Expression
def infer_type(self, scope: context.Scope) -> types.Type:
condition_scope = scope.create_child_scope()
condition_scope.declare_variable(self.variable, typ=types.REAL)
condition_type = condition_scope.get_type(self.condition)
if condition_type != types.BOOL:
raise errors.InvalidTypeError("condition must have type `types.BOOL`")
return types.REAL
@property
def children(self) -> t.Sequence[Expression]:
return (self.condition,)
[docs]
@d.dataclass(frozen=True)
class Derivative(Expression):
"""
Derivative of a continuous variable.
Attributes
==========
identifier:
The continuous variable.
"""
identifier: str
def infer_type(self, scope: context.Scope) -> types.Type:
return types.REAL
@property
def children(self) -> t.Sequence[Expression]:
return ()
[docs]
@d.dataclass(frozen=True)
class ArrayAccess(Expression):
"""
An array access expression.
Attributes
----------
array:
The array to access.
index:
The index where to access the array.
"""
array: Expression
index: Expression
@property
def children(self) -> t.Sequence[Expression]:
return self.array, self.index
def infer_type(self, scope: context.Scope) -> types.Type:
array_type = scope.get_type(self.array)
# TODO: check the type of the index
if isinstance(array_type, types.ArrayType):
return array_type.element
else:
raise errors.InvalidTypeError("array of array access must have array type")
def infer_target_type(self, scope: context.Scope) -> types.Type:
return self.infer_type(scope)
[docs]
class ArrayValue(Expression):
"""
An array value expression.
Attributes
----------
elements:
The elements of the array to construct.
"""
elements: t.Tuple[Expression, ...]
def __init__(self, elements: t.Iterable[Expression]) -> None:
self.elements = tuple(elements)
if not self.elements:
raise errors.ModelingError("array value expression needs to have elements")
@property
def children(self) -> t.Sequence[Expression]:
return self.elements
def infer_type(self, scope: context.Scope) -> types.Type:
common_type: t.Optional[types.Type] = None
for element in self.elements:
element_type = scope.get_type(element)
if common_type is None:
common_type = element_type
elif element_type.is_assignable_from(common_type):
common_type = element_type
elif not common_type.is_assignable_from(element_type):
raise errors.InvalidTypeError(
"element types are not assignable to a common type"
)
assert common_type is not None
return types.array_of(common_type)
[docs]
@d.dataclass(frozen=True)
class ArrayConstructor(Expression):
"""
An array constructor expression.
Attributes
----------
variable:
The identifier to range over.
length:
The length of the array.
expression:
The expression to compute the elements of the array.
"""
variable: str
length: Expression
expression: Expression
@property
def children(self) -> t.Sequence[Expression]:
return self.length, self.expression
def _create_scope(self, scope: context.Scope) -> context.Scope:
child_scope = scope.create_child_scope()
child_scope.declare_constant(self.variable, types.INT)
return child_scope
def infer_type(self, scope: context.Scope) -> types.Type:
if not types.INT.is_assignable_from(scope.get_type(self.length)):
raise errors.InvalidTypeError(
"length of array constructor has to be an integer"
)
return types.array_of(self._create_scope(scope).get_type(self.expression))
[docs]
class Trigonometric(UnaryExpression):
"""
A trigonometric expression.
Attributes
----------
operator:
The trigonometric function to apply
(:class:`~momba.model.operators.TrigonometricFunction`).
"""
operator: operators.TrigonometricFunction
def infer_type(self, scope: context.Scope) -> types.Type:
operand_type = scope.get_type(self.operand)
if not operand_type.is_numeric:
raise errors.InvalidTypeError(
"expected numeric type for operand of trigonometric function"
)
return types.REAL
RealValue = t.Union[t.Literal["π", "e"], float, fractions.Fraction, decimal.Decimal]
NumericValue = t.Union[int, RealValue]
Value = t.Union[bool, NumericValue]
ValueOrExpression = t.Union[Expression, Value]
[docs]
class ConversionError(ValueError):
"""
Unable to convert the provided value.
"""
[docs]
def ensure_expr(value_or_expression: ValueOrExpression) -> Expression:
"""
Takes a Python value or expression and returns an expression.
Implicitly converts floats, integers, and booleans to constant expressions.
Raises :class:`~momba.model.expressions.ConversionError` if the conversion fails.
"""
if isinstance(value_or_expression, Expression):
return value_or_expression
elif isinstance(value_or_expression, bool):
return BooleanConstant(value_or_expression)
elif isinstance(value_or_expression, int):
return IntegerConstant(value_or_expression)
elif isinstance(value_or_expression, (float, fractions.Fraction, decimal.Decimal)):
return RealConstant(fractions.Fraction(value_or_expression))
elif isinstance(value_or_expression, str):
try:
return RealConstant(_NAMED_REAL_MAP[value_or_expression])
except KeyError:
pass
raise ConversionError(
f"unable to convert Python value {value_or_expression!r} to expression"
)
[docs]
def ite(
condition: ValueOrExpression,
consequence: ValueOrExpression,
alternative: ValueOrExpression,
) -> Expression:
"""
Constructs a conditional expression implicitly converting the arguments.
"""
return Conditional(
ensure_expr(condition), ensure_expr(consequence), ensure_expr(alternative)
)
BinaryConstructor = t.Callable[[ValueOrExpression, ValueOrExpression], Expression]
def _boolean_binary_expression(
operator: operators.BooleanOperator, expressions: t.Sequence[ValueOrExpression]
) -> Expression:
if len(expressions) == 1:
return ensure_expr(expressions[0])
result = Boolean(
operator,
ensure_expr(expressions[0]),
ensure_expr(expressions[1]),
)
for operand in expressions[2:]:
result = Boolean(operator, result, ensure_expr(operand))
return result
[docs]
def logic_not(operand: ValueOrExpression) -> Expression:
"""
Constructs a logical negation expression.
"""
return Not(operators.NotOperator.NOT, ensure_expr(operand))
[docs]
def logic_any(*expressions: ValueOrExpression) -> Expression:
"""
Constructs a disjunction over the provided expressions.
Returns :attr:`~momba.model.expressions.FALSE` if there are no expressions.
"""
if len(expressions) == 0:
return BooleanConstant(False)
return logic_or(*expressions)
[docs]
def logic_or(*expressions: ValueOrExpression) -> Expression:
"""
Constructs a disjunction over the provided expressions.
"""
return _boolean_binary_expression(operators.BooleanOperator.OR, expressions)
[docs]
def logic_all(*expressions: ValueOrExpression) -> Expression:
"""
Constructs a conjunction over the provided expressions.
Returns :attr:`~momba.model.expressions.TRUE` if there are no expressions.
"""
if len(expressions) == 0:
return BooleanConstant(True)
return logic_and(*expressions)
[docs]
def logic_and(*expressions: ValueOrExpression) -> Expression:
"""
Constructs a conjunction over the provided expressions.
"""
return _boolean_binary_expression(operators.BooleanOperator.AND, expressions)
[docs]
def logic_xor(*expressions: ValueOrExpression) -> Expression:
"""
Constructs an exclusive disjunction over the provided expressions.
"""
return _boolean_binary_expression(operators.BooleanOperator.XOR, expressions)
[docs]
def logic_implies(left: ValueOrExpression, right: ValueOrExpression) -> Expression:
"""
Constructs a logical implication.
"""
return Boolean(
operators.BooleanOperator.IMPLY, ensure_expr(left), ensure_expr(right)
)
[docs]
def logic_equiv(left: ValueOrExpression, right: ValueOrExpression) -> Expression:
"""
Constructs a logical equivalence.
"""
return Boolean(
operators.BooleanOperator.EQUIV, ensure_expr(left), ensure_expr(right)
)
[docs]
def equals(left: ValueOrExpression, right: ValueOrExpression) -> Expression:
"""
Constructs an *equality* expression.
"""
return Equality(
operators.EqualityOperator.EQ, ensure_expr(left), ensure_expr(right)
)
[docs]
def not_equals(left: ValueOrExpression, right: ValueOrExpression) -> Expression:
"""
Constructs an *inequality* expression.
"""
return Equality(
operators.EqualityOperator.NEQ, ensure_expr(left), ensure_expr(right)
)
[docs]
def less(left: ValueOrExpression, right: ValueOrExpression) -> Expression:
"""
Constructs a *less than* expression.
"""
return Comparison(
operators.ComparisonOperator.LT, ensure_expr(left), ensure_expr(right)
)
[docs]
def less_or_equal(left: ValueOrExpression, right: ValueOrExpression) -> Expression:
"""
Constructs a *less than or equal to* expression.
"""
return Comparison(
operators.ComparisonOperator.LE, ensure_expr(left), ensure_expr(right)
)
[docs]
def greater(left: ValueOrExpression, right: ValueOrExpression) -> Expression:
"""
Constructs a *greater than* expression.
"""
return Comparison(
operators.ComparisonOperator.GT, ensure_expr(left), ensure_expr(right)
)
[docs]
def greater_or_equal(left: ValueOrExpression, right: ValueOrExpression) -> Expression:
"""
Constructs a *greater than or equal to* expression.
"""
return Comparison(
operators.ComparisonOperator.GE, ensure_expr(left), ensure_expr(right)
)
[docs]
def add(left: ValueOrExpression, right: ValueOrExpression) -> Expression:
"""
Constructs an arithmetic addition expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.ADD, ensure_expr(left), ensure_expr(right)
)
[docs]
def sub(left: ValueOrExpression, right: ValueOrExpression) -> BinaryExpression:
"""
Constructs an arithmetic substraction expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.SUB, ensure_expr(left), ensure_expr(right)
)
[docs]
def mul(left: ValueOrExpression, right: ValueOrExpression) -> BinaryExpression:
"""
Constructs an arithmetic multiplication expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.MUL, ensure_expr(left), ensure_expr(right)
)
[docs]
def mod(left: ValueOrExpression, right: ValueOrExpression) -> BinaryExpression:
"""
Constructs an euclidean remainder expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.MOD, ensure_expr(left), ensure_expr(right)
)
[docs]
def real_div(left: ValueOrExpression, right: ValueOrExpression) -> BinaryExpression:
"""
Constructs a real-division expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.REAL_DIV,
ensure_expr(left),
ensure_expr(right),
)
[docs]
def log(left: ValueOrExpression, right: ValueOrExpression) -> BinaryExpression:
"""
Constructs a logarithm expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.LOG, ensure_expr(left), ensure_expr(right)
)
[docs]
def power(left: ValueOrExpression, right: ValueOrExpression) -> BinaryExpression:
"""
Constructs a power expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.POW, ensure_expr(left), ensure_expr(right)
)
[docs]
def minimum(left: ValueOrExpression, right: ValueOrExpression) -> BinaryExpression:
"""
Constructs a minimum expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.MIN, ensure_expr(left), ensure_expr(right)
)
[docs]
def maximum(left: ValueOrExpression, right: ValueOrExpression) -> BinaryExpression:
"""
Constructs a maximum expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.MAX, ensure_expr(left), ensure_expr(right)
)
[docs]
def floor_div(left: ValueOrExpression, right: ValueOrExpression) -> BinaryExpression:
"""
Constructs an euclidean division expression.
"""
return ArithmeticBinary(
operators.ArithmeticBinaryOperator.FLOOR_DIV,
ensure_expr(left),
ensure_expr(right),
)
UnaryConstructor = t.Callable[[ValueOrExpression], Expression]
[docs]
def floor(operand: ValueOrExpression) -> Expression:
"""
Constructs a floor expression.
"""
return ArithmeticUnary(
operators.ArithmeticUnaryOperator.FLOOR, ensure_expr(operand)
)
[docs]
def ceil(operand: ValueOrExpression) -> Expression:
"""
Constructs a ceil expression.
"""
return ArithmeticUnary(operators.ArithmeticUnaryOperator.CEIL, ensure_expr(operand))
[docs]
def absolute(operand: ValueOrExpression) -> Expression:
"""
Constructs an absolute value expression.
"""
return ArithmeticUnary(operators.ArithmeticUnaryOperator.ABS, ensure_expr(operand))
[docs]
def sgn(operand: ValueOrExpression) -> Expression:
"""
Constructs a sign expression.
"""
return ArithmeticUnary(operators.ArithmeticUnaryOperator.SGN, ensure_expr(operand))
[docs]
def trunc(operand: ValueOrExpression) -> Expression:
"""
Constructs a truncate expression.
"""
return ArithmeticUnary(operators.ArithmeticUnaryOperator.TRC, ensure_expr(operand))
[docs]
def name(identifier: str) -> Name:
"""
Constructs a name expression.
"""
return Name(identifier)