From 5dc2ebc556a6c9d6844b94f11b14f699848fa203 Mon Sep 17 00:00:00 2001 From: Ben Lewis Date: Tue, 14 Jul 2020 19:05:21 +1200 Subject: [PATCH 1/4] Add fractions.py and test_fractions.py from CPython 3.8.3. --- Lib/fractions.py | 656 +++++++++++++++++++++++++++++++++ Lib/test/test_fractions.py | 731 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1387 insertions(+) create mode 100644 Lib/fractions.py create mode 100644 Lib/test/test_fractions.py diff --git a/Lib/fractions.py b/Lib/fractions.py new file mode 100644 index 000000000..e4fcc8901 --- /dev/null +++ b/Lib/fractions.py @@ -0,0 +1,656 @@ +# Originally contributed by Sjoerd Mullender. +# Significantly modified by Jeffrey Yasskin . + +"""Fraction, infinite-precision, real numbers.""" + +from decimal import Decimal +import math +import numbers +import operator +import re +import sys + +__all__ = ['Fraction', 'gcd'] + + + +def gcd(a, b): + """Calculate the Greatest Common Divisor of a and b. + + Unless b==0, the result will have the same sign as b (so that when + b is divided by it, the result comes out positive). + """ + import warnings + warnings.warn('fractions.gcd() is deprecated. Use math.gcd() instead.', + DeprecationWarning, 2) + if type(a) is int is type(b): + if (b or a) < 0: + return -math.gcd(a, b) + return math.gcd(a, b) + return _gcd(a, b) + +def _gcd(a, b): + # Supports non-integers for backward compatibility. + while b: + a, b = b, a%b + return a + +# Constants related to the hash implementation; hash(x) is based +# on the reduction of x modulo the prime _PyHASH_MODULUS. +_PyHASH_MODULUS = sys.hash_info.modulus +# Value to be used for rationals that reduce to infinity modulo +# _PyHASH_MODULUS. +_PyHASH_INF = sys.hash_info.inf + +_RATIONAL_FORMAT = re.compile(r""" + \A\s* # optional whitespace at the start, then + (?P[-+]?) # an optional sign, then + (?=\d|\.\d) # lookahead for digit or .digit + (?P\d*) # numerator (possibly empty) + (?: # followed by + (?:/(?P\d+))? # an optional denominator + | # or + (?:\.(?P\d*))? # an optional fractional part + (?:E(?P[-+]?\d+))? # and optional exponent + ) + \s*\Z # and optional whitespace to finish +""", re.VERBOSE | re.IGNORECASE) + + +class Fraction(numbers.Rational): + """This class implements rational numbers. + + In the two-argument form of the constructor, Fraction(8, 6) will + produce a rational number equivalent to 4/3. Both arguments must + be Rational. The numerator defaults to 0 and the denominator + defaults to 1 so that Fraction(3) == 3 and Fraction() == 0. + + Fractions can also be constructed from: + + - numeric strings similar to those accepted by the + float constructor (for example, '-2.3' or '1e10') + + - strings of the form '123/456' + + - float and Decimal instances + + - other Rational instances (including integers) + + """ + + __slots__ = ('_numerator', '_denominator') + + # We're immutable, so use __new__ not __init__ + def __new__(cls, numerator=0, denominator=None, *, _normalize=True): + """Constructs a Rational. + + Takes a string like '3/2' or '1.5', another Rational instance, a + numerator/denominator pair, or a float. + + Examples + -------- + + >>> Fraction(10, -8) + Fraction(-5, 4) + >>> Fraction(Fraction(1, 7), 5) + Fraction(1, 35) + >>> Fraction(Fraction(1, 7), Fraction(2, 3)) + Fraction(3, 14) + >>> Fraction('314') + Fraction(314, 1) + >>> Fraction('-35/4') + Fraction(-35, 4) + >>> Fraction('3.1415') # conversion from numeric string + Fraction(6283, 2000) + >>> Fraction('-47e-2') # string may include a decimal exponent + Fraction(-47, 100) + >>> Fraction(1.47) # direct construction from float (exact conversion) + Fraction(6620291452234629, 4503599627370496) + >>> Fraction(2.25) + Fraction(9, 4) + >>> Fraction(Decimal('1.47')) + Fraction(147, 100) + + """ + self = super(Fraction, cls).__new__(cls) + + if denominator is None: + if type(numerator) is int: + self._numerator = numerator + self._denominator = 1 + return self + + elif isinstance(numerator, numbers.Rational): + self._numerator = numerator.numerator + self._denominator = numerator.denominator + return self + + elif isinstance(numerator, (float, Decimal)): + # Exact conversion + self._numerator, self._denominator = numerator.as_integer_ratio() + return self + + elif isinstance(numerator, str): + # Handle construction from strings. + m = _RATIONAL_FORMAT.match(numerator) + if m is None: + raise ValueError('Invalid literal for Fraction: %r' % + numerator) + numerator = int(m.group('num') or '0') + denom = m.group('denom') + if denom: + denominator = int(denom) + else: + denominator = 1 + decimal = m.group('decimal') + if decimal: + scale = 10**len(decimal) + numerator = numerator * scale + int(decimal) + denominator *= scale + exp = m.group('exp') + if exp: + exp = int(exp) + if exp >= 0: + numerator *= 10**exp + else: + denominator *= 10**-exp + if m.group('sign') == '-': + numerator = -numerator + + else: + raise TypeError("argument should be a string " + "or a Rational instance") + + elif type(numerator) is int is type(denominator): + pass # *very* normal case + + elif (isinstance(numerator, numbers.Rational) and + isinstance(denominator, numbers.Rational)): + numerator, denominator = ( + numerator.numerator * denominator.denominator, + denominator.numerator * numerator.denominator + ) + else: + raise TypeError("both arguments should be " + "Rational instances") + + if denominator == 0: + raise ZeroDivisionError('Fraction(%s, 0)' % numerator) + if _normalize: + if type(numerator) is int is type(denominator): + # *very* normal case + g = math.gcd(numerator, denominator) + if denominator < 0: + g = -g + else: + g = _gcd(numerator, denominator) + numerator //= g + denominator //= g + self._numerator = numerator + self._denominator = denominator + return self + + @classmethod + def from_float(cls, f): + """Converts a finite float to a rational number, exactly. + + Beware that Fraction.from_float(0.3) != Fraction(3, 10). + + """ + if isinstance(f, numbers.Integral): + return cls(f) + elif not isinstance(f, float): + raise TypeError("%s.from_float() only takes floats, not %r (%s)" % + (cls.__name__, f, type(f).__name__)) + return cls(*f.as_integer_ratio()) + + @classmethod + def from_decimal(cls, dec): + """Converts a finite Decimal instance to a rational number, exactly.""" + from decimal import Decimal + if isinstance(dec, numbers.Integral): + dec = Decimal(int(dec)) + elif not isinstance(dec, Decimal): + raise TypeError( + "%s.from_decimal() only takes Decimals, not %r (%s)" % + (cls.__name__, dec, type(dec).__name__)) + return cls(*dec.as_integer_ratio()) + + def as_integer_ratio(self): + """Return the integer ratio as a tuple. + + Return a tuple of two integers, whose ratio is equal to the + Fraction and with a positive denominator. + """ + return (self._numerator, self._denominator) + + def limit_denominator(self, max_denominator=1000000): + """Closest Fraction to self with denominator at most max_denominator. + + >>> Fraction('3.141592653589793').limit_denominator(10) + Fraction(22, 7) + >>> Fraction('3.141592653589793').limit_denominator(100) + Fraction(311, 99) + >>> Fraction(4321, 8765).limit_denominator(10000) + Fraction(4321, 8765) + + """ + # Algorithm notes: For any real number x, define a *best upper + # approximation* to x to be a rational number p/q such that: + # + # (1) p/q >= x, and + # (2) if p/q > r/s >= x then s > q, for any rational r/s. + # + # Define *best lower approximation* similarly. Then it can be + # proved that a rational number is a best upper or lower + # approximation to x if, and only if, it is a convergent or + # semiconvergent of the (unique shortest) continued fraction + # associated to x. + # + # To find a best rational approximation with denominator <= M, + # we find the best upper and lower approximations with + # denominator <= M and take whichever of these is closer to x. + # In the event of a tie, the bound with smaller denominator is + # chosen. If both denominators are equal (which can happen + # only when max_denominator == 1 and self is midway between + # two integers) the lower bound---i.e., the floor of self, is + # taken. + + if max_denominator < 1: + raise ValueError("max_denominator should be at least 1") + if self._denominator <= max_denominator: + return Fraction(self) + + p0, q0, p1, q1 = 0, 1, 1, 0 + n, d = self._numerator, self._denominator + while True: + a = n//d + q2 = q0+a*q1 + if q2 > max_denominator: + break + p0, q0, p1, q1 = p1, q1, p0+a*p1, q2 + n, d = d, n-a*d + + k = (max_denominator-q0)//q1 + bound1 = Fraction(p0+k*p1, q0+k*q1) + bound2 = Fraction(p1, q1) + if abs(bound2 - self) <= abs(bound1-self): + return bound2 + else: + return bound1 + + @property + def numerator(a): + return a._numerator + + @property + def denominator(a): + return a._denominator + + def __repr__(self): + """repr(self)""" + return '%s(%s, %s)' % (self.__class__.__name__, + self._numerator, self._denominator) + + def __str__(self): + """str(self)""" + if self._denominator == 1: + return str(self._numerator) + else: + return '%s/%s' % (self._numerator, self._denominator) + + def _operator_fallbacks(monomorphic_operator, fallback_operator): + """Generates forward and reverse operators given a purely-rational + operator and a function from the operator module. + + Use this like: + __op__, __rop__ = _operator_fallbacks(just_rational_op, operator.op) + + In general, we want to implement the arithmetic operations so + that mixed-mode operations either call an implementation whose + author knew about the types of both arguments, or convert both + to the nearest built in type and do the operation there. In + Fraction, that means that we define __add__ and __radd__ as: + + def __add__(self, other): + # Both types have numerators/denominator attributes, + # so do the operation directly + if isinstance(other, (int, Fraction)): + return Fraction(self.numerator * other.denominator + + other.numerator * self.denominator, + self.denominator * other.denominator) + # float and complex don't have those operations, but we + # know about those types, so special case them. + elif isinstance(other, float): + return float(self) + other + elif isinstance(other, complex): + return complex(self) + other + # Let the other type take over. + return NotImplemented + + def __radd__(self, other): + # radd handles more types than add because there's + # nothing left to fall back to. + if isinstance(other, numbers.Rational): + return Fraction(self.numerator * other.denominator + + other.numerator * self.denominator, + self.denominator * other.denominator) + elif isinstance(other, Real): + return float(other) + float(self) + elif isinstance(other, Complex): + return complex(other) + complex(self) + return NotImplemented + + + There are 5 different cases for a mixed-type addition on + Fraction. I'll refer to all of the above code that doesn't + refer to Fraction, float, or complex as "boilerplate". 'r' + will be an instance of Fraction, which is a subtype of + Rational (r : Fraction <: Rational), and b : B <: + Complex. The first three involve 'r + b': + + 1. If B <: Fraction, int, float, or complex, we handle + that specially, and all is well. + 2. If Fraction falls back to the boilerplate code, and it + were to return a value from __add__, we'd miss the + possibility that B defines a more intelligent __radd__, + so the boilerplate should return NotImplemented from + __add__. In particular, we don't handle Rational + here, even though we could get an exact answer, in case + the other type wants to do something special. + 3. If B <: Fraction, Python tries B.__radd__ before + Fraction.__add__. This is ok, because it was + implemented with knowledge of Fraction, so it can + handle those instances before delegating to Real or + Complex. + + The next two situations describe 'b + r'. We assume that b + didn't know about Fraction in its implementation, and that it + uses similar boilerplate code: + + 4. If B <: Rational, then __radd_ converts both to the + builtin rational type (hey look, that's us) and + proceeds. + 5. Otherwise, __radd__ tries to find the nearest common + base ABC, and fall back to its builtin type. Since this + class doesn't subclass a concrete type, there's no + implementation to fall back to, so we need to try as + hard as possible to return an actual value, or the user + will get a TypeError. + + """ + def forward(a, b): + if isinstance(b, (int, Fraction)): + return monomorphic_operator(a, b) + elif isinstance(b, float): + return fallback_operator(float(a), b) + elif isinstance(b, complex): + return fallback_operator(complex(a), b) + else: + return NotImplemented + forward.__name__ = '__' + fallback_operator.__name__ + '__' + forward.__doc__ = monomorphic_operator.__doc__ + + def reverse(b, a): + if isinstance(a, numbers.Rational): + # Includes ints. + return monomorphic_operator(a, b) + elif isinstance(a, numbers.Real): + return fallback_operator(float(a), float(b)) + elif isinstance(a, numbers.Complex): + return fallback_operator(complex(a), complex(b)) + else: + return NotImplemented + reverse.__name__ = '__r' + fallback_operator.__name__ + '__' + reverse.__doc__ = monomorphic_operator.__doc__ + + return forward, reverse + + def _add(a, b): + """a + b""" + da, db = a.denominator, b.denominator + return Fraction(a.numerator * db + b.numerator * da, + da * db) + + __add__, __radd__ = _operator_fallbacks(_add, operator.add) + + def _sub(a, b): + """a - b""" + da, db = a.denominator, b.denominator + return Fraction(a.numerator * db - b.numerator * da, + da * db) + + __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) + + def _mul(a, b): + """a * b""" + return Fraction(a.numerator * b.numerator, a.denominator * b.denominator) + + __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) + + def _div(a, b): + """a / b""" + return Fraction(a.numerator * b.denominator, + a.denominator * b.numerator) + + __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv) + + def _floordiv(a, b): + """a // b""" + return (a.numerator * b.denominator) // (a.denominator * b.numerator) + + __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv) + + def _divmod(a, b): + """(a // b, a % b)""" + da, db = a.denominator, b.denominator + div, n_mod = divmod(a.numerator * db, da * b.numerator) + return div, Fraction(n_mod, da * db) + + __divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod) + + def _mod(a, b): + """a % b""" + da, db = a.denominator, b.denominator + return Fraction((a.numerator * db) % (b.numerator * da), da * db) + + __mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod) + + def __pow__(a, b): + """a ** b + + If b is not an integer, the result will be a float or complex + since roots are generally irrational. If b is an integer, the + result will be rational. + + """ + if isinstance(b, numbers.Rational): + if b.denominator == 1: + power = b.numerator + if power >= 0: + return Fraction(a._numerator ** power, + a._denominator ** power, + _normalize=False) + elif a._numerator >= 0: + return Fraction(a._denominator ** -power, + a._numerator ** -power, + _normalize=False) + else: + return Fraction((-a._denominator) ** -power, + (-a._numerator) ** -power, + _normalize=False) + else: + # A fractional power will generally produce an + # irrational number. + return float(a) ** float(b) + else: + return float(a) ** b + + def __rpow__(b, a): + """a ** b""" + if b._denominator == 1 and b._numerator >= 0: + # If a is an int, keep it that way if possible. + return a ** b._numerator + + if isinstance(a, numbers.Rational): + return Fraction(a.numerator, a.denominator) ** b + + if b._denominator == 1: + return a ** b._numerator + + return a ** float(b) + + def __pos__(a): + """+a: Coerces a subclass instance to Fraction""" + return Fraction(a._numerator, a._denominator, _normalize=False) + + def __neg__(a): + """-a""" + return Fraction(-a._numerator, a._denominator, _normalize=False) + + def __abs__(a): + """abs(a)""" + return Fraction(abs(a._numerator), a._denominator, _normalize=False) + + def __trunc__(a): + """trunc(a)""" + if a._numerator < 0: + return -(-a._numerator // a._denominator) + else: + return a._numerator // a._denominator + + def __floor__(a): + """math.floor(a)""" + return a.numerator // a.denominator + + def __ceil__(a): + """math.ceil(a)""" + # The negations cleverly convince floordiv to return the ceiling. + return -(-a.numerator // a.denominator) + + def __round__(self, ndigits=None): + """round(self, ndigits) + + Rounds half toward even. + """ + if ndigits is None: + floor, remainder = divmod(self.numerator, self.denominator) + if remainder * 2 < self.denominator: + return floor + elif remainder * 2 > self.denominator: + return floor + 1 + # Deal with the half case: + elif floor % 2 == 0: + return floor + else: + return floor + 1 + shift = 10**abs(ndigits) + # See _operator_fallbacks.forward to check that the results of + # these operations will always be Fraction and therefore have + # round(). + if ndigits > 0: + return Fraction(round(self * shift), shift) + else: + return Fraction(round(self / shift) * shift) + + def __hash__(self): + """hash(self)""" + + # XXX since this method is expensive, consider caching the result + + # In order to make sure that the hash of a Fraction agrees + # with the hash of a numerically equal integer, float or + # Decimal instance, we follow the rules for numeric hashes + # outlined in the documentation. (See library docs, 'Built-in + # Types'). + + # dinv is the inverse of self._denominator modulo the prime + # _PyHASH_MODULUS, or 0 if self._denominator is divisible by + # _PyHASH_MODULUS. + dinv = pow(self._denominator, _PyHASH_MODULUS - 2, _PyHASH_MODULUS) + if not dinv: + hash_ = _PyHASH_INF + else: + hash_ = abs(self._numerator) * dinv % _PyHASH_MODULUS + result = hash_ if self >= 0 else -hash_ + return -2 if result == -1 else result + + def __eq__(a, b): + """a == b""" + if type(b) is int: + return a._numerator == b and a._denominator == 1 + if isinstance(b, numbers.Rational): + return (a._numerator == b.numerator and + a._denominator == b.denominator) + if isinstance(b, numbers.Complex) and b.imag == 0: + b = b.real + if isinstance(b, float): + if math.isnan(b) or math.isinf(b): + # comparisons with an infinity or nan should behave in + # the same way for any finite a, so treat a as zero. + return 0.0 == b + else: + return a == a.from_float(b) + else: + # Since a doesn't know how to compare with b, let's give b + # a chance to compare itself with a. + return NotImplemented + + def _richcmp(self, other, op): + """Helper for comparison operators, for internal use only. + + Implement comparison between a Rational instance `self`, and + either another Rational instance or a float `other`. If + `other` is not a Rational instance or a float, return + NotImplemented. `op` should be one of the six standard + comparison operators. + + """ + # convert other to a Rational instance where reasonable. + if isinstance(other, numbers.Rational): + return op(self._numerator * other.denominator, + self._denominator * other.numerator) + if isinstance(other, float): + if math.isnan(other) or math.isinf(other): + return op(0.0, other) + else: + return op(self, self.from_float(other)) + else: + return NotImplemented + + def __lt__(a, b): + """a < b""" + return a._richcmp(b, operator.lt) + + def __gt__(a, b): + """a > b""" + return a._richcmp(b, operator.gt) + + def __le__(a, b): + """a <= b""" + return a._richcmp(b, operator.le) + + def __ge__(a, b): + """a >= b""" + return a._richcmp(b, operator.ge) + + def __bool__(a): + """a != 0""" + # bpo-39274: Use bool() because (a._numerator != 0) can return an + # object which is not a bool. + return bool(a._numerator) + + # support for pickling, copy, and deepcopy + + def __reduce__(self): + return (self.__class__, (str(self),)) + + def __copy__(self): + if type(self) == Fraction: + return self # I'm immutable; therefore I am my own clone + return self.__class__(self._numerator, self._denominator) + + def __deepcopy__(self, memo): + if type(self) == Fraction: + return self # My components are also immutable + return self.__class__(self._numerator, self._denominator) diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py new file mode 100644 index 000000000..86b49f30f --- /dev/null +++ b/Lib/test/test_fractions.py @@ -0,0 +1,731 @@ +"""Tests for Lib/fractions.py.""" + +from decimal import Decimal +from test.support import requires_IEEE_754 +import math +import numbers +import operator +import fractions +import functools +import sys +import unittest +import warnings +from copy import copy, deepcopy +from pickle import dumps, loads +F = fractions.Fraction +gcd = fractions.gcd + +class DummyFloat(object): + """Dummy float class for testing comparisons with Fractions""" + + def __init__(self, value): + if not isinstance(value, float): + raise TypeError("DummyFloat can only be initialized from float") + self.value = value + + def _richcmp(self, other, op): + if isinstance(other, numbers.Rational): + return op(F.from_float(self.value), other) + elif isinstance(other, DummyFloat): + return op(self.value, other.value) + else: + return NotImplemented + + def __eq__(self, other): return self._richcmp(other, operator.eq) + def __le__(self, other): return self._richcmp(other, operator.le) + def __lt__(self, other): return self._richcmp(other, operator.lt) + def __ge__(self, other): return self._richcmp(other, operator.ge) + def __gt__(self, other): return self._richcmp(other, operator.gt) + + # shouldn't be calling __float__ at all when doing comparisons + def __float__(self): + assert False, "__float__ should not be invoked for comparisons" + + # same goes for subtraction + def __sub__(self, other): + assert False, "__sub__ should not be invoked for comparisons" + __rsub__ = __sub__ + + +class DummyRational(object): + """Test comparison of Fraction with a naive rational implementation.""" + + def __init__(self, num, den): + g = math.gcd(num, den) + self.num = num // g + self.den = den // g + + def __eq__(self, other): + if isinstance(other, fractions.Fraction): + return (self.num == other._numerator and + self.den == other._denominator) + else: + return NotImplemented + + def __lt__(self, other): + return(self.num * other._denominator < self.den * other._numerator) + + def __gt__(self, other): + return(self.num * other._denominator > self.den * other._numerator) + + def __le__(self, other): + return(self.num * other._denominator <= self.den * other._numerator) + + def __ge__(self, other): + return(self.num * other._denominator >= self.den * other._numerator) + + # this class is for testing comparisons; conversion to float + # should never be used for a comparison, since it loses accuracy + def __float__(self): + assert False, "__float__ should not be invoked" + +class DummyFraction(fractions.Fraction): + """Dummy Fraction subclass for copy and deepcopy testing.""" + +class GcdTest(unittest.TestCase): + + def testMisc(self): + # fractions.gcd() is deprecated + with self.assertWarnsRegex(DeprecationWarning, r'fractions\.gcd'): + gcd(1, 1) + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', r'fractions\.gcd', + DeprecationWarning) + self.assertEqual(0, gcd(0, 0)) + self.assertEqual(1, gcd(1, 0)) + self.assertEqual(-1, gcd(-1, 0)) + self.assertEqual(1, gcd(0, 1)) + self.assertEqual(-1, gcd(0, -1)) + self.assertEqual(1, gcd(7, 1)) + self.assertEqual(-1, gcd(7, -1)) + self.assertEqual(1, gcd(-23, 15)) + self.assertEqual(12, gcd(120, 84)) + self.assertEqual(-12, gcd(84, -120)) + self.assertEqual(gcd(120.0, 84), 12.0) + self.assertEqual(gcd(120, 84.0), 12.0) + self.assertEqual(gcd(F(120), F(84)), F(12)) + self.assertEqual(gcd(F(120, 77), F(84, 55)), F(12, 385)) + + +def _components(r): + return (r.numerator, r.denominator) + + +class FractionTest(unittest.TestCase): + + def assertTypedEquals(self, expected, actual): + """Asserts that both the types and values are the same.""" + self.assertEqual(type(expected), type(actual)) + self.assertEqual(expected, actual) + + def assertTypedTupleEquals(self, expected, actual): + """Asserts that both the types and values in the tuples are the same.""" + self.assertTupleEqual(expected, actual) + self.assertListEqual(list(map(type, expected)), list(map(type, actual))) + + def assertRaisesMessage(self, exc_type, message, + callable, *args, **kwargs): + """Asserts that callable(*args, **kwargs) raises exc_type(message).""" + try: + callable(*args, **kwargs) + except exc_type as e: + self.assertEqual(message, str(e)) + else: + self.fail("%s not raised" % exc_type.__name__) + + def testInit(self): + self.assertEqual((0, 1), _components(F())) + self.assertEqual((7, 1), _components(F(7))) + self.assertEqual((7, 3), _components(F(F(7, 3)))) + + self.assertEqual((-1, 1), _components(F(-1, 1))) + self.assertEqual((-1, 1), _components(F(1, -1))) + self.assertEqual((1, 1), _components(F(-2, -2))) + self.assertEqual((1, 2), _components(F(5, 10))) + self.assertEqual((7, 15), _components(F(7, 15))) + self.assertEqual((10**23, 1), _components(F(10**23))) + + self.assertEqual((3, 77), _components(F(F(3, 7), 11))) + self.assertEqual((-9, 5), _components(F(2, F(-10, 9)))) + self.assertEqual((2486, 2485), _components(F(F(22, 7), F(355, 113)))) + + self.assertRaisesMessage(ZeroDivisionError, "Fraction(12, 0)", + F, 12, 0) + self.assertRaises(TypeError, F, 1.5 + 3j) + + self.assertRaises(TypeError, F, "3/2", 3) + self.assertRaises(TypeError, F, 3, 0j) + self.assertRaises(TypeError, F, 3, 1j) + self.assertRaises(TypeError, F, 1, 2, 3) + + @requires_IEEE_754 + def testInitFromFloat(self): + self.assertEqual((5, 2), _components(F(2.5))) + self.assertEqual((0, 1), _components(F(-0.0))) + self.assertEqual((3602879701896397, 36028797018963968), + _components(F(0.1))) + # bug 16469: error types should be consistent with float -> int + self.assertRaises(ValueError, F, float('nan')) + self.assertRaises(OverflowError, F, float('inf')) + self.assertRaises(OverflowError, F, float('-inf')) + + def testInitFromDecimal(self): + self.assertEqual((11, 10), + _components(F(Decimal('1.1')))) + self.assertEqual((7, 200), + _components(F(Decimal('3.5e-2')))) + self.assertEqual((0, 1), + _components(F(Decimal('.000e20')))) + # bug 16469: error types should be consistent with decimal -> int + self.assertRaises(ValueError, F, Decimal('nan')) + self.assertRaises(ValueError, F, Decimal('snan')) + self.assertRaises(OverflowError, F, Decimal('inf')) + self.assertRaises(OverflowError, F, Decimal('-inf')) + + def testFromString(self): + self.assertEqual((5, 1), _components(F("5"))) + self.assertEqual((3, 2), _components(F("3/2"))) + self.assertEqual((3, 2), _components(F(" \n +3/2"))) + self.assertEqual((-3, 2), _components(F("-3/2 "))) + self.assertEqual((13, 2), _components(F(" 013/02 \n "))) + self.assertEqual((16, 5), _components(F(" 3.2 "))) + self.assertEqual((-16, 5), _components(F(" -3.2 "))) + self.assertEqual((-3, 1), _components(F(" -3. "))) + self.assertEqual((3, 5), _components(F(" .6 "))) + self.assertEqual((1, 3125), _components(F("32.e-5"))) + self.assertEqual((1000000, 1), _components(F("1E+06"))) + self.assertEqual((-12300, 1), _components(F("-1.23e4"))) + self.assertEqual((0, 1), _components(F(" .0e+0\t"))) + self.assertEqual((0, 1), _components(F("-0.000e0"))) + + self.assertRaisesMessage( + ZeroDivisionError, "Fraction(3, 0)", + F, "3/0") + self.assertRaisesMessage( + ValueError, "Invalid literal for Fraction: '3/'", + F, "3/") + self.assertRaisesMessage( + ValueError, "Invalid literal for Fraction: '/2'", + F, "/2") + self.assertRaisesMessage( + ValueError, "Invalid literal for Fraction: '3 /2'", + F, "3 /2") + self.assertRaisesMessage( + # Denominators don't need a sign. + ValueError, "Invalid literal for Fraction: '3/+2'", + F, "3/+2") + self.assertRaisesMessage( + # Imitate float's parsing. + ValueError, "Invalid literal for Fraction: '+ 3/2'", + F, "+ 3/2") + self.assertRaisesMessage( + # Avoid treating '.' as a regex special character. + ValueError, "Invalid literal for Fraction: '3a2'", + F, "3a2") + self.assertRaisesMessage( + # Don't accept combinations of decimals and rationals. + ValueError, "Invalid literal for Fraction: '3/7.2'", + F, "3/7.2") + self.assertRaisesMessage( + # Don't accept combinations of decimals and rationals. + ValueError, "Invalid literal for Fraction: '3.2/7'", + F, "3.2/7") + self.assertRaisesMessage( + # Allow 3. and .3, but not . + ValueError, "Invalid literal for Fraction: '.'", + F, ".") + + def testImmutable(self): + r = F(7, 3) + r.__init__(2, 15) + self.assertEqual((7, 3), _components(r)) + + self.assertRaises(AttributeError, setattr, r, 'numerator', 12) + self.assertRaises(AttributeError, setattr, r, 'denominator', 6) + self.assertEqual((7, 3), _components(r)) + + # But if you _really_ need to: + r._numerator = 4 + r._denominator = 2 + self.assertEqual((4, 2), _components(r)) + # Which breaks some important operations: + self.assertNotEqual(F(4, 2), r) + + def testFromFloat(self): + self.assertRaises(TypeError, F.from_float, 3+4j) + self.assertEqual((10, 1), _components(F.from_float(10))) + bigint = 1234567890123456789 + self.assertEqual((bigint, 1), _components(F.from_float(bigint))) + self.assertEqual((0, 1), _components(F.from_float(-0.0))) + self.assertEqual((10, 1), _components(F.from_float(10.0))) + self.assertEqual((-5, 2), _components(F.from_float(-2.5))) + self.assertEqual((99999999999999991611392, 1), + _components(F.from_float(1e23))) + self.assertEqual(float(10**23), float(F.from_float(1e23))) + self.assertEqual((3602879701896397, 1125899906842624), + _components(F.from_float(3.2))) + self.assertEqual(3.2, float(F.from_float(3.2))) + + inf = 1e1000 + nan = inf - inf + # bug 16469: error types should be consistent with float -> int + self.assertRaisesMessage( + OverflowError, "cannot convert Infinity to integer ratio", + F.from_float, inf) + self.assertRaisesMessage( + OverflowError, "cannot convert Infinity to integer ratio", + F.from_float, -inf) + self.assertRaisesMessage( + ValueError, "cannot convert NaN to integer ratio", + F.from_float, nan) + + def testFromDecimal(self): + self.assertRaises(TypeError, F.from_decimal, 3+4j) + self.assertEqual(F(10, 1), F.from_decimal(10)) + self.assertEqual(F(0), F.from_decimal(Decimal("-0"))) + self.assertEqual(F(5, 10), F.from_decimal(Decimal("0.5"))) + self.assertEqual(F(5, 1000), F.from_decimal(Decimal("5e-3"))) + self.assertEqual(F(5000), F.from_decimal(Decimal("5e3"))) + self.assertEqual(1 - F(1, 10**30), + F.from_decimal(Decimal("0." + "9" * 30))) + + # bug 16469: error types should be consistent with decimal -> int + self.assertRaisesMessage( + OverflowError, "cannot convert Infinity to integer ratio", + F.from_decimal, Decimal("inf")) + self.assertRaisesMessage( + OverflowError, "cannot convert Infinity to integer ratio", + F.from_decimal, Decimal("-inf")) + self.assertRaisesMessage( + ValueError, "cannot convert NaN to integer ratio", + F.from_decimal, Decimal("nan")) + self.assertRaisesMessage( + ValueError, "cannot convert NaN to integer ratio", + F.from_decimal, Decimal("snan")) + + def test_as_integer_ratio(self): + self.assertEqual(F(4, 6).as_integer_ratio(), (2, 3)) + self.assertEqual(F(-4, 6).as_integer_ratio(), (-2, 3)) + self.assertEqual(F(4, -6).as_integer_ratio(), (-2, 3)) + self.assertEqual(F(0, 6).as_integer_ratio(), (0, 1)) + + def testLimitDenominator(self): + rpi = F('3.1415926535897932') + self.assertEqual(rpi.limit_denominator(10000), F(355, 113)) + self.assertEqual(-rpi.limit_denominator(10000), F(-355, 113)) + self.assertEqual(rpi.limit_denominator(113), F(355, 113)) + self.assertEqual(rpi.limit_denominator(112), F(333, 106)) + self.assertEqual(F(201, 200).limit_denominator(100), F(1)) + self.assertEqual(F(201, 200).limit_denominator(101), F(102, 101)) + self.assertEqual(F(0).limit_denominator(10000), F(0)) + for i in (0, -1): + self.assertRaisesMessage( + ValueError, "max_denominator should be at least 1", + F(1).limit_denominator, i) + + def testConversions(self): + self.assertTypedEquals(-1, math.trunc(F(-11, 10))) + self.assertTypedEquals(1, math.trunc(F(11, 10))) + self.assertTypedEquals(-2, math.floor(F(-11, 10))) + self.assertTypedEquals(-1, math.ceil(F(-11, 10))) + self.assertTypedEquals(-1, math.ceil(F(-10, 10))) + self.assertTypedEquals(-1, int(F(-11, 10))) + self.assertTypedEquals(0, round(F(-1, 10))) + self.assertTypedEquals(0, round(F(-5, 10))) + self.assertTypedEquals(-2, round(F(-15, 10))) + self.assertTypedEquals(-1, round(F(-7, 10))) + + self.assertEqual(False, bool(F(0, 1))) + self.assertEqual(True, bool(F(3, 2))) + self.assertTypedEquals(0.1, float(F(1, 10))) + + # Check that __float__ isn't implemented by converting the + # numerator and denominator to float before dividing. + self.assertRaises(OverflowError, float, int('2'*400+'7')) + self.assertAlmostEqual(2.0/3, + float(F(int('2'*400+'7'), int('3'*400+'1')))) + + self.assertTypedEquals(0.1+0j, complex(F(1,10))) + + def testBoolGuarateesBoolReturn(self): + # Ensure that __bool__ is used on numerator which guarantees a bool + # return. See also bpo-39274. + @functools.total_ordering + class CustomValue: + denominator = 1 + + def __init__(self, value): + self.value = value + + def __bool__(self): + return bool(self.value) + + @property + def numerator(self): + # required to preserve `self` during instantiation + return self + + def __eq__(self, other): + raise AssertionError("Avoid comparisons in Fraction.__bool__") + + __lt__ = __eq__ + + # We did not implement all abstract methods, so register: + numbers.Rational.register(CustomValue) + + numerator = CustomValue(1) + r = F(numerator) + # ensure the numerator was not lost during instantiation: + self.assertIs(r.numerator, numerator) + self.assertIs(bool(r), True) + + numerator = CustomValue(0) + r = F(numerator) + self.assertIs(bool(r), False) + + def testRound(self): + self.assertTypedEquals(F(-200), round(F(-150), -2)) + self.assertTypedEquals(F(-200), round(F(-250), -2)) + self.assertTypedEquals(F(30), round(F(26), -1)) + self.assertTypedEquals(F(-2, 10), round(F(-15, 100), 1)) + self.assertTypedEquals(F(-2, 10), round(F(-25, 100), 1)) + + def testArithmetic(self): + self.assertEqual(F(1, 2), F(1, 10) + F(2, 5)) + self.assertEqual(F(-3, 10), F(1, 10) - F(2, 5)) + self.assertEqual(F(1, 25), F(1, 10) * F(2, 5)) + self.assertEqual(F(1, 4), F(1, 10) / F(2, 5)) + self.assertTypedEquals(2, F(9, 10) // F(2, 5)) + self.assertTypedEquals(10**23, F(10**23, 1) // F(1)) + self.assertEqual(F(5, 6), F(7, 3) % F(3, 2)) + self.assertEqual(F(2, 3), F(-7, 3) % F(3, 2)) + self.assertEqual((F(1), F(5, 6)), divmod(F(7, 3), F(3, 2))) + self.assertEqual((F(-2), F(2, 3)), divmod(F(-7, 3), F(3, 2))) + self.assertEqual(F(8, 27), F(2, 3) ** F(3)) + self.assertEqual(F(27, 8), F(2, 3) ** F(-3)) + self.assertTypedEquals(2.0, F(4) ** F(1, 2)) + self.assertEqual(F(1, 1), +F(1, 1)) + z = pow(F(-1), F(1, 2)) + self.assertAlmostEqual(z.real, 0) + self.assertEqual(z.imag, 1) + # Regression test for #27539. + p = F(-1, 2) ** 0 + self.assertEqual(p, F(1, 1)) + self.assertEqual(p.numerator, 1) + self.assertEqual(p.denominator, 1) + p = F(-1, 2) ** -1 + self.assertEqual(p, F(-2, 1)) + self.assertEqual(p.numerator, -2) + self.assertEqual(p.denominator, 1) + p = F(-1, 2) ** -2 + self.assertEqual(p, F(4, 1)) + self.assertEqual(p.numerator, 4) + self.assertEqual(p.denominator, 1) + + def testLargeArithmetic(self): + self.assertTypedEquals( + F(10101010100808080808080808101010101010000000000000000, + 1010101010101010101010101011111111101010101010101010101010101), + F(10**35+1, 10**27+1) % F(10**27+1, 10**35-1) + ) + self.assertTypedEquals( + F(7, 1901475900342344102245054808064), + F(-2**100, 3) % F(5, 2**100) + ) + self.assertTypedTupleEquals( + (9999999999999999, + F(10101010100808080808080808101010101010000000000000000, + 1010101010101010101010101011111111101010101010101010101010101)), + divmod(F(10**35+1, 10**27+1), F(10**27+1, 10**35-1)) + ) + self.assertTypedEquals( + -2 ** 200 // 15, + F(-2**100, 3) // F(5, 2**100) + ) + self.assertTypedEquals( + 1, + F(5, 2**100) // F(3, 2**100) + ) + self.assertTypedEquals( + (1, F(2, 2**100)), + divmod(F(5, 2**100), F(3, 2**100)) + ) + self.assertTypedTupleEquals( + (-2 ** 200 // 15, + F(7, 1901475900342344102245054808064)), + divmod(F(-2**100, 3), F(5, 2**100)) + ) + + def testMixedArithmetic(self): + self.assertTypedEquals(F(11, 10), F(1, 10) + 1) + self.assertTypedEquals(1.1, F(1, 10) + 1.0) + self.assertTypedEquals(1.1 + 0j, F(1, 10) + (1.0 + 0j)) + self.assertTypedEquals(F(11, 10), 1 + F(1, 10)) + self.assertTypedEquals(1.1, 1.0 + F(1, 10)) + self.assertTypedEquals(1.1 + 0j, (1.0 + 0j) + F(1, 10)) + + self.assertTypedEquals(F(-9, 10), F(1, 10) - 1) + self.assertTypedEquals(-0.9, F(1, 10) - 1.0) + self.assertTypedEquals(-0.9 + 0j, F(1, 10) - (1.0 + 0j)) + self.assertTypedEquals(F(9, 10), 1 - F(1, 10)) + self.assertTypedEquals(0.9, 1.0 - F(1, 10)) + self.assertTypedEquals(0.9 + 0j, (1.0 + 0j) - F(1, 10)) + + self.assertTypedEquals(F(1, 10), F(1, 10) * 1) + self.assertTypedEquals(0.1, F(1, 10) * 1.0) + self.assertTypedEquals(0.1 + 0j, F(1, 10) * (1.0 + 0j)) + self.assertTypedEquals(F(1, 10), 1 * F(1, 10)) + self.assertTypedEquals(0.1, 1.0 * F(1, 10)) + self.assertTypedEquals(0.1 + 0j, (1.0 + 0j) * F(1, 10)) + + self.assertTypedEquals(F(1, 10), F(1, 10) / 1) + self.assertTypedEquals(0.1, F(1, 10) / 1.0) + self.assertTypedEquals(0.1 + 0j, F(1, 10) / (1.0 + 0j)) + self.assertTypedEquals(F(10, 1), 1 / F(1, 10)) + self.assertTypedEquals(10.0, 1.0 / F(1, 10)) + self.assertTypedEquals(10.0 + 0j, (1.0 + 0j) / F(1, 10)) + + self.assertTypedEquals(0, F(1, 10) // 1) + self.assertTypedEquals(0.0, F(1, 10) // 1.0) + self.assertTypedEquals(10, 1 // F(1, 10)) + self.assertTypedEquals(10**23, 10**22 // F(1, 10)) + self.assertTypedEquals(1.0 // 0.1, 1.0 // F(1, 10)) + + self.assertTypedEquals(F(1, 10), F(1, 10) % 1) + self.assertTypedEquals(0.1, F(1, 10) % 1.0) + self.assertTypedEquals(F(0, 1), 1 % F(1, 10)) + self.assertTypedEquals(1.0 % 0.1, 1.0 % F(1, 10)) + self.assertTypedEquals(0.1, F(1, 10) % float('inf')) + self.assertTypedEquals(float('-inf'), F(1, 10) % float('-inf')) + self.assertTypedEquals(float('inf'), F(-1, 10) % float('inf')) + self.assertTypedEquals(-0.1, F(-1, 10) % float('-inf')) + + self.assertTypedTupleEquals((0, F(1, 10)), divmod(F(1, 10), 1)) + self.assertTypedTupleEquals(divmod(0.1, 1.0), divmod(F(1, 10), 1.0)) + self.assertTypedTupleEquals((10, F(0)), divmod(1, F(1, 10))) + self.assertTypedTupleEquals(divmod(1.0, 0.1), divmod(1.0, F(1, 10))) + self.assertTypedTupleEquals(divmod(0.1, float('inf')), divmod(F(1, 10), float('inf'))) + self.assertTypedTupleEquals(divmod(0.1, float('-inf')), divmod(F(1, 10), float('-inf'))) + self.assertTypedTupleEquals(divmod(-0.1, float('inf')), divmod(F(-1, 10), float('inf'))) + self.assertTypedTupleEquals(divmod(-0.1, float('-inf')), divmod(F(-1, 10), float('-inf'))) + + # ** has more interesting conversion rules. + self.assertTypedEquals(F(100, 1), F(1, 10) ** -2) + self.assertTypedEquals(F(100, 1), F(10, 1) ** 2) + self.assertTypedEquals(0.1, F(1, 10) ** 1.0) + self.assertTypedEquals(0.1 + 0j, F(1, 10) ** (1.0 + 0j)) + self.assertTypedEquals(4 , 2 ** F(2, 1)) + z = pow(-1, F(1, 2)) + self.assertAlmostEqual(0, z.real) + self.assertEqual(1, z.imag) + self.assertTypedEquals(F(1, 4) , 2 ** F(-2, 1)) + self.assertTypedEquals(2.0 , 4 ** F(1, 2)) + self.assertTypedEquals(0.25, 2.0 ** F(-2, 1)) + self.assertTypedEquals(1.0 + 0j, (1.0 + 0j) ** F(1, 10)) + self.assertRaises(ZeroDivisionError, operator.pow, + F(0, 1), -2) + + def testMixingWithDecimal(self): + # Decimal refuses mixed arithmetic (but not mixed comparisons) + self.assertRaises(TypeError, operator.add, + F(3,11), Decimal('3.1415926')) + self.assertRaises(TypeError, operator.add, + Decimal('3.1415926'), F(3,11)) + + def testComparisons(self): + self.assertTrue(F(1, 2) < F(2, 3)) + self.assertFalse(F(1, 2) < F(1, 2)) + self.assertTrue(F(1, 2) <= F(2, 3)) + self.assertTrue(F(1, 2) <= F(1, 2)) + self.assertFalse(F(2, 3) <= F(1, 2)) + self.assertTrue(F(1, 2) == F(1, 2)) + self.assertFalse(F(1, 2) == F(1, 3)) + self.assertFalse(F(1, 2) != F(1, 2)) + self.assertTrue(F(1, 2) != F(1, 3)) + + def testComparisonsDummyRational(self): + self.assertTrue(F(1, 2) == DummyRational(1, 2)) + self.assertTrue(DummyRational(1, 2) == F(1, 2)) + self.assertFalse(F(1, 2) == DummyRational(3, 4)) + self.assertFalse(DummyRational(3, 4) == F(1, 2)) + + self.assertTrue(F(1, 2) < DummyRational(3, 4)) + self.assertFalse(F(1, 2) < DummyRational(1, 2)) + self.assertFalse(F(1, 2) < DummyRational(1, 7)) + self.assertFalse(F(1, 2) > DummyRational(3, 4)) + self.assertFalse(F(1, 2) > DummyRational(1, 2)) + self.assertTrue(F(1, 2) > DummyRational(1, 7)) + self.assertTrue(F(1, 2) <= DummyRational(3, 4)) + self.assertTrue(F(1, 2) <= DummyRational(1, 2)) + self.assertFalse(F(1, 2) <= DummyRational(1, 7)) + self.assertFalse(F(1, 2) >= DummyRational(3, 4)) + self.assertTrue(F(1, 2) >= DummyRational(1, 2)) + self.assertTrue(F(1, 2) >= DummyRational(1, 7)) + + self.assertTrue(DummyRational(1, 2) < F(3, 4)) + self.assertFalse(DummyRational(1, 2) < F(1, 2)) + self.assertFalse(DummyRational(1, 2) < F(1, 7)) + self.assertFalse(DummyRational(1, 2) > F(3, 4)) + self.assertFalse(DummyRational(1, 2) > F(1, 2)) + self.assertTrue(DummyRational(1, 2) > F(1, 7)) + self.assertTrue(DummyRational(1, 2) <= F(3, 4)) + self.assertTrue(DummyRational(1, 2) <= F(1, 2)) + self.assertFalse(DummyRational(1, 2) <= F(1, 7)) + self.assertFalse(DummyRational(1, 2) >= F(3, 4)) + self.assertTrue(DummyRational(1, 2) >= F(1, 2)) + self.assertTrue(DummyRational(1, 2) >= F(1, 7)) + + def testComparisonsDummyFloat(self): + x = DummyFloat(1./3.) + y = F(1, 3) + self.assertTrue(x != y) + self.assertTrue(x < y or x > y) + self.assertFalse(x == y) + self.assertFalse(x <= y and x >= y) + self.assertTrue(y != x) + self.assertTrue(y < x or y > x) + self.assertFalse(y == x) + self.assertFalse(y <= x and y >= x) + + def testMixedLess(self): + self.assertTrue(2 < F(5, 2)) + self.assertFalse(2 < F(4, 2)) + self.assertTrue(F(5, 2) < 3) + self.assertFalse(F(4, 2) < 2) + + self.assertTrue(F(1, 2) < 0.6) + self.assertFalse(F(1, 2) < 0.4) + self.assertTrue(0.4 < F(1, 2)) + self.assertFalse(0.5 < F(1, 2)) + + self.assertFalse(float('inf') < F(1, 2)) + self.assertTrue(float('-inf') < F(0, 10)) + self.assertFalse(float('nan') < F(-3, 7)) + self.assertTrue(F(1, 2) < float('inf')) + self.assertFalse(F(17, 12) < float('-inf')) + self.assertFalse(F(144, -89) < float('nan')) + + def testMixedLessEqual(self): + self.assertTrue(0.5 <= F(1, 2)) + self.assertFalse(0.6 <= F(1, 2)) + self.assertTrue(F(1, 2) <= 0.5) + self.assertFalse(F(1, 2) <= 0.4) + self.assertTrue(2 <= F(4, 2)) + self.assertFalse(2 <= F(3, 2)) + self.assertTrue(F(4, 2) <= 2) + self.assertFalse(F(5, 2) <= 2) + + self.assertFalse(float('inf') <= F(1, 2)) + self.assertTrue(float('-inf') <= F(0, 10)) + self.assertFalse(float('nan') <= F(-3, 7)) + self.assertTrue(F(1, 2) <= float('inf')) + self.assertFalse(F(17, 12) <= float('-inf')) + self.assertFalse(F(144, -89) <= float('nan')) + + def testBigFloatComparisons(self): + # Because 10**23 can't be represented exactly as a float: + self.assertFalse(F(10**23) == float(10**23)) + # The first test demonstrates why these are important. + self.assertFalse(1e23 < float(F(math.trunc(1e23) + 1))) + self.assertTrue(1e23 < F(math.trunc(1e23) + 1)) + self.assertFalse(1e23 <= F(math.trunc(1e23) - 1)) + self.assertTrue(1e23 > F(math.trunc(1e23) - 1)) + self.assertFalse(1e23 >= F(math.trunc(1e23) + 1)) + + def testBigComplexComparisons(self): + self.assertFalse(F(10**23) == complex(10**23)) + self.assertRaises(TypeError, operator.gt, F(10**23), complex(10**23)) + self.assertRaises(TypeError, operator.le, F(10**23), complex(10**23)) + + x = F(3, 8) + z = complex(0.375, 0.0) + w = complex(0.375, 0.2) + self.assertTrue(x == z) + self.assertFalse(x != z) + self.assertFalse(x == w) + self.assertTrue(x != w) + for op in operator.lt, operator.le, operator.gt, operator.ge: + self.assertRaises(TypeError, op, x, z) + self.assertRaises(TypeError, op, z, x) + self.assertRaises(TypeError, op, x, w) + self.assertRaises(TypeError, op, w, x) + + def testMixedEqual(self): + self.assertTrue(0.5 == F(1, 2)) + self.assertFalse(0.6 == F(1, 2)) + self.assertTrue(F(1, 2) == 0.5) + self.assertFalse(F(1, 2) == 0.4) + self.assertTrue(2 == F(4, 2)) + self.assertFalse(2 == F(3, 2)) + self.assertTrue(F(4, 2) == 2) + self.assertFalse(F(5, 2) == 2) + self.assertFalse(F(5, 2) == float('nan')) + self.assertFalse(float('nan') == F(3, 7)) + self.assertFalse(F(5, 2) == float('inf')) + self.assertFalse(float('-inf') == F(2, 5)) + + def testStringification(self): + self.assertEqual("Fraction(7, 3)", repr(F(7, 3))) + self.assertEqual("Fraction(6283185307, 2000000000)", + repr(F('3.1415926535'))) + self.assertEqual("Fraction(-1, 100000000000000000000)", + repr(F(1, -10**20))) + self.assertEqual("7/3", str(F(7, 3))) + self.assertEqual("7", str(F(7, 1))) + + def testHash(self): + hmod = sys.hash_info.modulus + hinf = sys.hash_info.inf + self.assertEqual(hash(2.5), hash(F(5, 2))) + self.assertEqual(hash(10**50), hash(F(10**50))) + self.assertNotEqual(hash(float(10**23)), hash(F(10**23))) + self.assertEqual(hinf, hash(F(1, hmod))) + # Check that __hash__ produces the same value as hash(), for + # consistency with int and Decimal. (See issue #10356.) + self.assertEqual(hash(F(-1)), F(-1).__hash__()) + + def testApproximatePi(self): + # Algorithm borrowed from + # http://docs.python.org/lib/decimal-recipes.html + three = F(3) + lasts, t, s, n, na, d, da = 0, three, 3, 1, 0, 0, 24 + while abs(s - lasts) > F(1, 10**9): + lasts = s + n, na = n+na, na+8 + d, da = d+da, da+32 + t = (t * n) / d + s += t + self.assertAlmostEqual(math.pi, s) + + def testApproximateCos1(self): + # Algorithm borrowed from + # http://docs.python.org/lib/decimal-recipes.html + x = F(1) + i, lasts, s, fact, num, sign = 0, 0, F(1), 1, 1, 1 + while abs(s - lasts) > F(1, 10**9): + lasts = s + i += 2 + fact *= i * (i-1) + num *= x * x + sign *= -1 + s += num / fact * sign + self.assertAlmostEqual(math.cos(1), s) + + def test_copy_deepcopy_pickle(self): + r = F(13, 7) + dr = DummyFraction(13, 7) + self.assertEqual(r, loads(dumps(r))) + self.assertEqual(id(r), id(copy(r))) + self.assertEqual(id(r), id(deepcopy(r))) + self.assertNotEqual(id(dr), id(copy(dr))) + self.assertNotEqual(id(dr), id(deepcopy(dr))) + self.assertTypedEquals(dr, copy(dr)) + self.assertTypedEquals(dr, deepcopy(dr)) + + def test_slots(self): + # Issue 4998 + r = F(13, 7) + self.assertRaises(AttributeError, setattr, r, 'a', 10) + +if __name__ == '__main__': + unittest.main() From 0ac4f3a642a736af16991a7e16c13b7f706b1b4c Mon Sep 17 00:00:00 2001 From: Ben Lewis Date: Tue, 14 Jul 2020 20:08:17 +1200 Subject: [PATCH 2/4] Change builtins module to use pymodule proc macro. --- vm/src/builtins.rs | 1645 ++++++++++++++++++++++---------------------- 1 file changed, 823 insertions(+), 822 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 7ea6d8fb9..a6b12dfce 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -1,71 +1,838 @@ //! Builtin function definitions. //! //! Implements functions listed here: https://docs.python.org/3/library/builtins.html - -use std::char; -use std::str; - -use num_bigint::Sign; -use num_traits::{Signed, ToPrimitive, Zero}; -#[cfg(feature = "rustpython-compiler")] -use rustpython_compiler::compile; -#[cfg(feature = "rustpython-parser")] -use rustpython_parser::parser; - -use crate::exceptions::PyBaseExceptionRef; -use crate::function::{single_or_tuple_any, Args, KwArgs, OptionalArg, PyFuncArgs}; -use crate::obj::objbool::{self, IntoPyBool}; -use crate::obj::objbyteinner::PyByteInner; -use crate::obj::objbytes::PyBytesRef; -use crate::obj::objcode::PyCodeRef; -use crate::obj::objdict::PyDictRef; -use crate::obj::objfunction::PyFunctionRef; -use crate::obj::objint::{self, PyIntRef}; -use crate::obj::objiter; -use crate::obj::objsequence; -use crate::obj::objstr::{PyString, PyStringRef}; -use crate::obj::objtype::{self, PyClassRef}; -use crate::pyhash; -use crate::pyobject::{ - Either, IdProtocol, ItemProtocol, PyCallable, PyIterable, PyObjectRef, PyResult, PyValue, - TryFromObject, TypeProtocol, -}; -use crate::readline::{Readline, ReadlineResult}; -use crate::scope::Scope; -#[cfg(feature = "rustpython-parser")] -use crate::stdlib::ast; +use crate::pyobject::PyObjectRef; use crate::vm::VirtualMachine; -fn builtin_abs(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let method = vm.get_method_or_type_error(x.clone(), "__abs__", || { - format!("bad operand type for abs(): '{}'", x.class().name) - })?; - vm.invoke(&method, PyFuncArgs::new(vec![], vec![])) -} +#[pymodule(name = "builtins")] +mod decl { + use std::char; + use std::str; -fn builtin_all(iterable: PyIterable, vm: &VirtualMachine) -> PyResult { - for item in iterable.iter(vm)? { - if !item?.to_bool() { - return Ok(false); + use num_bigint::Sign; + use num_traits::{Signed, ToPrimitive, Zero}; + #[cfg(feature = "rustpython-compiler")] + use rustpython_compiler::compile; + #[cfg(feature = "rustpython-parser")] + use rustpython_parser::parser; + + use super::to_ascii; + use crate::exceptions::PyBaseExceptionRef; + use crate::function::{single_or_tuple_any, Args, KwArgs, OptionalArg, PyFuncArgs}; + use crate::obj::objbool::{self, IntoPyBool}; + use crate::obj::objbyteinner::PyByteInner; + use crate::obj::objbytes::PyBytesRef; + use crate::obj::objcode::PyCodeRef; + use crate::obj::objdict::PyDictRef; + use crate::obj::objfunction::PyFunctionRef; + use crate::obj::objint::{self, PyIntRef}; + use crate::obj::objiter; + use crate::obj::objsequence; + use crate::obj::objstr::{PyString, PyStringRef}; + use crate::obj::objtype::{self, PyClassRef}; + use crate::pyhash; + use crate::pyobject::{ + Either, IdProtocol, ItemProtocol, PyCallable, PyIterable, PyObjectRef, PyResult, PyValue, + TryFromObject, TypeProtocol, + }; + use crate::readline::{Readline, ReadlineResult}; + use crate::scope::Scope; + #[cfg(feature = "rustpython-parser")] + use crate::stdlib::ast; + use crate::vm::VirtualMachine; + + #[pyfunction] + fn abs(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let method = vm.get_method_or_type_error(x.clone(), "__abs__", || { + format!("bad operand type for abs(): '{}'", x.class().name) + })?; + vm.invoke(&method, PyFuncArgs::new(vec![], vec![])) + } + + #[pyfunction] + fn all(iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + for item in iterable.iter(vm)? { + if !item?.to_bool() { + return Ok(false); + } + } + Ok(true) + } + + #[pyfunction] + fn any(iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + for item in iterable.iter(vm)? { + if item?.to_bool() { + return Ok(true); + } + } + Ok(false) + } + + #[pyfunction] + fn ascii(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let repr = vm.to_repr(&obj)?; + let ascii = to_ascii(repr.as_str()); + Ok(ascii) + } + + #[pyfunction] + fn bin(x: PyIntRef) -> String { + let x = x.as_bigint(); + if x.is_negative() { + format!("-0b{:b}", x.abs()) + } else { + format!("0b{:b}", x) } } - Ok(true) -} -fn builtin_any(iterable: PyIterable, vm: &VirtualMachine) -> PyResult { - for item in iterable.iter(vm)? { - if item?.to_bool() { - return Ok(true); + // builtin_breakpoint + + #[pyfunction] + fn callable(obj: PyObjectRef, vm: &VirtualMachine) -> bool { + vm.is_callable(&obj) + } + + #[pyfunction] + fn chr(i: u32, vm: &VirtualMachine) -> PyResult { + match char::from_u32(i) { + Some(value) => Ok(value.to_string()), + None => Err(vm.new_value_error("chr() arg not in range(0x110000)".to_owned())), } } - Ok(false) + + #[derive(FromArgs)] + #[allow(dead_code)] + struct CompileArgs { + #[pyarg(positional_only, optional = false)] + source: Either, + #[pyarg(positional_only, optional = false)] + filename: PyStringRef, + #[pyarg(positional_only, optional = false)] + mode: PyStringRef, + #[pyarg(positional_or_keyword, optional = true)] + flags: OptionalArg, + #[pyarg(positional_or_keyword, optional = true)] + dont_inherit: OptionalArg, + #[pyarg(positional_or_keyword, optional = true)] + optimize: OptionalArg, + } + + #[cfg(feature = "rustpython-compiler")] + #[pyfunction] + fn compile(args: CompileArgs, vm: &VirtualMachine) -> PyResult { + // TODO: compile::compile should probably get bytes + let source = match &args.source { + Either::A(string) => string.as_str(), + Either::B(bytes) => str::from_utf8(bytes).unwrap(), + }; + + let mode_str = args.mode.as_str(); + + let flags = args + .flags + .map_or(Ok(0), |v| i32::try_from_object(vm, v.into_object()))?; + + #[cfg(feature = "rustpython-parser")] + { + if (flags & ast::PY_COMPILE_FLAG_AST_ONLY).is_zero() { + let mode = mode_str + .parse::() + .map_err(|err| vm.new_value_error(err.to_string()))?; + + vm.compile(&source, mode, args.filename.as_str().to_owned()) + .map(|o| o.into_object()) + .map_err(|err| vm.new_syntax_error(&err)) + } else { + let mode = mode_str + .parse::() + .map_err(|err| vm.new_value_error(err.to_string()))?; + ast::parse(&vm, &source, mode) + } + } + #[cfg(not(feature = "rustpython-parser"))] + { + Err(vm.new_value_error( + "PyCF_ONLY_AST flag is not supported without parser support".to_string(), + )) + } + } + + #[pyfunction] + fn delattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + vm.del_attr(&obj, attr.into_object()) + } + + #[pyfunction] + fn dir(obj: OptionalArg, vm: &VirtualMachine) -> PyResult { + let seq = match obj { + OptionalArg::Present(obj) => vm.call_method(&obj, "__dir__", vec![])?, + OptionalArg::Missing => { + vm.call_method(&vm.get_locals().into_object(), "keys", vec![])? + } + }; + let sorted = sorted(vm, PyFuncArgs::new(vec![seq], vec![]))?; + Ok(sorted) + } + + #[pyfunction] + fn divmod(a: PyObjectRef, b: PyObjectRef, vm: &VirtualMachine) -> PyResult { + vm.call_or_reflection( + a.clone(), + b.clone(), + "__divmod__", + "__rdivmod__", + |vm, a, b| Err(vm.new_unsupported_operand_error(a, b, "divmod")), + ) + } + + #[cfg(feature = "rustpython-compiler")] + #[derive(FromArgs)] + struct ScopeArgs { + #[pyarg(positional_or_keyword, default = "None")] + globals: Option, + // TODO: support any mapping for `locals` + #[pyarg(positional_or_keyword, default = "None")] + locals: Option, + } + + /// Implements `eval`. + /// See also: https://docs.python.org/3/library/functions.html#eval + #[cfg(feature = "rustpython-compiler")] + #[pyfunction] + fn eval( + source: Either, + scope: ScopeArgs, + vm: &VirtualMachine, + ) -> PyResult { + run_code(vm, source, scope, compile::Mode::Eval) + } + + /// Implements `exec` + /// https://docs.python.org/3/library/functions.html#exec + #[cfg(feature = "rustpython-compiler")] + #[pyfunction] + fn exec( + source: Either, + scope: ScopeArgs, + vm: &VirtualMachine, + ) -> PyResult { + run_code(vm, source, scope, compile::Mode::Exec) + } + + #[cfg(feature = "rustpython-compiler")] + fn run_code( + vm: &VirtualMachine, + source: Either, + scope: ScopeArgs, + mode: compile::Mode, + ) -> PyResult { + let scope = make_scope(vm, scope)?; + + // Determine code object: + let code_obj = match source { + Either::A(string) => vm + .compile(string.as_str(), mode, "".to_owned()) + .map_err(|err| vm.new_syntax_error(&err))?, + Either::B(code_obj) => code_obj, + }; + + // Run the code: + vm.run_code_obj(code_obj, scope) + } + + #[cfg(feature = "rustpython-compiler")] + fn make_scope(vm: &VirtualMachine, scope: ScopeArgs) -> PyResult { + let globals = scope.globals; + let current_scope = vm.current_scope(); + let locals = match scope.locals { + Some(dict) => Some(dict), + None => { + if globals.is_some() { + None + } else { + current_scope.get_only_locals() + } + } + }; + let globals = match globals { + Some(dict) => { + if !dict.contains_key("__builtins__", vm) { + let builtins_dict = vm.builtins.dict().unwrap().as_object().clone(); + dict.set_item("__builtins__", builtins_dict, vm).unwrap(); + } + dict + } + None => current_scope.globals.clone(), + }; + + let scope = Scope::with_builtins(locals, globals, vm); + Ok(scope) + } + + #[pyfunction] + fn format( + value: PyObjectRef, + format_spec: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let format_spec = format_spec + .into_option() + .unwrap_or_else(|| PyString::from("").into_ref(vm)); + + vm.call_method(&value, "__format__", vec![format_spec.into_object()])? + .downcast() + .map_err(|obj| { + vm.new_type_error(format!( + "__format__ must return a str, not {}", + obj.class().name + )) + }) + } + + fn catch_attr_exception( + ex: PyBaseExceptionRef, + default: T, + vm: &VirtualMachine, + ) -> PyResult { + if objtype::isinstance(&ex, &vm.ctx.exceptions.attribute_error) { + Ok(default) + } else { + Err(ex) + } + } + + #[pyfunction] + fn getattr( + obj: PyObjectRef, + attr: PyStringRef, + default: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let ret = vm.get_attribute(obj.clone(), attr); + if let OptionalArg::Present(default) = default { + ret.or_else(|ex| catch_attr_exception(ex, default, vm)) + } else { + ret + } + } + + #[pyfunction] + fn globals(vm: &VirtualMachine) -> PyResult { + Ok(vm.current_scope().globals.clone()) + } + + #[pyfunction] + fn hasattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> PyResult { + if let Err(ex) = vm.get_attribute(obj.clone(), attr) { + catch_attr_exception(ex, false, vm) + } else { + Ok(true) + } + } + + #[pyfunction] + fn hash(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + vm._hash(&obj) + } + + // builtin_help + + #[pyfunction] + fn hex(number: PyIntRef, vm: &VirtualMachine) -> PyResult { + let n = number.as_bigint(); + let s = if n.is_negative() { + format!("-0x{:x}", -n) + } else { + format!("0x{:x}", n) + }; + + Ok(vm.new_str(s)) + } + + #[pyfunction] + fn id(obj: PyObjectRef) -> usize { + obj.get_id() + } + + #[pyfunction] + fn input(prompt: OptionalArg, vm: &VirtualMachine) -> PyResult { + let prompt = prompt.as_ref().map_or("", |s| s.as_str()); + let mut readline = Readline::new(()); + match readline.readline(prompt) { + ReadlineResult::Line(s) => Ok(s), + ReadlineResult::EOF => Err(vm.new_exception_empty(vm.ctx.exceptions.eof_error.clone())), + ReadlineResult::Interrupt => { + Err(vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.clone())) + } + ReadlineResult::IO(e) => Err(vm.new_os_error(e.to_string())), + ReadlineResult::EncodingError => { + Err(vm.new_unicode_decode_error("Error decoding readline input".to_owned())) + } + ReadlineResult::Other(e) => Err(vm.new_runtime_error(e.to_string())), + } + } + + #[pyfunction] + pub fn isinstance(obj: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult { + single_or_tuple_any( + typ, + |cls: &PyClassRef| vm.isinstance(&obj, cls), + |o| { + format!( + "isinstance() arg 2 must be a type or tuple of types, not {}", + o.class() + ) + }, + vm, + ) + } + + #[pyfunction] + fn issubclass(subclass: PyClassRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult { + single_or_tuple_any( + typ, + |cls: &PyClassRef| vm.issubclass(&subclass, cls), + |o| { + format!( + "issubclass() arg 2 must be a class or tuple of classes, not {}", + o.class() + ) + }, + vm, + ) + } + + #[pyfunction] + fn iter( + iter_target: PyObjectRef, + sentinel: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + if let OptionalArg::Present(sentinel) = sentinel { + let callable = PyCallable::try_from_object(vm, iter_target)?; + Ok(objiter::PyCallableIterator::new(callable, sentinel) + .into_ref(vm) + .into_object()) + } else { + objiter::get_iter(vm, &iter_target) + } + } + + #[pyfunction] + fn len(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + objsequence::len(&obj, vm) + } + + #[pyfunction] + fn locals(vm: &VirtualMachine) -> PyDictRef { + let locals = vm.get_locals(); + locals.copy().into_ref(vm) + } + + #[pyfunction] + fn max(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + let candidates = match args.args.len().cmp(&1) { + std::cmp::Ordering::Greater => args.args.clone(), + std::cmp::Ordering::Equal => vm.extract_elements(&args.args[0])?, + std::cmp::Ordering::Less => { + // zero arguments means type error: + return Err(vm.new_type_error("Expected 1 or more arguments".to_owned())); + } + }; + + if candidates.is_empty() { + let default = args.get_optional_kwarg("default"); + return default + .ok_or_else(|| vm.new_value_error("max() arg is an empty sequence".to_owned())); + } + + let key_func = args.get_optional_kwarg("key"); + + // Start with first assumption: + let mut candidates_iter = candidates.into_iter(); + let mut x = candidates_iter.next().unwrap(); + // TODO: this key function looks pretty duplicate. Maybe we can create + // a local function? + let mut x_key = if let Some(ref f) = &key_func { + vm.invoke(f, vec![x.clone()])? + } else { + x.clone() + }; + + for y in candidates_iter { + let y_key = if let Some(ref f) = &key_func { + vm.invoke(f, vec![y.clone()])? + } else { + y.clone() + }; + let order = vm._gt(x_key.clone(), y_key.clone())?; + + if !objbool::get_value(&order) { + x = y.clone(); + x_key = y_key; + } + } + + Ok(x) + } + + #[pyfunction] + fn min(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + let candidates = match args.args.len().cmp(&1) { + std::cmp::Ordering::Greater => args.args.clone(), + std::cmp::Ordering::Equal => vm.extract_elements(&args.args[0])?, + std::cmp::Ordering::Less => { + // zero arguments means type error: + return Err(vm.new_type_error("Expected 1 or more arguments".to_owned())); + } + }; + + if candidates.is_empty() { + let default = args.get_optional_kwarg("default"); + return default + .ok_or_else(|| vm.new_value_error("min() arg is an empty sequence".to_owned())); + } + + let key_func = args.get_optional_kwarg("key"); + + let mut candidates_iter = candidates.into_iter(); + let mut x = candidates_iter.next().unwrap(); + // TODO: this key function looks pretty duplicate. Maybe we can create + // a local function? + let mut x_key = if let Some(ref f) = &key_func { + vm.invoke(f, vec![x.clone()])? + } else { + x.clone() + }; + + for y in candidates_iter { + let y_key = if let Some(ref f) = &key_func { + vm.invoke(f, vec![y.clone()])? + } else { + y.clone() + }; + let order = vm._gt(x_key.clone(), y_key.clone())?; + + if objbool::get_value(&order) { + x = y.clone(); + x_key = y_key; + } + } + + Ok(x) + } + + #[pyfunction] + fn next( + iterator: PyObjectRef, + default_value: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + match vm.call_method(&iterator, "__next__", vec![]) { + Ok(value) => Ok(value), + Err(value) => { + if objtype::isinstance(&value, &vm.ctx.exceptions.stop_iteration) { + match default_value { + OptionalArg::Missing => Err(value), + OptionalArg::Present(value) => Ok(value.clone()), + } + } else { + Err(value) + } + } + } + } + + #[pyfunction] + fn oct(number: PyIntRef, vm: &VirtualMachine) -> PyResult { + let n = number.as_bigint(); + let s = if n.is_negative() { + format!("-0o{:o}", n.abs()) + } else { + format!("0o{:o}", n) + }; + + Ok(vm.new_str(s)) + } + + #[pyfunction] + fn ord(string: Either, vm: &VirtualMachine) -> PyResult { + match string { + Either::A(bytes) => { + let bytes_len = bytes.elements.len(); + if bytes_len != 1 { + return Err(vm.new_type_error(format!( + "ord() expected a character, but string of length {} found", + bytes_len + ))); + } + Ok(u32::from(bytes.elements[0])) + } + Either::B(string) => { + let string = string.as_str(); + let string_len = string.chars().count(); + if string_len != 1 { + return Err(vm.new_type_error(format!( + "ord() expected a character, but string of length {} found", + string_len + ))); + } + match string.chars().next() { + Some(character) => Ok(character as u32), + None => Err(vm.new_type_error( + "ord() could not guess the integer representing this character".to_owned(), + )), + } + } + } + } + + #[pyfunction] + fn pow( + x: PyObjectRef, + y: PyObjectRef, + mod_value: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + match mod_value { + OptionalArg::Missing => { + vm.call_or_reflection(x.clone(), y.clone(), "__pow__", "__rpow__", |vm, x, y| { + Err(vm.new_unsupported_operand_error(x, y, "pow")) + }) + } + OptionalArg::Present(m) => { + // Check if the 3rd argument is defined and perform modulus on the result + if !(objtype::isinstance(&x, &vm.ctx.int_type()) + && objtype::isinstance(&y, &vm.ctx.int_type())) + { + return Err(vm.new_type_error( + "pow() 3rd argument not allowed unless all arguments are integers" + .to_owned(), + )); + } + let y = objint::get_value(&y); + if y.sign() == Sign::Minus { + return Err(vm.new_value_error( + "pow() 2nd argument cannot be negative when 3rd argument specified" + .to_owned(), + )); + } + let m = m.as_bigint(); + if m.is_zero() { + return Err(vm.new_value_error("pow() 3rd argument cannot be 0".to_owned())); + } + let x = objint::get_value(&x); + Ok(vm.new_int(x.modpow(&y, &m))) + } + } + } + + #[pyfunction] + pub fn exit(exit_code_arg: OptionalArg, vm: &VirtualMachine) -> PyResult { + let code = exit_code_arg.unwrap_or_else(|| vm.new_int(0)); + Err(vm.new_exception(vm.ctx.exceptions.system_exit.clone(), vec![code])) + } + + #[derive(Debug, Default, FromArgs)] + pub struct PrintOptions { + #[pyarg(keyword_only, default = "None")] + sep: Option, + #[pyarg(keyword_only, default = "None")] + end: Option, + #[pyarg(keyword_only, default = "IntoPyBool::FALSE")] + flush: IntoPyBool, + #[pyarg(keyword_only, default = "None")] + file: Option, + } + + #[pyfunction] + pub fn print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { + let file = match options.file { + Some(f) => f, + None => vm.get_attribute(vm.sys_module.clone(), "stdout")?, + }; + let write = |obj: PyStringRef| vm.call_method(&file, "write", vec![obj.into_object()]); + + let sep = options + .sep + .unwrap_or_else(|| PyString::from(" ").into_ref(vm)); + + let mut first = true; + for object in objects { + if first { + first = false; + } else { + write(sep.clone())?; + } + + write(vm.to_str(&object)?)?; + } + + let end = options + .end + .unwrap_or_else(|| PyString::from("\n").into_ref(vm)); + write(end)?; + + if options.flush.to_bool() { + vm.call_method(&file, "flush", vec![])?; + } + + Ok(()) + } + + #[pyfunction] + fn repr(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + vm.to_repr(&obj) + } + + #[pyfunction] + fn reversed(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(reversed_method) = vm.get_method(obj.clone(), "__reversed__") { + vm.invoke(&reversed_method?, PyFuncArgs::default()) + } else { + vm.get_method_or_type_error(obj.clone(), "__getitem__", || { + "argument to reversed() must be a sequence".to_owned() + })?; + let len = vm.call_method(&obj, "__len__", PyFuncArgs::default())?; + let len = objint::get_value(&len).to_isize().unwrap(); + let obj_iterator = objiter::PySequenceIterator::new_reversed(obj, len); + Ok(obj_iterator.into_ref(vm).into_object()) + } + } + + #[pyfunction] + fn round( + number: PyObjectRef, + ndigits: OptionalArg>, + vm: &VirtualMachine, + ) -> PyResult { + let rounded = match ndigits { + OptionalArg::Present(ndigits) => match ndigits { + Some(int) => { + let ndigits = vm.call_method(int.as_object(), "__int__", vec![])?; + vm.call_method(&number, "__round__", vec![ndigits])? + } + None => vm.call_method(&number, "__round__", vec![])?, + }, + OptionalArg::Missing => { + // without a parameter, the result type is coerced to int + vm.call_method(&number, "__round__", vec![])? + } + }; + Ok(rounded) + } + + #[pyfunction] + fn setattr( + obj: PyObjectRef, + attr: PyStringRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + vm.set_attr(&obj, attr.into_object(), value)?; + Ok(()) + } + + // builtin_slice + + #[pyfunction] + fn sorted(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { + let iterable = args.take_positional(); + if iterable.is_none() { + return Err(vm.new_type_error("sorted expected 1 arguments, got 0".to_string())); + } + let items = vm.extract_elements(&iterable.unwrap())?; + let lst = vm.ctx.new_list(items); + + vm.call_method(&lst, "sort", args)?; + Ok(lst) + } + + #[pyfunction] + fn sum(iterable: PyIterable, start: OptionalArg, vm: &VirtualMachine) -> PyResult { + // Start with zero and add at will: + let mut sum = start.into_option().unwrap_or_else(|| vm.ctx.new_int(0)); + for item in iterable.iter(vm)? { + sum = vm._add(sum, item?)?; + } + Ok(sum) + } + + #[pyfunction] + fn __import__(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + vm.invoke(&vm.import_func, args) + } + + #[pyfunction] + fn vars(obj: OptionalArg, vm: &VirtualMachine) -> PyResult { + if let OptionalArg::Present(obj) = obj { + vm.get_attribute(obj, "__dict__") + } else { + Ok(vm.get_locals().into_object()) + } + } + + #[pyfunction] + pub fn __build_class__( + function: PyFunctionRef, + qualified_name: PyStringRef, + bases: Args, + mut kwargs: KwArgs, + vm: &VirtualMachine, + ) -> PyResult { + let name = qualified_name.as_str().split('.').next_back().unwrap(); + let name_obj = vm.new_str(name.to_owned()); + + let mut metaclass = if let Some(metaclass) = kwargs.pop_kwarg("metaclass") { + PyClassRef::try_from_object(vm, metaclass)? + } else { + vm.get_type() + }; + + for base in bases.clone() { + if objtype::issubclass(&base.class(), &metaclass) { + metaclass = base.class(); + } else if !objtype::issubclass(&metaclass, &base.class()) { + return Err(vm.new_type_error( + "metaclass conflict: the metaclass of a derived class must be a (non-strict) \ + subclass of the metaclasses of all its bases" + .to_owned(), + )); + } + } + + let bases = bases.into_tuple(vm); + + // Prepare uses full __getattribute__ resolution chain. + let prepare = vm.get_attribute(metaclass.clone().into_object(), "__prepare__")?; + let namespace = vm.invoke(&prepare, vec![name_obj.clone(), bases.clone()])?; + + let namespace: PyDictRef = TryFromObject::try_from_object(vm, namespace)?; + + let cells = vm.ctx.new_dict(); + + let scope = function + .scope() + .new_child_scope_with_locals(cells.clone()) + .new_child_scope_with_locals(namespace.clone()); + + function.invoke_with_scope(vec![].into(), &scope, vm)?; + + let class = vm.invoke( + metaclass.as_object(), + ( + Args::from(vec![name_obj, bases, namespace.into_object()]), + kwargs, + ), + )?; + cells.set_item("__class__", class.clone(), vm)?; + Ok(class) + } } -fn builtin_ascii(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let repr = vm.to_repr(&obj)?; - let ascii = to_ascii(repr.as_str()); - Ok(ascii) -} +pub use decl::isinstance as builtin_isinstance; +pub use decl::print as builtin_print; /// Convert a string to ascii compatible, escaping unicodes into escape /// sequences. @@ -89,679 +856,10 @@ pub fn to_ascii(value: &str) -> String { ascii } -fn builtin_bin(x: PyIntRef) -> String { - let x = x.as_bigint(); - if x.is_negative() { - format!("-0b{:b}", x.abs()) - } else { - format!("0b{:b}", x) - } -} - -// builtin_breakpoint - -fn builtin_callable(obj: PyObjectRef, vm: &VirtualMachine) -> bool { - vm.is_callable(&obj) -} - -fn builtin_chr(i: u32, vm: &VirtualMachine) -> PyResult { - match char::from_u32(i) { - Some(value) => Ok(value.to_string()), - None => Err(vm.new_value_error("chr() arg not in range(0x110000)".to_owned())), - } -} - -#[derive(FromArgs)] -#[allow(dead_code)] -struct CompileArgs { - #[pyarg(positional_only, optional = false)] - source: Either, - #[pyarg(positional_only, optional = false)] - filename: PyStringRef, - #[pyarg(positional_only, optional = false)] - mode: PyStringRef, - #[pyarg(positional_or_keyword, optional = true)] - flags: OptionalArg, - #[pyarg(positional_or_keyword, optional = true)] - dont_inherit: OptionalArg, - #[pyarg(positional_or_keyword, optional = true)] - optimize: OptionalArg, -} - -#[cfg(feature = "rustpython-compiler")] -fn builtin_compile(args: CompileArgs, vm: &VirtualMachine) -> PyResult { - // TODO: compile::compile should probably get bytes - let source = match &args.source { - Either::A(string) => string.as_str(), - Either::B(bytes) => str::from_utf8(bytes).unwrap(), - }; - - let mode_str = args.mode.as_str(); - - let flags = args - .flags - .map_or(Ok(0), |v| i32::try_from_object(vm, v.into_object()))?; - - #[cfg(feature = "rustpython-parser")] - { - if (flags & ast::PY_COMPILE_FLAG_AST_ONLY).is_zero() { - let mode = mode_str - .parse::() - .map_err(|err| vm.new_value_error(err.to_string()))?; - - vm.compile(&source, mode, args.filename.as_str().to_owned()) - .map(|o| o.into_object()) - .map_err(|err| vm.new_syntax_error(&err)) - } else { - let mode = mode_str - .parse::() - .map_err(|err| vm.new_value_error(err.to_string()))?; - ast::parse(&vm, &source, mode) - } - } - #[cfg(not(feature = "rustpython-parser"))] - { - Err(vm.new_value_error( - "PyCF_ONLY_AST flag is not supported without parser support".to_string(), - )) - } -} - -fn builtin_delattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { - vm.del_attr(&obj, attr.into_object()) -} - -fn builtin_dir(obj: OptionalArg, vm: &VirtualMachine) -> PyResult { - let seq = match obj { - OptionalArg::Present(obj) => vm.call_method(&obj, "__dir__", vec![])?, - OptionalArg::Missing => vm.call_method(&vm.get_locals().into_object(), "keys", vec![])?, - }; - let sorted = builtin_sorted(vm, PyFuncArgs::new(vec![seq], vec![]))?; - Ok(sorted) -} - -fn builtin_divmod(a: PyObjectRef, b: PyObjectRef, vm: &VirtualMachine) -> PyResult { - vm.call_or_reflection( - a.clone(), - b.clone(), - "__divmod__", - "__rdivmod__", - |vm, a, b| Err(vm.new_unsupported_operand_error(a, b, "divmod")), - ) -} - -#[cfg(feature = "rustpython-compiler")] -#[derive(FromArgs)] -struct ScopeArgs { - #[pyarg(positional_or_keyword, default = "None")] - globals: Option, - // TODO: support any mapping for `locals` - #[pyarg(positional_or_keyword, default = "None")] - locals: Option, -} - -/// Implements `eval`. -/// See also: https://docs.python.org/3/library/functions.html#eval -#[cfg(feature = "rustpython-compiler")] -fn builtin_eval( - source: Either, - scope: ScopeArgs, - vm: &VirtualMachine, -) -> PyResult { - run_code(vm, source, scope, compile::Mode::Eval) -} - -/// Implements `exec` -/// https://docs.python.org/3/library/functions.html#exec -#[cfg(feature = "rustpython-compiler")] -fn builtin_exec( - source: Either, - scope: ScopeArgs, - vm: &VirtualMachine, -) -> PyResult { - run_code(vm, source, scope, compile::Mode::Exec) -} - -#[cfg(feature = "rustpython-compiler")] -fn run_code( - vm: &VirtualMachine, - source: Either, - scope: ScopeArgs, - mode: compile::Mode, -) -> PyResult { - let scope = make_scope(vm, scope)?; - - // Determine code object: - let code_obj = match source { - Either::A(string) => vm - .compile(string.as_str(), mode, "".to_owned()) - .map_err(|err| vm.new_syntax_error(&err))?, - Either::B(code_obj) => code_obj, - }; - - // Run the code: - vm.run_code_obj(code_obj, scope) -} - -#[cfg(feature = "rustpython-compiler")] -fn make_scope(vm: &VirtualMachine, scope: ScopeArgs) -> PyResult { - let globals = scope.globals; - let current_scope = vm.current_scope(); - let locals = match scope.locals { - Some(dict) => Some(dict), - None => { - if globals.is_some() { - None - } else { - current_scope.get_only_locals() - } - } - }; - let globals = match globals { - Some(dict) => { - if !dict.contains_key("__builtins__", vm) { - let builtins_dict = vm.builtins.dict().unwrap().as_object().clone(); - dict.set_item("__builtins__", builtins_dict, vm).unwrap(); - } - dict - } - None => current_scope.globals.clone(), - }; - - let scope = Scope::with_builtins(locals, globals, vm); - Ok(scope) -} - -fn builtin_format( - value: PyObjectRef, - format_spec: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - let format_spec = format_spec - .into_option() - .unwrap_or_else(|| PyString::from("").into_ref(vm)); - - vm.call_method(&value, "__format__", vec![format_spec.into_object()])? - .downcast() - .map_err(|obj| { - vm.new_type_error(format!( - "__format__ must return a str, not {}", - obj.class().name - )) - }) -} - -fn catch_attr_exception(ex: PyBaseExceptionRef, default: T, vm: &VirtualMachine) -> PyResult { - if objtype::isinstance(&ex, &vm.ctx.exceptions.attribute_error) { - Ok(default) - } else { - Err(ex) - } -} - -fn builtin_getattr( - obj: PyObjectRef, - attr: PyStringRef, - default: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - let ret = vm.get_attribute(obj.clone(), attr); - if let OptionalArg::Present(default) = default { - ret.or_else(|ex| catch_attr_exception(ex, default, vm)) - } else { - ret - } -} - -fn builtin_globals(vm: &VirtualMachine) -> PyResult { - Ok(vm.current_scope().globals.clone()) -} - -fn builtin_hasattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> PyResult { - if let Err(ex) = vm.get_attribute(obj.clone(), attr) { - catch_attr_exception(ex, false, vm) - } else { - Ok(true) - } -} - -fn builtin_hash(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - vm._hash(&obj) -} - -// builtin_help - -fn builtin_hex(number: PyIntRef, vm: &VirtualMachine) -> PyResult { - let n = number.as_bigint(); - let s = if n.is_negative() { - format!("-0x{:x}", -n) - } else { - format!("0x{:x}", n) - }; - - Ok(vm.new_str(s)) -} - -fn builtin_id(obj: PyObjectRef) -> usize { - obj.get_id() -} - -fn builtin_input(prompt: OptionalArg, vm: &VirtualMachine) -> PyResult { - let prompt = prompt.as_ref().map_or("", |s| s.as_str()); - let mut readline = Readline::new(()); - match readline.readline(prompt) { - ReadlineResult::Line(s) => Ok(s), - ReadlineResult::EOF => Err(vm.new_exception_empty(vm.ctx.exceptions.eof_error.clone())), - ReadlineResult::Interrupt => { - Err(vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.clone())) - } - ReadlineResult::IO(e) => Err(vm.new_os_error(e.to_string())), - ReadlineResult::EncodingError => { - Err(vm.new_unicode_decode_error("Error decoding readline input".to_owned())) - } - ReadlineResult::Other(e) => Err(vm.new_runtime_error(e.to_string())), - } -} - -pub fn builtin_isinstance( - obj: PyObjectRef, - typ: PyObjectRef, - vm: &VirtualMachine, -) -> PyResult { - single_or_tuple_any( - typ, - |cls: &PyClassRef| vm.isinstance(&obj, cls), - |o| { - format!( - "isinstance() arg 2 must be a type or tuple of types, not {}", - o.class() - ) - }, - vm, - ) -} - -fn builtin_issubclass( - subclass: PyClassRef, - typ: PyObjectRef, - vm: &VirtualMachine, -) -> PyResult { - single_or_tuple_any( - typ, - |cls: &PyClassRef| vm.issubclass(&subclass, cls), - |o| { - format!( - "issubclass() arg 2 must be a class or tuple of classes, not {}", - o.class() - ) - }, - vm, - ) -} - -fn builtin_iter( - iter_target: PyObjectRef, - sentinel: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - if let OptionalArg::Present(sentinel) = sentinel { - let callable = PyCallable::try_from_object(vm, iter_target)?; - Ok(objiter::PyCallableIterator::new(callable, sentinel) - .into_ref(vm) - .into_object()) - } else { - objiter::get_iter(vm, &iter_target) - } -} - -fn builtin_len(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - objsequence::len(&obj, vm) -} - -fn builtin_locals(vm: &VirtualMachine) -> PyDictRef { - let locals = vm.get_locals(); - locals.copy().into_ref(vm) -} - -fn builtin_max(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let candidates = match args.args.len().cmp(&1) { - std::cmp::Ordering::Greater => args.args.clone(), - std::cmp::Ordering::Equal => vm.extract_elements(&args.args[0])?, - std::cmp::Ordering::Less => { - // zero arguments means type error: - return Err(vm.new_type_error("Expected 1 or more arguments".to_owned())); - } - }; - - if candidates.is_empty() { - let default = args.get_optional_kwarg("default"); - return default - .ok_or_else(|| vm.new_value_error("max() arg is an empty sequence".to_owned())); - } - - let key_func = args.get_optional_kwarg("key"); - - // Start with first assumption: - let mut candidates_iter = candidates.into_iter(); - let mut x = candidates_iter.next().unwrap(); - // TODO: this key function looks pretty duplicate. Maybe we can create - // a local function? - let mut x_key = if let Some(ref f) = &key_func { - vm.invoke(f, vec![x.clone()])? - } else { - x.clone() - }; - - for y in candidates_iter { - let y_key = if let Some(ref f) = &key_func { - vm.invoke(f, vec![y.clone()])? - } else { - y.clone() - }; - let order = vm._gt(x_key.clone(), y_key.clone())?; - - if !objbool::get_value(&order) { - x = y.clone(); - x_key = y_key; - } - } - - Ok(x) -} - -fn builtin_min(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let candidates = match args.args.len().cmp(&1) { - std::cmp::Ordering::Greater => args.args.clone(), - std::cmp::Ordering::Equal => vm.extract_elements(&args.args[0])?, - std::cmp::Ordering::Less => { - // zero arguments means type error: - return Err(vm.new_type_error("Expected 1 or more arguments".to_owned())); - } - }; - - if candidates.is_empty() { - let default = args.get_optional_kwarg("default"); - return default - .ok_or_else(|| vm.new_value_error("min() arg is an empty sequence".to_owned())); - } - - let key_func = args.get_optional_kwarg("key"); - - let mut candidates_iter = candidates.into_iter(); - let mut x = candidates_iter.next().unwrap(); - // TODO: this key function looks pretty duplicate. Maybe we can create - // a local function? - let mut x_key = if let Some(ref f) = &key_func { - vm.invoke(f, vec![x.clone()])? - } else { - x.clone() - }; - - for y in candidates_iter { - let y_key = if let Some(ref f) = &key_func { - vm.invoke(f, vec![y.clone()])? - } else { - y.clone() - }; - let order = vm._gt(x_key.clone(), y_key.clone())?; - - if objbool::get_value(&order) { - x = y.clone(); - x_key = y_key; - } - } - - Ok(x) -} - -fn builtin_next( - iterator: PyObjectRef, - default_value: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - match vm.call_method(&iterator, "__next__", vec![]) { - Ok(value) => Ok(value), - Err(value) => { - if objtype::isinstance(&value, &vm.ctx.exceptions.stop_iteration) { - match default_value { - OptionalArg::Missing => Err(value), - OptionalArg::Present(value) => Ok(value.clone()), - } - } else { - Err(value) - } - } - } -} - -fn builtin_oct(number: PyIntRef, vm: &VirtualMachine) -> PyResult { - let n = number.as_bigint(); - let s = if n.is_negative() { - format!("-0o{:o}", n.abs()) - } else { - format!("0o{:o}", n) - }; - - Ok(vm.new_str(s)) -} - -fn builtin_ord(string: Either, vm: &VirtualMachine) -> PyResult { - match string { - Either::A(bytes) => { - let bytes_len = bytes.elements.len(); - if bytes_len != 1 { - return Err(vm.new_type_error(format!( - "ord() expected a character, but string of length {} found", - bytes_len - ))); - } - Ok(u32::from(bytes.elements[0])) - } - Either::B(string) => { - let string = string.as_str(); - let string_len = string.chars().count(); - if string_len != 1 { - return Err(vm.new_type_error(format!( - "ord() expected a character, but string of length {} found", - string_len - ))); - } - match string.chars().next() { - Some(character) => Ok(character as u32), - None => Err(vm.new_type_error( - "ord() could not guess the integer representing this character".to_owned(), - )), - } - } - } -} - -fn builtin_pow( - x: PyObjectRef, - y: PyObjectRef, - mod_value: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - match mod_value { - OptionalArg::Missing => { - vm.call_or_reflection(x.clone(), y.clone(), "__pow__", "__rpow__", |vm, x, y| { - Err(vm.new_unsupported_operand_error(x, y, "pow")) - }) - } - OptionalArg::Present(m) => { - // Check if the 3rd argument is defined and perform modulus on the result - if !(objtype::isinstance(&x, &vm.ctx.int_type()) - && objtype::isinstance(&y, &vm.ctx.int_type())) - { - return Err(vm.new_type_error( - "pow() 3rd argument not allowed unless all arguments are integers".to_owned(), - )); - } - let y = objint::get_value(&y); - if y.sign() == Sign::Minus { - return Err(vm.new_value_error( - "pow() 2nd argument cannot be negative when 3rd argument specified".to_owned(), - )); - } - let m = m.as_bigint(); - if m.is_zero() { - return Err(vm.new_value_error("pow() 3rd argument cannot be 0".to_owned())); - } - let x = objint::get_value(&x); - Ok(vm.new_int(x.modpow(&y, &m))) - } - } -} - -pub fn builtin_exit(exit_code_arg: OptionalArg, vm: &VirtualMachine) -> PyResult { - let code = exit_code_arg.unwrap_or_else(|| vm.new_int(0)); - Err(vm.new_exception(vm.ctx.exceptions.system_exit.clone(), vec![code])) -} - -#[derive(Debug, Default, FromArgs)] -pub struct PrintOptions { - #[pyarg(keyword_only, default = "None")] - sep: Option, - #[pyarg(keyword_only, default = "None")] - end: Option, - #[pyarg(keyword_only, default = "IntoPyBool::FALSE")] - flush: IntoPyBool, - #[pyarg(keyword_only, default = "None")] - file: Option, -} - -pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { - let file = match options.file { - Some(f) => f, - None => vm.get_attribute(vm.sys_module.clone(), "stdout")?, - }; - let write = |obj: PyStringRef| vm.call_method(&file, "write", vec![obj.into_object()]); - - let sep = options - .sep - .unwrap_or_else(|| PyString::from(" ").into_ref(vm)); - - let mut first = true; - for object in objects { - if first { - first = false; - } else { - write(sep.clone())?; - } - - write(vm.to_str(&object)?)?; - } - - let end = options - .end - .unwrap_or_else(|| PyString::from("\n").into_ref(vm)); - write(end)?; - - if options.flush.to_bool() { - vm.call_method(&file, "flush", vec![])?; - } - - Ok(()) -} - -fn builtin_repr(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - vm.to_repr(&obj) -} - -fn builtin_reversed(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if let Some(reversed_method) = vm.get_method(obj.clone(), "__reversed__") { - vm.invoke(&reversed_method?, PyFuncArgs::default()) - } else { - vm.get_method_or_type_error(obj.clone(), "__getitem__", || { - "argument to reversed() must be a sequence".to_owned() - })?; - let len = vm.call_method(&obj, "__len__", PyFuncArgs::default())?; - let len = objint::get_value(&len).to_isize().unwrap(); - let obj_iterator = objiter::PySequenceIterator::new_reversed(obj, len); - Ok(obj_iterator.into_ref(vm).into_object()) - } -} - -fn builtin_round( - number: PyObjectRef, - ndigits: OptionalArg>, - vm: &VirtualMachine, -) -> PyResult { - let rounded = match ndigits { - OptionalArg::Present(ndigits) => match ndigits { - Some(int) => { - let ndigits = vm.call_method(int.as_object(), "__int__", vec![])?; - vm.call_method(&number, "__round__", vec![ndigits])? - } - None => vm.call_method(&number, "__round__", vec![])?, - }, - OptionalArg::Missing => { - // without a parameter, the result type is coerced to int - vm.call_method(&number, "__round__", vec![])? - } - }; - Ok(rounded) -} - -fn builtin_setattr( - obj: PyObjectRef, - attr: PyStringRef, - value: PyObjectRef, - vm: &VirtualMachine, -) -> PyResult<()> { - vm.set_attr(&obj, attr.into_object(), value)?; - Ok(()) -} - -// builtin_slice - -fn builtin_sorted(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { - let iterable = args.take_positional(); - if iterable.is_none() { - return Err(vm.new_type_error("sorted expected 1 arguments, got 0".to_string())); - } - let items = vm.extract_elements(&iterable.unwrap())?; - let lst = vm.ctx.new_list(items); - - vm.call_method(&lst, "sort", args)?; - Ok(lst) -} - -fn builtin_sum(iterable: PyIterable, start: OptionalArg, vm: &VirtualMachine) -> PyResult { - // Start with zero and add at will: - let mut sum = start.into_option().unwrap_or_else(|| vm.ctx.new_int(0)); - for item in iterable.iter(vm)? { - sum = vm._add(sum, item?)?; - } - Ok(sum) -} - -// Should be renamed to builtin___import__? -fn builtin_import(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - vm.invoke(&vm.import_func, args) -} - -fn builtin_vars(obj: OptionalArg, vm: &VirtualMachine) -> PyResult { - if let OptionalArg::Present(obj) = obj { - vm.get_attribute(obj, "__dict__") - } else { - Ok(vm.get_locals().into_object()) - } -} - -// builtin_vars - pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { let ctx = &vm.ctx; - #[cfg(feature = "rustpython-compiler")] - { - extend_module!(vm, module, { - "eval" => ctx.new_function(builtin_eval), - "exec" => ctx.new_function(builtin_exec), - "compile" => ctx.new_function(builtin_compile), - }); - } + decl::extend_module(vm, &module); let debug_mode: bool = vm.state.settings.optimize == 0; extend_module!(vm, module, { @@ -769,72 +867,31 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { //set __name__ fixes: https://github.com/RustPython/RustPython/issues/146 "__name__" => ctx.new_str(String::from("__main__")), - "abs" => ctx.new_function(builtin_abs), - "all" => ctx.new_function(builtin_all), - "any" => ctx.new_function(builtin_any), - "ascii" => ctx.new_function(builtin_ascii), - "bin" => ctx.new_function(builtin_bin), "bool" => ctx.bool_type(), "bytearray" => ctx.bytearray_type(), "bytes" => ctx.bytes_type(), - "callable" => ctx.new_function(builtin_callable), - "chr" => ctx.new_function(builtin_chr), "classmethod" => ctx.classmethod_type(), "complex" => ctx.complex_type(), - "delattr" => ctx.new_function(builtin_delattr), "dict" => ctx.dict_type(), - "divmod" => ctx.new_function(builtin_divmod), - "dir" => ctx.new_function(builtin_dir), "enumerate" => ctx.enumerate_type(), "float" => ctx.float_type(), "frozenset" => ctx.frozenset_type(), "filter" => ctx.filter_type(), - "format" => ctx.new_function(builtin_format), - "getattr" => ctx.new_function(builtin_getattr), - "globals" => ctx.new_function(builtin_globals), - "hasattr" => ctx.new_function(builtin_hasattr), - "hash" => ctx.new_function(builtin_hash), - "hex" => ctx.new_function(builtin_hex), - "id" => ctx.new_function(builtin_id), - "input" => ctx.new_function(builtin_input), "int" => ctx.int_type(), - "isinstance" => ctx.new_function(builtin_isinstance), - "issubclass" => ctx.new_function(builtin_issubclass), - "iter" => ctx.new_function(builtin_iter), - "len" => ctx.new_function(builtin_len), "list" => ctx.list_type(), - "locals" => ctx.new_function(builtin_locals), "map" => ctx.map_type(), - "max" => ctx.new_function(builtin_max), "memoryview" => ctx.memoryview_type(), - "min" => ctx.new_function(builtin_min), "object" => ctx.object(), - "oct" => ctx.new_function(builtin_oct), - "ord" => ctx.new_function(builtin_ord), - "next" => ctx.new_function(builtin_next), - "pow" => ctx.new_function(builtin_pow), - "print" => ctx.new_function(builtin_print), "property" => ctx.property_type(), "range" => ctx.range_type(), - "repr" => ctx.new_function(builtin_repr), - "reversed" => ctx.new_function(builtin_reversed), - "round" => ctx.new_function(builtin_round), "set" => ctx.set_type(), - "setattr" => ctx.new_function(builtin_setattr), - "sorted" => ctx.new_function(builtin_sorted), "slice" => ctx.slice_type(), "staticmethod" => ctx.staticmethod_type(), "str" => ctx.str_type(), - "sum" => ctx.new_function(builtin_sum), "super" => ctx.super_type(), "tuple" => ctx.tuple_type(), "type" => ctx.type_type(), - "vars" => ctx.new_function(builtin_vars), "zip" => ctx.zip_type(), - "exit" => ctx.new_function(builtin_exit), - "quit" => ctx.new_function(builtin_exit), - "__import__" => ctx.new_function(builtin_import), - "__build_class__" => ctx.new_function(builtin_build_class_), // Constants "NotImplemented" => ctx.not_implemented(), @@ -913,59 +970,3 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "ResourceWarning" => ctx.exceptions.resource_warning.clone(), }); } - -pub fn builtin_build_class_( - function: PyFunctionRef, - qualified_name: PyStringRef, - bases: Args, - mut kwargs: KwArgs, - vm: &VirtualMachine, -) -> PyResult { - let name = qualified_name.as_str().split('.').next_back().unwrap(); - let name_obj = vm.new_str(name.to_owned()); - - let mut metaclass = if let Some(metaclass) = kwargs.pop_kwarg("metaclass") { - PyClassRef::try_from_object(vm, metaclass)? - } else { - vm.get_type() - }; - - for base in bases.clone() { - if objtype::issubclass(&base.class(), &metaclass) { - metaclass = base.class(); - } else if !objtype::issubclass(&metaclass, &base.class()) { - return Err(vm.new_type_error( - "metaclass conflict: the metaclass of a derived class must be a (non-strict) \ - subclass of the metaclasses of all its bases" - .to_owned(), - )); - } - } - - let bases = bases.into_tuple(vm); - - // Prepare uses full __getattribute__ resolution chain. - let prepare = vm.get_attribute(metaclass.clone().into_object(), "__prepare__")?; - let namespace = vm.invoke(&prepare, vec![name_obj.clone(), bases.clone()])?; - - let namespace: PyDictRef = TryFromObject::try_from_object(vm, namespace)?; - - let cells = vm.ctx.new_dict(); - - let scope = function - .scope() - .new_child_scope_with_locals(cells.clone()) - .new_child_scope_with_locals(namespace.clone()); - - function.invoke_with_scope(vec![].into(), &scope, vm)?; - - let class = vm.invoke( - metaclass.as_object(), - ( - Args::from(vec![name_obj, bases, namespace.into_object()]), - kwargs, - ), - )?; - cells.set_item("__class__", class.clone(), vm)?; - Ok(class) -} From e0eef3d93ccaf7582e845aa91ff47e3e149b28c7 Mon Sep 17 00:00:00 2001 From: Ben Lewis Date: Tue, 14 Jul 2020 20:20:27 +1200 Subject: [PATCH 3/4] Set name and module for functions declared with #[pyfunction]. --- derive/src/pymodule.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/derive/src/pymodule.rs b/derive/src/pymodule.rs index 1b1ee966c..7a2669647 100644 --- a/derive/src/pymodule.rs +++ b/derive/src/pymodule.rs @@ -115,7 +115,10 @@ impl Module { } } -fn extract_module_items(mut items: Vec) -> Result { +fn extract_module_items( + mut items: Vec, + module_name: &str, +) -> Result { let mut diagnostics: Vec = Vec::new(); let mut module = Module::default(); @@ -132,7 +135,10 @@ fn extract_module_items(mut items: Vec) -> Result { - let new_func = quote_spanned!(item_ident.span() => .new_function(#item_ident)); + let new_func = quote_spanned!(item_ident.span() => + .new_function_named(#item_ident, + #module_name.to_owned(), + #py_name.to_owned())); quote! { vm.__module_set_attr(&module, #py_name, vm.ctx#new_func).unwrap(); } @@ -183,7 +189,7 @@ pub fn impl_pymodule(attr: AttributeArgs, item: Item) -> Result Date: Tue, 14 Jul 2020 20:25:01 +1200 Subject: [PATCH 4/4] Mark failing test_fractions.py tests as expected failures. --- Lib/test/test_fractions.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 86b49f30f..63f66fc7c 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1,7 +1,7 @@ """Tests for Lib/fractions.py.""" from decimal import Decimal -from test.support import requires_IEEE_754 +# from test.support import requires_IEEE_754 import math import numbers import operator @@ -158,7 +158,7 @@ class FractionTest(unittest.TestCase): self.assertRaises(TypeError, F, 3, 1j) self.assertRaises(TypeError, F, 1, 2, 3) - @requires_IEEE_754 + # @requires_IEEE_754 def testInitFromFloat(self): self.assertEqual((5, 2), _components(F(2.5))) self.assertEqual((0, 1), _components(F(-0.0))) @@ -323,6 +323,8 @@ class FractionTest(unittest.TestCase): ValueError, "max_denominator should be at least 1", F(1).limit_denominator, i) + # TODO: RUSTPYTHON + @unittest.expectedFailure def testConversions(self): self.assertTypedEquals(-1, math.trunc(F(-11, 10))) self.assertTypedEquals(1, math.trunc(F(11, 10))) @@ -390,6 +392,8 @@ class FractionTest(unittest.TestCase): self.assertTypedEquals(F(-2, 10), round(F(-15, 100), 1)) self.assertTypedEquals(F(-2, 10), round(F(-25, 100), 1)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def testArithmetic(self): self.assertEqual(F(1, 2), F(1, 10) + F(2, 5)) self.assertEqual(F(-3, 10), F(1, 10) - F(2, 5)) @@ -456,6 +460,8 @@ class FractionTest(unittest.TestCase): divmod(F(-2**100, 3), F(5, 2**100)) ) + # TODO: RUSTPYTHON + @unittest.expectedFailure def testMixedArithmetic(self): self.assertTypedEquals(F(11, 10), F(1, 10) + 1) self.assertTypedEquals(1.1, F(1, 10) + 1.0) @@ -711,6 +717,8 @@ class FractionTest(unittest.TestCase): s += num / fact * sign self.assertAlmostEqual(math.cos(1), s) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_copy_deepcopy_pickle(self): r = F(13, 7) dr = DummyFraction(13, 7) @@ -722,6 +730,8 @@ class FractionTest(unittest.TestCase): self.assertTypedEquals(dr, copy(dr)) self.assertTypedEquals(dr, deepcopy(dr)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_slots(self): # Issue 4998 r = F(13, 7)