Module refinery.lib.inno.delphi.variants
Delphi's Variants: a value with its Variant type, the conversions of a Variant to the types of Delphi, and the operators and comparisons of Variants. The rules follow what Delphi 12 computes; the oracle log of the IFPS ground-truth tests holds them for every pair of the common types.
Expand source code Browse git
"""
Delphi's Variants: a value with its Variant type, the conversions of a Variant to the types of
Delphi, and the operators and comparisons of Variants. The rules follow what Delphi 12 computes;
the oracle log of the IFPS ground-truth tests holds them for every pair of the common types.
"""
from __future__ import annotations
import enum
from dataclasses import dataclass
from fractions import Fraction
from typing import Any
from refinery.lib.inno.delphi import x87
from refinery.lib.inno.delphi.errors import (
EDivByZero,
EIntOverflow,
EVariantInvalidOpError,
EVariantOverflowError,
EVariantTypeCastError,
)
from refinery.lib.inno.delphi.numbers import (
FormatSettings,
OleNumber,
float_to_str,
ole_number,
val_integer,
val_unsigned,
)
from refinery.lib.inno.delphi.runtime import Runtime, units
from refinery.lib.inno.delphi.x87 import FPU, Format, Real
class VarType(enum.IntEnum):
"""
The type codes of Delphi's Variants, as the function `VarType` returns them.
"""
Empty = 0x0000 # noqa
Null = 0x0001 # noqa
SmallInt = 0x0002 # noqa
Integer = 0x0003 # noqa
Single = 0x0004 # noqa
Double = 0x0005 # noqa
Currency = 0x0006 # noqa
Date = 0x0007 # noqa
OleStr = 0x0008 # noqa
Dispatch = 0x0009 # noqa
Error = 0x000A # noqa
Boolean = 0x000B # noqa
Variant = 0x000C # noqa
Unknown = 0x000D # noqa
ShortInt = 0x0010 # noqa
Byte = 0x0011 # noqa
Word = 0x0012 # noqa
LongWord = 0x0013 # noqa
Int64 = 0x0014 # noqa
UInt64 = 0x0015 # noqa
String = 0x0100 # noqa
UString = 0x0102 # noqa
Array = 0x2000 # noqa
@property
def text(self) -> str:
"""
The name that Delphi's messages give the type.
"""
return _TYPE_TEXT.get(self, self.name)
_TYPE_TEXT = {
VarType.SmallInt : 'Smallint',
VarType.UString : 'UnicodeString',
}
@dataclass(frozen=True)
class Variant:
"""
A Delphi Variant: its type and its value. An integer type holds an `int`, Single and Double a
`float`, Currency a `Fraction` with at most four decimals, a string type a `str`, Boolean a
`bool`, Dispatch the object, an array the list of its Variants, and Empty and Null nothing.
"""
type: int
value: Any = None
def __repr__(self):
try:
name = VarType(self.type).name
except ValueError:
name = F'0x{self.type:04X}'
return F'Variant({name}, {self.value!r})'
EMPTY = Variant(VarType.Empty)
NULL = Variant(VarType.Null)
_INT32 = range(-0x80000000, 0x80000000)
_CURRENCY_UNITS = 0x8000000000000000
class _Kind(enum.Enum):
Empty = enum.auto()
Null = enum.auto()
Integer = enum.auto()
Int64 = enum.auto()
UInt64 = enum.auto()
Float = enum.auto()
Currency = enum.auto()
String = enum.auto()
Boolean = enum.auto()
Other = enum.auto()
_KINDS = {
VarType.Empty : _Kind.Empty,
VarType.Null : _Kind.Null,
VarType.SmallInt : _Kind.Integer,
VarType.Integer : _Kind.Integer,
VarType.ShortInt : _Kind.Integer,
VarType.Byte : _Kind.Integer,
VarType.Word : _Kind.Integer,
VarType.LongWord : _Kind.Int64,
VarType.Int64 : _Kind.Int64,
VarType.UInt64 : _Kind.UInt64,
VarType.Single : _Kind.Float,
VarType.Double : _Kind.Float,
VarType.Currency : _Kind.Currency,
VarType.OleStr : _Kind.String,
VarType.String : _Kind.String,
VarType.UString : _Kind.String,
VarType.Boolean : _Kind.Boolean,
}
_INTEGERS = frozenset((_Kind.Integer, _Kind.Int64, _Kind.UInt64))
_INTEGER_WIDTHS = {
VarType.ShortInt : (8, True),
VarType.Byte : (8, False),
VarType.SmallInt : (16, True),
VarType.Word : (16, False),
VarType.Integer : (32, True),
VarType.LongWord : (32, False),
VarType.Int64 : (64, True),
VarType.UInt64 : (64, False),
}
def _kind(v: Variant) -> _Kind:
return _KINDS.get(v.type, _Kind.Other) # type: ignore
def _in_currency_range(value: Fraction) -> bool:
return -_CURRENCY_UNITS <= value * 10000 < _CURRENCY_UNITS
def type_text(v: Variant) -> str:
"""
The name of the Variant's type in Delphi's messages.
"""
if v.type & VarType.Array:
return 'Array Variant'
try:
return VarType(v.type).text
except ValueError:
return F'${v.type:04X}'
def _wrap(value: int, bits: int, signed: bool = True) -> int:
value &= (1 << bits) - 1
if signed and value >> (bits - 1):
value -= 1 << bits
return value
def from_python(value: Any) -> Variant:
"""
The Variant that holds a value that Python code produced: `None` is Empty, and the type follows
the Python type of the value.
"""
if isinstance(value, Variant):
return value
if value is None:
return EMPTY
if isinstance(value, bool):
return Variant(VarType.Boolean, value)
if isinstance(value, int):
if value in _INT32:
return Variant(VarType.Integer, value)
return Variant(VarType.Int64, _wrap(value, 64))
if isinstance(value, float):
return Variant(VarType.Double, value)
if isinstance(value, x87.Extended):
return Variant(VarType.Double, _NEAREST.store(value, Format.Double))
if isinstance(value, Fraction):
return Variant(VarType.Currency, value)
if isinstance(value, str):
return Variant(VarType.UString, value)
if isinstance(value, (list, tuple)):
return Variant(VarType.Array | VarType.Variant, [from_python(item) for item in value])
return Variant(VarType.Dispatch, value)
_NEAREST = FPU(x87.MASKED_CONTROL_WORD)
def _string_to_integer(v: Variant, runtime: Runtime, bits: int, target: str) -> int:
text: str = v.value
value, error = val_integer(text, bits)
if not error:
return value
if (number := ole_number(text, runtime.settings)) is not None:
double = _ole_double(number, v, target, runtime)
if x87.is_special(double):
raise EVariantOverflowError(type_text(v), target)
integer = runtime.fpu.integer(double)
if integer not in range(-(1 << (bits - 1)), 1 << (bits - 1)):
raise EVariantOverflowError(type_text(v), target)
return integer
if (truth := _text_truth(text)) is not None:
return int(truth)
raise EVariantTypeCastError(type_text(v), target)
def _ole_double(number: OleNumber, v: Variant, target: str, runtime: Runtime) -> Real:
"""
The Double that Windows makes of a number it read from a string; a number beyond the range of a
Double overflows the conversion to the target type. On 64-bit Windows, a number written without
an exponent becomes an infinity instead.
"""
exact = number.value
value = _NEAREST.rational(exact.numerator, exact.denominator, Format.Double)
if x87.is_special(value):
if runtime.parses_long_digits_as_infinity and not number.exponent:
return value
raise EVariantOverflowError(type_text(v), target)
assert isinstance(value, float)
return value
def _text_truth(text: str) -> bool | None:
lowered = text.lower()
if lowered == 'true':
return True
if lowered == 'false':
return False
return None
def _integer(v: Variant, runtime: Runtime, bits: int, target: str) -> int:
kind = _kind(v)
valid = range(-(1 << (bits - 1)), 1 << (bits - 1))
if kind is _Kind.Empty:
return 0
if kind is _Kind.UInt64 and bits == 64:
value = _wrap(v.value, 64)
elif kind in _INTEGERS:
value = v.value
elif kind is _Kind.Boolean:
return -1 if v.value else 0
elif kind is _Kind.Float:
value = runtime.fpu.integer(v.value)
elif kind is _Kind.Currency:
value = runtime.fpu.integer(runtime.currency_to_real(v.value))
elif kind is _Kind.String:
return _string_to_integer(v, runtime, bits, target)
else:
raise EVariantTypeCastError(type_text(v), target)
if value not in valid:
raise EVariantOverflowError(type_text(v), target)
return value
def to_integer(v: Variant, runtime: Runtime) -> int:
"""
Delphi's conversion of a Variant to an Integer: floats are rounded by the x87, strings parsed.
"""
return _integer(v, runtime, 32, 'Integer')
def to_int64(v: Variant, runtime: Runtime) -> int:
"""
Delphi's conversion of a Variant to an Int64.
"""
return _integer(v, runtime, 64, 'Int64')
def to_uint64(v: Variant, runtime: Runtime) -> int:
"""
Delphi's conversion of a Variant to a UInt64: a negative integer or a float out of range
overflows, a string is read as an unsigned number, and Null cannot be converted.
"""
kind = _kind(v)
if kind is _Kind.UInt64:
return v.value
if kind is _Kind.Null:
raise EVariantTypeCastError(type_text(v), 'Cardinal')
if kind is _Kind.String:
value, error = val_unsigned(v.value)
if not error:
return value
if kind is _Kind.Float:
value = runtime.fpu.integer(v.value)
if x87.is_special(v.value) or not 0 <= x87.exact(v.value) < (1 << 64):
raise EVariantOverflowError(type_text(v), 'UInt64')
return value & 0xFFFFFFFFFFFFFFFF
value = to_int64(v, runtime)
if value < 0 and kind in (_Kind.Integer, _Kind.Int64):
raise EVariantOverflowError(type_text(v), 'UInt64')
return value & 0xFFFFFFFFFFFFFFFF
def to_cardinal(v: Variant, runtime: Runtime) -> int:
"""
Delphi's conversion of a Variant to a Cardinal, which converts it to an Int64 and keeps the low
32 bits.
"""
return to_int64(v, runtime) & 0xFFFFFFFF
def to_real(v: Variant, runtime: Runtime) -> Real:
"""
Delphi's conversion of a Variant to a float. An integer is loaded as the unit loads it, and a
string is read as Windows reads a number in the regional format, as a Double.
"""
kind = _kind(v)
if kind is _Kind.Empty:
return 0.0
if kind in _INTEGERS:
return runtime.fpu.load(v.value)
if kind is _Kind.Boolean:
return -1.0 if v.value else 0.0
if kind is _Kind.Float:
return v.value
if kind is _Kind.Currency:
return runtime.currency_to_real(v.value)
if kind is _Kind.String and (number := ole_number(v.value, runtime.settings)) is not None:
return _ole_double(number, v, 'Double', runtime)
raise EVariantTypeCastError(type_text(v), 'Double')
def to_currency(v: Variant, runtime: Runtime) -> Fraction:
"""
Delphi's conversion of a Variant to a Currency.
"""
kind = _kind(v)
if kind is _Kind.Empty:
return Fraction(0)
if kind in _INTEGERS:
value = Fraction(v.value)
elif kind is _Kind.Boolean:
value = Fraction(-1 if v.value else 0)
elif kind is _Kind.Currency:
return v.value
elif kind is _Kind.Float:
if x87.is_special(v.value) or not _in_currency_range(x87.exact(v.value)):
raise EVariantOverflowError(type_text(v), 'Currency')
return runtime.variant_currency(v.value)
elif kind is _Kind.String and (number := ole_number(v.value, runtime.settings)) is not None:
value = Fraction(round(number.value * 10000), 10000)
else:
raise EVariantTypeCastError(type_text(v), 'Currency')
if not _in_currency_range(value):
raise EVariantOverflowError(type_text(v), 'Currency')
return value
def currency_text(value: Fraction, settings: FormatSettings) -> str:
"""
Delphi's `CurrToStr`: all digits of a Currency without trailing zeros, with the regional
decimal separator.
"""
count = units(value)
whole, fraction = divmod(abs(count), 10000)
text = str(whole)
if decimals := F'{fraction:04d}'.rstrip('0'):
text = F'{text}{settings.decimal_separator}{decimals}'
return F'-{text}' if count < 0 else text
def to_string(v: Variant, runtime: Runtime, target: str = 'OleStr') -> str:
"""
Delphi's conversion of a Variant to a string; the target names the string type in the message
for a Variant that cannot be converted.
"""
kind = _kind(v)
if kind is _Kind.Empty:
return ''
if kind in _INTEGERS:
return str(v.value)
if kind is _Kind.Float:
return float_to_str(v.value, runtime)
if kind is _Kind.Currency:
return currency_text(v.value, runtime.settings)
if kind is _Kind.String:
return v.value
if kind is _Kind.Boolean:
return 'True' if v.value else 'False'
raise EVariantTypeCastError(type_text(v), target)
def to_text(v: Variant, runtime: Runtime) -> str:
"""
The text of a Variant in `Format`: Null is empty.
"""
if _kind(v) is _Kind.Null:
return ''
return to_string(v, runtime)
def to_dispatch(v: Variant, runtime: Runtime) -> Any:
"""
Delphi's conversion of a Variant to an IDispatch: the object that it holds, and nil for Empty.
"""
if v.type == VarType.Dispatch:
return v.value
if v.type == VarType.Empty:
return None
raise EVariantTypeCastError(type_text(v), 'Dispatch')
def to_boolean(v: Variant, runtime: Runtime) -> bool:
"""
Delphi's conversion of a Variant to a Boolean.
"""
kind = _kind(v)
if kind is _Kind.Empty:
return False
if kind is _Kind.Boolean:
return bool(v.value)
if kind in _INTEGERS or kind is _Kind.Float or kind is _Kind.Currency:
return v.value != 0
if kind is _Kind.String:
if (truth := _text_truth(v.value)) is not None:
return truth
if ole_number(v.value, runtime.settings) is not None:
return _string_to_integer(v, runtime, 64, 'Int64') & 0xFFFFFFFF != 0
raise EVariantTypeCastError(type_text(v), 'Boolean')
class Operator(enum.Enum):
"""
The binary operators of Variants. Pascal Script divides with `Divide` when the left operand
holds a Single, Double or Currency, and with `IntDivide` otherwise.
"""
Add = '+'
Subtract = '-'
Multiply = '*'
Divide = '/'
IntDivide = 'div'
Modulus = 'mod'
ShiftLeft = 'shl'
ShiftRight = 'shr'
And = 'and'
Or = 'or'
Xor = 'xor'
def is_float(v: Variant) -> bool:
"""
Whether the Variant holds a Single, a Double, or a Currency.
"""
return v.type in (VarType.Single, VarType.Double, VarType.Currency)
_LOGICAL = frozenset((_Kind.Boolean, _Kind.String, _Kind.Empty))
_INT64_OPERANDS = frozenset((_Kind.Empty, _Kind.Integer, _Kind.Int64, _Kind.UInt64, _Kind.Boolean))
def _is_logical(ka: _Kind, kb: _Kind) -> bool:
return (ka is _Kind.Boolean and kb in _LOGICAL) or (kb is _Kind.Boolean and ka in _LOGICAL)
def operate(op: Operator, a: Variant, b: Variant, runtime: Runtime) -> Variant:
"""
Delphi's binary operator on two Variants.
"""
ka = _kind(a)
kb = _kind(b)
if ka is _Kind.Other or kb is _Kind.Other:
raise EVariantInvalidOpError
if op is Operator.And or op is Operator.Or:
return _logic(op, a, b, ka, kb, runtime)
if ka is _Kind.Null or kb is _Kind.Null:
return NULL
if ka is _Kind.Empty and kb is _Kind.Empty:
if op is Operator.IntDivide or op is Operator.Modulus:
raise EDivByZero
return EMPTY
if op is Operator.Xor and _is_logical(ka, kb):
return Variant(VarType.Boolean, to_boolean(a, runtime) != to_boolean(b, runtime))
if op is Operator.Add and (ka is _Kind.String or kb is _Kind.String):
if ka is _Kind.String and kb is _Kind.String:
return Variant(a.type, a.value + b.value)
if ka is _Kind.Empty:
return Variant(VarType.OleStr, b.value)
if kb is _Kind.Empty:
return a
if _is_unsigned(ka, kb):
return _unsigned(op, a, b, runtime)
if op in (Operator.Add, Operator.Subtract, Operator.Multiply):
return _arithmetic(op, a, b, ka, kb, runtime)
if op is Operator.Divide:
return _divide(a, b, runtime)
return _integral(op, a, b, ka, kb, runtime)
def _logic(op: Operator, a: Variant, b: Variant, ka: _Kind, kb: _Kind, runtime: Runtime) -> Variant:
conjunction = op is Operator.And
if ka is _Kind.Null or kb is _Kind.Null:
if ka is kb:
return NULL
other = b if ka is _Kind.Null else a
if to_boolean(other, runtime) is conjunction:
return NULL
return other
if ka is _Kind.Empty and kb is _Kind.Empty:
return EMPTY
if _is_logical(ka, kb):
left = to_boolean(a, runtime)
if left is not conjunction:
return Variant(VarType.Boolean, left)
return Variant(VarType.Boolean, to_boolean(b, runtime))
if _is_unsigned(ka, kb):
return _unsigned(op, a, b, runtime)
return _integral(op, a, b, ka, kb, runtime)
def _arithmetic(op: Operator, a: Variant, b: Variant, ka: _Kind, kb: _Kind, runtime: Runtime) -> Variant:
kinds = {ka, kb}
fpu = runtime.fpu
if _Kind.Currency in kinds:
if op is Operator.Multiply:
x = to_real(a, runtime)
y = to_real(b, runtime)
return Variant(VarType.Currency, runtime.currency(fpu.multiply(x, y)))
x = to_currency(a, runtime)
y = to_currency(b, runtime)
result = x + y if op is Operator.Add else x - y
if not _in_currency_range(result):
raise EVariantOverflowError('Currency', 'Currency')
return Variant(VarType.Currency, result)
if (
_Kind.Float in kinds
or _Kind.String in kinds
or kinds <= {_Kind.Boolean, _Kind.Empty}
):
x = to_real(a, runtime)
y = to_real(b, runtime)
if op is Operator.Add:
result = fpu.add(x, y)
elif op is Operator.Subtract:
result = fpu.subtract(x, y)
else:
result = fpu.multiply(x, y)
return Variant(VarType.Double, fpu.store(result, Format.Double))
if _Kind.Int64 in kinds:
x = to_int64(a, runtime)
y = to_int64(b, runtime)
return Variant(VarType.Int64, _wrap(_ARITHMETIC[op](x, y), 64))
x = to_integer(a, runtime)
y = to_integer(b, runtime)
result = _ARITHMETIC[op](x, y)
if result in _INT32:
return Variant(VarType.Integer, result)
return Variant(VarType.Double, float(result))
_ARITHMETIC = {
Operator.Add : lambda x, y: x + y,
Operator.Subtract : lambda x, y: x - y,
Operator.Multiply : lambda x, y: x * y,
}
def _divide(a: Variant, b: Variant, runtime: Runtime) -> Variant:
"""
Delphi's division of Variants. A float divided by a Currency becomes a Currency first, which
can overflow; a float that divides a Currency counts with its value. The quotient is a Currency
when the dividend is one and a Double otherwise.
"""
fpu = runtime.fpu
x = to_real(a, runtime)
if b.type == VarType.Currency:
if _kind(a) is _Kind.Float:
x = runtime.currency_to_real(to_currency(a, runtime))
if _kind(a) in (_Kind.Float, _Kind.Currency):
runtime.check_currency_divisor(b.value)
quotient = fpu.divide(x, to_real(b, runtime))
if a.type == VarType.Currency:
return Variant(VarType.Currency, runtime.currency(quotient))
return Variant(VarType.Double, fpu.store(quotient, Format.Double))
def _is_unsigned(ka: _Kind, kb: _Kind) -> bool:
return _Kind.UInt64 in (ka, kb) and ka in _INT64_OPERANDS and kb in _INT64_OPERANDS
def _unsigned(op: Operator, a: Variant, b: Variant, runtime: Runtime) -> Variant:
"""
Delphi's operators on a UInt64 and another integer: both operands become UInt64 values, which
overflows for a negative one, and the operator computes on 64 bits without a sign. Both
divisions give the integer quotient.
"""
x = to_uint64(a, runtime)
y = to_uint64(b, runtime)
if op is Operator.Add:
result = x + y
elif op is Operator.Subtract:
result = x - y
elif op is Operator.Multiply:
result = x * y
elif op in (Operator.Divide, Operator.IntDivide, Operator.Modulus):
if y == 0:
raise EDivByZero
result = x // y if op is not Operator.Modulus else x % y
elif op is Operator.ShiftLeft:
result = x << (y & 63)
elif op is Operator.ShiftRight:
result = x >> (y & 63)
elif op is Operator.And:
result = x & y
elif op is Operator.Or:
result = x | y
else:
result = x ^ y
return Variant(VarType.UInt64, result & 0xFFFFFFFFFFFFFFFF)
def _integral(op: Operator, a: Variant, b: Variant, ka: _Kind, kb: _Kind, runtime: Runtime) -> Variant:
if (ka is _Kind.Int64 or kb is _Kind.Int64) and ka in _INT64_OPERANDS and kb in _INT64_OPERANDS:
bits = 64
vtype = VarType.Int64
x = to_int64(a, runtime)
y = to_int64(b, runtime)
else:
bits = 32
vtype = VarType.Integer
x = to_integer(a, runtime)
y = to_integer(b, runtime)
if op is Operator.IntDivide or op is Operator.Modulus:
if y == 0:
raise EDivByZero
if x == -(1 << (bits - 1)) and y == -1:
raise EIntOverflow
quotient = abs(x) // abs(y)
if (x < 0) != (y < 0):
quotient = -quotient
result = quotient if op is Operator.IntDivide else x - quotient * y
elif op is Operator.ShiftLeft:
result = x << (y & (bits - 1))
elif op is Operator.ShiftRight:
result = (x & ((1 << bits) - 1)) >> (y & (bits - 1))
elif op is Operator.And:
result = x & y
elif op is Operator.Or:
result = x | y
else:
result = x ^ y
return Variant(vtype, _wrap(result, bits))
def compare(a: Variant, b: Variant, runtime: Runtime) -> int:
"""
Delphi's comparison of two Variants: negative, zero, or positive. Null is below every other
value and Empty below every value but Null; strings compare by their characters; a string and
a Boolean compare as Booleans, two integers as integers, a string and a number as numbers.
"""
ka = _kind(a)
kb = _kind(b)
if ka is _Kind.Other or kb is _Kind.Other:
raise EVariantInvalidOpError
for low in (_Kind.Null, _Kind.Empty):
if ka is low or kb is low:
if ka is kb:
return 0
return -1 if ka is low else 1
if ka is _Kind.String and kb is _Kind.String:
return (a.value > b.value) - (a.value < b.value)
if (ka is _Kind.Boolean or kb is _Kind.Boolean) and (ka in _LOGICAL and kb in _LOGICAL):
x = -1 if to_boolean(a, runtime) else 0
y = -1 if to_boolean(b, runtime) else 0
return (x > y) - (x < y)
if ka is _Kind.Currency or kb is _Kind.Currency:
if ka is _Kind.String or kb is _Kind.String:
x = to_currency(a, runtime)
y = to_currency(b, runtime)
return (x > y) - (x < y)
if ka in _INTEGERS and kb in _INTEGERS:
return (a.value > b.value) - (a.value < b.value)
order = runtime.fpu.compare(to_real(a, runtime), to_real(b, runtime))
return 0 if order is None else order
def negate(v: Variant, runtime: Runtime) -> Variant:
"""
Delphi's negation of a Variant.
"""
kind = _kind(v)
if kind is _Kind.Empty:
return Variant(VarType.SmallInt, 0)
if kind is _Kind.Null:
return NULL
if kind is _Kind.Integer:
return Variant(VarType.Integer, _wrap(-v.value, 32))
if kind is _Kind.Int64 or kind is _Kind.UInt64:
return Variant(VarType.Int64, _wrap(-v.value, 64))
if kind is _Kind.Float or kind is _Kind.Currency:
return Variant(v.type, -v.value)
if kind is _Kind.Boolean:
return Variant(VarType.SmallInt, -1 if v.value else 0)
if kind is _Kind.String:
return Variant(VarType.Double, runtime.fpu.negate(to_real(v, runtime)))
raise EVariantInvalidOpError
def invert(v: Variant, runtime: Runtime) -> Variant:
"""
Delphi's `not` of a Variant: logical for a Boolean, bitwise otherwise.
"""
kind = _kind(v)
if kind is _Kind.Empty:
return Variant(VarType.ShortInt, -1)
if kind is _Kind.Null:
return NULL
if kind is _Kind.Boolean:
return Variant(VarType.Boolean, not v.value)
if kind in _INTEGERS:
bits, signed = _INTEGER_WIDTHS[v.type] # type: ignore
return Variant(v.type, _wrap(~v.value, bits, signed))
return Variant(VarType.Integer, ~to_integer(v, runtime))
Functions
def type_text(v)-
The name of the Variant's type in Delphi's messages.
Expand source code Browse git
def type_text(v: Variant) -> str: """ The name of the Variant's type in Delphi's messages. """ if v.type & VarType.Array: return 'Array Variant' try: return VarType(v.type).text except ValueError: return F'${v.type:04X}' def from_python(value)-
The Variant that holds a value that Python code produced:
Noneis Empty, and the type follows the Python type of the value.Expand source code Browse git
def from_python(value: Any) -> Variant: """ The Variant that holds a value that Python code produced: `None` is Empty, and the type follows the Python type of the value. """ if isinstance(value, Variant): return value if value is None: return EMPTY if isinstance(value, bool): return Variant(VarType.Boolean, value) if isinstance(value, int): if value in _INT32: return Variant(VarType.Integer, value) return Variant(VarType.Int64, _wrap(value, 64)) if isinstance(value, float): return Variant(VarType.Double, value) if isinstance(value, x87.Extended): return Variant(VarType.Double, _NEAREST.store(value, Format.Double)) if isinstance(value, Fraction): return Variant(VarType.Currency, value) if isinstance(value, str): return Variant(VarType.UString, value) if isinstance(value, (list, tuple)): return Variant(VarType.Array | VarType.Variant, [from_python(item) for item in value]) return Variant(VarType.Dispatch, value) def to_integer(v, runtime)-
Delphi's conversion of a Variant to an Integer: floats are rounded by the x87, strings parsed.
Expand source code Browse git
def to_integer(v: Variant, runtime: Runtime) -> int: """ Delphi's conversion of a Variant to an Integer: floats are rounded by the x87, strings parsed. """ return _integer(v, runtime, 32, 'Integer') def to_int64(v, runtime)-
Delphi's conversion of a Variant to an Int64.
Expand source code Browse git
def to_int64(v: Variant, runtime: Runtime) -> int: """ Delphi's conversion of a Variant to an Int64. """ return _integer(v, runtime, 64, 'Int64') def to_uint64(v, runtime)-
Delphi's conversion of a Variant to a UInt64: a negative integer or a float out of range overflows, a string is read as an unsigned number, and Null cannot be converted.
Expand source code Browse git
def to_uint64(v: Variant, runtime: Runtime) -> int: """ Delphi's conversion of a Variant to a UInt64: a negative integer or a float out of range overflows, a string is read as an unsigned number, and Null cannot be converted. """ kind = _kind(v) if kind is _Kind.UInt64: return v.value if kind is _Kind.Null: raise EVariantTypeCastError(type_text(v), 'Cardinal') if kind is _Kind.String: value, error = val_unsigned(v.value) if not error: return value if kind is _Kind.Float: value = runtime.fpu.integer(v.value) if x87.is_special(v.value) or not 0 <= x87.exact(v.value) < (1 << 64): raise EVariantOverflowError(type_text(v), 'UInt64') return value & 0xFFFFFFFFFFFFFFFF value = to_int64(v, runtime) if value < 0 and kind in (_Kind.Integer, _Kind.Int64): raise EVariantOverflowError(type_text(v), 'UInt64') return value & 0xFFFFFFFFFFFFFFFF def to_cardinal(v, runtime)-
Delphi's conversion of a Variant to a Cardinal, which converts it to an Int64 and keeps the low 32 bits.
Expand source code Browse git
def to_cardinal(v: Variant, runtime: Runtime) -> int: """ Delphi's conversion of a Variant to a Cardinal, which converts it to an Int64 and keeps the low 32 bits. """ return to_int64(v, runtime) & 0xFFFFFFFF def to_real(v, runtime)-
Delphi's conversion of a Variant to a float. An integer is loaded as the unit loads it, and a string is read as Windows reads a number in the regional format, as a Double.
Expand source code Browse git
def to_real(v: Variant, runtime: Runtime) -> Real: """ Delphi's conversion of a Variant to a float. An integer is loaded as the unit loads it, and a string is read as Windows reads a number in the regional format, as a Double. """ kind = _kind(v) if kind is _Kind.Empty: return 0.0 if kind in _INTEGERS: return runtime.fpu.load(v.value) if kind is _Kind.Boolean: return -1.0 if v.value else 0.0 if kind is _Kind.Float: return v.value if kind is _Kind.Currency: return runtime.currency_to_real(v.value) if kind is _Kind.String and (number := ole_number(v.value, runtime.settings)) is not None: return _ole_double(number, v, 'Double', runtime) raise EVariantTypeCastError(type_text(v), 'Double') def to_currency(v, runtime)-
Delphi's conversion of a Variant to a Currency.
Expand source code Browse git
def to_currency(v: Variant, runtime: Runtime) -> Fraction: """ Delphi's conversion of a Variant to a Currency. """ kind = _kind(v) if kind is _Kind.Empty: return Fraction(0) if kind in _INTEGERS: value = Fraction(v.value) elif kind is _Kind.Boolean: value = Fraction(-1 if v.value else 0) elif kind is _Kind.Currency: return v.value elif kind is _Kind.Float: if x87.is_special(v.value) or not _in_currency_range(x87.exact(v.value)): raise EVariantOverflowError(type_text(v), 'Currency') return runtime.variant_currency(v.value) elif kind is _Kind.String and (number := ole_number(v.value, runtime.settings)) is not None: value = Fraction(round(number.value * 10000), 10000) else: raise EVariantTypeCastError(type_text(v), 'Currency') if not _in_currency_range(value): raise EVariantOverflowError(type_text(v), 'Currency') return value def currency_text(value, settings)-
Delphi's
CurrToStr: all digits of a Currency without trailing zeros, with the regional decimal separator.Expand source code Browse git
def currency_text(value: Fraction, settings: FormatSettings) -> str: """ Delphi's `CurrToStr`: all digits of a Currency without trailing zeros, with the regional decimal separator. """ count = units(value) whole, fraction = divmod(abs(count), 10000) text = str(whole) if decimals := F'{fraction:04d}'.rstrip('0'): text = F'{text}{settings.decimal_separator}{decimals}' return F'-{text}' if count < 0 else text def to_string(v, runtime, target='OleStr')-
Delphi's conversion of a Variant to a string; the target names the string type in the message for a Variant that cannot be converted.
Expand source code Browse git
def to_string(v: Variant, runtime: Runtime, target: str = 'OleStr') -> str: """ Delphi's conversion of a Variant to a string; the target names the string type in the message for a Variant that cannot be converted. """ kind = _kind(v) if kind is _Kind.Empty: return '' if kind in _INTEGERS: return str(v.value) if kind is _Kind.Float: return float_to_str(v.value, runtime) if kind is _Kind.Currency: return currency_text(v.value, runtime.settings) if kind is _Kind.String: return v.value if kind is _Kind.Boolean: return 'True' if v.value else 'False' raise EVariantTypeCastError(type_text(v), target) def to_text(v, runtime)-
The text of a Variant in
Format: Null is empty.Expand source code Browse git
def to_text(v: Variant, runtime: Runtime) -> str: """ The text of a Variant in `Format`: Null is empty. """ if _kind(v) is _Kind.Null: return '' return to_string(v, runtime) def to_dispatch(v, runtime)-
Delphi's conversion of a Variant to an IDispatch: the object that it holds, and nil for Empty.
Expand source code Browse git
def to_dispatch(v: Variant, runtime: Runtime) -> Any: """ Delphi's conversion of a Variant to an IDispatch: the object that it holds, and nil for Empty. """ if v.type == VarType.Dispatch: return v.value if v.type == VarType.Empty: return None raise EVariantTypeCastError(type_text(v), 'Dispatch') def to_boolean(v, runtime)-
Delphi's conversion of a Variant to a Boolean.
Expand source code Browse git
def to_boolean(v: Variant, runtime: Runtime) -> bool: """ Delphi's conversion of a Variant to a Boolean. """ kind = _kind(v) if kind is _Kind.Empty: return False if kind is _Kind.Boolean: return bool(v.value) if kind in _INTEGERS or kind is _Kind.Float or kind is _Kind.Currency: return v.value != 0 if kind is _Kind.String: if (truth := _text_truth(v.value)) is not None: return truth if ole_number(v.value, runtime.settings) is not None: return _string_to_integer(v, runtime, 64, 'Int64') & 0xFFFFFFFF != 0 raise EVariantTypeCastError(type_text(v), 'Boolean') def is_float(v)-
Whether the Variant holds a Single, a Double, or a Currency.
Expand source code Browse git
def is_float(v: Variant) -> bool: """ Whether the Variant holds a Single, a Double, or a Currency. """ return v.type in (VarType.Single, VarType.Double, VarType.Currency) def operate(op, a, b, runtime)-
Delphi's binary operator on two Variants.
Expand source code Browse git
def operate(op: Operator, a: Variant, b: Variant, runtime: Runtime) -> Variant: """ Delphi's binary operator on two Variants. """ ka = _kind(a) kb = _kind(b) if ka is _Kind.Other or kb is _Kind.Other: raise EVariantInvalidOpError if op is Operator.And or op is Operator.Or: return _logic(op, a, b, ka, kb, runtime) if ka is _Kind.Null or kb is _Kind.Null: return NULL if ka is _Kind.Empty and kb is _Kind.Empty: if op is Operator.IntDivide or op is Operator.Modulus: raise EDivByZero return EMPTY if op is Operator.Xor and _is_logical(ka, kb): return Variant(VarType.Boolean, to_boolean(a, runtime) != to_boolean(b, runtime)) if op is Operator.Add and (ka is _Kind.String or kb is _Kind.String): if ka is _Kind.String and kb is _Kind.String: return Variant(a.type, a.value + b.value) if ka is _Kind.Empty: return Variant(VarType.OleStr, b.value) if kb is _Kind.Empty: return a if _is_unsigned(ka, kb): return _unsigned(op, a, b, runtime) if op in (Operator.Add, Operator.Subtract, Operator.Multiply): return _arithmetic(op, a, b, ka, kb, runtime) if op is Operator.Divide: return _divide(a, b, runtime) return _integral(op, a, b, ka, kb, runtime) def compare(a, b, runtime)-
Delphi's comparison of two Variants: negative, zero, or positive. Null is below every other value and Empty below every value but Null; strings compare by their characters; a string and a Boolean compare as Booleans, two integers as integers, a string and a number as numbers.
Expand source code Browse git
def compare(a: Variant, b: Variant, runtime: Runtime) -> int: """ Delphi's comparison of two Variants: negative, zero, or positive. Null is below every other value and Empty below every value but Null; strings compare by their characters; a string and a Boolean compare as Booleans, two integers as integers, a string and a number as numbers. """ ka = _kind(a) kb = _kind(b) if ka is _Kind.Other or kb is _Kind.Other: raise EVariantInvalidOpError for low in (_Kind.Null, _Kind.Empty): if ka is low or kb is low: if ka is kb: return 0 return -1 if ka is low else 1 if ka is _Kind.String and kb is _Kind.String: return (a.value > b.value) - (a.value < b.value) if (ka is _Kind.Boolean or kb is _Kind.Boolean) and (ka in _LOGICAL and kb in _LOGICAL): x = -1 if to_boolean(a, runtime) else 0 y = -1 if to_boolean(b, runtime) else 0 return (x > y) - (x < y) if ka is _Kind.Currency or kb is _Kind.Currency: if ka is _Kind.String or kb is _Kind.String: x = to_currency(a, runtime) y = to_currency(b, runtime) return (x > y) - (x < y) if ka in _INTEGERS and kb in _INTEGERS: return (a.value > b.value) - (a.value < b.value) order = runtime.fpu.compare(to_real(a, runtime), to_real(b, runtime)) return 0 if order is None else order def negate(v, runtime)-
Delphi's negation of a Variant.
Expand source code Browse git
def negate(v: Variant, runtime: Runtime) -> Variant: """ Delphi's negation of a Variant. """ kind = _kind(v) if kind is _Kind.Empty: return Variant(VarType.SmallInt, 0) if kind is _Kind.Null: return NULL if kind is _Kind.Integer: return Variant(VarType.Integer, _wrap(-v.value, 32)) if kind is _Kind.Int64 or kind is _Kind.UInt64: return Variant(VarType.Int64, _wrap(-v.value, 64)) if kind is _Kind.Float or kind is _Kind.Currency: return Variant(v.type, -v.value) if kind is _Kind.Boolean: return Variant(VarType.SmallInt, -1 if v.value else 0) if kind is _Kind.String: return Variant(VarType.Double, runtime.fpu.negate(to_real(v, runtime))) raise EVariantInvalidOpError def invert(v, runtime)-
Delphi's
notof a Variant: logical for a Boolean, bitwise otherwise.Expand source code Browse git
def invert(v: Variant, runtime: Runtime) -> Variant: """ Delphi's `not` of a Variant: logical for a Boolean, bitwise otherwise. """ kind = _kind(v) if kind is _Kind.Empty: return Variant(VarType.ShortInt, -1) if kind is _Kind.Null: return NULL if kind is _Kind.Boolean: return Variant(VarType.Boolean, not v.value) if kind in _INTEGERS: bits, signed = _INTEGER_WIDTHS[v.type] # type: ignore return Variant(v.type, _wrap(~v.value, bits, signed)) return Variant(VarType.Integer, ~to_integer(v, runtime))
Classes
class VarType (*args, **kwds)-
The type codes of Delphi's Variants, as the function
VarTypereturns them.Expand source code Browse git
class VarType(enum.IntEnum): """ The type codes of Delphi's Variants, as the function `VarType` returns them. """ Empty = 0x0000 # noqa Null = 0x0001 # noqa SmallInt = 0x0002 # noqa Integer = 0x0003 # noqa Single = 0x0004 # noqa Double = 0x0005 # noqa Currency = 0x0006 # noqa Date = 0x0007 # noqa OleStr = 0x0008 # noqa Dispatch = 0x0009 # noqa Error = 0x000A # noqa Boolean = 0x000B # noqa Variant = 0x000C # noqa Unknown = 0x000D # noqa ShortInt = 0x0010 # noqa Byte = 0x0011 # noqa Word = 0x0012 # noqa LongWord = 0x0013 # noqa Int64 = 0x0014 # noqa UInt64 = 0x0015 # noqa String = 0x0100 # noqa UString = 0x0102 # noqa Array = 0x2000 # noqa @property def text(self) -> str: """ The name that Delphi's messages give the type. """ return _TYPE_TEXT.get(self, self.name)Ancestors
- enum.IntEnum
- builtins.int
- enum.ReprEnum
- enum.Enum
Class variables
var Empty-
The type of the None singleton.
var Null-
The type of the None singleton.
var SmallInt-
The type of the None singleton.
var Integer-
The type of the None singleton.
var Single-
The type of the None singleton.
var Double-
The type of the None singleton.
var Currency-
The type of the None singleton.
var Date-
The type of the None singleton.
var OleStr-
The type of the None singleton.
var Dispatch-
The type of the None singleton.
var Error-
The type of the None singleton.
var Boolean-
The type of the None singleton.
var Variant-
The type of the None singleton.
var Unknown-
The type of the None singleton.
var ShortInt-
The type of the None singleton.
var Byte-
The type of the None singleton.
var Word-
The type of the None singleton.
var LongWord-
The type of the None singleton.
var Int64-
The type of the None singleton.
var UInt64-
The type of the None singleton.
var String-
The type of the None singleton.
var UString-
The type of the None singleton.
var Array-
The type of the None singleton.
Instance variables
var text-
The name that Delphi's messages give the type.
Expand source code Browse git
@property def text(self) -> str: """ The name that Delphi's messages give the type. """ return _TYPE_TEXT.get(self, self.name)
class Variant (type, value=None)-
A Delphi Variant: its type and its value. An integer type holds an
int, Single and Double afloat, Currency aFractionwith at most four decimals, a string type astr, Boolean abool, Dispatch the object, an array the list of its Variants, and Empty and Null nothing.Expand source code Browse git
@dataclass(frozen=True) class Variant: """ A Delphi Variant: its type and its value. An integer type holds an `int`, Single and Double a `float`, Currency a `Fraction` with at most four decimals, a string type a `str`, Boolean a `bool`, Dispatch the object, an array the list of its Variants, and Empty and Null nothing. """ type: int value: Any = None def __repr__(self): try: name = VarType(self.type).name except ValueError: name = F'0x{self.type:04X}' return F'Variant({name}, {self.value!r})'Instance variables
var type-
The type of the None singleton.
var value-
The type of the None singleton.
class Operator (*args, **kwds)-
The binary operators of Variants. Pascal Script divides with
Dividewhen the left operand holds a Single, Double or Currency, and withIntDivideotherwise.Expand source code Browse git
class Operator(enum.Enum): """ The binary operators of Variants. Pascal Script divides with `Divide` when the left operand holds a Single, Double or Currency, and with `IntDivide` otherwise. """ Add = '+' Subtract = '-' Multiply = '*' Divide = '/' IntDivide = 'div' Modulus = 'mod' ShiftLeft = 'shl' ShiftRight = 'shr' And = 'and' Or = 'or' Xor = 'xor'Ancestors
- enum.Enum
Class variables
var Add-
The type of the None singleton.
var Subtract-
The type of the None singleton.
var Multiply-
The type of the None singleton.
var Divide-
The type of the None singleton.
var IntDivide-
The type of the None singleton.
var Modulus-
The type of the None singleton.
var ShiftLeft-
The type of the None singleton.
var ShiftRight-
The type of the None singleton.
var And-
The type of the None singleton.
var Or-
The type of the None singleton.
var Xor-
The type of the None singleton.