Module refinery.lib.inno.pascal
Pascal Script's rules for the values of its types: how it reads a value of one type as another,
stores a value into a variable, computes with two values, and compares them. Integers, sets, and
strings follow Pascal Script's own code; floats, Currency, and Variants are left to the Delphi
run-time library of refinery.lib.inno.delphi, which Pascal Script calls for them.
Expand source code Browse git
"""
Pascal Script's rules for the values of its types: how it reads a value of one type as another,
stores a value into a variable, computes with two values, and compares them. Integers, sets, and
strings follow Pascal Script's own code; floats, Currency, and Variants are left to the Delphi
run-time library of `refinery.lib.inno.delphi`, which Pascal Script calls for them.
"""
from __future__ import annotations
import enum
import math
import operator
import struct
from dataclasses import dataclass
from fractions import Fraction
from typing import Any, Callable, TypeVar
from refinery.lib.inno.delphi import variants, x87
from refinery.lib.inno.delphi.errors import (
DelphiException,
EDivByZero,
EIntOverflow,
EMathError,
EZeroDivide,
)
from refinery.lib.inno.delphi.runtime import Operation, Runtime, truncated_division, units
from refinery.lib.inno.delphi.variants import Variant, VarType
from refinery.lib.inno.delphi.x87 import FPU, MASKED_CONTROL_WORD, Format, Real
from refinery.lib.inno.ifps import (
TC,
AOp,
COp,
Dialect,
IFPSType,
TArray,
TRecord,
TSet,
TStaticArray,
)
_T = TypeVar('_T')
@dataclass
class Machine:
"""
The state that Pascal Script's rules for values read: the version of the rules and the state
of the Delphi run-time library that Pascal Script runs on.
"""
dialect: Dialect
runtime: Runtime
class PSError(enum.IntEnum):
"""
The kinds of runtime error that Pascal Script distinguishes, numbered as Pascal Script numbers
them. Pascal Script keeps the kind with every error of the script.
"""
InvalidType = 2
InvalidOpcodeParameter = 6
OutOfRange = 10
TypeMismatch = 12
DivideByZero = 15
MathError = 16
CouldNotCallProc = 17
OutOfRecordRange = 18
Exception = 20
NullPointerException = 21
CustomError = 24
@property
def text(self) -> str:
"""
The message that Pascal Script gives an error of this kind when it carries no exception of
Delphi.
"""
return _PS_ERROR_TEXT[self]
_PS_ERROR_TEXT = {
PSError.InvalidType : 'Invalid Type',
PSError.InvalidOpcodeParameter : 'Invalid Opcode Parameter',
PSError.OutOfRange : 'Out Of Range',
PSError.TypeMismatch : 'Type Mismatch',
PSError.DivideByZero : 'divide by Zero',
PSError.MathError : 'Math error',
PSError.CouldNotCallProc : 'Could not call proc',
PSError.OutOfRecordRange : 'Out of Record Fields Range',
PSError.Exception : 'Exception: ',
PSError.NullPointerException : 'Null Pointer Exception',
PSError.CustomError : '',
}
class IFPSException(RuntimeError):
"""
An error of the script, which its exception handlers can handle. Pascal Script keeps the `kind`
of every error, and the exception of Delphi that raised it, if any. The script reads the
`message` of that exception with `GetExceptionMessage`, or the text of the kind when there is
no exception. An error that escapes from a call of an emulated function names the `called`
function in its report. An emulated function that both raises the error and reports that it
could not be called sets `failed_call`: when an except block takes the error, Pascal Script
raises `Could not call proc` at the start of that block.
"""
def __init__(
self,
message: str | None = None,
kind: PSError = PSError.Exception,
called: str | None = None,
failed_call: bool = False,
):
message = kind.text if message is None else str(message)
report = message if called is None else F'Error calling {called}: {message}'
super().__init__(report)
self.kind = kind
self.message = message
self.called = called
self.failed_call = failed_call
def type_mismatch() -> IFPSException:
return IFPSException(kind=PSError.TypeMismatch)
def delphi_error(error: DelphiException) -> IFPSException:
"""
The error of the script that Pascal Script makes of an exception of Delphi: a float division
by zero and an integer division by zero keep the kind of a division by zero, the other float
errors are math errors.
"""
if isinstance(error, (EZeroDivide, EDivByZero)):
kind = PSError.DivideByZero
elif isinstance(error, EMathError):
kind = PSError.MathError
else:
kind = PSError.Exception
return IFPSException(str(error), kind)
def _delphi(function: Callable[..., _T], *args) -> _T:
"""
Calls a function of the Delphi run-time library; an exception of Delphi becomes the error of
the script that Pascal Script makes of it.
"""
try:
return function(*args)
except DelphiException as error:
raise delphi_error(error) from error
def signed32(value: int) -> int:
return ((value + 0x80000000) & 0xFFFFFFFF) - 0x80000000
def _signed(value: int, bits: int) -> int:
value &= (1 << bits) - 1
return value - (1 << bits) if value >> (bits - 1) else value
_FLOAT_FORMATS = {
TC.Single : Format.Single,
TC.Double : Format.Double,
TC.Extended : Format.Extended,
}
_STRINGS = frozenset((TC.AnsiString, TC.PChar, TC.WideString, TC.UnicodeString))
_TEXTS = _STRINGS | {TC.Char, TC.WideChar}
def representation(t: IFPSType, value: Any) -> Any:
"""
The value that a variable of the given primitive type holds for a value of Python code, like
the result of an emulated function: integers are wrapped into their range, a one-character
string is read as its code, floats are rounded to their format, a character holds the first
character of a string, and a Variant holds the value with the Variant type that matches its
Python type.
"""
code = t.code
if (valid := code.integer_range) is not None:
if type(value) is not int:
value = _python_integer(t, value)
start = valid.start
return start + (value - start) % (valid.stop - start)
if code in _TEXTS:
if type(value) is not str:
if not isinstance(value, int) or isinstance(value, bool):
raise _python_mismatch(t, value)
value = chr(value)
if code.character and len(value) != 1:
return value[:1] or '\0'
return value
if code is TC.Variant:
return variants.from_python(value)
if (fmt := _FLOAT_FORMATS.get(code)) is not None:
if isinstance(value, bool) or not isinstance(value, (int, float, x87.Extended)):
raise _python_mismatch(t, value)
if isinstance(value, int):
value = x87.real(value)
return _NEAREST.store(value, fmt)
if code is TC.Currency:
if isinstance(value, bool) or not isinstance(value, (int, Fraction)):
raise _python_mismatch(t, value)
return Fraction(round(Fraction(value) * 10000), 10000)
if code is TC.Set and not isinstance(value, int):
raise _python_mismatch(t, value)
return value
def _python_integer(t: IFPSType, value: Any) -> int:
if isinstance(value, str) and len(value) == 1:
return ord(value)
if isinstance(value, bool):
return int(value)
if isinstance(value, float) and math.isfinite(value):
return int(value)
if not isinstance(value, int):
raise _python_mismatch(t, value)
return value
def _python_mismatch(t: IFPSType, value: Any) -> TypeError:
return TypeError(F'Assigning value {value!r} to variable of type {t}.')
_NEAREST = FPU(MASKED_CONTROL_WORD)
"""
Rounds values from Python code to the nearest value of a float format.
"""
def default(t: IFPSType) -> Any:
"""
The value of a new variable of a type that holds no other variables. Pascal Script fills a new
variable with zero bytes: zero, the empty string, the character 0, the empty set, nil, and an
unassigned Variant. It leaves a return address, a resource pointer, and a type as they are;
these start as zero and nil.
"""
code = t.code
if code.integer or code is TC.Enum or code is TC.Set or code is TC.ReturnAddress:
return 0
if code in _FLOAT_FORMATS:
return 0.0
if code is TC.Currency:
return Fraction(0)
if code.character:
return '\0'
if code in _STRINGS:
return ''
if code is TC.Variant:
return variants.EMPTY
if code in (TC.Class, TC.Interface, TC.ProcPtr, TC.ResourcePointer, TC.Type):
return None
raise TypeError(F'The type {t} has no default value.')
def read_cardinal(t: IFPSType, value: Any, runtime: Runtime) -> int:
"""
Pascal Script's `PSGetUInt`: a value read as a Cardinal.
"""
code = t.code
if code.integer:
return value & 0xFFFFFFFF
if code.character:
return ord(value)
if code is TC.Variant:
if value.type in (VarType.String, VarType.OleStr):
if len(value.value) != 1:
raise type_mismatch()
return ord(value.value)
return _delphi(variants.to_cardinal, value, runtime)
raise type_mismatch()
def read_integer(t: IFPSType, value: Any, runtime: Runtime) -> int:
"""
Pascal Script's `PSGetInt`: a value read as a Longint.
"""
code = t.code
if code.integer:
return signed32(value)
if code.character:
return ord(value)
if code is TC.Variant:
return _delphi(variants.to_integer, value, runtime)
raise type_mismatch()
def read_int64(t: IFPSType, value: Any, runtime: Runtime) -> int:
"""
Pascal Script's `PSGetInt64`: a value read as an Int64.
"""
code = t.code
if code.integer:
return value if code is not TC.U64 else _signed(value, 64)
if code.character:
return ord(value)
if code is TC.Variant:
return _delphi(variants.to_int64, value, runtime)
raise type_mismatch()
def read_uint64(t: IFPSType, value: Any, runtime: Runtime) -> int:
"""
Pascal Script's `PSGetUInt64` of Inno Setup 7: a value read as a UInt64.
"""
code = t.code
if code.integer:
return value & 0xFFFFFFFFFFFFFFFF
if code.character:
return ord(value)
if code is TC.Variant:
return _delphi(variants.to_uint64, value, runtime)
raise type_mismatch()
def read_real(t: IFPSType, value: Any, runtime: Runtime) -> Real:
"""
Pascal Script's `PSGetReal`: a value read as an Extended.
"""
code = t.code
if code.integer:
return runtime.fpu.load(value)
if code in _FLOAT_FORMATS:
return value
if code is TC.Currency:
return _delphi(runtime.currency_to_real, value)
if code is TC.Variant:
return _delphi(variants.to_real, value, runtime)
raise type_mismatch()
def read_currency(t: IFPSType, value: Any, runtime: Runtime) -> Fraction:
"""
Pascal Script's `PSGetCurrency`: a value read as a Currency.
"""
code = t.code
if code.integer:
return _delphi(runtime.currency_of_integer, value)
if code in _FLOAT_FORMATS:
return _delphi(runtime.currency, value)
if code is TC.Currency:
return value
if code is TC.Variant:
return _delphi(variants.to_currency, value, runtime)
raise type_mismatch()
def read_string(t: IFPSType, value: Any, runtime: Runtime, target: TC = TC.UnicodeString) -> str:
"""
Pascal Script's `PSGetAnsiString`, `PSGetWideString`, and `PSGetUnicodeString`: a value read as
a string of the given type.
"""
code = t.code
if code is TC.U08:
return chr(value)
if code is TC.U16 and target is not TC.AnsiString:
return chr(value)
if code.character or code in _STRINGS:
return value
if code is TC.Variant:
name = 'String' if target is TC.AnsiString else 'OleStr'
return _delphi(variants.to_string, value, runtime, name)
raise type_mismatch()
def to_variant(t: IFPSType, value: Any, machine: Machine) -> Variant:
"""
Pascal Script's `IntPIFVariantToVariant`: the Variant that holds a value of the given type.
Before Inno Setup 7, Pascal Script asks for the length of a static array as for a dynamic
array, which fails.
"""
runtime = machine.runtime
code = t.code
if code is TC.Variant:
return value
if code is TC.U08:
if t.boolean:
return Variant(VarType.Boolean, value != 0)
return Variant(VarType.Byte, value)
vtype = _VARIANT_TYPES.get(code)
if vtype is not None:
if code is TC.Extended:
return Variant(vtype, _delphi(runtime.fpu.store, value, Format.Double))
if code is TC.Single:
return Variant(vtype, float(value))
return Variant(vtype, value)
if isinstance(t, TStaticArray) and machine.dialect is Dialect.Inno6:
raise IFPSException('Invalid array')
if isinstance(t, (TArray, TStaticArray)):
element = t.type
return Variant(VarType.Array | VarType.Variant, [to_variant(element, v, machine) for v in value])
if code is TC.Interface:
return Variant(VarType.Dispatch, value)
raise type_mismatch()
_VARIANT_TYPES = {
TC.S08 : VarType.ShortInt,
TC.U16 : VarType.Word,
TC.S16 : VarType.SmallInt,
TC.U32 : VarType.LongWord,
TC.S32 : VarType.Integer,
TC.S64 : VarType.Int64,
TC.U64 : VarType.UInt64,
TC.Single : VarType.Double,
TC.Double : VarType.Double,
TC.Extended : VarType.Double,
TC.Currency : VarType.Currency,
TC.AnsiString : VarType.String,
TC.PChar : VarType.String,
TC.Char : VarType.String,
TC.WideString : VarType.OleStr,
TC.WideChar : VarType.OleStr,
TC.UnicodeString : VarType.UString,
}
def store(target: IFPSType, source: IFPSType, value: Any, machine: Machine) -> Any:
"""
Pascal Script's `SetVariantValue` for a primitive destination: the value that a variable of the
target type holds after the assignment of a value of the source type.
"""
runtime = machine.runtime
code = target.code
sc = source.code
if code is TC.U08:
return read_cardinal(source, value, runtime) & 0xFF
if code is TC.S08:
return _signed(read_integer(source, value, runtime), 8)
if code is TC.U16:
return read_cardinal(source, value, runtime) & 0xFFFF
if code is TC.S16:
return _signed(read_integer(source, value, runtime), 16)
if code is TC.U32:
if sc.integer:
return value & 0xFFFFFFFF
if sc.character:
return ord(value)
if sc is TC.Variant:
return _delphi(variants.to_cardinal, value, runtime)
raise type_mismatch()
if code is TC.S32:
if sc.integer:
return signed32(value)
if sc.character:
return ord(value)
if sc is TC.Variant:
return _delphi(variants.to_integer, value, runtime)
if sc is TC.Class:
return value
raise type_mismatch()
if code is TC.S64:
return read_int64(source, value, runtime)
if code is TC.U64:
return read_uint64(source, value, runtime)
if (fmt := _FLOAT_FORMATS.get(code)) is not None:
if sc.integer:
real = x87.real(value)
elif sc in _FLOAT_FORMATS or sc is TC.Currency or sc is TC.Variant:
real = read_real(source, value, runtime)
else:
raise type_mismatch()
return _delphi(runtime.fpu.store, real, fmt)
if code is TC.Currency:
return read_currency(source, value, runtime)
if code is TC.AnsiString or code is TC.PChar:
return read_string(source, value, runtime, TC.AnsiString)
if code is TC.WideString:
return read_string(source, value, runtime, TC.WideString)
if code is TC.UnicodeString:
return read_string(source, value, runtime, TC.UnicodeString)
if code is TC.Char:
return chr(read_cardinal(source, value, runtime) & 0xFF)
if code is TC.WideChar:
return chr(read_cardinal(source, value, runtime) & 0xFFFF)
if code is TC.Variant:
if sc is TC.Interface:
return Variant(VarType.Dispatch, value)
return to_variant(source, value, machine)
if code is TC.Interface:
if sc is TC.Variant:
return _delphi(variants.to_dispatch, value, runtime)
if sc is TC.Interface or sc is TC.Class:
return value
raise type_mismatch()
if code is TC.Set:
if target != source:
raise type_mismatch()
return value
if code is TC.Class or code is TC.ProcPtr:
return value
raise type_mismatch()
def assignable(target: IFPSType, source: IFPSType, machine: Machine) -> bool:
"""
Whether Pascal Script assigns a value of the source type to a variable of the target type that
holds other variables: a dynamic array only takes a dynamic array whose elements have the same
type, and from Inno Setup 7 on also one whose elements have the same base type, unless that is
a record, a set, or an array.
"""
if (
isinstance(target, TArray)
and isinstance(source, TArray)
and target.code is TC.Array
and source.code is TC.Array
):
if target.type is source.type:
return True
if machine.dialect is Dialect.Inno6:
return False
code = target.type.code
return code is source.type.code and code not in _CONTAINERS
return True
_CONTAINERS = frozenset((TC.Record, TC.Set, TC.StaticArray, TC.Array))
def _in_range(code: TC, value: int) -> int:
valid = code.integer_range
assert valid is not None
start = valid.start
return start + (value - start) % (valid.stop - start)
def ordinal(code: TC, value) -> int:
"""
Reads an operand as Pascal Script reads it for integer arithmetic and comparisons: an integer as
its value and a character as its code point. Any other operand is a type mismatch.
"""
if code.integer:
return value
if code.character:
return ord(value)
raise type_mismatch()
_INTEGER_OPERATIONS = {
AOp.Add : operator.add,
AOp.Sub : operator.sub,
AOp.Mul : operator.mul,
AOp.And : operator.and_,
AOp.BOr : operator.or_,
AOp.Xor : operator.xor,
}
_SET_OPERATIONS = {
AOp.Add: operator.or_,
AOp.Sub: lambda a, b: a & ~b,
AOp.Mul: operator.and_,
}
_VARIANT_OPERATORS = {
AOp.Add : variants.Operator.Add,
AOp.Sub : variants.Operator.Subtract,
AOp.Mul : variants.Operator.Multiply,
AOp.Mod : variants.Operator.Modulus,
AOp.Shl : variants.Operator.ShiftLeft,
AOp.Shr : variants.Operator.ShiftRight,
AOp.And : variants.Operator.And,
AOp.BOr : variants.Operator.Or,
AOp.Xor : variants.Operator.Xor,
}
_VARIANT_ARITHMETIC = frozenset((AOp.Add, AOp.Sub, AOp.Mul, AOp.Div, AOp.Mod))
_OPERATIONS = {
AOp.Add : Operation.Add,
AOp.Sub : Operation.Subtract,
AOp.Mul : Operation.Multiply,
AOp.Div : Operation.Divide,
}
def _integer_arithmetic(aop: AOp, target: TC, a: int, source: TC, b: int, runtime: Runtime) -> int:
"""
Pascal Script's arithmetic for a destination of an integer type. Addition, subtraction,
multiplication, and the bitwise operators give the bits that the destination keeps however the
operands are read. For division and shifts, the destination's type decides how the source is
read and how wide the computation is. An Int64 destination reads the source as an Int64 and a
UInt64 destination as a UInt64. A 32-bit destination with a 64-bit source divides on 64 bits,
without a sign when the source is a UInt64. Otherwise the division takes 32 bits, unsigned for
an unsigned destination.
"""
if (calculate := _INTEGER_OPERATIONS.get(aop)) is not None:
return calculate(a, b)
wide = target.width > 4
if aop is AOp.Shl:
return a << (b & (63 if wide else 31))
if aop is AOp.Shr:
if wide:
return (a & 0xFFFFFFFFFFFFFFFF) >> (b & 63)
return (a & 0xFFFFFFFF) >> (b & 31)
if target is TC.S64:
quotient, remainder = _delphi(runtime.int64_division, a, _signed(b, 64))
return quotient if aop is AOp.Div else remainder
if target is TC.U64:
b &= 0xFFFFFFFFFFFFFFFF
elif not wide and (target.width < 4 or source.width <= 4):
if target.signed:
b = signed32(b)
if a == -0x80000000 and b == -1:
raise delphi_error(EIntOverflow())
else:
b &= 0xFFFFFFFF
elif source is TC.U64:
a &= 0xFFFFFFFFFFFFFFFF
quotient, remainder = _delphi(truncated_division, a, b)
return quotient if aop is AOp.Div else remainder
def _variant_operator(aop: AOp, left: Variant) -> variants.Operator:
if aop is AOp.Div:
return variants.Operator.Divide if variants.is_float(left) else variants.Operator.IntDivide
return _VARIANT_OPERATORS[aop]
def _operate(aop: AOp, left: Variant, right: Variant, runtime: Runtime, divide: bool = False) -> Variant:
op = variants.Operator.Divide if divide and aop is AOp.Div else _variant_operator(aop, left)
return _delphi(variants.operate, op, left, right, runtime)
def calculate(aop: AOp, target: IFPSType, a: Any, source: IFPSType, b: Any, machine: Machine) -> Any:
"""
Pascal Script's `DoCalc`: the value that a variable of the target type holds after computing
with the source operand. Byte, ShortInt, Word, SmallInt, and Int64 destinations read a Variant
operand as an integer; Cardinal and Integer destinations compute with Delphi's Variant
operators for addition, subtraction, multiplication, and division; float and Currency
destinations compute with Delphi's Variant operators for any Variant operand. From Inno Setup
7 on, `as` keeps the object; Setup's classes are not modelled, so its class is not checked.
"""
runtime = machine.runtime
code = target.code
sc = source.code
if aop is AOp.As:
if machine.dialect is Dialect.Inno6:
raise IFPSException(kind=PSError.InvalidOpcodeParameter)
return a
if code.integer:
if sc is TC.Variant:
if code in (TC.U32, TC.S32) and aop in _VARIANT_ARITHMETIC:
vtype = VarType.LongWord if code is TC.U32 and aop is not AOp.Mod else VarType.Integer
left = Variant(vtype, a if vtype is VarType.LongWord else signed32(a))
result = _operate(aop, left, b, runtime)
if code is TC.U32:
return _delphi(variants.to_cardinal, result, runtime)
return _delphi(variants.to_integer, result, runtime)
if code is TC.S64:
b, sc = read_int64(source, b, runtime), TC.S64
elif code is TC.U64:
b, sc = read_uint64(source, b, runtime), TC.U64
elif code.signed:
b, sc = read_integer(source, b, runtime), TC.S32
else:
b, sc = read_cardinal(source, b, runtime), TC.U32
return _in_range(code, _integer_arithmetic(aop, code, a, sc, ordinal(sc, b), runtime))
if code is TC.Set:
if (operation := _SET_OPERATIONS.get(aop)) is None or not isinstance(source, TSet):
raise type_mismatch()
return operation(a, b)
if (fmt := _FLOAT_FORMATS.get(code)) is not None:
if aop not in (AOp.Add, AOp.Sub, AOp.Mul, AOp.Div):
raise type_mismatch()
if sc is TC.Variant:
double = a if code is not TC.Extended else _delphi(runtime.fpu.store, a, Format.Double)
result = _operate(aop, Variant(VarType.Double, double), b, runtime, True)
real = _delphi(variants.to_real, result, runtime)
return _delphi(runtime.fpu.store, real, fmt)
if not (sc.integer or sc in _FLOAT_FORMATS or sc is TC.Currency):
raise type_mismatch()
if sc is TC.Currency and aop is AOp.Div:
return _delphi(runtime.real_divided_by_currency, a, b, fmt)
other = read_real(source, b, runtime)
return _delphi(runtime.real_arithmetic, _OPERATIONS[aop], a, other, fmt)
if code is TC.Currency:
if aop not in (AOp.Add, AOp.Sub, AOp.Mul, AOp.Div):
raise type_mismatch()
if sc is TC.Variant:
result = _operate(aop, Variant(VarType.Currency, a), b, runtime, True)
return _delphi(variants.to_currency, result, runtime)
if not (sc.integer or sc in _FLOAT_FORMATS or sc is TC.Currency):
raise type_mismatch()
if machine.dialect is Dialect.Inno6 and (sc is TC.U32 or sc is TC.S64):
return _delphi(_currency_read_as_double, runtime, aop, a, b)
return _delphi(runtime.currency_arithmetic, _OPERATIONS[aop], a, b)
if code in _STRINGS:
if aop is not AOp.Add:
raise type_mismatch()
target_code = TC.AnsiString if code is TC.PChar else code
return a + read_string(source, b, runtime, target_code)
if code is TC.Char:
if aop is not AOp.Add:
raise type_mismatch()
return chr((ord(a) + read_cardinal(source, b, runtime)) & 0xFF)
if code is TC.WideChar:
if aop is not AOp.Add:
raise type_mismatch()
return chr((ord(a) + read_cardinal(source, b, runtime)) & 0xFFFF)
if code is TC.Variant:
return _operate(aop, a, to_variant(source, b, machine), runtime)
raise type_mismatch()
def _currency_read_as_double(runtime: Runtime, aop: AOp, a: Fraction, b: int) -> Fraction:
"""
Pascal Script before Inno Setup 7 computes a Currency with a Cardinal or an Int64 from the eight
bytes of the Currency read as a Double, and stores the result as a Currency.
"""
double, = struct.unpack('<d', struct.pack('<q', _signed(units(a), 64)))
return runtime.currency(runtime.real_arithmetic(_OPERATIONS[aop], double, runtime.fpu.load(b)))
def _integer_comparands(left: TC, a: int, right: TC, b) -> tuple:
"""
Reads the operands of a comparison whose left operand has an integer type as Pascal Script
reads them. The left operand's type decides how the right operand is read.
"""
if left is TC.S32:
if right is TC.U32:
return a, signed32(b)
if right.integer or right in _FLOAT_FORMATS:
return a, b
if right.character:
return a, ord(b)
raise type_mismatch()
if left is TC.U64:
return a, b & 0xFFFFFFFFFFFFFFFF if right.integer else ordinal(right, b)
if left is TC.S64 and right is TC.U64:
return a, _signed(b, 64)
if left is TC.U08 and right in (TC.AnsiString, TC.PChar):
return chr(a), b
b = ordinal(right, b)
if left.width > 4:
return a, b
if left.signed:
return a, signed32(b)
return a, b & 0xFFFFFFFF
def _set_contains(members: int, set_type: TSet, ordinal_value: int) -> bool:
ordinal_value &= 0xFFFFFFFF
if ordinal_value >= set_type.size:
raise IFPSException(kind=PSError.OutOfRecordRange)
return bool((members >> ordinal_value) & 1)
_TESTS = {
COp.GE : operator.ge,
COp.LE : operator.le,
COp.GT : operator.gt,
COp.LT : operator.lt,
COp.NE : operator.ne,
COp.EQ : operator.eq,
}
def compare(cop: COp, left: IFPSType, a: Any, right: IFPSType, b: Any, machine: Machine) -> bool:
"""
Pascal Script's `DoBooleanCalc`: compares `a`, which has the type `left`, with `b`, which has
the type `right`. The left operand's type decides how the right one is read. From Inno Setup 7
on, an Integer compares with a Currency as floats.
"""
runtime = machine.runtime
lc = left.code
rc = right.code
if cop is COp.IN:
if isinstance(right, TSet):
return _set_contains(b, right, read_cardinal(left, a, runtime))
return a in b
if cop is COp.IS:
return a is b
test = _TESTS[cop]
if lc.integer:
if rc is TC.Variant:
if lc is TC.S32:
return test(_variant_order(Variant(VarType.Integer, a), b, runtime), 0)
if lc is TC.S64:
return test(a, read_int64(right, b, runtime))
if lc is TC.U64:
return test(a, read_uint64(right, b, runtime))
if lc.signed:
return test(a, read_integer(right, b, runtime))
return test(a, read_cardinal(right, b, runtime))
if lc is TC.S32 and (rc in _FLOAT_FORMATS or rc is TC.Currency and machine.dialect is Dialect.Inno7):
return _real_test(test, read_real(left, a, runtime), read_real(right, b, runtime), runtime)
a, b = _integer_comparands(lc, a, rc, b)
return test(a, b)
if lc is TC.Set:
if not isinstance(right, TSet) or right != left:
raise type_mismatch()
if cop is COp.GE:
return b & ~a == 0
if cop is COp.LE:
return a & ~b == 0
if cop is COp.EQ:
return a == b
if cop is COp.NE:
return a != b
raise type_mismatch()
if lc in _FLOAT_FORMATS:
return _real_test(test, a, read_real(right, b, runtime), runtime)
if lc is TC.Currency:
return test(a, read_currency(right, b, runtime))
if lc is TC.AnsiString or lc is TC.PChar or lc is TC.Char:
return test(a, read_string(right, b, runtime, TC.AnsiString))
if lc is TC.WideChar or lc is TC.WideString:
return test(a, read_string(right, b, runtime, TC.WideString))
if lc is TC.UnicodeString:
return test(a, read_string(right, b, runtime, TC.UnicodeString))
if lc is TC.Variant:
return test(_variant_order(a, to_variant(right, b, machine), runtime), 0)
if lc in _REFERENCES and cop in (COp.EQ, COp.NE):
if rc is not lc:
raise type_mismatch()
return test(0 if a is b or a == b else 1, 0)
if lc is TC.Record and cop in (COp.EQ, COp.NE) and right == left:
return test(0 if a == b else 1, 0)
raise type_mismatch()
_REFERENCES = frozenset((TC.Class, TC.Interface, TC.ProcPtr))
def _real_test(test, a: Real, b: Real, runtime: Runtime) -> bool:
order = _delphi(runtime.fpu.compare, a, b)
if order is None:
return test is operator.ne
return test(order, 0)
def _variant_order(a: Variant, b: Variant, runtime: Runtime) -> int:
return _delphi(variants.compare, a, b, runtime)
def boolean(t: IFPSType, value: bool, machine: Machine) -> Any:
"""
The value that a comparison writes into a destination of the given type: an integer of up to
32 bits, and from Inno Setup 7 on any integer, holds 1 or 0, and a Variant holds a Boolean.
"""
code = t.code
if code.integer and (code.width <= 4 or machine.dialect is Dialect.Inno7):
return int(value)
if code is TC.Variant:
return Variant(VarType.Boolean, value)
raise type_mismatch()
def negate(t: IFPSType, value: Any, runtime: Runtime) -> Any:
"""
Pascal Script's `DoMinus`; a Variant that Delphi cannot negate is a type mismatch.
"""
code = t.code
if code in _FLOAT_FORMATS:
return runtime.fpu.negate(value)
if code.integer:
return _in_range(code, -value)
if code is TC.Currency:
return Fraction(_signed(-units(value), 64), 10000)
if code is TC.Variant:
try:
return variants.negate(value, runtime)
except DelphiException:
raise type_mismatch()
raise type_mismatch()
def invert(t: IFPSType, value: Any, runtime: Runtime) -> Any:
"""
Pascal Script's `DoIntegerNot`; a Variant that Delphi cannot invert is a type mismatch.
"""
code = t.code
if code.integer:
return _in_range(code, ~value)
if code is TC.Variant:
try:
return variants.invert(value, runtime)
except DelphiException:
raise type_mismatch()
raise type_mismatch()
def logical_not(t: IFPSType, value: Any, runtime: Runtime) -> Any:
"""
Pascal Script's `DoBooleanNot`: an integer becomes 1 when it is zero and 0 otherwise, a Variant
becomes the Boolean of its comparison with zero.
"""
code = t.code
if code.integer:
return int(value == 0)
if code is TC.Variant:
try:
return Variant(VarType.Boolean, variants.compare(value, Variant(VarType.Integer, 0), runtime) == 0)
except DelphiException:
raise type_mismatch()
raise type_mismatch()
def size_of(t: IFPSType, runtime: Runtime) -> int:
"""
Pascal Script's `SizeOf`: the size of a variable of the given type on the Setup's platform.
"""
code = t.code
pointer = runtime.pointer_size
if isinstance(t, TStaticArray):
return t.size * size_of(t.type, runtime)
if isinstance(t, TRecord):
return sum(size_of(m, runtime) for m in t.members)
if isinstance(t, TSet):
return t.size_in_bytes
if code in (TC.AnsiString, TC.WideString, TC.UnicodeString, TC.PChar, TC.Class, TC.Interface, TC.Array):
return pointer
if code is TC.Variant:
return runtime.variant_size
if code is TC.Extended:
return runtime.extended_size
if code in (TC.ProcPtr, TC.Pointer):
return 3 * pointer
return code.width
def step(t: IFPSType, value: Any, up: bool, machine: Machine) -> Any:
"""
Pascal Script's increment or decrement of the counter of a for loop: an integer wraps around in
its range; from Inno Setup 7 on, a Variant counts with Delphi's Variant addition or subtraction.
"""
code = t.code
if code.integer:
return _in_range(code, value + 1 if up else value - 1)
if code is TC.Variant and machine.dialect is Dialect.Inno7:
op = variants.Operator.Add if up else variants.Operator.Subtract
return _delphi(variants.operate, op, value, Variant(VarType.Integer, 1), machine.runtime)
raise type_mismatch()
def truth(t: IFPSType, value: Any) -> bool:
"""
The condition of a jump: Pascal Script accepts integers up to 32 bits.
"""
if t.code.integer and t.code.width <= 4:
return value != 0
raise IFPSException(kind=PSError.InvalidType)
Functions
def type_mismatch()-
Expand source code Browse git
def type_mismatch() -> IFPSException: return IFPSException(kind=PSError.TypeMismatch) def delphi_error(error)-
The error of the script that Pascal Script makes of an exception of Delphi: a float division by zero and an integer division by zero keep the kind of a division by zero, the other float errors are math errors.
Expand source code Browse git
def delphi_error(error: DelphiException) -> IFPSException: """ The error of the script that Pascal Script makes of an exception of Delphi: a float division by zero and an integer division by zero keep the kind of a division by zero, the other float errors are math errors. """ if isinstance(error, (EZeroDivide, EDivByZero)): kind = PSError.DivideByZero elif isinstance(error, EMathError): kind = PSError.MathError else: kind = PSError.Exception return IFPSException(str(error), kind) def signed32(value)-
Expand source code Browse git
def signed32(value: int) -> int: return ((value + 0x80000000) & 0xFFFFFFFF) - 0x80000000 def representation(t, value)-
The value that a variable of the given primitive type holds for a value of Python code, like the result of an emulated function: integers are wrapped into their range, a one-character string is read as its code, floats are rounded to their format, a character holds the first character of a string, and a Variant holds the value with the Variant type that matches its Python type.
Expand source code Browse git
def representation(t: IFPSType, value: Any) -> Any: """ The value that a variable of the given primitive type holds for a value of Python code, like the result of an emulated function: integers are wrapped into their range, a one-character string is read as its code, floats are rounded to their format, a character holds the first character of a string, and a Variant holds the value with the Variant type that matches its Python type. """ code = t.code if (valid := code.integer_range) is not None: if type(value) is not int: value = _python_integer(t, value) start = valid.start return start + (value - start) % (valid.stop - start) if code in _TEXTS: if type(value) is not str: if not isinstance(value, int) or isinstance(value, bool): raise _python_mismatch(t, value) value = chr(value) if code.character and len(value) != 1: return value[:1] or '\0' return value if code is TC.Variant: return variants.from_python(value) if (fmt := _FLOAT_FORMATS.get(code)) is not None: if isinstance(value, bool) or not isinstance(value, (int, float, x87.Extended)): raise _python_mismatch(t, value) if isinstance(value, int): value = x87.real(value) return _NEAREST.store(value, fmt) if code is TC.Currency: if isinstance(value, bool) or not isinstance(value, (int, Fraction)): raise _python_mismatch(t, value) return Fraction(round(Fraction(value) * 10000), 10000) if code is TC.Set and not isinstance(value, int): raise _python_mismatch(t, value) return value def default(t)-
The value of a new variable of a type that holds no other variables. Pascal Script fills a new variable with zero bytes: zero, the empty string, the character 0, the empty set, nil, and an unassigned Variant. It leaves a return address, a resource pointer, and a type as they are; these start as zero and nil.
Expand source code Browse git
def default(t: IFPSType) -> Any: """ The value of a new variable of a type that holds no other variables. Pascal Script fills a new variable with zero bytes: zero, the empty string, the character 0, the empty set, nil, and an unassigned Variant. It leaves a return address, a resource pointer, and a type as they are; these start as zero and nil. """ code = t.code if code.integer or code is TC.Enum or code is TC.Set or code is TC.ReturnAddress: return 0 if code in _FLOAT_FORMATS: return 0.0 if code is TC.Currency: return Fraction(0) if code.character: return '\0' if code in _STRINGS: return '' if code is TC.Variant: return variants.EMPTY if code in (TC.Class, TC.Interface, TC.ProcPtr, TC.ResourcePointer, TC.Type): return None raise TypeError(F'The type {t} has no default value.') def read_cardinal(t, value, runtime)-
Pascal Script's
PSGetUInt: a value read as a Cardinal.Expand source code Browse git
def read_cardinal(t: IFPSType, value: Any, runtime: Runtime) -> int: """ Pascal Script's `PSGetUInt`: a value read as a Cardinal. """ code = t.code if code.integer: return value & 0xFFFFFFFF if code.character: return ord(value) if code is TC.Variant: if value.type in (VarType.String, VarType.OleStr): if len(value.value) != 1: raise type_mismatch() return ord(value.value) return _delphi(variants.to_cardinal, value, runtime) raise type_mismatch() def read_integer(t, value, runtime)-
Pascal Script's
PSGetInt: a value read as a Longint.Expand source code Browse git
def read_integer(t: IFPSType, value: Any, runtime: Runtime) -> int: """ Pascal Script's `PSGetInt`: a value read as a Longint. """ code = t.code if code.integer: return signed32(value) if code.character: return ord(value) if code is TC.Variant: return _delphi(variants.to_integer, value, runtime) raise type_mismatch() def read_int64(t, value, runtime)-
Pascal Script's
PSGetInt64: a value read as an Int64.Expand source code Browse git
def read_int64(t: IFPSType, value: Any, runtime: Runtime) -> int: """ Pascal Script's `PSGetInt64`: a value read as an Int64. """ code = t.code if code.integer: return value if code is not TC.U64 else _signed(value, 64) if code.character: return ord(value) if code is TC.Variant: return _delphi(variants.to_int64, value, runtime) raise type_mismatch() def read_uint64(t, value, runtime)-
Pascal Script's
PSGetUInt64of Inno Setup 7: a value read as a UInt64.Expand source code Browse git
def read_uint64(t: IFPSType, value: Any, runtime: Runtime) -> int: """ Pascal Script's `PSGetUInt64` of Inno Setup 7: a value read as a UInt64. """ code = t.code if code.integer: return value & 0xFFFFFFFFFFFFFFFF if code.character: return ord(value) if code is TC.Variant: return _delphi(variants.to_uint64, value, runtime) raise type_mismatch() def read_real(t, value, runtime)-
Pascal Script's
PSGetReal: a value read as an Extended.Expand source code Browse git
def read_real(t: IFPSType, value: Any, runtime: Runtime) -> Real: """ Pascal Script's `PSGetReal`: a value read as an Extended. """ code = t.code if code.integer: return runtime.fpu.load(value) if code in _FLOAT_FORMATS: return value if code is TC.Currency: return _delphi(runtime.currency_to_real, value) if code is TC.Variant: return _delphi(variants.to_real, value, runtime) raise type_mismatch() def read_currency(t, value, runtime)-
Pascal Script's
PSGetCurrency: a value read as a Currency.Expand source code Browse git
def read_currency(t: IFPSType, value: Any, runtime: Runtime) -> Fraction: """ Pascal Script's `PSGetCurrency`: a value read as a Currency. """ code = t.code if code.integer: return _delphi(runtime.currency_of_integer, value) if code in _FLOAT_FORMATS: return _delphi(runtime.currency, value) if code is TC.Currency: return value if code is TC.Variant: return _delphi(variants.to_currency, value, runtime) raise type_mismatch() def read_string(t, value, runtime, target=UnicodeString)-
Pascal Script's
PSGetAnsiString,PSGetWideString, andPSGetUnicodeString: a value read as a string of the given type.Expand source code Browse git
def read_string(t: IFPSType, value: Any, runtime: Runtime, target: TC = TC.UnicodeString) -> str: """ Pascal Script's `PSGetAnsiString`, `PSGetWideString`, and `PSGetUnicodeString`: a value read as a string of the given type. """ code = t.code if code is TC.U08: return chr(value) if code is TC.U16 and target is not TC.AnsiString: return chr(value) if code.character or code in _STRINGS: return value if code is TC.Variant: name = 'String' if target is TC.AnsiString else 'OleStr' return _delphi(variants.to_string, value, runtime, name) raise type_mismatch() def to_variant(t, value, machine)-
Pascal Script's
IntPIFVariantToVariant: the Variant that holds a value of the given type. Before Inno Setup 7, Pascal Script asks for the length of a static array as for a dynamic array, which fails.Expand source code Browse git
def to_variant(t: IFPSType, value: Any, machine: Machine) -> Variant: """ Pascal Script's `IntPIFVariantToVariant`: the Variant that holds a value of the given type. Before Inno Setup 7, Pascal Script asks for the length of a static array as for a dynamic array, which fails. """ runtime = machine.runtime code = t.code if code is TC.Variant: return value if code is TC.U08: if t.boolean: return Variant(VarType.Boolean, value != 0) return Variant(VarType.Byte, value) vtype = _VARIANT_TYPES.get(code) if vtype is not None: if code is TC.Extended: return Variant(vtype, _delphi(runtime.fpu.store, value, Format.Double)) if code is TC.Single: return Variant(vtype, float(value)) return Variant(vtype, value) if isinstance(t, TStaticArray) and machine.dialect is Dialect.Inno6: raise IFPSException('Invalid array') if isinstance(t, (TArray, TStaticArray)): element = t.type return Variant(VarType.Array | VarType.Variant, [to_variant(element, v, machine) for v in value]) if code is TC.Interface: return Variant(VarType.Dispatch, value) raise type_mismatch() def store(target, source, value, machine)-
Pascal Script's
SetVariantValuefor a primitive destination: the value that a variable of the target type holds after the assignment of a value of the source type.Expand source code Browse git
def store(target: IFPSType, source: IFPSType, value: Any, machine: Machine) -> Any: """ Pascal Script's `SetVariantValue` for a primitive destination: the value that a variable of the target type holds after the assignment of a value of the source type. """ runtime = machine.runtime code = target.code sc = source.code if code is TC.U08: return read_cardinal(source, value, runtime) & 0xFF if code is TC.S08: return _signed(read_integer(source, value, runtime), 8) if code is TC.U16: return read_cardinal(source, value, runtime) & 0xFFFF if code is TC.S16: return _signed(read_integer(source, value, runtime), 16) if code is TC.U32: if sc.integer: return value & 0xFFFFFFFF if sc.character: return ord(value) if sc is TC.Variant: return _delphi(variants.to_cardinal, value, runtime) raise type_mismatch() if code is TC.S32: if sc.integer: return signed32(value) if sc.character: return ord(value) if sc is TC.Variant: return _delphi(variants.to_integer, value, runtime) if sc is TC.Class: return value raise type_mismatch() if code is TC.S64: return read_int64(source, value, runtime) if code is TC.U64: return read_uint64(source, value, runtime) if (fmt := _FLOAT_FORMATS.get(code)) is not None: if sc.integer: real = x87.real(value) elif sc in _FLOAT_FORMATS or sc is TC.Currency or sc is TC.Variant: real = read_real(source, value, runtime) else: raise type_mismatch() return _delphi(runtime.fpu.store, real, fmt) if code is TC.Currency: return read_currency(source, value, runtime) if code is TC.AnsiString or code is TC.PChar: return read_string(source, value, runtime, TC.AnsiString) if code is TC.WideString: return read_string(source, value, runtime, TC.WideString) if code is TC.UnicodeString: return read_string(source, value, runtime, TC.UnicodeString) if code is TC.Char: return chr(read_cardinal(source, value, runtime) & 0xFF) if code is TC.WideChar: return chr(read_cardinal(source, value, runtime) & 0xFFFF) if code is TC.Variant: if sc is TC.Interface: return Variant(VarType.Dispatch, value) return to_variant(source, value, machine) if code is TC.Interface: if sc is TC.Variant: return _delphi(variants.to_dispatch, value, runtime) if sc is TC.Interface or sc is TC.Class: return value raise type_mismatch() if code is TC.Set: if target != source: raise type_mismatch() return value if code is TC.Class or code is TC.ProcPtr: return value raise type_mismatch() def assignable(target, source, machine)-
Whether Pascal Script assigns a value of the source type to a variable of the target type that holds other variables: a dynamic array only takes a dynamic array whose elements have the same type, and from Inno Setup 7 on also one whose elements have the same base type, unless that is a record, a set, or an array.
Expand source code Browse git
def assignable(target: IFPSType, source: IFPSType, machine: Machine) -> bool: """ Whether Pascal Script assigns a value of the source type to a variable of the target type that holds other variables: a dynamic array only takes a dynamic array whose elements have the same type, and from Inno Setup 7 on also one whose elements have the same base type, unless that is a record, a set, or an array. """ if ( isinstance(target, TArray) and isinstance(source, TArray) and target.code is TC.Array and source.code is TC.Array ): if target.type is source.type: return True if machine.dialect is Dialect.Inno6: return False code = target.type.code return code is source.type.code and code not in _CONTAINERS return True def ordinal(code, value)-
Reads an operand as Pascal Script reads it for integer arithmetic and comparisons: an integer as its value and a character as its code point. Any other operand is a type mismatch.
Expand source code Browse git
def ordinal(code: TC, value) -> int: """ Reads an operand as Pascal Script reads it for integer arithmetic and comparisons: an integer as its value and a character as its code point. Any other operand is a type mismatch. """ if code.integer: return value if code.character: return ord(value) raise type_mismatch() def calculate(aop, target, a, source, b, machine)-
Pascal Script's
DoCalc: the value that a variable of the target type holds after computing with the source operand. Byte, ShortInt, Word, SmallInt, and Int64 destinations read a Variant operand as an integer; Cardinal and Integer destinations compute with Delphi's Variant operators for addition, subtraction, multiplication, and division; float and Currency destinations compute with Delphi's Variant operators for any Variant operand. From Inno Setup 7 on,askeeps the object; Setup's classes are not modelled, so its class is not checked.Expand source code Browse git
def calculate(aop: AOp, target: IFPSType, a: Any, source: IFPSType, b: Any, machine: Machine) -> Any: """ Pascal Script's `DoCalc`: the value that a variable of the target type holds after computing with the source operand. Byte, ShortInt, Word, SmallInt, and Int64 destinations read a Variant operand as an integer; Cardinal and Integer destinations compute with Delphi's Variant operators for addition, subtraction, multiplication, and division; float and Currency destinations compute with Delphi's Variant operators for any Variant operand. From Inno Setup 7 on, `as` keeps the object; Setup's classes are not modelled, so its class is not checked. """ runtime = machine.runtime code = target.code sc = source.code if aop is AOp.As: if machine.dialect is Dialect.Inno6: raise IFPSException(kind=PSError.InvalidOpcodeParameter) return a if code.integer: if sc is TC.Variant: if code in (TC.U32, TC.S32) and aop in _VARIANT_ARITHMETIC: vtype = VarType.LongWord if code is TC.U32 and aop is not AOp.Mod else VarType.Integer left = Variant(vtype, a if vtype is VarType.LongWord else signed32(a)) result = _operate(aop, left, b, runtime) if code is TC.U32: return _delphi(variants.to_cardinal, result, runtime) return _delphi(variants.to_integer, result, runtime) if code is TC.S64: b, sc = read_int64(source, b, runtime), TC.S64 elif code is TC.U64: b, sc = read_uint64(source, b, runtime), TC.U64 elif code.signed: b, sc = read_integer(source, b, runtime), TC.S32 else: b, sc = read_cardinal(source, b, runtime), TC.U32 return _in_range(code, _integer_arithmetic(aop, code, a, sc, ordinal(sc, b), runtime)) if code is TC.Set: if (operation := _SET_OPERATIONS.get(aop)) is None or not isinstance(source, TSet): raise type_mismatch() return operation(a, b) if (fmt := _FLOAT_FORMATS.get(code)) is not None: if aop not in (AOp.Add, AOp.Sub, AOp.Mul, AOp.Div): raise type_mismatch() if sc is TC.Variant: double = a if code is not TC.Extended else _delphi(runtime.fpu.store, a, Format.Double) result = _operate(aop, Variant(VarType.Double, double), b, runtime, True) real = _delphi(variants.to_real, result, runtime) return _delphi(runtime.fpu.store, real, fmt) if not (sc.integer or sc in _FLOAT_FORMATS or sc is TC.Currency): raise type_mismatch() if sc is TC.Currency and aop is AOp.Div: return _delphi(runtime.real_divided_by_currency, a, b, fmt) other = read_real(source, b, runtime) return _delphi(runtime.real_arithmetic, _OPERATIONS[aop], a, other, fmt) if code is TC.Currency: if aop not in (AOp.Add, AOp.Sub, AOp.Mul, AOp.Div): raise type_mismatch() if sc is TC.Variant: result = _operate(aop, Variant(VarType.Currency, a), b, runtime, True) return _delphi(variants.to_currency, result, runtime) if not (sc.integer or sc in _FLOAT_FORMATS or sc is TC.Currency): raise type_mismatch() if machine.dialect is Dialect.Inno6 and (sc is TC.U32 or sc is TC.S64): return _delphi(_currency_read_as_double, runtime, aop, a, b) return _delphi(runtime.currency_arithmetic, _OPERATIONS[aop], a, b) if code in _STRINGS: if aop is not AOp.Add: raise type_mismatch() target_code = TC.AnsiString if code is TC.PChar else code return a + read_string(source, b, runtime, target_code) if code is TC.Char: if aop is not AOp.Add: raise type_mismatch() return chr((ord(a) + read_cardinal(source, b, runtime)) & 0xFF) if code is TC.WideChar: if aop is not AOp.Add: raise type_mismatch() return chr((ord(a) + read_cardinal(source, b, runtime)) & 0xFFFF) if code is TC.Variant: return _operate(aop, a, to_variant(source, b, machine), runtime) raise type_mismatch() def compare(cop, left, a, right, b, machine)-
Pascal Script's
DoBooleanCalc: comparesa, which has the typeleft, withb, which has the typeright. The left operand's type decides how the right one is read. From Inno Setup 7 on, an Integer compares with a Currency as floats.Expand source code Browse git
def compare(cop: COp, left: IFPSType, a: Any, right: IFPSType, b: Any, machine: Machine) -> bool: """ Pascal Script's `DoBooleanCalc`: compares `a`, which has the type `left`, with `b`, which has the type `right`. The left operand's type decides how the right one is read. From Inno Setup 7 on, an Integer compares with a Currency as floats. """ runtime = machine.runtime lc = left.code rc = right.code if cop is COp.IN: if isinstance(right, TSet): return _set_contains(b, right, read_cardinal(left, a, runtime)) return a in b if cop is COp.IS: return a is b test = _TESTS[cop] if lc.integer: if rc is TC.Variant: if lc is TC.S32: return test(_variant_order(Variant(VarType.Integer, a), b, runtime), 0) if lc is TC.S64: return test(a, read_int64(right, b, runtime)) if lc is TC.U64: return test(a, read_uint64(right, b, runtime)) if lc.signed: return test(a, read_integer(right, b, runtime)) return test(a, read_cardinal(right, b, runtime)) if lc is TC.S32 and (rc in _FLOAT_FORMATS or rc is TC.Currency and machine.dialect is Dialect.Inno7): return _real_test(test, read_real(left, a, runtime), read_real(right, b, runtime), runtime) a, b = _integer_comparands(lc, a, rc, b) return test(a, b) if lc is TC.Set: if not isinstance(right, TSet) or right != left: raise type_mismatch() if cop is COp.GE: return b & ~a == 0 if cop is COp.LE: return a & ~b == 0 if cop is COp.EQ: return a == b if cop is COp.NE: return a != b raise type_mismatch() if lc in _FLOAT_FORMATS: return _real_test(test, a, read_real(right, b, runtime), runtime) if lc is TC.Currency: return test(a, read_currency(right, b, runtime)) if lc is TC.AnsiString or lc is TC.PChar or lc is TC.Char: return test(a, read_string(right, b, runtime, TC.AnsiString)) if lc is TC.WideChar or lc is TC.WideString: return test(a, read_string(right, b, runtime, TC.WideString)) if lc is TC.UnicodeString: return test(a, read_string(right, b, runtime, TC.UnicodeString)) if lc is TC.Variant: return test(_variant_order(a, to_variant(right, b, machine), runtime), 0) if lc in _REFERENCES and cop in (COp.EQ, COp.NE): if rc is not lc: raise type_mismatch() return test(0 if a is b or a == b else 1, 0) if lc is TC.Record and cop in (COp.EQ, COp.NE) and right == left: return test(0 if a == b else 1, 0) raise type_mismatch() def boolean(t, value, machine)-
The value that a comparison writes into a destination of the given type: an integer of up to 32 bits, and from Inno Setup 7 on any integer, holds 1 or 0, and a Variant holds a Boolean.
Expand source code Browse git
def boolean(t: IFPSType, value: bool, machine: Machine) -> Any: """ The value that a comparison writes into a destination of the given type: an integer of up to 32 bits, and from Inno Setup 7 on any integer, holds 1 or 0, and a Variant holds a Boolean. """ code = t.code if code.integer and (code.width <= 4 or machine.dialect is Dialect.Inno7): return int(value) if code is TC.Variant: return Variant(VarType.Boolean, value) raise type_mismatch() def negate(t, value, runtime)-
Pascal Script's
DoMinus; a Variant that Delphi cannot negate is a type mismatch.Expand source code Browse git
def negate(t: IFPSType, value: Any, runtime: Runtime) -> Any: """ Pascal Script's `DoMinus`; a Variant that Delphi cannot negate is a type mismatch. """ code = t.code if code in _FLOAT_FORMATS: return runtime.fpu.negate(value) if code.integer: return _in_range(code, -value) if code is TC.Currency: return Fraction(_signed(-units(value), 64), 10000) if code is TC.Variant: try: return variants.negate(value, runtime) except DelphiException: raise type_mismatch() raise type_mismatch() def invert(t, value, runtime)-
Pascal Script's
DoIntegerNot; a Variant that Delphi cannot invert is a type mismatch.Expand source code Browse git
def invert(t: IFPSType, value: Any, runtime: Runtime) -> Any: """ Pascal Script's `DoIntegerNot`; a Variant that Delphi cannot invert is a type mismatch. """ code = t.code if code.integer: return _in_range(code, ~value) if code is TC.Variant: try: return variants.invert(value, runtime) except DelphiException: raise type_mismatch() raise type_mismatch() def logical_not(t, value, runtime)-
Pascal Script's
DoBooleanNot: an integer becomes 1 when it is zero and 0 otherwise, a Variant becomes the Boolean of its comparison with zero.Expand source code Browse git
def logical_not(t: IFPSType, value: Any, runtime: Runtime) -> Any: """ Pascal Script's `DoBooleanNot`: an integer becomes 1 when it is zero and 0 otherwise, a Variant becomes the Boolean of its comparison with zero. """ code = t.code if code.integer: return int(value == 0) if code is TC.Variant: try: return Variant(VarType.Boolean, variants.compare(value, Variant(VarType.Integer, 0), runtime) == 0) except DelphiException: raise type_mismatch() raise type_mismatch() def size_of(t, runtime)-
Pascal Script's
SizeOf: the size of a variable of the given type on the Setup's platform.Expand source code Browse git
def size_of(t: IFPSType, runtime: Runtime) -> int: """ Pascal Script's `SizeOf`: the size of a variable of the given type on the Setup's platform. """ code = t.code pointer = runtime.pointer_size if isinstance(t, TStaticArray): return t.size * size_of(t.type, runtime) if isinstance(t, TRecord): return sum(size_of(m, runtime) for m in t.members) if isinstance(t, TSet): return t.size_in_bytes if code in (TC.AnsiString, TC.WideString, TC.UnicodeString, TC.PChar, TC.Class, TC.Interface, TC.Array): return pointer if code is TC.Variant: return runtime.variant_size if code is TC.Extended: return runtime.extended_size if code in (TC.ProcPtr, TC.Pointer): return 3 * pointer return code.width def step(t, value, up, machine)-
Pascal Script's increment or decrement of the counter of a for loop: an integer wraps around in its range; from Inno Setup 7 on, a Variant counts with Delphi's Variant addition or subtraction.
Expand source code Browse git
def step(t: IFPSType, value: Any, up: bool, machine: Machine) -> Any: """ Pascal Script's increment or decrement of the counter of a for loop: an integer wraps around in its range; from Inno Setup 7 on, a Variant counts with Delphi's Variant addition or subtraction. """ code = t.code if code.integer: return _in_range(code, value + 1 if up else value - 1) if code is TC.Variant and machine.dialect is Dialect.Inno7: op = variants.Operator.Add if up else variants.Operator.Subtract return _delphi(variants.operate, op, value, Variant(VarType.Integer, 1), machine.runtime) raise type_mismatch() def truth(t, value)-
The condition of a jump: Pascal Script accepts integers up to 32 bits.
Expand source code Browse git
def truth(t: IFPSType, value: Any) -> bool: """ The condition of a jump: Pascal Script accepts integers up to 32 bits. """ if t.code.integer and t.code.width <= 4: return value != 0 raise IFPSException(kind=PSError.InvalidType)
Classes
class Machine (dialect, runtime)-
The state that Pascal Script's rules for values read: the version of the rules and the state of the Delphi run-time library that Pascal Script runs on.
Expand source code Browse git
@dataclass class Machine: """ The state that Pascal Script's rules for values read: the version of the rules and the state of the Delphi run-time library that Pascal Script runs on. """ dialect: Dialect runtime: RuntimeInstance variables
var dialect-
The type of the None singleton.
var runtime-
The type of the None singleton.
class PSError (*args, **kwds)-
The kinds of runtime error that Pascal Script distinguishes, numbered as Pascal Script numbers them. Pascal Script keeps the kind with every error of the script.
Expand source code Browse git
class PSError(enum.IntEnum): """ The kinds of runtime error that Pascal Script distinguishes, numbered as Pascal Script numbers them. Pascal Script keeps the kind with every error of the script. """ InvalidType = 2 InvalidOpcodeParameter = 6 OutOfRange = 10 TypeMismatch = 12 DivideByZero = 15 MathError = 16 CouldNotCallProc = 17 OutOfRecordRange = 18 Exception = 20 NullPointerException = 21 CustomError = 24 @property def text(self) -> str: """ The message that Pascal Script gives an error of this kind when it carries no exception of Delphi. """ return _PS_ERROR_TEXT[self]Ancestors
- enum.IntEnum
- builtins.int
- enum.ReprEnum
- enum.Enum
Class variables
var InvalidType-
The type of the None singleton.
var InvalidOpcodeParameter-
The type of the None singleton.
var OutOfRange-
The type of the None singleton.
var TypeMismatch-
The type of the None singleton.
var DivideByZero-
The type of the None singleton.
var MathError-
The type of the None singleton.
var CouldNotCallProc-
The type of the None singleton.
var OutOfRecordRange-
The type of the None singleton.
var Exception-
The type of the None singleton.
var NullPointerException-
The type of the None singleton.
var CustomError-
The type of the None singleton.
Instance variables
var text-
The message that Pascal Script gives an error of this kind when it carries no exception of Delphi.
Expand source code Browse git
@property def text(self) -> str: """ The message that Pascal Script gives an error of this kind when it carries no exception of Delphi. """ return _PS_ERROR_TEXT[self]
class IFPSException (message=None, kind=20, called=None, failed_call=False)-
An error of the script, which its exception handlers can handle. Pascal Script keeps the
kindof every error, and the exception of Delphi that raised it, if any. The script reads themessageof that exception withGetExceptionMessage, or the text of the kind when there is no exception. An error that escapes from a call of an emulated function names thecalledfunction in its report. An emulated function that both raises the error and reports that it could not be called setsfailed_call: when an except block takes the error, Pascal Script raisesCould not call procat the start of that block.Expand source code Browse git
class IFPSException(RuntimeError): """ An error of the script, which its exception handlers can handle. Pascal Script keeps the `kind` of every error, and the exception of Delphi that raised it, if any. The script reads the `message` of that exception with `GetExceptionMessage`, or the text of the kind when there is no exception. An error that escapes from a call of an emulated function names the `called` function in its report. An emulated function that both raises the error and reports that it could not be called sets `failed_call`: when an except block takes the error, Pascal Script raises `Could not call proc` at the start of that block. """ def __init__( self, message: str | None = None, kind: PSError = PSError.Exception, called: str | None = None, failed_call: bool = False, ): message = kind.text if message is None else str(message) report = message if called is None else F'Error calling {called}: {message}' super().__init__(report) self.kind = kind self.message = message self.called = called self.failed_call = failed_callAncestors
- builtins.RuntimeError
- builtins.Exception
- builtins.BaseException