Merge branch 'master' into slice_new

# Conflicts:
#	tests/snippets/builtin_slice.py
#	vm/src/frame.rs
#	vm/src/obj/objrange.rs
#	vm/src/obj/objslice.rs
This commit is contained in:
ben committed 2019-04-17 19:44:46 +12:00
commit f2873a519d
130 files changed
+9497 -4309

No files matched your search

+1
View File
@@ -9,3 +9,4 @@ __pycache__
.vscode
wasm-pack.log
.idea/
tests/snippets/resources
Generated
+1
View File
@@ -809,6 +809,7 @@ dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+254
View File
@@ -0,0 +1,254 @@
A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
year, the PythonLabs team moved to Digital Creations, which became
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
https://www.python.org/psf/) was formed, a non-profit organization
created specifically to own Python-related Intellectual Property.
Zope Corporation was a sponsoring member of the PSF.
All Python releases are Open Source (see http://www.opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
Release Derived Year Owner GPL-
from compatible? (1)
0.9.0 thru 1.2 1991-1995 CWI yes
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
1.6 1.5.2 2000 CNRI no
2.0 1.6 2000 BeOpen.com no
1.6.1 1.6 2001 CNRI yes (2)
2.1 2.0+1.6.1 2001 PSF no
2.0.1 2.0+1.6.1 2001 PSF yes
2.1.1 2.1+2.0.1 2001 PSF yes
2.1.2 2.1.1 2002 PSF yes
2.1.3 2.1.2 2002 PSF yes
2.2 and above 2.1.1 2001-now PSF yes
Footnotes:
(1) GPL-compatible doesn't mean that we're distributing Python under
the GPL. All Python licenses, unlike the GPL, let you distribute
a modified version without making your changes open source. The
GPL-compatible licenses make it possible to combine Python with
other software that is released under the GPL; the others don't.
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
because its license has a choice of law clause. According to
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
is "not incompatible" with the GPL.
Thanks to the many outside volunteers who have worked under Guido's
direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All
Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
-------------------------------------------
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
Individual or Organization ("Licensee") accessing and otherwise using
this software in source or binary form and its associated
documentation ("the Software").
2. Subject to the terms and conditions of this BeOpen Python License
Agreement, BeOpen hereby grants Licensee a non-exclusive,
royalty-free, world-wide license to reproduce, analyze, test, perform
and/or display publicly, prepare derivative works, distribute, and
otherwise use the Software alone or in any derivative version,
provided, however, that the BeOpen Python License is retained in the
Software, alone or in any derivative version prepared by Licensee.
3. BeOpen is making the Software available to Licensee on an "AS IS"
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
5. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
6. This License Agreement shall be governed by and interpreted in all
respects by the law of the State of California, excluding conflict of
law provisions. Nothing in this License Agreement shall be deemed to
create any relationship of agency, partnership, or joint venture
between BeOpen and Licensee. This License Agreement does not grant
permission to use BeOpen trademarks or trade names in a trademark
sense to endorse or promote products or services of Licensee, or any
third party. As an exception, the "BeOpen Python" logos available at
http://www.pythonlabs.com/logos.html may be used according to the
permissions granted on that web page.
7. By copying, installing or otherwise using the software, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
---------------------------------------
1. This LICENSE AGREEMENT is between the Corporation for National
Research Initiatives, having an office at 1895 Preston White Drive,
Reston, VA 20191 ("CNRI"), and the Individual or Organization
("Licensee") accessing and otherwise using Python 1.6.1 software in
source or binary form and its associated documentation.
2. Subject to the terms and conditions of this License Agreement, CNRI
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 1.6.1
alone or in any derivative version, provided, however, that CNRI's
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
1995-2001 Corporation for National Research Initiatives; All Rights
Reserved" are retained in Python 1.6.1 alone or in any derivative
version prepared by Licensee. Alternately, in lieu of CNRI's License
Agreement, Licensee may substitute the following text (omitting the
quotes): "Python 1.6.1 is made available subject to the terms and
conditions in CNRI's License Agreement. This Agreement together with
Python 1.6.1 may be located on the Internet using the following
unique, persistent identifier (known as a handle): 1895.22/1013. This
Agreement may also be obtained from a proxy server on the Internet
using the following URL: http://hdl.handle.net/1895.22/1013".
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 1.6.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 1.6.1.
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. This License Agreement shall be governed by the federal
intellectual property law of the United States, including without
limitation the federal copyright law, and, to the extent such
U.S. federal law does not apply, by the law of the Commonwealth of
Virginia, excluding Virginia's conflict of law provisions.
Notwithstanding the foregoing, with regard to derivative works based
on Python 1.6.1 that incorporate non-separable material that was
previously distributed under the GNU General Public License (GPL), the
law of the Commonwealth of Virginia shall govern this License
Agreement only as to issues arising under or with respect to
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
License Agreement shall be deemed to create any relationship of
agency, partnership, or joint venture between CNRI and Licensee. This
License Agreement does not grant permission to use CNRI trademarks or
trade name in a trademark sense to endorse or promote products or
services of Licensee, or any third party.
8. By clicking on the "ACCEPT" button where indicated, or by copying,
installing or otherwise using Python 1.6.1, Licensee agrees to be
bound by the terms and conditions of this License Agreement.
ACCEPT
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
--------------------------------------------------
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
The Netherlands. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+10
View File
@@ -0,0 +1,10 @@
Standard Library for RustPython
===============================
This directory contains all of the Python files that make up the standard library for RustPython.
Most of these files are copied over from the CPython repository(the 3.7 branch), with slight modifications to allow them
to work under RustPython. The current goal is to complete the standard library with as few modifications as possible.
Current modifications are just temporary workarounds for bugs/missing feature within the RustPython implementation.
The first target is to run the ``unittest`` module, so we can leverage the CPython test suite.
File diff suppressed because it is too large. Load diff
+147
View File
@@ -0,0 +1,147 @@
from _weakrefset import WeakSet
def get_cache_token():
"""Returns the current ABC cache token.
The token is an opaque object (supporting equality testing) identifying the
current version of the ABC cache for virtual subclasses. The token changes
with every call to ``register()`` on any ABC.
"""
return ABCMeta._abc_invalidation_counter
class ABCMeta(type):
"""Metaclass for defining Abstract Base Classes (ABCs).
Use this metaclass to create an ABC. An ABC can be subclassed
directly, and then acts as a mix-in class. You can also register
unrelated concrete classes (even built-in classes) and unrelated
ABCs as 'virtual subclasses' -- these and their descendants will
be considered subclasses of the registering ABC by the built-in
issubclass() function, but the registering ABC won't show up in
their MRO (Method Resolution Order) nor will method
implementations defined by the registering ABC be callable (not
even via super()).
"""
# A global counter that is incremented each time a class is
# registered as a virtual subclass of anything. It forces the
# negative cache to be cleared before its next use.
# Note: this counter is private. Use `abc.get_cache_token()` for
# external code.
_abc_invalidation_counter = 0
def __new__(mcls, name, bases, namespace, **kwargs):
cls = type.__new__(mcls, name, bases, namespace, **kwargs)
# Compute set of abstract method names
abstracts = {name
for name, value in namespace.items()
if getattr(value, "__isabstractmethod__", False)}
for base in bases:
for name in getattr(base, "__abstractmethods__", set()):
value = getattr(cls, name, None)
if getattr(value, "__isabstractmethod__", False):
abstracts.add(name)
cls.__abstractmethods__ = set(abstracts)
# Set up inheritance registry
cls._abc_registry = WeakSet()
cls._abc_cache = WeakSet()
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
return cls
def register(cls, subclass):
"""Register a virtual subclass of an ABC.
Returns the subclass, to allow usage as a class decorator.
"""
if not isinstance(subclass, type):
raise TypeError("Can only register classes")
if issubclass(subclass, cls):
return subclass # Already a subclass
# Subtle: test for cycles *after* testing for "already a subclass";
# this means we allow X.register(X) and interpret it as a no-op.
if issubclass(cls, subclass):
# This would create a cycle, which is bad for the algorithm below
raise RuntimeError("Refusing to create an inheritance cycle")
cls._abc_registry.add(subclass)
ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache
return subclass
def _dump_registry(cls, file=None):
"""Debug helper to print the ABC registry."""
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
print(f"Inv. counter: {get_cache_token()}", file=file)
for name in cls.__dict__:
if name.startswith("_abc_"):
value = getattr(cls, name)
if isinstance(value, WeakSet):
value = set(value)
print(f"{name}: {value!r}", file=file)
def _abc_registry_clear(cls):
"""Clear the registry (for debugging or testing)."""
cls._abc_registry.clear()
def _abc_caches_clear(cls):
"""Clear the caches (for debugging or testing)."""
cls._abc_cache.clear()
cls._abc_negative_cache.clear()
def __instancecheck__(cls, instance):
"""Override for isinstance(instance, cls)."""
# Inline the cache checking
subclass = instance.__class__
if subclass in cls._abc_cache:
return True
subtype = type(instance)
if subtype is subclass:
if (cls._abc_negative_cache_version ==
ABCMeta._abc_invalidation_counter and
subclass in cls._abc_negative_cache):
return False
# Fall back to the subclass check.
return cls.__subclasscheck__(subclass)
return any(cls.__subclasscheck__(c) for c in (subclass, subtype))
def __subclasscheck__(cls, subclass):
"""Override for issubclass(subclass, cls)."""
if not isinstance(subclass, type):
raise TypeError('issubclass() arg 1 must be a class')
# Check cache
if subclass in cls._abc_cache:
return True
# Check negative cache; may have to invalidate
if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter:
# Invalidate the negative cache
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
elif subclass in cls._abc_negative_cache:
return False
# Check the subclass hook
ok = cls.__subclasshook__(subclass)
if ok is not NotImplemented:
assert isinstance(ok, bool)
if ok:
cls._abc_cache.add(subclass)
else:
cls._abc_negative_cache.add(subclass)
return ok
# Check if it's a direct subclass
if cls in getattr(subclass, '__mro__', ()):
cls._abc_cache.add(subclass)
return True
# Check if it's a subclass of a registered class (recursive)
for rcls in cls._abc_registry:
if issubclass(subclass, rcls):
cls._abc_cache.add(subclass)
return True
# Check if it's a subclass of a subclass (recursive)
for scls in cls.__subclasses__():
if issubclass(subclass, scls):
cls._abc_cache.add(subclass)
return True
# No dice; update negative cache
cls._abc_negative_cache.add(subclass)
return False
+196
View File
@@ -0,0 +1,196 @@
# Access WeakSet through the weakref module.
# This code is separated-out because it is needed
# by abc.py to load everything else at startup.
from _weakref import ref
__all__ = ['WeakSet']
class _IterationGuard:
# This context manager registers itself in the current iterators of the
# weak container, such as to delay all removals until the context manager
# exits.
# This technique should be relatively thread-safe (since sets are).
def __init__(self, weakcontainer):
# Don't create cycles
self.weakcontainer = ref(weakcontainer)
def __enter__(self):
w = self.weakcontainer()
if w is not None:
w._iterating.add(self)
return self
def __exit__(self, e, t, b):
w = self.weakcontainer()
if w is not None:
s = w._iterating
s.remove(self)
if not s:
w._commit_removals()
class WeakSet:
def __init__(self, data=None):
self.data = set()
def _remove(item, selfref=ref(self)):
self = selfref()
if self is not None:
if self._iterating:
self._pending_removals.append(item)
else:
self.data.discard(item)
self._remove = _remove
# A list of keys to be removed
self._pending_removals = []
self._iterating = set()
if data is not None:
self.update(data)
def _commit_removals(self):
l = self._pending_removals
discard = self.data.discard
while l:
discard(l.pop())
def __iter__(self):
with _IterationGuard(self):
for itemref in self.data:
item = itemref()
if item is not None:
# Caveat: the iterator will keep a strong reference to
# `item` until it is resumed or closed.
yield item
def __len__(self):
return len(self.data) - len(self._pending_removals)
def __contains__(self, item):
try:
wr = ref(item)
except TypeError:
return False
return wr in self.data
def __reduce__(self):
return (self.__class__, (list(self),),
getattr(self, '__dict__', None))
def add(self, item):
if self._pending_removals:
self._commit_removals()
self.data.add(ref(item, self._remove))
def clear(self):
if self._pending_removals:
self._commit_removals()
self.data.clear()
def copy(self):
return self.__class__(self)
def pop(self):
if self._pending_removals:
self._commit_removals()
while True:
try:
itemref = self.data.pop()
except KeyError:
raise KeyError('pop from empty WeakSet') from None
item = itemref()
if item is not None:
return item
def remove(self, item):
if self._pending_removals:
self._commit_removals()
self.data.remove(ref(item))
def discard(self, item):
if self._pending_removals:
self._commit_removals()
self.data.discard(ref(item))
def update(self, other):
if self._pending_removals:
self._commit_removals()
for element in other:
self.add(element)
def __ior__(self, other):
self.update(other)
return self
def difference(self, other):
newset = self.copy()
newset.difference_update(other)
return newset
__sub__ = difference
def difference_update(self, other):
self.__isub__(other)
def __isub__(self, other):
if self._pending_removals:
self._commit_removals()
if self is other:
self.data.clear()
else:
self.data.difference_update(ref(item) for item in other)
return self
def intersection(self, other):
return self.__class__(item for item in other if item in self)
__and__ = intersection
def intersection_update(self, other):
self.__iand__(other)
def __iand__(self, other):
if self._pending_removals:
self._commit_removals()
self.data.intersection_update(ref(item) for item in other)
return self
def issubset(self, other):
return self.data.issubset(ref(item) for item in other)
__le__ = issubset
def __lt__(self, other):
return self.data < set(map(ref, other))
def issuperset(self, other):
return self.data.issuperset(ref(item) for item in other)
__ge__ = issuperset
def __gt__(self, other):
return self.data > set(map(ref, other))
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self.data == set(map(ref, other))
def symmetric_difference(self, other):
newset = self.copy()
newset.symmetric_difference_update(other)
return newset
__xor__ = symmetric_difference
def symmetric_difference_update(self, other):
self.__ixor__(other)
def __ixor__(self, other):
if self._pending_removals:
self._commit_removals()
if self is other:
self.data.clear()
else:
self.data.symmetric_difference_update(ref(item, self._remove) for item in other)
return self
def union(self, other):
return self.__class__(e for s in (self, other) for e in s)
__or__ = union
def isdisjoint(self, other):
return len(self.intersection(other)) == 0
+170
View File
@@ -0,0 +1,170 @@
# Copyright 2007 Google, Inc. All Rights Reserved.
# Licensed to PSF under a Contributor Agreement.
"""Abstract Base Classes (ABCs) according to PEP 3119."""
def abstractmethod(funcobj):
"""A decorator indicating abstract methods.
Requires that the metaclass is ABCMeta or derived from it. A
class that has a metaclass derived from ABCMeta cannot be
instantiated unless all of its abstract methods are overridden.
The abstract methods can be called using any of the normal
'super' call mechanisms.
Usage:
class C(metaclass=ABCMeta):
@abstractmethod
def my_abstract_method(self, ...):
...
"""
funcobj.__isabstractmethod__ = True
return funcobj
class abstractclassmethod(classmethod):
"""A decorator indicating abstract classmethods.
Similar to abstractmethod.
Usage:
class C(metaclass=ABCMeta):
@abstractclassmethod
def my_abstract_classmethod(cls, ...):
...
'abstractclassmethod' is deprecated. Use 'classmethod' with
'abstractmethod' instead.
"""
__isabstractmethod__ = True
def __init__(self, callable):
callable.__isabstractmethod__ = True
super().__init__(callable)
class abstractstaticmethod(staticmethod):
"""A decorator indicating abstract staticmethods.
Similar to abstractmethod.
Usage:
class C(metaclass=ABCMeta):
@abstractstaticmethod
def my_abstract_staticmethod(...):
...
'abstractstaticmethod' is deprecated. Use 'staticmethod' with
'abstractmethod' instead.
"""
__isabstractmethod__ = True
def __init__(self, callable):
callable.__isabstractmethod__ = True
super().__init__(callable)
class abstractproperty(property):
"""A decorator indicating abstract properties.
Requires that the metaclass is ABCMeta or derived from it. A
class that has a metaclass derived from ABCMeta cannot be
instantiated unless all of its abstract properties are overridden.
The abstract properties can be called using any of the normal
'super' call mechanisms.
Usage:
class C(metaclass=ABCMeta):
@abstractproperty
def my_abstract_property(self):
...
This defines a read-only property; you can also define a read-write
abstract property using the 'long' form of property declaration:
class C(metaclass=ABCMeta):
def getx(self): ...
def setx(self, value): ...
x = abstractproperty(getx, setx)
'abstractproperty' is deprecated. Use 'property' with 'abstractmethod'
instead.
"""
__isabstractmethod__ = True
try:
from _abc import (get_cache_token, _abc_init, _abc_register,
_abc_instancecheck, _abc_subclasscheck, _get_dump,
_reset_registry, _reset_caches)
except ImportError:
from _py_abc import ABCMeta, get_cache_token
ABCMeta.__module__ = 'abc'
else:
class ABCMeta(type):
"""Metaclass for defining Abstract Base Classes (ABCs).
Use this metaclass to create an ABC. An ABC can be subclassed
directly, and then acts as a mix-in class. You can also register
unrelated concrete classes (even built-in classes) and unrelated
ABCs as 'virtual subclasses' -- these and their descendants will
be considered subclasses of the registering ABC by the built-in
issubclass() function, but the registering ABC won't show up in
their MRO (Method Resolution Order) nor will method
implementations defined by the registering ABC be callable (not
even via super()).
"""
def __new__(mcls, name, bases, namespace, **kwargs):
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
_abc_init(cls)
return cls
def register(cls, subclass):
"""Register a virtual subclass of an ABC.
Returns the subclass, to allow usage as a class decorator.
"""
return _abc_register(cls, subclass)
def __instancecheck__(cls, instance):
"""Override for isinstance(instance, cls)."""
return _abc_instancecheck(cls, instance)
def __subclasscheck__(cls, subclass):
"""Override for issubclass(subclass, cls)."""
return _abc_subclasscheck(cls, subclass)
def _dump_registry(cls, file=None):
"""Debug helper to print the ABC registry."""
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
print(f"Inv. counter: {get_cache_token()}", file=file)
(_abc_registry, _abc_cache, _abc_negative_cache,
_abc_negative_cache_version) = _get_dump(cls)
print(f"_abc_registry: {_abc_registry!r}", file=file)
print(f"_abc_cache: {_abc_cache!r}", file=file)
print(f"_abc_negative_cache: {_abc_negative_cache!r}", file=file)
print(f"_abc_negative_cache_version: {_abc_negative_cache_version!r}",
file=file)
def _abc_registry_clear(cls):
"""Clear the registry (for debugging or testing)."""
_reset_registry(cls)
def _abc_caches_clear(cls):
"""Clear the caches (for debugging or testing)."""
_reset_caches(cls)
class ABC(metaclass=ABCMeta):
"""Helper class that provides a standard way to create an ABC using
inheritance.
"""
__slots__ = ()
+122
View File
@@ -0,0 +1,122 @@
from _os import *
# Change environ to automatically call putenv(), unsetenv if they exist.
from _collections_abc import MutableMapping
class _Environ(MutableMapping):
def __init__(self, data, encodekey, decodekey, encodevalue, decodevalue, putenv, unsetenv):
self.encodekey = encodekey
self.decodekey = decodekey
self.encodevalue = encodevalue
self.decodevalue = decodevalue
self.putenv = putenv
self.unsetenv = unsetenv
self._data = data
def __getitem__(self, key):
try:
value = self._data[self.encodekey(key)]
except KeyError:
# raise KeyError with the original key value
# raise KeyError(key) from None
raise KeyError(key)
return self.decodevalue(value)
def __setitem__(self, key, value):
key = self.encodekey(key)
value = self.encodevalue(value)
self.putenv(key, value)
self._data[key] = value
def __delitem__(self, key):
encodedkey = self.encodekey(key)
self.unsetenv(encodedkey)
try:
del self._data[encodedkey]
except KeyError:
# raise KeyError with the original key value
# raise KeyError(key) from None
raise KeyError(key)
def __iter__(self):
# list() from dict object is an atomic operation
keys = list(self._data)
for key in keys:
yield self.decodekey(key)
def __len__(self):
return len(self._data)
def __repr__(self):
return 'environ({{{}}})'.format(', '.join(
('{!r}: {!r}'.format(self.decodekey(key), self.decodevalue(value))
for key, value in self._data.items())))
def copy(self):
return dict(self)
def setdefault(self, key, value):
if key not in self:
self[key] = value
return self[key]
try:
_putenv = putenv
except NameError:
_putenv = lambda key, value: None
# else:
# if "putenv" not in __all__:
# __all__.append("putenv")
try:
_unsetenv = unsetenv
except NameError:
_unsetenv = lambda key: _putenv(key, "")
# else:
# if "unsetenv" not in __all__:
# __all__.append("unsetenv")
def _createenviron():
# if name == 'nt':
# # Where Env Var Names Must Be UPPERCASE
# def check_str(value):
# if not isinstance(value, str):
# raise TypeError("str expected, not %s" % type(value).__name__)
# return value
# encode = check_str
# decode = str
# def encodekey(key):
# return encode(key).upper()
# data = {}
# for key, value in environ.items():
# data[encodekey(key)] = value
# else:
# # Where Env Var Names Can Be Mixed Case
# encoding = sys.getfilesystemencoding()
# def encode(value):
# if not isinstance(value, str):
# raise TypeError("str expected, not %s" % type(value).__name__)
# return value.encode(encoding, 'surrogateescape')
# def decode(value):
# return value.decode(encoding, 'surrogateescape')
# encodekey = encode
decode = str
encode = str
encodekey = encode
data = environ
return _Environ(data,
encodekey, decode,
encode, decode,
_putenv, _unsetenv)
# unicode environ
environ = _createenviron()
del _createenviron
def getenv(key, default=None):
"""Get an environment variable, return None if it doesn't exist.
The optional second argument can specify an alternate default.
key, default and the result are str."""
return environ.get(key, default)
-11
View File
@@ -1,11 +0,0 @@
""" Regular expressions """
def match(pattern, string, flags=0):
return _compile(pattern, flags).match(string)
def _compile(pattern, flags):
p = sre_compile.compile(pattern, flags)
return p
+1 -1
View File
@@ -8,6 +8,6 @@ edition = "2018"
proc-macro = true
[dependencies]
syn = "0.15.29"
syn = { version = "0.15.29", features = ["full"] }
quote = "0.6.11"
proc-macro2 = "0.4.27"
+200
View File
@@ -0,0 +1,200 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Attribute, Data, DeriveInput, Expr, Field, Fields, Ident, Lit, Meta, NestedMeta};
/// The kind of the python parameter, this corresponds to the value of Parameter.kind
/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind)
enum ParameterKind {
PositionalOnly,
PositionalOrKeyword,
KeywordOnly,
}
impl ParameterKind {
fn from_ident(ident: &Ident) -> ParameterKind {
if ident == "positional_only" {
ParameterKind::PositionalOnly
} else if ident == "positional_or_keyword" {
ParameterKind::PositionalOrKeyword
} else if ident == "keyword_only" {
ParameterKind::KeywordOnly
} else {
panic!("Unrecognised attribute")
}
}
}
struct ArgAttribute {
kind: ParameterKind,
default: Option<Expr>,
optional: bool,
}
impl ArgAttribute {
fn from_attribute(attr: &Attribute) -> Option<ArgAttribute> {
if !attr.path.is_ident("pyarg") {
return None;
}
match attr.parse_meta().unwrap() {
Meta::List(list) => {
let mut iter = list.nested.iter();
let first_arg = iter.next().expect("at least one argument in pyarg list");
let kind = match first_arg {
NestedMeta::Meta(Meta::Word(ident)) => ParameterKind::from_ident(ident),
_ => panic!("Bad syntax for first pyarg attribute argument"),
};
let mut attribute = ArgAttribute {
kind,
default: None,
optional: false,
};
while let Some(arg) = iter.next() {
attribute.parse_argument(arg);
}
assert!(
attribute.default.is_none() || !attribute.optional,
"Can't set both a default value and optional"
);
Some(attribute)
}
_ => panic!("Bad syntax for pyarg attribute"),
}
}
fn parse_argument(&mut self, arg: &NestedMeta) {
match arg {
NestedMeta::Meta(Meta::Word(ident)) => {
if ident == "default" {
assert!(self.default.is_none(), "Default already set");
let expr = syn::parse_str::<Expr>("Default::default()").unwrap();
self.default = Some(expr);
} else if ident == "optional" {
self.optional = true;
} else {
panic!("Unrecognised pyarg attribute '{}'", ident);
}
}
NestedMeta::Meta(Meta::NameValue(name_value)) => {
if name_value.ident == "default" {
assert!(self.default.is_none(), "Default already set");
match name_value.lit {
Lit::Str(ref val) => {
let expr = val
.parse::<Expr>()
.expect("a valid expression for default argument");
self.default = Some(expr);
}
_ => panic!("Expected string value for default argument"),
}
} else if name_value.ident == "optional" {
match name_value.lit {
Lit::Bool(ref val) => {
self.optional = val.value;
}
_ => panic!("Expected boolean value for optional argument"),
}
} else {
panic!("Unrecognised pyarg attribute '{}'", name_value.ident);
}
}
_ => panic!("Bad syntax for first pyarg attribute argument"),
};
}
}
fn generate_field(field: &Field) -> TokenStream2 {
let mut pyarg_attrs = field
.attrs
.iter()
.filter_map(ArgAttribute::from_attribute)
.collect::<Vec<_>>();
let attr = if pyarg_attrs.is_empty() {
ArgAttribute {
kind: ParameterKind::PositionalOrKeyword,
default: None,
optional: false,
}
} else if pyarg_attrs.len() == 1 {
pyarg_attrs.remove(0)
} else {
panic!(
"Multiple pyarg attributes on field '{}'",
field.ident.as_ref().unwrap()
);
};
let name = &field.ident;
let middle = quote! {
.map(|x| ::rustpython_vm::pyobject::TryFromObject::try_from_object(vm, x)).transpose()?
};
let ending = if let Some(default) = attr.default {
quote! {
.unwrap_or_else(|| #default)
}
} else if attr.optional {
quote! {
.map(::rustpython_vm::function::OptionalArg::Present)
.unwrap_or(::rustpython_vm::function::OptionalArg::Missing)
}
} else {
let err = match attr.kind {
ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! {
::rustpython_vm::function::ArgumentError::TooFewArgs
},
ParameterKind::KeywordOnly => quote! {
::rustpython_vm::function::ArgumentError::RequiredKeywordArgument(tringify!(#name))
},
};
quote! {
.ok_or_else(|| #err)?
}
};
match attr.kind {
ParameterKind::PositionalOnly => {
quote! {
#name: args.take_positional()#middle#ending,
}
}
ParameterKind::PositionalOrKeyword => {
quote! {
#name: args.take_positional_keyword(stringify!(#name))#middle#ending,
}
}
ParameterKind::KeywordOnly => {
quote! {
#name: args.take_keyword(stringify!(#name))#middle#ending,
}
}
}
}
pub fn impl_from_args(input: DeriveInput) -> TokenStream2 {
let fields = match input.data {
Data::Struct(ref data) => {
match data.fields {
Fields::Named(ref fields) => fields.named.iter().map(generate_field),
Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(), // TODO: better error message
};
let name = &input.ident;
quote! {
impl ::rustpython_vm::function::FromArgs for #name {
fn from_args(
vm: &::rustpython_vm::VirtualMachine,
args: &mut ::rustpython_vm::function::PyFuncArgs
) -> Result<Self, ::rustpython_vm::function::ArgumentError> {
Ok(#name { #(#fields)* })
}
}
}
}
+15 -202
View File
@@ -1,215 +1,28 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Attribute, Data, DeriveInput, Expr, Field, Fields, Ident, Lit, Meta, NestedMeta};
use syn::{parse_macro_input, AttributeArgs, DeriveInput, Item};
mod from_args;
mod pyclass;
#[proc_macro_derive(FromArgs, attributes(pyarg))]
pub fn derive_from_args(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let gen = impl_from_args(&ast);
gen.to_string().parse().unwrap()
from_args::impl_from_args(ast).into()
}
/// The kind of the python parameter, this corresponds to the value of Parameter.kind
/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind)
enum ParameterKind {
PositionalOnly,
PositionalOrKeyword,
KeywordOnly,
#[proc_macro_attribute]
pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = parse_macro_input!(attr as AttributeArgs);
let item = parse_macro_input!(item as Item);
pyclass::impl_pyclass(attr, item).into()
}
impl ParameterKind {
fn from_ident(ident: &Ident) -> ParameterKind {
if ident == "positional_only" {
ParameterKind::PositionalOnly
} else if ident == "positional_or_keyword" {
ParameterKind::PositionalOrKeyword
} else if ident == "keyword_only" {
ParameterKind::KeywordOnly
} else {
panic!("Unrecognised attribute")
}
}
}
struct ArgAttribute {
kind: ParameterKind,
default: Option<Expr>,
optional: bool,
}
impl ArgAttribute {
fn from_attribute(attr: &Attribute) -> Option<ArgAttribute> {
if !attr.path.is_ident("pyarg") {
return None;
}
match attr.parse_meta().unwrap() {
Meta::List(list) => {
let mut iter = list.nested.iter();
let first_arg = iter.next().expect("at least one argument in pyarg list");
let kind = match first_arg {
NestedMeta::Meta(Meta::Word(ident)) => ParameterKind::from_ident(ident),
_ => panic!("Bad syntax for first pyarg attribute argument"),
};
let mut attribute = ArgAttribute {
kind,
default: None,
optional: false,
};
while let Some(arg) = iter.next() {
attribute.parse_argument(arg);
}
assert!(
attribute.default.is_none() || !attribute.optional,
"Can't set both a default value and optional"
);
Some(attribute)
}
_ => panic!("Bad syntax for pyarg attribute"),
}
}
fn parse_argument(&mut self, arg: &NestedMeta) {
match arg {
NestedMeta::Meta(Meta::Word(ident)) => {
if ident == "default" {
assert!(self.default.is_none(), "Default already set");
let expr = syn::parse_str::<Expr>("Default::default()").unwrap();
self.default = Some(expr);
} else if ident == "optional" {
self.optional = true;
} else {
panic!("Unrecognised pyarg attribute '{}'", ident);
}
}
NestedMeta::Meta(Meta::NameValue(name_value)) => {
if name_value.ident == "default" {
assert!(self.default.is_none(), "Default already set");
match name_value.lit {
Lit::Str(ref val) => {
let expr = val
.parse::<Expr>()
.expect("a valid expression for default argument");
self.default = Some(expr);
}
_ => panic!("Expected string value for default argument"),
}
} else if name_value.ident == "optional" {
match name_value.lit {
Lit::Bool(ref val) => {
self.optional = val.value;
}
_ => panic!("Expected boolean value for optional argument"),
}
} else {
panic!("Unrecognised pyarg attribute '{}'", name_value.ident);
}
}
_ => panic!("Bad syntax for first pyarg attribute argument"),
};
}
}
fn generate_field(field: &Field) -> TokenStream2 {
let mut pyarg_attrs = field
.attrs
.iter()
.filter_map(ArgAttribute::from_attribute)
.collect::<Vec<_>>();
let attr = if pyarg_attrs.is_empty() {
ArgAttribute {
kind: ParameterKind::PositionalOrKeyword,
default: None,
optional: false,
}
} else if pyarg_attrs.len() == 1 {
pyarg_attrs.remove(0)
} else {
panic!(
"Multiple pyarg attributes on field '{}'",
field.ident.as_ref().unwrap()
);
};
let name = &field.ident;
let middle = quote! {
.map(|x| crate::pyobject::TryFromObject::try_from_object(vm, x)).transpose()?
};
let ending = if let Some(default) = attr.default {
quote! {
.unwrap_or_else(|| #default)
}
} else if attr.optional {
quote! {
.map(crate::function::OptionalArg::Present)
.unwrap_or(crate::function::OptionalArg::Missing)
}
} else {
let err = match attr.kind {
ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! {
crate::function::ArgumentError::TooFewArgs
},
ParameterKind::KeywordOnly => quote! {
crate::function::ArgumentError::RequiredKeywordArgument(tringify!(#name))
},
};
quote! {
.ok_or_else(|| #err)?
}
};
match attr.kind {
ParameterKind::PositionalOnly => {
quote! {
#name: args.take_positional()#middle#ending,
}
}
ParameterKind::PositionalOrKeyword => {
quote! {
#name: args.take_positional_keyword(stringify!(#name))#middle#ending,
}
}
ParameterKind::KeywordOnly => {
quote! {
#name: args.take_keyword(stringify!(#name))#middle#ending,
}
}
}
}
fn impl_from_args(input: &DeriveInput) -> TokenStream2 {
// FIXME: This references types using `crate` instead of `rustpython_vm`
// so that it can be used in the latter. How can we support both?
// Can use extern crate self as rustpython_vm; once in stable.
// https://github.com/rust-lang/rust/issues/56409
let fields = match input.data {
Data::Struct(ref data) => {
match data.fields {
Fields::Named(ref fields) => fields.named.iter().map(generate_field),
Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(), // TODO: better error message
};
let name = &input.ident;
quote! {
impl crate::function::FromArgs for #name {
fn from_args(
vm: &crate::vm::VirtualMachine,
args: &mut crate::function::PyFuncArgs
) -> Result<Self, crate::function::ArgumentError> {
Ok(#name { #(#fields)* })
}
}
}
#[proc_macro_attribute]
pub fn pyimpl(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = parse_macro_input!(attr as AttributeArgs);
let item = parse_macro_input!(item as Item);
pyclass::impl_pyimpl(attr, item).into()
}
+276
View File
@@ -0,0 +1,276 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::collections::HashMap;
use syn::{Attribute, AttributeArgs, Ident, ImplItem, Item, Lit, Meta, MethodSig, NestedMeta};
enum ClassItem {
Method {
item_ident: Ident,
py_name: String,
},
Property {
item_ident: Ident,
py_name: String,
setter: bool,
},
}
fn meta_to_vec(meta: Meta) -> Option<Vec<NestedMeta>> {
match meta {
Meta::Word(_) => Some(Vec::new()),
Meta::List(list) => Some(list.nested.into_iter().collect()),
Meta::NameValue(_) => None,
}
}
impl ClassItem {
fn extract_from_syn(attrs: &mut Vec<Attribute>, sig: &MethodSig) -> Option<ClassItem> {
let mut item = None;
let mut attr_idx = None;
// TODO: better error handling throughout this
for (i, meta) in attrs
.iter()
.filter_map(|attr| attr.parse_meta().ok())
.enumerate()
{
let name = meta.name();
if name == "pymethod" {
if item.is_some() {
panic!("You can only have one #[py*] attribute on an impl item")
}
let nesteds = meta_to_vec(meta).expect(
"#[pyproperty = \"...\"] cannot be a name/value, you probably meant \
#[pyproperty(name = \"...\")]",
);
let mut py_name = None;
for meta in nesteds {
let meta = match meta {
NestedMeta::Meta(meta) => meta,
NestedMeta::Literal(_) => continue,
};
match meta {
Meta::NameValue(name_value) => {
if name_value.ident == "name" {
if let Lit::Str(s) = &name_value.lit {
py_name = Some(s.value());
} else {
panic!("#[pymethod(name = ...)] must be a string");
}
}
}
_ => {}
}
}
item = Some(ClassItem::Method {
item_ident: sig.ident.clone(),
py_name: py_name.unwrap_or_else(|| sig.ident.to_string()),
});
attr_idx = Some(i);
} else if name == "pyproperty" {
if item.is_some() {
panic!("You can only have one #[py*] attribute on an impl item")
}
let nesteds = meta_to_vec(meta).expect(
"#[pyproperty = \"...\"] cannot be a name/value, you probably meant \
#[pyproperty(name = \"...\")]",
);
let mut setter = false;
let mut py_name = None;
for meta in nesteds {
let meta = match meta {
NestedMeta::Meta(meta) => meta,
NestedMeta::Literal(_) => continue,
};
match meta {
Meta::NameValue(name_value) => {
if name_value.ident == "name" {
if let Lit::Str(s) = &name_value.lit {
py_name = Some(s.value());
} else {
panic!("#[pyproperty(name = ...)] must be a string");
}
}
}
Meta::Word(ident) => {
if ident == "setter" {
setter = true;
}
}
_ => {}
}
}
let py_name = py_name.unwrap_or_else(|| {
let item_ident = sig.ident.to_string();
if setter {
if item_ident.starts_with("set_") {
let name = &item_ident["set_".len()..];
if name.is_empty() {
panic!(
"A #[pyproperty(setter)] fn with a set_* name have something \
after \"set_\""
)
} else {
name.to_string()
}
} else {
panic!(
"A #[pyproperty(setter)] fn must either have a `name` parameter or a \
fn name along the lines of \"set_*\""
)
}
} else {
item_ident
}
});
item = Some(ClassItem::Property {
py_name,
item_ident: sig.ident.clone(),
setter,
});
attr_idx = Some(i);
}
}
if let Some(attr_idx) = attr_idx {
attrs.remove(attr_idx);
}
item
}
}
pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> TokenStream2 {
let mut imp = if let Item::Impl(imp) = item {
imp
} else {
return quote!(#item);
};
let items = imp
.items
.iter_mut()
.filter_map(|item| {
if let ImplItem::Method(meth) = item {
ClassItem::extract_from_syn(&mut meth.attrs, &meth.sig)
} else {
None
}
})
.collect::<Vec<_>>();
let ty = &imp.self_ty;
let mut properties: HashMap<&str, (Option<&Ident>, Option<&Ident>)> = HashMap::new();
for item in items.iter() {
match item {
ClassItem::Property {
item_ident,
py_name,
setter,
} => {
let entry = properties.entry(py_name).or_default();
let func = if *setter { &mut entry.1 } else { &mut entry.0 };
if func.is_some() {
panic!("Multiple property accessors with name {:?}", py_name)
}
*func = Some(item_ident);
}
_ => {}
}
}
let methods = items.iter().filter_map(|item| {
if let ClassItem::Method {
item_ident,
py_name,
} = item
{
Some(quote! {
class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_ident));
})
} else {
None
}
});
let properties = properties.iter().map(|(name, prop)| {
let getter = match prop.0 {
Some(getter) => getter,
None => panic!("Property {:?} is missing a getter", name),
};
let add_setter = prop.1.map(|setter| quote!(.add_setter(Self::#setter)));
quote! {
class.set_str_attr(
#name,
::rustpython_vm::obj::objproperty::PropertyBuilder::new(ctx)
.add_getter(Self::#getter)
#add_setter
.create(),
);
}
});
quote! {
#imp
impl ::rustpython_vm::pyobject::PyClassImpl for #ty {
fn impl_extend_class(
ctx: &::rustpython_vm::pyobject::PyContext,
class: &::rustpython_vm::obj::objtype::PyClassRef,
) {
#(#methods)*
#(#properties)*
}
}
}
}
pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 {
let (item, ident, attrs) = match item {
Item::Struct(struc) => (quote!(#struc), struc.ident, struc.attrs),
Item::Enum(enu) => (quote!(#enu), enu.ident, enu.attrs),
_ => panic!("#[pyclass] can only be on a struct or enum declaration"),
};
let mut class_name = None;
for attr in attr {
if let NestedMeta::Meta(meta) = attr {
if let Meta::NameValue(name_value) = meta {
if name_value.ident == "name" {
if let Lit::Str(s) = name_value.lit {
class_name = Some(s.value());
} else {
panic!("#[pyclass(name = ...)] must be a string");
}
}
}
}
}
let class_name = class_name.unwrap_or_else(|| ident.to_string());
let mut doc: Option<Vec<String>> = None;
for attr in attrs.iter() {
if attr.path.is_ident("doc") {
let meta = attr.parse_meta().expect("expected doc attr to be a meta");
if let Meta::NameValue(name_value) = meta {
if let Lit::Str(s) = name_value.lit {
let val = s.value().trim().to_string();
match doc {
Some(ref mut doc) => doc.push(val),
None => doc = Some(vec![val]),
}
} else {
panic!("expected #[doc = ...] to be a string")
}
}
}
}
let doc = match doc {
Some(doc) => {
let doc = doc.join("\n");
quote!(Some(#doc))
}
None => quote!(None),
};
quote! {
#item
impl ::rustpython_vm::pyobject::PyClassDef for #ident {
const NAME: &'static str = #class_name;
const DOC: Option<&'static str> = #doc;
}
}
}
-4
View File
@@ -1,4 +0,0 @@
src/python.rs
target/
Cargo.lock
+1 -4
View File
@@ -1,8 +1,5 @@
use lalrpop;
fn main() {
lalrpop::Configuration::new()
.generate_in_source_tree()
.process()
.unwrap();
lalrpop::process_root().unwrap()
}
+5 -8
View File
@@ -16,7 +16,7 @@ pub struct Node {
#[derive(Debug, PartialEq)]
pub enum Top {
Program(Program),
Statement(LocatedStatement),
Statement(Vec<LocatedStatement>),
Expression(Expression),
}
@@ -47,7 +47,7 @@ pub enum Statement {
Break,
Continue,
Return {
value: Option<Vec<Expression>>,
value: Option<Box<Expression>>,
},
Import {
import_parts: Vec<SingleImport>,
@@ -94,7 +94,7 @@ pub enum Statement {
},
For {
target: Expression,
iter: Vec<Expression>,
iter: Expression,
body: Vec<LocatedStatement>,
orelse: Option<Vec<LocatedStatement>>,
},
@@ -114,12 +114,10 @@ pub enum Statement {
bases: Vec<Expression>,
keywords: Vec<Keyword>,
decorator_list: Vec<Expression>,
// TODO: docstring: String,
},
FunctionDef {
name: String,
args: Parameters,
// docstring: String,
body: Vec<LocatedStatement>,
decorator_list: Vec<Expression>,
returns: Option<Expression>,
@@ -159,9 +157,8 @@ pub enum Expression {
value: Box<Expression>,
},
Compare {
a: Box<Expression>,
op: Comparison,
b: Box<Expression>,
vals: Vec<Expression>,
ops: Vec<Comparison>,
},
Attribute {
value: Box<Expression>,
+38 -15
View File
@@ -466,8 +466,16 @@ where
loop {
match self.next_char() {
Some('\\') => {
if is_raw {
if self.chr0 == Some(quote_char) {
string_content.push(quote_char);
self.next_char();
} else if is_raw {
string_content.push('\\');
if let Some(c) = self.next_char() {
string_content.push(c)
} else {
return Err(LexicalError::StringError);
}
} else {
match self.next_char() {
Some('\\') => {
@@ -532,8 +540,12 @@ where
let end_pos = self.get_pos();
let tok = if is_bytes {
Tok::Bytes {
value: string_content.as_bytes().to_vec(),
if string_content.is_ascii() {
Tok::Bytes {
value: string_content.as_bytes().to_vec(),
}
} else {
return Err(LexicalError::StringError);
}
} else {
Tok::String {
@@ -711,7 +723,6 @@ where
let tok_start = self.get_pos();
self.next_char();
let tok_end = self.get_pos();
println!("Emoji: {}", c);
return Some(Ok((
tok_start,
Tok::Name {
@@ -815,7 +826,11 @@ where
}
_ => {
let tok_end = self.get_pos();
return Some(Ok((tok_start, Tok::DoubleSlash, tok_end)));
return Some(Ok((
tok_start,
Tok::DoubleSlash,
tok_end,
)));
}
}
}
@@ -1037,16 +1052,20 @@ where
return Some(Ok((tok_start, Tok::Comma, tok_end)));
}
'.' => {
let tok_start = self.get_pos();
self.next_char();
if let (Some('.'), Some('.')) = (&self.chr0, &self.chr1) {
self.next_char();
self.next_char();
let tok_end = self.get_pos();
return Some(Ok((tok_start, Tok::Ellipsis, tok_end)));
if let Some('0'..='9') = self.chr1 {
return Some(self.lex_number());
} else {
let tok_end = self.get_pos();
return Some(Ok((tok_start, Tok::Dot, tok_end)));
let tok_start = self.get_pos();
self.next_char();
if let (Some('.'), Some('.')) = (&self.chr0, &self.chr1) {
self.next_char();
self.next_char();
let tok_end = self.get_pos();
return Some(Ok((tok_start, Tok::Ellipsis, tok_end)));
} else {
let tok_end = self.get_pos();
return Some(Ok((tok_start, Tok::Dot, tok_end)));
}
}
}
'\n' => {
@@ -1438,7 +1457,7 @@ mod tests {
#[test]
fn test_string() {
let source = String::from(r#""double" 'single' 'can\'t' "\\\"" '\t\r\n' '\g'"#);
let source = String::from(r#""double" 'single' 'can\'t' "\\\"" '\t\r\n' '\g' r'raw\''"#);
let tokens = lex_source(&source);
assert_eq!(
tokens,
@@ -1467,6 +1486,10 @@ mod tests {
value: String::from("\\g"),
is_fstring: false,
},
Tok::String {
value: String::from("raw\'"),
is_fstring: false,
},
]
);
}
+5 -2
View File
@@ -1,11 +1,14 @@
#[macro_use]
extern crate log;
use lalrpop_util::lalrpop_mod;
pub mod ast;
pub mod error;
mod fstring;
pub mod lexer;
pub mod parser;
#[cfg_attr(rustfmt, rustfmt_skip)]
mod python;
lalrpop_mod!(
#[allow(clippy::all)]
python
);
pub mod token;
+29 -25
View File
@@ -35,7 +35,7 @@ pub fn parse_program(source: &str) -> Result<ast::Program, ParseError> {
do_lalr_parsing!(source, Program, StartProgram)
}
pub fn parse_statement(source: &str) -> Result<ast::LocatedStatement, ParseError> {
pub fn parse_statement(source: &str) -> Result<Vec<ast::LocatedStatement>, ParseError> {
do_lalr_parsing!(source, Statement, StartStatement)
}
@@ -180,7 +180,7 @@ mod tests {
let parse_ast = parse_statement(&source).unwrap();
assert_eq!(
parse_ast,
ast::LocatedStatement {
vec![ast::LocatedStatement {
location: ast::Location::new(1, 1),
node: ast::Statement::If {
test: ast::Expression::Number {
@@ -229,7 +229,7 @@ mod tests {
}
},]),
}
}
}]
);
}
@@ -239,7 +239,7 @@ mod tests {
let parse_ast = parse_statement(&source);
assert_eq!(
parse_ast,
Ok(ast::LocatedStatement {
Ok(vec![ast::LocatedStatement {
location: ast::Location::new(1, 1),
node: ast::Statement::Expression {
expression: ast::Expression::Lambda {
@@ -271,7 +271,7 @@ mod tests {
})
}
}
})
}])
)
}
@@ -281,7 +281,7 @@ mod tests {
assert_eq!(
parse_statement(&source),
Ok(ast::LocatedStatement {
Ok(vec![ast::LocatedStatement {
location: ast::Location::new(1, 1),
node: ast::Statement::Assign {
targets: vec![ast::Expression::Tuple {
@@ -309,7 +309,7 @@ mod tests {
]
}
}
})
}])
)
}
@@ -320,7 +320,7 @@ mod tests {
);
assert_eq!(
parse_statement(&source),
Ok(ast::LocatedStatement {
Ok(vec![ast::LocatedStatement {
location: ast::Location::new(1, 1),
node: ast::Statement::ClassDef {
name: String::from("Foo"),
@@ -393,7 +393,7 @@ mod tests {
],
decorator_list: vec![],
}
})
}])
)
}
@@ -460,26 +460,30 @@ mod tests {
},
ifs: vec![
ast::Expression::Compare {
a: Box::new(ast::Expression::Identifier {
name: "a".to_string()
}),
op: ast::Comparison::Less,
b: Box::new(ast::Expression::Number {
value: ast::Number::Integer {
value: BigInt::from(5)
vals: vec![
ast::Expression::Identifier {
name: "a".to_string()
},
ast::Expression::Number {
value: ast::Number::Integer {
value: BigInt::from(5)
}
}
}),
],
ops: vec![ast::Comparison::Less],
},
ast::Expression::Compare {
a: Box::new(ast::Expression::Identifier {
name: "a".to_string()
}),
op: ast::Comparison::Greater,
b: Box::new(ast::Expression::Number {
value: ast::Number::Integer {
value: BigInt::from(10)
vals: vec![
ast::Expression::Identifier {
name: "a".to_string()
},
ast::Expression::Number {
value: ast::Number::Integer {
value: BigInt::from(10)
}
}
}),
],
ops: vec![ast::Comparison::Greater],
},
],
}
+130 -200
View File
@@ -2,7 +2,6 @@
// See also: https://github.com/antlr/grammars-v4/blob/master/python3/Python3.g4
// See also: file:///usr/share/doc/python/html/reference/compound_stmts.html#function-definitions
// See also: https://greentreesnakes.readthedocs.io/en/latest/nodes.html#keyword
#![allow(unknown_lints,clippy)]
use std::iter::FromIterator;
@@ -25,29 +24,32 @@ pub Top: ast::Top = {
Program: ast::Program = {
<lines:FileLine*> => ast::Program {
statements: Vec::from_iter(lines.into_iter().filter_map(|e| e))
statements: Vec::from_iter(lines.into_iter().flatten())
},
};
// A file line either has a declaration, or an empty newline:
FileLine: Option<ast::LocatedStatement> = {
<s:Statement> => Some(s),
"\n" => None,
FileLine: Vec<ast::LocatedStatement> = {
Statement,
"\n" => vec![],
};
Suite: Vec<ast::LocatedStatement> = {
<s:SimpleStatement> => vec![s],
"\n" indent <s:Statement+> dedent => s,
};
Statement: ast::LocatedStatement = {
SimpleStatement,
CompoundStatement,
"\n" indent <s:Statement+> dedent => s.into_iter().flatten().collect(),
};
SimpleStatement: ast::LocatedStatement = {
<s:SmallStatement> "\n" => s,
<s:SmallStatement> ";" => s,
Statement: Vec<ast::LocatedStatement> = {
SimpleStatement,
<s:CompoundStatement> => vec![s],
};
SimpleStatement: Vec<ast::LocatedStatement> = {
<s1:SmallStatement> <s2:(";" SmallStatement)*> ";"? "\n" => {
let mut statements = vec![s1];
statements.extend(s2.into_iter().map(|e| e.1));
statements
}
};
SmallStatement: ast::LocatedStatement = {
@@ -103,9 +105,7 @@ ExpressionStatement: ast::LocatedStatement = {
}
}
},
<loc:@L> <expr:TestOrStarExprList> <op:AugAssign> <e2:TestList> => {
// TODO: this works in most cases:
let rhs = e2.into_iter().next().unwrap();
<loc:@L> <expr:TestOrStarExprList> <op:AugAssign> <rhs:TestList> => {
ast::LocatedStatement {
location: loc,
node: ast::Statement::AugAssign {
@@ -118,31 +118,17 @@ ExpressionStatement: ast::LocatedStatement = {
};
AssignSuffix: ast::Expression = {
"=" <e:TestList> => {
if e.len() > 1 {
ast::Expression::Tuple {
elements: e
}
} else {
e.into_iter().next().unwrap()
}
},
"=" <e:TestList> => e,
"=" <e:YieldExpr> => e,
};
TestOrStarExprList: ast::Expression = {
<e:TestOrStarExpr> <e2:("," TestOrStarExpr)*> <comma:","?> => {
let mut res = vec![e];
res.extend(e2.into_iter().map(|x| x.1));
// First build tuple from first item:
let expr = if (res.len() > 1) || comma.is_some() {
ast::Expression::Tuple { elements: res }
<elements:OneOrMore<TestOrStarExpr>> <comma:","?> => {
if elements.len() == 1 && comma.is_none() {
elements.into_iter().next().unwrap()
} else {
res.into_iter().next().unwrap()
};
expr
ast::Expression::Tuple { elements }
}
}
};
@@ -183,7 +169,7 @@ FlowStatement: ast::LocatedStatement = {
<loc:@L> "return" <t:TestList?> => {
ast::LocatedStatement {
location: loc,
node: ast::Statement::Return { value: t },
node: ast::Statement::Return { value: t.map(Box::new) },
}
},
<loc:@L> <y:YieldExpr> => {
@@ -310,15 +296,11 @@ NonlocalStatement: ast::LocatedStatement = {
};
AssertStatement: ast::LocatedStatement = {
<loc:@L> "assert" <t:Test> <m: ("," Test)?> => {
<loc:@L> "assert" <test:Test> <msg: ("," Test)?> => {
ast::LocatedStatement {
location: loc,
node: ast::Statement::Assert {
test: t,
msg: match m {
Some(e) => Some(e.1),
None => None,
}
test, msg: msg.map(|e| e.1)
}
}
},
@@ -335,12 +317,9 @@ CompoundStatement: ast::LocatedStatement = {
};
IfStatement: ast::LocatedStatement = {
<loc:@L> "if" <t:Test> ":" <s1:Suite> <s2:(@L "elif" Test ":" Suite)*> <s3:("else" ":" Suite)?> => {
<loc:@L> "if" <test:Test> ":" <s1:Suite> <s2:(@L "elif" Test ":" Suite)*> <s3:("else" ":" Suite)?> => {
// Determine last else:
let mut last = match s3 {
Some(s) => Some(s.2),
None => None,
};
let mut last = s3.map(|s| s.2);
// handle elif:
for i in s2.into_iter().rev() {
@@ -353,30 +332,24 @@ IfStatement: ast::LocatedStatement = {
ast::LocatedStatement {
location: loc,
node: ast::Statement::If { test: t, body: s1, orelse: last }
node: ast::Statement::If { test, body: s1, orelse: last }
}
},
};
WhileStatement: ast::LocatedStatement = {
<loc:@L> "while" <e:Test> ":" <s:Suite> <s2:("else" ":" Suite)?> => {
let or_else = match s2 {
Some(s) => Some(s.2),
None => None,
};
<loc:@L> "while" <test:Test> ":" <body:Suite> <s2:("else" ":" Suite)?> => {
let or_else = s2.map(|s| s.2);
ast::LocatedStatement {
location: loc,
node: ast::Statement::While { test: e, body: s, orelse: or_else },
node: ast::Statement::While { test, body, orelse: or_else },
}
},
};
ForStatement: ast::LocatedStatement = {
<loc:@L> "for" <e:ExpressionList> "in" <t:TestList> ":" <s:Suite> <s2:("else" ":" Suite)?> => {
let or_else = match s2 {
Some(s) => Some(s.2),
None => None,
};
let or_else = s2.map(|s| s.2);
ast::LocatedStatement {
location: loc,
node: ast::Statement::For {
@@ -389,14 +362,8 @@ ForStatement: ast::LocatedStatement = {
TryStatement: ast::LocatedStatement = {
<loc:@L> "try" ":" <body:Suite> <handlers:ExceptClause*> <else_suite:("else" ":" Suite)?> <finally:("finally" ":" Suite)?> => {
let or_else = match else_suite {
Some(s) => Some(s.2),
None => None,
};
let finalbody = match finally {
Some(s) => Some(s.2),
None => None,
};
let or_else = else_suite.map(|s| s.2);
let finalbody = finally.map(|s| s.2);
ast::LocatedStatement {
location: loc,
node: ast::Statement::Try {
@@ -437,10 +404,7 @@ WithStatement: ast::LocatedStatement = {
WithItem: ast::WithItem = {
<t:Test> <n:("as" Expression)?> => {
let optional_vars = match n {
Some(val) => Some(val.1),
None => None,
};
let optional_vars = n.map(|val| val.1);
ast::WithItem { context_expr: t, optional_vars }
},
};
@@ -461,27 +425,17 @@ FuncDef: ast::LocatedStatement = {
};
Parameters: ast::Parameters = {
"(" <a: (TypedArgsList<TypedParameter>)?> ")" => {
match a {
Some(a) => a,
None => Default::default(),
}
},
"(" <a: (ParameterList<TypedParameter>)?> ")" => a.unwrap_or_else(Default::default),
};
// parameters are (String, None), kwargs are (String, Some(Test)) where Test is
// the default
// Note that this is a macro which is used once for function defs, and
// once for lambda defs.
TypedArgsList<ArgType>: ast::Parameters = {
<param1:TypedParameters<ArgType>> <args2:("," ParameterListStarArgs<ArgType>)?> => {
ParameterList<ArgType>: ast::Parameters = {
<param1:ParameterDefs<ArgType>> <args2:("," ParameterListStarArgs<ArgType>)?> ","? => {
let (names, default_elements) = param1;
// Now gather rest of parameters:
let (vararg, kwonlyargs, kw_defaults, kwarg) = match args2 {
Some((_, x)) => x,
None => (None, vec![], vec![], None),
};
let (vararg, kwonlyargs, kw_defaults, kwarg) = args2.map_or((None, vec![], vec![], None), |x| x.1);
ast::Parameters {
args: names,
@@ -492,7 +446,7 @@ TypedArgsList<ArgType>: ast::Parameters = {
kw_defaults: kw_defaults,
}
},
<param1:TypedParameters<ArgType>> <kw:("," KwargParameter<ArgType>)> => {
<param1:ParameterDefs<ArgType>> <kw:("," KwargParameter<ArgType>)> ","? => {
let (names, default_elements) = param1;
// Now gather rest of parameters:
@@ -510,7 +464,7 @@ TypedArgsList<ArgType>: ast::Parameters = {
kw_defaults: kw_defaults,
}
},
<params:ParameterListStarArgs<ArgType>> => {
<params:ParameterListStarArgs<ArgType>> ","? => {
let (vararg, kwonlyargs, kw_defaults, kwarg) = params;
ast::Parameters {
args: vec![],
@@ -521,7 +475,7 @@ TypedArgsList<ArgType>: ast::Parameters = {
kw_defaults: kw_defaults,
}
},
<kw:KwargParameter<ArgType>> => {
<kw:KwargParameter<ArgType>> ","? => {
ast::Parameters {
args: vec![],
kwonlyargs: vec![],
@@ -535,8 +489,8 @@ TypedArgsList<ArgType>: ast::Parameters = {
// Use inline here to make sure the "," is not creating an ambiguity.
#[inline]
TypedParameters<ArgType>: (Vec<ast::Parameter>, Vec<ast::Expression>) = {
<param1:TypedParameterDef<ArgType>> <param2:("," TypedParameterDef<ArgType>)*> => {
ParameterDefs<ArgType>: (Vec<ast::Parameter>, Vec<ast::Expression>) = {
<param1:ParameterDef<ArgType>> <param2:("," ParameterDef<ArgType>)*> => {
// Combine first parameters:
let mut args = vec![param1];
args.extend(param2.into_iter().map(|x| x.1));
@@ -564,7 +518,7 @@ TypedParameters<ArgType>: (Vec<ast::Parameter>, Vec<ast::Expression>) = {
}
};
TypedParameterDef<ArgType>: (ast::Parameter, Option<ast::Expression>) = {
ParameterDef<ArgType>: (ast::Parameter, Option<ast::Expression>) = {
<i:ArgType> => (i, None),
<i:ArgType> "=" <e:Test> => (i, Some(e)),
};
@@ -580,8 +534,11 @@ TypedParameter: ast::Parameter = {
},
};
// Use inline here to make sure the "," is not creating an ambiguity.
// TODO: figure out another grammar that makes this inline no longer required.
#[inline]
ParameterListStarArgs<ArgType>: (Option<Option<ast::Parameter>>, Vec<ast::Parameter>, Vec<Option<ast::Expression>>, Option<Option<ast::Parameter>>) = {
"*" <va:ArgType?> <kw:("," TypedParameterDef<ArgType>)*> <kwarg:("," KwargParameter<ArgType>)?> => {
"*" <va:ArgType?> <kw:("," ParameterDef<ArgType>)*> <kwarg:("," KwargParameter<ArgType>)?> => {
// Extract keyword arguments:
let mut kwonlyargs = vec![];
let mut kw_defaults = vec![];
@@ -590,10 +547,7 @@ ParameterListStarArgs<ArgType>: (Option<Option<ast::Parameter>>, Vec<ast::Parame
kw_defaults.push(value);
}
let kwarg = match kwarg {
Some((_, name)) => Some(name),
None => None,
};
let kwarg = kwarg.map(|n| n.1);
(Some(va), kwonlyargs, kw_defaults, kwarg)
}
@@ -649,66 +603,59 @@ Decorator: ast::Expression = {
};
YieldExpr: ast::Expression = {
"yield" <ex:TestList?> => {
ast::Expression::Yield {
value: ex.map(|expr| Box::new(
if expr.len() > 1 {
ast::Expression::Tuple { elements: expr }
} else {
expr.into_iter().next().unwrap()
})
)
}
},
"yield" "from" <e:Test> => {
ast::Expression::YieldFrom {
value: Box::new(e),
}
},
"yield" <value:TestList?> => ast::Expression::Yield { value: value.map(Box::new) },
"yield" "from" <e:Test> => ast::Expression::YieldFrom { value: Box::new(e) },
};
Test: ast::Expression = {
<e:OrTest> <c: ("if" OrTest "else" Test)?> => {
match c {
Some(c) => {
ast::Expression::IfExpression {
test: Box::new(c.1),
body: Box::new(e),
orelse: Box::new(c.3),
}
},
None => e,
<expr:OrTest> <condition: ("if" OrTest "else" Test)?> => {
if let Some(c) = condition {
ast::Expression::IfExpression {
test: Box::new(c.1),
body: Box::new(expr),
orelse: Box::new(c.3),
}
} else {
expr
}
},
<e:LambdaDef> => e,
LambdaDef,
};
LambdaDef: ast::Expression = {
"lambda" <p:TypedArgsList<UntypedParameter>?> ":" <b:Test> =>
"lambda" <p:ParameterList<UntypedParameter>?> ":" <body:Test> =>
ast::Expression::Lambda {
args: p.unwrap_or(Default::default()),
body:Box::new(b)
body: Box::new(body)
}
}
OrTest: ast::Expression = {
<e:AndTest> => e,
AndTest,
<e1:OrTest> "or" <e2:AndTest> => ast::Expression::BoolOp { a: Box::new(e1), op: ast::BooleanOperator::Or, b: Box::new(e2) },
};
AndTest: ast::Expression = {
<e:NotTest> => e,
NotTest,
<e1:AndTest> "and" <e2:NotTest> => ast::Expression::BoolOp { a: Box::new(e1), op: ast::BooleanOperator::And, b: Box::new(e2) },
};
NotTest: ast::Expression = {
"not" <e:NotTest> => ast::Expression::Unop { a: Box::new(e), op: ast::UnaryOperator::Not },
<e:Comparison> => e,
Comparison,
};
Comparison: ast::Expression = {
<e1:Comparison> <op:CompOp> <e2:Expression> => ast::Expression::Compare { a: Box::new(e1), op: op, b: Box::new(e2) },
<e:Expression> => e,
<e:Expression> <comparisons:(CompOp Expression)+> => {
let mut vals = vec![e];
let mut ops = vec![];
for x in comparisons {
ops.push(x.0);
vals.push(x.1);
}
ast::Expression::Compare { vals, ops }
},
Expression,
};
CompOp: ast::Comparison = {
@@ -726,7 +673,7 @@ CompOp: ast::Comparison = {
Expression: ast::Expression = {
<e1:Expression> "|" <e2:XorExpression> => ast::Expression::Binop { a: Box::new(e1), op: ast::Operator::BitOr, b: Box::new(e2) },
<e:XorExpression> => e,
XorExpression,
};
XorExpression: ast::Expression = {
@@ -776,7 +723,7 @@ Factor: ast::Expression = {
"+" <e:Factor> => ast::Expression::Unop { a: Box::new(e), op: ast::UnaryOperator::Pos },
"-" <e:Factor> => ast::Expression::Unop { a: Box::new(e), op: ast::UnaryOperator::Neg },
"~" <e:Factor> => ast::Expression::Unop { a: Box::new(e), op: ast::UnaryOperator::Inv },
<e:Power> => e,
Power,
};
Power: ast::Expression = {
@@ -789,7 +736,7 @@ Power: ast::Expression = {
};
AtomExpr: ast::Expression = {
<e:Atom> => e,
Atom,
<f:AtomExpr> "(" <a:ArgumentList> ")" => ast::Expression::Call { function: Box::new(f), args: a.0, keywords: a.1 },
<e:AtomExpr> "[" <s:SubscriptList> "]" => ast::Expression::Subscript { a: Box::new(e), b: Box::new(s) },
<e:AtomExpr> "." <n:Identifier> => ast::Expression::Attribute { value: Box::new(e), name: n },
@@ -810,7 +757,7 @@ SubscriptList: ast::Expression = {
};
Subscript: ast::Expression = {
<e:Test> => e,
Test,
<e1:Test?> ":" <e2:Test?> <e3:SliceOp?> => {
let s1 = e1.unwrap_or(ast::Expression::None);
let s2 = e2.unwrap_or(ast::Expression::None);
@@ -824,30 +771,17 @@ SliceOp: ast::Expression = {
}
Atom: ast::Expression = {
<s:StringGroup> => ast::Expression::String { value: s },
<b:Bytes> => ast::Expression::Bytes { value: b },
<n:Number> => ast::Expression::Number { value: n },
<i:Identifier> => ast::Expression::Identifier { name: i },
<value:StringGroup> => ast::Expression::String { value },
<value:Bytes> => ast::Expression::Bytes { value },
<value:Number> => ast::Expression::Number { value },
<name:Identifier> => ast::Expression::Identifier { name },
"[" <e:TestListComp?> "]" => {
let elements = e.unwrap_or(Vec::new());
ast::Expression::List { elements }
},
"[" <e:TestListComp2> "]" => {
// List comprehension:
e
},
"(" <e:TestList?> <trailing_comma:","?> ")" => {
match e {
None => ast::Expression::Tuple { elements: Vec::new() },
Some(elements) => {
if elements.len() == 1 && trailing_comma.is_none() {
// This is "(e)", which is equivalent to "e"
elements.into_iter().next().unwrap()
} else {
ast::Expression::Tuple { elements }
}
}
}
"[" <e:TestListComp2> "]" => e,
"(" <elements:TestList?> ")" => {
elements.unwrap_or(ast::Expression::Tuple { elements: Vec::new() })
},
"(" <e:Test> <c:CompFor> ")" => {
ast::Expression::Comprehension {
@@ -866,9 +800,7 @@ Atom: ast::Expression = {
};
TestListComp: Vec<ast::Expression> = {
<e:OneOrMore<TestOrStarExpr>> <_trailing_comma:","?> => {
e
},
<e:OneOrMore<TestOrStarExpr>> <_trailing_comma:","?> => e,
};
TestListComp2: ast::Expression = {
@@ -881,9 +813,7 @@ TestListComp2: ast::Expression = {
};
TestDict: Vec<(ast::Expression, ast::Expression)> = {
<e1:OneOrMore<DictEntry>> <_trailing_comma:","?> => {
e1
}
<elements:OneOrMore<DictEntry>> <_trailing_comma:","?> => elements,
};
TestDictComp: ast::Expression = {
@@ -900,9 +830,7 @@ DictEntry: (ast::Expression, ast::Expression) = {
};
TestSet: Vec<ast::Expression> = {
<e1:OneOrMore<Test>> ","? => {
e1
}
<e1:OneOrMore<Test>> ","? => e1
};
TestSetComp: ast::Expression = {
@@ -914,31 +842,37 @@ TestSetComp: ast::Expression = {
}
};
ExpressionOrStarExpression = {
Expression,
StarExpr
};
ExpressionList: ast::Expression = {
<e: ExpressionList2> => {
if e.len() == 1 {
e.into_iter().next().unwrap()
<elements: OneOrMore<ExpressionOrStarExpression>> <trailing_comma:","?> => {
if elements.len() == 1 && trailing_comma.is_none() {
elements.into_iter().next().unwrap()
} else {
ast::Expression::Tuple { elements: e }
ast::Expression::Tuple { elements }
}
},
};
ExpressionList2: Vec<ast::Expression> = {
<e1:Expression> <e2:("," Expression)*> ","? => {
let mut l = vec![e1];
l.extend(e2.into_iter().map(|x| x.1));
l
},
<elements:OneOrMore<Expression>> ","? => elements,
};
#[inline]
TestList: Vec<ast::Expression> = {
<e1:Test> <e2: ("," Test)*> => {
let mut l = vec![e1];
l.extend(e2.into_iter().map(|x| x.1));
l
}
// A test list is one of:
// - a list of expressions
// - a single expression
// - a single expression followed by a trailing comma
TestList: ast::Expression = {
<elements:OneOrMore<Test>> <trailing_comma: ","?> => {
if elements.len() == 1 && trailing_comma.is_none() {
elements.into_iter().next().unwrap()
} else {
ast::Expression::Tuple { elements }
}
}
};
// Test
@@ -947,27 +881,16 @@ StarExpr: ast::Expression = {
};
// Comprehensions:
CompFor: Vec<ast::Comprehension> = {
<c:SingleForComprehension+> => c,
};
CompFor: Vec<ast::Comprehension> = <c:SingleForComprehension+> => c;
SingleForComprehension: ast::Comprehension = {
"for" <e:ExpressionList> "in" <i:OrTest> <c2:ComprehensionIf*> => {
ast::Comprehension {
target: e,
iter: i,
ifs: c2,
}
"for" <target:ExpressionList> "in" <iter:OrTest> <c2:ComprehensionIf*> => {
ast::Comprehension { target, iter, ifs: c2 }
}
};
ExpressionNoCond: ast::Expression = {
OrTest,
};
ComprehensionIf: ast::Expression = {
"if" <c:ExpressionNoCond> => c,
};
ExpressionNoCond: ast::Expression = OrTest;
ComprehensionIf: ast::Expression = "if" <c:ExpressionNoCond> => c;
ArgumentList: (Vec<ast::Expression>, Vec<ast::Keyword>) = {
<e: Comma<FunctionArgument>> => {
@@ -979,8 +902,15 @@ ArgumentList: (Vec<ast::Expression>, Vec<ast::Keyword>) = {
keywords.push(ast::Keyword { name: n, value: value });
},
None => {
if keywords.len() > 0 {
panic!("positional argument follows keyword argument");
// Allow starred args after keyword arguments.
let is_starred = if let ast::Expression::Starred { .. } = &value {
true
} else {
false
};
if keywords.len() > 0 && !is_starred {
panic!("positional argument follows keyword argument {:?}", keywords);
};
args.push(value);
},
@@ -1024,8 +954,8 @@ OneOrMore<T>: Vec<T> = {
};
Number: ast::Number = {
<s:int> => { ast::Number::Integer { value: s } },
<s:float> => { ast::Number::Float { value: s } },
<value:int> => { ast::Number::Integer { value } },
<value:float> => { ast::Number::Float { value } },
<s:complex> => { ast::Number::Complex { real: s.0, imag: s.1 } },
};
+3 -7
View File
@@ -65,11 +65,8 @@ fn main() {
}
fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResult {
let code_obj =
compile::compile(vm, source, &compile::Mode::Exec, source_path).map_err(|err| {
let syntax_error = vm.context().exceptions.syntax_error.clone();
vm.new_exception(syntax_error, err.to_string())
})?;
let code_obj = compile::compile(vm, source, &compile::Mode::Exec, source_path)
.map_err(|err| vm.new_syntax_error(&err))?;
// trace!("Code object: {:?}", code_obj.borrow());
let vars = vm.ctx.new_scope(); // Keep track of local variables
vm.run_code_obj(code_obj, vars)
@@ -120,8 +117,7 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), Com
// Don't inject syntax errors for line continuation
Err(err @ CompileError::Parse(ParseError::EOF(_))) => Err(err),
Err(err) => {
let syntax_error = vm.context().exceptions.syntax_error.clone();
let exc = vm.new_exception(syntax_error, format!("{}", err));
let exc = vm.new_syntax_error(&err);
print_exception(vm, &exc);
Err(err)
}
+1
View File
@@ -0,0 +1 @@
snippets/whats_left_to_implement.py
+4 -2
View File
@@ -1,4 +1,3 @@
# Test snippets
This directory contains two sets of test snippets which can be run in
@@ -6,6 +5,10 @@ Python. The `snippets/` directory contains functional tests, and the
`benchmarks/` directory contains snippets for use in benchmarking
RustPython's performance.
## Generates the test for not implemented methods
run using cpython not_impl_gen.py it automatically generate a
test snippet to check not yet implemented methods
## Running with CPython + RustPython
@@ -15,4 +18,3 @@ compilation to bytecode. When this is done, run the bytecode with rustpython.
## Running with RustPython
The other option is to run all snippets with RustPython.
+12
View File
@@ -0,0 +1,12 @@
not_implemented = [(name, method)
for name, (val, methods) in expected_methods.items()
for method in methods
if not hasattr(val, method)]
for r in not_implemented:
print(r[0], ".", r[1], sep="")
if not not_implemented:
print("Not much \\o/")
if platform.python_implementation() == "CPython":
assert len(not_implemented) == 0, "CPython should have all the methods"
+7
View File
@@ -0,0 +1,7 @@
# WARNING: THIS IS AN AUTOMATICALLY GENERATED FILE
# EDIT tests/not_impl_gen.py, NOT THIS FILE.
# RESULTS OF THIS TEST DEPEND ON THE CPYTHON
# VERSION USED TO RUN not_impl_gen.py
import platform
+33
View File
@@ -0,0 +1,33 @@
objects = [
bool,
bytearray,
bytes,
complex,
dict,
float,
frozenset,
int,
iter,
list,
memoryview,
range,
set,
str,
tuple,
object
]
header = open("generator/not_impl_header.txt")
footer = open("generator/not_impl_footer.txt")
output = open("snippets/whats_left_to_implement.py", "w")
output.write(header.read())
output.write("expected_methods = {\n")
for obj in objects:
output.write(f" '{obj.__name__}': ({obj.__name__}, [\n")
output.write("\n".join(f" '{attr}'," for attr in dir(obj)))
output.write("\n ])," + ("\n" if objects[-1] == obj else "\n\n"))
output.write("}\n\n")
output.write(footer.read())
+7
View File
@@ -21,3 +21,10 @@ print(foo.body[0].value.func.id)
assert foo.body[0].value.func.id == 'print'
assert foo.body[0].lineno == 3
assert foo.body[1].lineno == 4
n = ast.parse("3 < 4 > 5\n")
assert n.body[0].value.left.n == 3
assert 'Lt' in str(n.body[0].value.ops[0])
assert 'Gt' in str(n.body[0].value.ops[1])
assert n.body[0].value.comparators[0].n == 4
assert n.body[0].value.comparators[1].n == 5
+3
View File
@@ -8,6 +8,9 @@ a = A()
assert "test" in dir(a), "test not in a"
assert "test" in dir(A), "test not in A"
a.x = 3
assert "x" in dir(a), "x not in a"
class B(A):
def __dir__(self):
return ('q', 'h')
+48 -9
View File
@@ -2,9 +2,9 @@ from testutils import assert_raises
a = []
assert a[:] == []
assert a[:2**100] == []
assert a[-2**100:] == []
assert a[::2**100] == []
assert a[: 2 ** 100] == []
assert a[-2 ** 100 :] == []
assert a[:: 2 ** 100] == []
assert a[10:20] == []
assert a[-20:-10] == []
@@ -12,15 +12,15 @@ b = [1, 2]
assert b[:] == [1, 2]
assert b[slice(None)] == [1, 2]
assert b[:2**100] == [1, 2]
assert b[-2**100:] == [1, 2]
assert b[2**100:] == []
assert b[::2**100] == [1]
assert b[: 2 ** 100] == [1, 2]
assert b[-2 ** 100 :] == [1, 2]
assert b[2 ** 100 :] == []
assert b[:: 2 ** 100] == [1]
assert b[-10:1] == [1]
assert b[0:0] == []
assert b[1:0] == []
assert_raises(ValueError, lambda: b[::0], 'zero step slice')
assert_raises(ValueError, lambda: b[::0], "zero step slice")
assert b[::-1] == [2, 1]
assert b[1::-1] == [2, 1]
@@ -34,7 +34,7 @@ c = list(range(10))
assert c[9:6:-3] == [9]
assert c[9::-3] == [9, 6, 3, 0]
assert c[9::-4] == [9, 5, 1]
assert c[8::-2**100] == [8]
assert c[8 :: -2 ** 100] == [8]
assert c[7:7:-2] == []
assert c[7:8:-2] == []
@@ -44,6 +44,7 @@ d = "123456"
assert d[3::-1] == "4321"
assert d[4::-3] == "52"
assert [1, 2, 3, 5, 6][-1:-5:-1] == [6, 5, 3, 2] # #746
slice_a = slice(5)
assert slice_a.start is None
@@ -90,3 +91,41 @@ class CustomIndex:
assert c[CustomIndex(1):CustomIndex(3)] == [1, 2]
assert d[CustomIndex(1):CustomIndex(3)] == "23"
def test_all_slices():
"""
test all possible slices except big number
"""
mod = __import__('cpython_generated_slices')
ll = mod.LL
start = mod.START
end = mod.END
step = mod.STEP
slices_res = mod.SLICES_RES
count = 0
failures = []
for s in start:
for e in end:
for t in step:
lhs = ll[s:e:t]
try:
assert lhs == slices_res[count]
except AssertionError:
failures.append(
"start: {} ,stop: {}, step {}. Expected: {}, found: {}".format(
s, e, t, lhs, slices_res[count]
)
)
count += 1
if failures:
for f in failures:
print(f)
print(len(failures), "slices failed")
test_all_slices()
+160
View File
@@ -0,0 +1,160 @@
from testutils import assertRaises
# new
assert bytes([1, 2, 3])
assert bytes((1, 2, 3))
assert bytes(range(4))
assert bytes(3)
assert b"bla"
assert bytes("bla", "utf8")
with assertRaises(TypeError):
bytes("bla")
a = b"abcd"
b = b"ab"
c = b"abcd"
#
# repr
assert repr(bytes([0, 1, 2])) == repr(b"\x00\x01\x02")
assert repr(
bytes([0, 1, 9, 10, 11, 13, 31, 32, 33, 89, 120, 255])
== "b'\\x00\\x01\\t\\n\\x0b\\r\\x1f !Yx\\xff'"
)
assert repr(b"abcd") == "b'abcd'"
# len
assert len(bytes("abcdé", "utf8")) == 6
# comp
assert a == b"abcd"
assert a > b
assert a >= b
assert b < a
assert b <= a
assert b"foobar".__eq__(2) == NotImplemented
assert b"foobar".__ne__(2) == NotImplemented
assert b"foobar".__gt__(2) == NotImplemented
assert b"foobar".__ge__(2) == NotImplemented
assert b"foobar".__lt__(2) == NotImplemented
assert b"foobar".__le__(2) == NotImplemented
# hash
hash(a) == hash(b"abcd")
# iter
[i for i in b"abcd"] == ["a", "b", "c", "d"]
assert list(bytes(3)) == [0, 0, 0]
# add
assert a + b == b"abcdab"
# contains
assert b"ab" in b"abcd"
assert b"cd" in b"abcd"
assert b"abcd" in b"abcd"
assert b"a" in b"abcd"
assert b"d" in b"abcd"
assert b"dc" not in b"abcd"
assert 97 in b"abcd"
assert 150 not in b"abcd"
with assertRaises(ValueError):
350 in b"abcd"
# getitem
d = b"abcdefghij"
assert d[1] == 98
assert d[-1] == 106
assert d[2:6] == b"cdef"
assert d[-6:] == b"efghij"
assert d[1:8:2] == b"bdfh"
assert d[8:1:-2] == b"igec"
# is_xx methods
assert bytes(b"1a23").isalnum()
assert not bytes(b"1%a23").isalnum()
assert bytes(b"abc").isalpha()
assert not bytes(b"abc1").isalpha()
# travis doesn't like this
# assert bytes(b'xyz').isascii()
# assert not bytes([128, 157, 32]).isascii()
assert bytes(b"1234567890").isdigit()
assert not bytes(b"12ab").isdigit()
l = bytes(b"lower")
b = bytes(b"UPPER")
assert l.islower()
assert not l.isupper()
assert b.isupper()
assert not bytes(b"Super Friends").islower()
assert bytes(b" \n\t").isspace()
assert not bytes(b"\td\n").isspace()
assert b.isupper()
assert not b.islower()
assert l.islower()
assert not bytes(b"tuPpEr").isupper()
assert bytes(b"Is Title Case").istitle()
assert not bytes(b"is Not title casE").istitle()
# upper lower, capitalize
l = bytes(b"lower")
b = bytes(b"UPPER")
assert l.lower().islower()
assert b.upper().isupper()
assert l.capitalize() == b"Lower"
assert b.capitalize() == b"Upper"
assert bytes().capitalize() == bytes()
# hex from hex
assert bytes([0, 1, 9, 23, 90, 234]).hex() == "000109175aea"
bytes.fromhex("62 6c7a 34350a ") == b"blz45\n"
try:
bytes.fromhex("62 a 21")
except ValueError as e:
str(e) == "non-hexadecimal number found in fromhex() arg at position 4"
try:
bytes.fromhex("6Z2")
except ValueError as e:
str(e) == "non-hexadecimal number found in fromhex() arg at position 1"
# center
assert [b"koki".center(i, b"|") for i in range(3, 10)] == [
b"koki",
b"koki",
b"|koki",
b"|koki|",
b"||koki|",
b"||koki||",
b"|||koki||",
]
assert [b"kok".center(i, b"|") for i in range(2, 10)] == [
b"kok",
b"kok",
b"kok|",
b"|kok|",
b"|kok||",
b"||kok||",
b"||kok|||",
b"|||kok|||",
]
b"kok".center(4) == b" kok" # " test no arg"
with assertRaises(TypeError):
b"b".center(2, "a")
with assertRaises(TypeError):
b"b".center(2, b"ba")
b"kok".center(5, bytearray(b"x"))
+38 -3
View File
@@ -1,3 +1,6 @@
__name__ = "class"
class Foo:
def __init__(self, x):
assert x == 5
@@ -14,7 +17,12 @@ foo = Foo(5)
assert foo.y == Foo.y
assert foo.x == 5
assert foo.square() == 25
assert Foo.__name__ == "Foo"
assert Foo.__qualname__ == "Foo"
assert Foo.__module__ == "class"
assert Foo.square.__name__ == "square"
assert Foo.square.__qualname__ == "Foo.square"
assert Foo.square.__module__ == "class"
class Bar:
""" W00t """
@@ -37,8 +45,7 @@ class Bar:
assert x == 3
# TODO:
# assert Bar.__doc__ == " W00t "
assert Bar.__doc__ == " W00t "
bar = Bar(42)
@@ -117,3 +124,31 @@ assert isinstance(a, super)
assert type(a) is super
assert a.conjugate() == 1
class T1:
"test1"
assert T1.__doc__ == "test1"
class T2:
'''test2'''
assert T2.__doc__ == "test2"
class T3:
"""
test3
"""
assert T3.__doc__ == "\n test3\n "
class T4:
"""test4"""
def t1(self):
"""t1"""
pass
assert T4.__doc__ == "test4"
assert T4.t1.__doc__ == "t1"
+13
View File
@@ -0,0 +1,13 @@
assert 1 < 2
assert 1 < 2 < 3
assert 5 == 5 == 5
assert (5 == 5) == True
assert 5 == 5 != 4 == 4 > 3 > 2 < 3 <= 3 != 0 == 0
assert not 1 > 2
assert not 5 == 5 == True
assert not 5 == 5 != 5 == 5
assert not 1 < 2 < 3 > 4
assert not 1 < 2 > 3 < 4
assert not 1 > 2 < 3 < 4
+5
View File
@@ -22,3 +22,8 @@ assert u['1'] == -1
y = [a+2 for a in x if a % 2]
print(y)
assert y == [3, 5]
z = [(9,), (10,)]
w = [x for x, in z]
assert w == [9, 10]
+10
View File
@@ -15,6 +15,16 @@ c = add(10, 3)
assert c == 14
@logged
def add3(a, b, c=2):
return a + b + c
d = add3(12, 5)
assert d == 20
def f(func): return lambda: 42
class A: pass
a = A()
+144 -12
View File
@@ -1,19 +1,26 @@
def dict_eq(d1, d2):
return (all(k in d2 and d1[k] == d2[k] for k in d1)
and all(k in d1 and d1[k] == d2[k] for k in d2))
from testutils import assertRaises
assert dict(a=2, b=3) == {'a': 2, 'b': 3}
assert dict({'a': 2, 'b': 3}, b=4) == {'a': 2, 'b': 4}
assert dict([('a', 2), ('b', 3)]) == {'a': 2, 'b': 3}
assert dict_eq(dict(a=2, b=3), {'a': 2, 'b': 3})
assert dict_eq(dict({'a': 2, 'b': 3}, b=4), {'a': 2, 'b': 4})
assert dict_eq(dict([('a', 2), ('b', 3)]), {'a': 2, 'b': 3})
assert {} == {}
assert not {'a': 2} == {}
assert not {} == {'a': 2}
assert not {'b': 2} == {'a': 2}
assert not {'a': 4} == {'a': 2}
assert {'a': 2} == {'a': 2}
nan = float('nan')
assert {'a': nan} == {'a': nan}
a = {'g': 5}
b = {'a': a, 'd': 9}
c = dict(b)
c['d'] = 3
c['a']['g'] = 2
assert dict_eq(a, {'g': 2})
assert dict_eq(b, {'a': a, 'd': 9})
assert a == {'g': 2}
assert b == {'a': a, 'd': 9}
a.clear()
assert len(a) == 0
@@ -21,16 +28,141 @@ assert len(a) == 0
a = {'a': 5, 'b': 6}
res = set()
for value in a.values():
res.add(value)
res.add(value)
assert res == set([5,6])
count = 0
for (key, value) in a.items():
assert a[key] == value
count += 1
assert a[key] == value
count += 1
assert count == len(a)
res = set()
for key in a.keys():
res.add(key)
res.add(key)
assert res == set(['a','b'])
# Deleted values are correctly skipped over:
x = {'a': 1, 'b': 2, 'c': 3, 'd': 3}
del x['c']
it = iter(x.items())
assert ('a', 1) == next(it)
assert ('b', 2) == next(it)
assert ('d', 3) == next(it)
with assertRaises(StopIteration):
next(it)
# Iterating a dictionary is just its keys:
assert ['a', 'b', 'd'] == list(x)
# Iterating view captures dictionary when iterated.
data = {1: 2, 3: 4}
items = data.items()
assert list(items) == [(1, 2), (3, 4)]
data[5] = 6
assert list(items) == [(1, 2), (3, 4), (5, 6)]
# Values can be changed during iteration.
data = {1: 2, 3: 4}
items = iter(data.items())
assert (1, 2) == next(items)
data[3] = "changed"
assert (3, "changed") == next(items)
# But we can't add or delete items during iteration.
d = {}
a = iter(d.items())
d['a'] = 2
b = iter(d.items())
assert ('a', 2) == next(b)
with assertRaises(RuntimeError):
next(a)
del d['a']
with assertRaises(RuntimeError):
next(b)
# View isn't itself an iterator.
with assertRaises(TypeError):
next(data.keys())
assert len(data.keys()) == 2
x = {}
x[1] = 1
assert x[1] == 1
x[7] = 7
x[2] = 2
x[(5, 6)] = 5
with assertRaises(KeyError):
x["not here"]
with assertRaises(TypeError):
x[[]] # Unhashable type.
x["here"] = "here"
assert x.get("not here", "default") == "default"
assert x.get("here", "default") == "here"
assert x.get("not here") == None
class LengthDict(dict):
def __getitem__(self, k):
return len(k)
x = LengthDict()
assert type(x) == LengthDict
assert x['word'] == 4
assert x.get('word') is None
assert 5 == eval("a + word", LengthDict())
class Squares(dict):
def __missing__(self, k):
v = k * k
self[k] = v
return v
x = Squares()
assert x[-5] == 25
# An object that hashes to the same value always, and compares equal if any its values match.
class Hashable(object):
def __init__(self, *args):
self.values = args
def __hash__(self):
return 1
def __eq__(self, other):
for x in self.values:
for y in other.values:
if x == y:
return True
return False
x = {}
x[Hashable(1,2)] = 8
assert x[Hashable(1,2)] == 8
assert x[Hashable(3,1)] == 8
x[Hashable(8)] = 19
x[Hashable(19,8)] = 1
assert x[Hashable(8)] == 1
assert len(x) == 2
assert list({'a': 2, 'b': 10}) == ['a', 'b']
x = {}
x['a'] = 2
x['b'] = 10
assert list(x) == ['a', 'b']
y = x.copy()
x['c'] = 12
assert y == {'a': 2, 'b': 10}
y.update({'c': 19, "d": -1, 'b': 12})
assert y == {'a': 2, 'b': 12, 'c': 19, 'd': -1}
y.update(y)
assert y == {'a': 2, 'b': 12, 'c': 19, 'd': -1} # hasn't changed
+4
View File
@@ -106,3 +106,7 @@ assert_raises(OverflowError, float('inf').as_integer_ratio)
assert_raises(OverflowError, float('-inf').as_integer_ratio)
assert_raises(ValueError, float('nan').as_integer_ratio)
# Test special case for lexer, float starts with a dot:
a = .5
assert a == 0.5
+12
View File
@@ -10,3 +10,15 @@ else:
x = 3
assert x == 3
y = []
for x, in [(9,), [2]]:
y.append(x)
assert y == [9, 2], str(y)
y = []
for x, *z in [(9,88,'b'), [2, 'bla'], [None]*4]:
y.append(z)
assert y == [[88, 'b'], ['bla'], [None]*3], str(y)
+70
View File
@@ -1,4 +1,74 @@
__name__ = "function"
def foo():
"""test"""
return 42
assert foo() == 42
assert foo.__doc__ == "test"
assert foo.__name__ == "foo"
assert foo.__qualname__ == "foo"
assert foo.__module__ == "function"
def my_func(a,):
return a+2
assert my_func(2) == 4
def fubar():
return 42,
assert fubar() == (42,)
def f1():
"""test1"""
pass
assert f1.__doc__ == "test1"
def f2():
'''test2'''
pass
assert f2.__doc__ == "test2"
def f3():
"""
test3
"""
pass
assert f3.__doc__ == "\n test3\n "
def f4():
"test4"
pass
assert f4.__doc__ == "test4"
def revdocstr(f):
d = f.__doc__
d = d + 'w00t'
f.__doc__ = d
return f
@revdocstr
def f5():
"""abc"""
assert f5.__doc__ == 'abcw00t', f5.__doc__
def f6():
def nested():
pass
assert nested.__name__ == "nested"
assert nested.__qualname__ == "f6.<locals>.nested"
f6()
+47 -8
View File
@@ -1,21 +1,28 @@
from testutils import assertRaises
def sum(x, y):
return x+y
# def total(a, b, c, d):
# return sum(sum(a,b), sum(c,d))
#
# assert total(1,1,1,1) == 4
# assert total(1,2,3,4) == 10
def total(a, b, c, d):
return sum(sum(a,b), sum(c,d))
assert total(1,1,1,1) == 4
assert total(1,2,3,4) == 10
assert sum(1, 1) == 2
assert sum(1, 3) == 4
def sum2y(x, y):
return x+y*2
assert sum2y(1, 1) == 3
assert sum2y(1, 3) == 7
def va(a, b=2, *c, d, **e):
assert a == 1
assert b == 22
@@ -23,22 +30,54 @@ def va(a, b=2, *c, d, **e):
assert d == 1337
assert e['f'] == 42
va(1, 22, 3, 4, d=1337, f=42)
assert va.__defaults__ == (2,)
assert va.__kwdefaults__ is None
def va2(*args, **kwargs):
assert args == (5, 4)
assert len(kwargs) == 0
va2(5, 4)
x = (5, 4)
va2(*x)
va2(5, *x[1:])
# def va3(x, *, b=2):
# pass
# va3(1, 2, 3, b=10)
def va3(x, *, a, b=2, c=9):
return x + b + c
assert va3(1, a=1, b=10) == 20
with assertRaises(TypeError):
va3(1, 2, 3, a=1, b=10)
with assertRaises(TypeError):
va3(1, b=10)
assert va3.__defaults__ is None
kw_defaults = va3.__kwdefaults__
# assert va3.__kwdefaults__ == {'b': 2, 'c': 9}
assert set(kw_defaults) == {'b', 'c'}
assert kw_defaults['b'] == 2
assert kw_defaults['c'] == 9
x = {'f': 42, 'e': 1337}
y = {'d': 1337}
va(1, 22, 3, 4, **x, **y)
# star arg after keyword args:
def fubar(x, y, obj=None):
assert x == 4
assert y == 5
assert obj == 6
rest = [4, 5]
fubar(obj=6, *rest)
+29
View File
@@ -1,3 +1,5 @@
from testutils import assertRaises
r = []
@@ -35,3 +37,30 @@ r = list(g3())
# print(r)
assert r == [23, 1, 2, 3, 44]
def g4():
yield
yield 2,
r = list(g4())
assert r == [None, (2,)]
def catch_exception():
try:
yield 1
except ValueError:
yield 2
yield 3
g = catch_exception()
assert next(g) == 1
assert g.throw(ValueError, ValueError(), None) == 2
assert next(g) == 3
g = catch_exception()
assert next(g) == 1
with assertRaises(KeyError):
assert g.throw(KeyError, KeyError(), None) == 2
+13
View File
@@ -0,0 +1,13 @@
# Test global and nonlocal funkyness
a = 2
def b():
global a
a = 4
assert a == 2
b()
assert a == 4
+23
View File
@@ -0,0 +1,23 @@
from testutils import assertRaises
class A:
pass
assert type(hash(None)) is int
assert type(hash(object())) is int
assert type(hash(A())) is int
assert type(hash(1)) is int
assert type(hash(1.1)) is int
assert type(hash("")) is int
with assertRaises(TypeError):
hash({})
with assertRaises(TypeError):
hash(set())
with assertRaises(TypeError):
hash([])
+32
View File
@@ -0,0 +1,32 @@
from testutils import assert_raises
def test_container(x):
assert 3 in x
assert 4 not in x
assert list(x) == list(iter(x))
assert list(x) == [0, 1, 2, 3]
assert [*x] == [0, 1, 2, 3]
lst = []
lst.extend(x)
assert lst == [0, 1, 2, 3]
class C:
def __iter__(self):
return iter([0, 1, 2, 3])
test_container(C())
class C:
def __getitem__(self, x):
return (0, 1, 2, 3)[x] # raises IndexError on x==4
test_container(C())
class C:
def __getitem__(self, x):
if x > 3:
raise StopIteration
return x
test_container(C())
class C: pass
assert_raises(TypeError, lambda: 5 in C())
assert_raises(TypeError, lambda: iter(C))
+314
View File
@@ -170,3 +170,317 @@ def f(x):
return x
assert_raises(ValueError, lambda: lst.sort(key=f)) # "list modified during sort"
assert lst == [1, 2, 3, 4, 5]
# __delitem__
x = ['a', 'b', 'c']
del x[0]
assert x == ['b', 'c']
x = ['a', 'b', 'c']
del x[-1]
assert x == ['a', 'b']
x = y = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15]
del x[2:14:3]
assert x == y
assert x == [1, 2, 4, 5, 7, 8, 11, 12, 14, 15]
assert y == [1, 2, 4, 5, 7, 8, 11, 12, 14, 15]
x = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15]
del x[-5:]
assert x == [1, 2, 3, 4, 5, 6, 7, 8, 10]
x = list(range(12))
del x[10:2:-2]
assert x == [0,1,2,3,5,7,9,11]
def bad_del_1():
del ['a', 'b']['a']
assert_raises(TypeError, bad_del_1)
def bad_del_2():
del ['a', 'b'][2]
assert_raises(IndexError, bad_del_2)
# __setitem__
# simple index
x = [1, 2, 3, 4, 5]
x[0] = 'a'
assert x == ['a', 2, 3, 4, 5]
x[-1] = 'b'
assert x == ['a', 2, 3, 4, 'b']
# make sure refrences are assigned correctly
y = []
x[1] = y
y.append(100)
assert x[1] == y
assert x[1] == [100]
#index bounds
def set_index_out_of_bounds_high():
x = [0, 1, 2, 3, 4]
x[5] = 'a'
def set_index_out_of_bounds_low():
x = [0, 1, 2, 3, 4]
x[-6] = 'a'
assert_raises(IndexError, set_index_out_of_bounds_high)
assert_raises(IndexError, set_index_out_of_bounds_low)
# non stepped slice index
a = list(range(10))
x = a[:]
y = a[:]
assert x == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# replace whole list
x[:] = ['a', 'b', 'c']
y[::1] = ['a', 'b', 'c']
assert x == ['a', 'b', 'c']
assert x == y
# splice list start
x = a[:]
y = a[:]
z = a[:]
zz = a[:]
x[:1] = ['a', 'b', 'c']
y[0:1] = ['a', 'b', 'c']
z[:1:1] = ['a', 'b', 'c']
zz[0:1:1] = ['a', 'b', 'c']
assert x == ['a', 'b', 'c', 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert x == y
assert x == z
assert x == zz
# splice list end
x = a[:]
y = a[:]
z = a[:]
zz = a[:]
x[5:] = ['a', 'b', 'c']
y[5::1] = ['a', 'b', 'c']
z[5:10] = ['a', 'b', 'c']
zz[5:10:1] = ['a', 'b', 'c']
assert x == [0, 1, 2, 3, 4, 'a', 'b', 'c']
assert x == y
assert x == z
assert x == zz
# insert sec
x = a[:]
y = a[:]
z = a[:]
zz = a[:]
x[1:1] = ['a', 'b', 'c']
y[1:0] = ['a', 'b', 'c']
z[1:1:1] = ['a', 'b', 'c']
zz[1:0:1] = ['a', 'b', 'c']
assert x == [0, 'a', 'b', 'c', 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert x == y
assert x == z
assert x == zz
# same but negative indexes?
x = a[:]
y = a[:]
z = a[:]
zz = a[:]
x[-1:-1] = ['a', 'b', 'c']
y[-1:9] = ['a', 'b', 'c']
z[-1:-1:1] = ['a', 'b', 'c']
zz[-1:9:1] = ['a', 'b', 'c']
assert x == [0, 1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', 'c', 9]
assert x == y
assert x == z
assert x == zz
# splice mid
x = a[:]
y = a[:]
x[3:5] = ['a', 'b', 'c', 'd', 'e']
y[3:5:1] = ['a', 'b', 'c', 'd', 'e']
assert x == [0, 1, 2, 'a', 'b', 'c', 'd', 'e', 5, 6, 7, 8, 9]
assert x == y
x = a[:]
x[3:5] = ['a']
assert x == [0, 1, 2, 'a', 5, 6, 7, 8, 9]
# assign empty to non stepped empty slice does nothing
x = a[:]
y = a[:]
x[5:2] = []
y[5:2:1] = []
assert x == a
assert y == a
# assign empty to non stepped slice removes elems
x = a[:]
y = a[:]
x[2:8] = []
y[2:8:1] = []
assert x == [0, 1, 8, 9]
assert x == y
# make sure refrences are assigned correctly
yy = []
x = a[:]
y = a[:]
x[3:5] = ['a', 'b', 'c', 'd', yy]
y[3:5:1] = ['a', 'b', 'c', 'd', yy]
assert x == [0, 1, 2, 'a', 'b', 'c', 'd', [], 5, 6, 7, 8, 9]
assert x == y
yy.append(100)
assert x == [0, 1, 2, 'a', 'b', 'c', 'd', [100], 5, 6, 7, 8, 9]
assert x == y
assert x[7] == yy
assert x[7] == [100]
assert y[7] == yy
assert y[7] == [100]
# no zero step
def no_zero_step_set():
x = [1, 2, 3, 4, 5]
x[0:4:0] = [11, 12, 13, 14, 15]
assert_raises(ValueError, no_zero_step_set)
# stepped slice index
# forward slice
x = a[:]
x[2:8:2] = ['a', 'b', 'c']
assert x == [0, 1, 'a', 3, 'b', 5, 'c', 7, 8, 9]
x = a[:]
y = a[:]
z = a[:]
zz = a[:]
c = ['a', 'b', 'c', 'd', 'e']
x[::2] = c
y[-10::2] = c
z[0:10:2] = c
zz[-13:13:2] = c # slice indexes will be truncated to bounds
assert x == ['a', 1, 'b', 3, 'c', 5, 'd', 7, 'e', 9]
assert x == y
assert x == z
assert x == zz
# backward slice
x = a[:]
x[8:2:-2] = ['a', 'b', 'c']
assert x == [0, 1, 2, 3, 'c', 5, 'b', 7, 'a', 9]
x = a[:]
y = a[:]
z = a[:]
zz = a[:]
c = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
x[::-1] = c
y[9:-11:-1] = c
z[9::-1] = c
zz[11:-13:-1] = c # slice indexes will be truncated to bounds
assert x == ['j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
assert x == y
assert x == z
assert x == zz
# step size bigger than len
x = a[:]
x[::200] = ['a']
assert x == ['a', 1, 2, 3, 4, 5, 6, 7, 8, 9]
x = a[:]
x[5::200] = ['a']
assert x == [0, 1, 2, 3, 4, 'a', 6, 7, 8, 9]
# bad stepped slices
def stepped_slice_assign_too_big():
x = [0, 1, 2, 3, 4]
x[::2] = ['a', 'b', 'c', 'd']
assert_raises(ValueError, stepped_slice_assign_too_big)
def stepped_slice_assign_too_small():
x = [0, 1, 2, 3, 4]
x[::2] = ['a', 'b']
assert_raises(ValueError, stepped_slice_assign_too_small)
# must assign iter t0 slice
def must_assign_iter_to_slice():
x = [0, 1, 2, 3, 4]
x[::2] = 42
assert_raises(TypeError, must_assign_iter_to_slice)
# other iterables?
a = list(range(10))
# string
x = a[:]
x[3:8] = "abcdefghi"
assert x == [0, 1, 2, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 8, 9]
# tuple
x = a[:]
x[3:8] = (11, 12, 13, 14, 15)
assert x == [0, 1, 2, 11, 12, 13, 14, 15, 8, 9]
# class
# __next__
class CIterNext:
def __init__(self, sec=(1, 2, 3)):
self.sec = sec
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.sec):
raise StopIteration
v = self.sec[self.index]
self.index += 1
return v
x = list(range(10))
x[3:8] = CIterNext()
assert x == [0, 1, 2, 1, 2, 3, 8, 9]
# __iter__ yield
class CIter:
def __init__(self, sec=(1, 2, 3)):
self.sec = sec
def __iter__(self):
for n in self.sec:
yield n
x = list(range(10))
x[3:8] = CIter()
assert x == [0, 1, 2, 1, 2, 3, 8, 9]
# __getitem but no __iter__ sequence
class CGetItem:
def __init__(self, sec=(1, 2, 3)):
self.sec = sec
def __getitem__(self, sub):
return self.sec[sub]
x = list(range(10))
x[3:8] = CGetItem()
assert x == [0, 1, 2, 1, 2, 3, 8, 9]
# iter raises error
class CIterError:
def __iter__(self):
for i in range(10):
if i > 5:
raise RuntimeError
yield i
def bad_iter_assign():
x = list(range(10))
x[3:8] = CIterError()
assert_raises(RuntimeError, bad_iter_assign)
# slice assign when step or stop is -1
a = list(range(10))
x = a[:]
x[-1:-5:-1] = ['a', 'b', 'c', 'd']
assert x == [0, 1, 2, 3, 4, 5, 'd', 'c', 'b', 'a']
x = a[:]
x[-5:-1:-1] = []
assert x == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# is step != 1 and start or stop of slice == -1
x = list(range(10))
del x[-1:-5:-1]
assert x == [0, 1, 2, 3, 4, 5]
x = list(range(10))
del x[-5:-1:-1]
+31
View File
@@ -49,27 +49,35 @@ assert len(a) == 0
assert set([1,2,3]).union(set([4,5])) == set([1,2,3,4,5])
assert set([1,2,3]).union(set([1,2,3,4,5])) == set([1,2,3,4,5])
assert set([1,2,3]).union([1,2,3,4,5]) == set([1,2,3,4,5])
assert set([1,2,3]) | set([4,5]) == set([1,2,3,4,5])
assert set([1,2,3]) | set([1,2,3,4,5]) == set([1,2,3,4,5])
assert_raises(TypeError, lambda: set([1,2,3]) | [1,2,3,4,5])
assert set([1,2,3]).intersection(set([1,2])) == set([1,2])
assert set([1,2,3]).intersection(set([5,6])) == set([])
assert set([1,2,3]).intersection([1,2]) == set([1,2])
assert set([1,2,3]) & set([4,5]) == set([])
assert set([1,2,3]) & set([1,2,3,4,5]) == set([1,2,3])
assert_raises(TypeError, lambda: set([1,2,3]) & [1,2,3,4,5])
assert set([1,2,3]).difference(set([1,2])) == set([3])
assert set([1,2,3]).difference(set([5,6])) == set([1,2,3])
assert set([1,2,3]).difference([1,2]) == set([3])
assert set([1,2,3]) - set([4,5]) == set([1,2,3])
assert set([1,2,3]) - set([1,2,3,4,5]) == set([])
assert_raises(TypeError, lambda: set([1,2,3]) - [1,2,3,4,5])
assert set([1,2,3]).symmetric_difference(set([1,2])) == set([3])
assert set([1,2,3]).symmetric_difference(set([5,6])) == set([1,2,3,5,6])
assert set([1,2,3]).symmetric_difference([1,2]) == set([3])
assert set([1,2,3]) ^ set([4,5]) == set([1,2,3,4,5])
assert set([1,2,3]) ^ set([1,2,3,4,5]) == set([4,5])
assert_raises(TypeError, lambda: set([1,2,3]) ^ [1,2,3,4,5])
assert_raises(TypeError, lambda: set([[]]))
assert_raises(TypeError, lambda: set().add([]))
@@ -111,6 +119,8 @@ a |= set([3,4,5])
assert a == set([1,2,3,4,5])
with assertRaises(TypeError):
a |= 1
with assertRaises(TypeError):
a |= [1,2,3]
a = set([1,2,3])
a.intersection_update([2,3,4,5])
@@ -122,6 +132,8 @@ a &= set([2,3,4,5])
assert a == set([2,3])
with assertRaises(TypeError):
a &= 1
with assertRaises(TypeError):
a &= [1,2,3]
a = set([1,2,3])
a.difference_update([3,4,5])
@@ -133,6 +145,8 @@ a -= set([3,4,5])
assert a == set([1,2])
with assertRaises(TypeError):
a -= 1
with assertRaises(TypeError):
a -= [1,2,3]
a = set([1,2,3])
a.symmetric_difference_update([3,4,5])
@@ -144,6 +158,8 @@ a ^= set([3,4,5])
assert a == set([1,2,4,5])
with assertRaises(TypeError):
a ^= 1
with assertRaises(TypeError):
a ^= [1,2,3]
# frozen set
@@ -181,30 +197,45 @@ assert b == a
assert frozenset([1,2,3]).union(frozenset([4,5])) == frozenset([1,2,3,4,5])
assert frozenset([1,2,3]).union(frozenset([1,2,3,4,5])) == frozenset([1,2,3,4,5])
assert frozenset([1,2,3]).union([1,2,3,4,5]) == frozenset([1,2,3,4,5])
assert frozenset([1,2,3]) | frozenset([4,5]) == frozenset([1,2,3,4,5])
assert frozenset([1,2,3]) | frozenset([1,2,3,4,5]) == frozenset([1,2,3,4,5])
assert_raises(TypeError, lambda: frozenset([1,2,3]) | [1,2,3,4,5])
assert frozenset([1,2,3]).intersection(frozenset([1,2])) == frozenset([1,2])
assert frozenset([1,2,3]).intersection(frozenset([5,6])) == frozenset([])
assert frozenset([1,2,3]).intersection([1,2]) == frozenset([1,2])
assert frozenset([1,2,3]) & frozenset([4,5]) == frozenset([])
assert frozenset([1,2,3]) & frozenset([1,2,3,4,5]) == frozenset([1,2,3])
assert_raises(TypeError, lambda: frozenset([1,2,3]) & [1,2,3,4,5])
assert frozenset([1,2,3]).difference(frozenset([1,2])) == frozenset([3])
assert frozenset([1,2,3]).difference(frozenset([5,6])) == frozenset([1,2,3])
assert frozenset([1,2,3]).difference([1,2]) == frozenset([3])
assert frozenset([1,2,3]) - frozenset([4,5]) == frozenset([1,2,3])
assert frozenset([1,2,3]) - frozenset([1,2,3,4,5]) == frozenset([])
assert_raises(TypeError, lambda: frozenset([1,2,3]) - [1,2,3,4,5])
assert frozenset([1,2,3]).symmetric_difference(frozenset([1,2])) == frozenset([3])
assert frozenset([1,2,3]).symmetric_difference(frozenset([5,6])) == frozenset([1,2,3,5,6])
assert frozenset([1,2,3]).symmetric_difference([1,2]) == frozenset([3])
assert frozenset([1,2,3]) ^ frozenset([4,5]) == frozenset([1,2,3,4,5])
assert frozenset([1,2,3]) ^ frozenset([1,2,3,4,5]) == frozenset([4,5])
assert_raises(TypeError, lambda: frozenset([1,2,3]) ^ [1,2,3,4,5])
assert_raises(TypeError, lambda: frozenset([[]]))
a = frozenset([1,2,3])
b = set()
for e in a:
assert e == 1 or e == 2 or e == 3
b.add(e)
assert a == b
# set and frozen set
assert frozenset([1,2,3]).union(set([4,5])) == frozenset([1,2,3,4,5])
assert set([1,2,3]).union(frozenset([4,5])) == set([1,2,3,4,5])
+9
View File
@@ -0,0 +1,9 @@
# Test several statement types
# small ones, seperated by ';':
if True:
5;4;
b=4
b;
+44 -1
View File
@@ -2,7 +2,36 @@ import os
from testutils import assert_raises
assert os.open('README.md', 0) > 0
fd = os.open('README.md', 0)
assert fd > 0
os.close(fd)
assert_raises(OSError, lambda: os.read(fd, 10))
FNAME = "test_file_that_no_one_will_have_on_disk"
CONTENT = b"testing"
CONTENT2 = b"rustpython"
CONTENT3 = b"BOYA"
class TestWithFile():
def __enter__(self):
open(FNAME, "wb")
return FNAME
def __exit__(self, exc_type, exc_val, exc_tb):
os.remove(FNAME)
with TestWithFile() as fname:
fd = os.open(fname, 1)
assert os.write(fd, CONTENT2) == len(CONTENT2)
assert os.write(fd, CONTENT3) == len(CONTENT3)
os.close(fd)
fd = os.open(fname, 0)
assert os.read(fd, len(CONTENT2)) == CONTENT2
assert os.read(fd, len(CONTENT3)) == CONTENT3
os.close(fd)
assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', 0))
@@ -11,3 +40,17 @@ assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', 0))
assert os.O_RDONLY == 0
assert os.O_WRONLY == 1
assert os.O_RDWR == 2
ENV_KEY = "TEST_ENV_VAR"
ENV_VALUE = "value"
assert os.getenv(ENV_KEY) == None
assert ENV_KEY not in os.environ
assert os.getenv(ENV_KEY, 5) == 5
os.environ[ENV_KEY] = ENV_VALUE
assert ENV_KEY in os.environ
assert os.getenv(ENV_KEY) == ENV_VALUE
del os.environ[ENV_KEY]
os.unsetenv(ENV_KEY)
assert ENV_KEY not in os.environ
assert os.getenv(ENV_KEY) == None
+23
View File
@@ -55,6 +55,29 @@ assert b.rstrip() == ' hallo'
c = 'hallo'
assert c.capitalize() == 'Hallo'
assert c.center(11, '-') == '---hallo---'
assert ["koki".center(i, "|") for i in range(3, 10)] == [
"koki",
"koki",
"|koki",
"|koki|",
"||koki|",
"||koki||",
"|||koki||",
]
assert ["kok".center(i, "|") for i in range(2, 10)] == [
"kok",
"kok",
"kok|",
"|kok|",
"|kok||",
"||kok||",
"||kok|||",
"|||kok|||",
]
# assert c.isascii()
assert c.index('a') == 1
assert c.rindex('l') == 3
+39
View File
@@ -0,0 +1,39 @@
import abc
from testutils import assertRaises
class CustomInterface(abc.ABC):
@abc.abstractmethod
def a(self):
pass
@classmethod
def __subclasshook__(cls, subclass):
return NotImplemented
# TODO raise an error if there are in any abstract methods not fulfilled
# with assertRaises(TypeError):
# CustomInterface()
class Concrete:
def a(self):
pass
CustomInterface.register(Concrete)
class SubConcrete(Concrete):
pass
assert issubclass(Concrete, CustomInterface)
assert issubclass(SubConcrete, CustomInterface)
assert not issubclass(tuple, CustomInterface)
assert isinstance(Concrete(), CustomInterface)
assert isinstance(SubConcrete(), CustomInterface)
assert not isinstance((), CustomInterface)
+73 -1
View File
@@ -1,4 +1,4 @@
from testutils import assertRaises
try:
raise BaseException()
@@ -95,3 +95,75 @@ except TypeError as ex:
l.append(3)
print('boom', type(ex))
assert l == [1, 3]
cause = None
try:
try:
raise ZeroDivisionError
except ZeroDivisionError as ex:
assert ex.__cause__ == None
cause = ex
raise NameError from ex
except NameError as ex2:
assert ex2.__cause__ == cause
assert ex2.__context__ == cause
try:
raise ZeroDivisionError from None
except ZeroDivisionError as ex:
assert ex.__cause__ == None
try:
raise ZeroDivisionError
except ZeroDivisionError as ex:
assert ex.__cause__ == None
with assertRaises(TypeError):
raise ZeroDivisionError from 5
try:
raise ZeroDivisionError from NameError
except ZeroDivisionError as ex:
assert type(ex.__cause__) == NameError
with assertRaises(NameError):
try:
raise NameError
except:
raise
with assertRaises(RuntimeError):
raise
context = None
try:
try:
raise ZeroDivisionError
except ZeroDivisionError as ex:
assert ex.__context__ == None
context = ex
raise NameError
except NameError as ex2:
assert ex2.__context__ == context
assert type(ex2.__context__) == ZeroDivisionError
try:
raise ZeroDivisionError
except ZeroDivisionError as ex:
assert ex.__context__ == None
try:
raise ZeroDivisionError from NameError
except ZeroDivisionError as ex:
assert type(ex.__cause__) == NameError
assert ex.__context__ == None
try:
try:
raise ZeroDivisionError
except ZeroDivisionError as ex:
pass
finally:
raise NameError
except NameError as ex2:
assert ex2.__context__ == None
+4
View File
@@ -34,3 +34,7 @@ class Foo(object):
foo = Foo()
assert (foo,) == (foo,)
a = (1, 2, 3)
a += 1,
assert a == (1, 2, 3, 1)
+16 -1
View File
@@ -1,4 +1,5 @@
from _weakref import ref
from _weakref import ref, proxy
from testutils import assert_raises
class X:
@@ -11,3 +12,17 @@ b = ref(a)
assert callable(b)
assert b() is a
class G:
def __init__(self, h):
self.h = h
g = G(5)
p = proxy(g)
assert p.h == 5
del g
assert_raises(ReferenceError, lambda: p.h)
-878
View File
@@ -1,878 +0,0 @@
expected_methods = []
# TODO: using tuple could have been better
expected_methods.append({'name': 'bool', 'methods': [
'__abs__',
'__add__',
'__and__',
'__bool__',
'__ceil__',
'__class__',
'__delattr__',
'__dir__',
'__divmod__',
'__doc__',
'__eq__',
'__float__',
'__floor__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getnewargs__',
'__gt__',
'__hash__',
'__index__',
'__init__',
'__init_subclass__',
'__int__',
'__invert__',
'__le__',
'__lshift__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__or__',
'__pos__',
'__pow__',
'__radd__',
'__rand__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rlshift__',
'__rmod__',
'__rmul__',
'__ror__',
'__round__',
'__rpow__',
'__rrshift__',
'__rshift__',
'__rsub__',
'__rtruediv__',
'__rxor__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'__trunc__',
'__xor__',
'bit_length',
'conjugate',
'denominator',
'from_bytes',
'imag',
'numerator',
'real',
'to_bytes',
], 'type': bool})
expected_methods.append({'name': 'bytearray', 'type': bytearray, 'methods': [
'__add__',
'__alloc__',
'__class__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__iadd__',
'__imul__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmod__',
'__rmul__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'append',
'capitalize',
'center',
'clear',
'copy',
'count',
'decode',
'endswith',
'expandtabs',
'extend',
'find',
'fromhex',
'hex',
'index',
'insert',
'isalnum',
'isalpha',
'isascii',
'isdigit',
'islower',
'isspace',
'istitle',
'isupper',
'join',
'ljust',
'lower',
'lstrip',
'maketrans',
'partition',
'pop',
'remove',
'replace',
'reverse',
'rfind',
'rindex',
'rjust',
'rpartition',
'rsplit',
'rstrip',
'split',
'splitlines',
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill',
]})
expected_methods.append({'name': 'bytes', 'type': bytes, 'methods': [
'__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmod__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'capitalize',
'center',
'count',
'decode',
'endswith',
'expandtabs',
'find',
'fromhex',
'hex',
'index',
'isalnum',
'isalpha',
'isascii',
'isdigit',
'islower',
'isspace',
'istitle',
'isupper',
'join',
'ljust',
'lower',
'lstrip',
'maketrans',
'partition',
'replace',
'rfind',
'rindex',
'rjust',
'rpartition',
'rsplit',
'rstrip',
'split',
'splitlines',
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill',
]})
expected_methods.append({'name': 'complex', 'type': complex, 'methods': [
'__abs__',
'__add__',
'__bool__',
'__class__',
'__delattr__',
'__dir__',
'__divmod__',
'__doc__',
'__eq__',
'__float__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__int__',
'__le__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__pos__',
'__pow__',
'__radd__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rmod__',
'__rmul__',
'__rpow__',
'__rsub__',
'__rtruediv__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'conjugate',
'imag',
'real',
]})
expected_methods.append({'name': 'dict', 'type': dict, 'methods': [
'__class__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'clear',
'copy',
'fromkeys',
'get',
'items',
'keys',
'pop',
'popitem',
'setdefault',
'update',
'values',
]})
expected_methods.append({'name': 'float','type':float,'methods':[
'__abs__',
'__add__',
'__bool__',
'__class__',
'__delattr__',
'__dir__',
'__divmod__',
'__doc__',
'__eq__',
'__float__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getformat__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__int__',
'__le__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__pos__',
'__pow__',
'__radd__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rmod__',
'__rmul__',
'__round__',
'__rpow__',
'__rsub__',
'__rtruediv__',
'__set_format__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'__trunc__',
'as_integer_ratio',
'conjugate',
'fromhex',
'hex',
'imag',
'is_integer',
'real',
]})
expected_methods.append({'name': 'frozenset','type':frozenset, 'methods': [
'__and__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__ne__',
'__new__',
'__or__',
'__rand__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__ror__',
'__rsub__',
'__rxor__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__xor__',
'copy',
'difference',
'intersection',
'isdisjoint',
'issubset',
'issuperset',
'symmetric_difference',
'union',
]})
expected_methods.append({'name': 'int', 'type':int, 'methods': [
'__abs__',
'__add__',
'__and__',
'__bool__',
'__ceil__',
'__class__',
'__delattr__',
'__dir__',
'__divmod__',
'__doc__',
'__eq__',
'__float__',
'__floor__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getnewargs__',
'__gt__',
'__hash__',
'__index__',
'__init__',
'__init_subclass__',
'__int__',
'__invert__',
'__le__',
'__lshift__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__or__',
'__pos__',
'__pow__',
'__radd__',
'__rand__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rlshift__',
'__rmod__',
'__rmul__',
'__ror__',
'__round__',
'__rpow__',
'__rrshift__',
'__rshift__',
'__rsub__',
'__rtruediv__',
'__rxor__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'__trunc__',
'__xor__',
'bit_length',
'conjugate',
'denominator',
'from_bytes',
'imag',
'numerator',
'real',
'to_bytes',
]})
expected_methods.append({'name': 'iter','type':iter,'methods':[
'__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__length_hint__',
'__lt__',
'__ne__',
'__new__',
'__next__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setstate__',
'__sizeof__',
'__str__',
'__subclasshook__',
]})
expected_methods.append({'name': 'list','type':list,'methods':[
'__add__',
'__class__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__iadd__',
'__imul__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__rmul__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'append',
'clear',
'copy',
'count',
'extend',
'index',
'insert',
'pop',
'remove',
'reverse',
'sort',
]})
expected_methods.append({'name': 'memoryview','type':memoryview,'methods':[
'__class__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__enter__',
'__eq__',
'__exit__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__len__',
'__lt__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'c_contiguous',
'cast',
'contiguous',
'f_contiguous',
'format',
'hex',
'itemsize',
'nbytes',
'ndim',
'obj',
'readonly',
'release',
'shape',
'strides',
'suboffsets',
'tobytes',
'tolist',
]})
expected_methods.append({'name': 'range','type':range,'methods':[
'__bool__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'count',
'index',
'start',
'step',
'stop',
]})
expected_methods.append({'name': 'set','type':set,'methods':[
'__and__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__iand__',
'__init__',
'__init_subclass__',
'__ior__',
'__isub__',
'__iter__',
'__ixor__',
'__le__',
'__len__',
'__lt__',
'__ne__',
'__new__',
'__or__',
'__rand__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__ror__',
'__rsub__',
'__rxor__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__xor__',
'add',
'clear',
'copy',
'difference',
'difference_update',
'discard',
'intersection',
'intersection_update',
'isdisjoint',
'issubset',
'issuperset',
'pop',
'remove',
'symmetric_difference',
'symmetric_difference_update',
'union',
'update',
]})
expected_methods.append({'name': 'string','type':str,'methods':[
'__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmod__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'capitalize',
'casefold',
'center',
'count',
'encode',
'endswith',
'expandtabs',
'find',
'format',
'format_map',
'index',
'isalnum',
'isalpha',
'isascii',
'isdecimal',
'isdigit',
'isidentifier',
'islower',
'isnumeric',
'isprintable',
'isspace',
'istitle',
'isupper',
'join',
'ljust',
'lower',
'lstrip',
'maketrans',
'partition',
'replace',
'rfind',
'rindex',
'rjust',
'rpartition',
'rsplit',
'rstrip',
'split',
'splitlines',
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill'
]})
expected_methods.append({'name': 'tuple','type':tuple, 'methods': [
'__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'count',
'index',
]})
expected_methods.append({'name': 'object', 'type':object, 'methods':[
'__repr__',
'__hash__',
'__str__',
'__getattribute__',
'__setattr__',
'__delattr__',
'__lt__',
'__le__',
'__eq__',
'__ne__',
'__gt__',
'__ge__',
'__init__',
'__new__',
'__reduce_ex__',
'__reduce__',
'__subclasshook__',
'__init_subclass__',
'__format__',
'__sizeof__',
'__dir__',
'__class__',
'__doc__'
]})
not_implemented = []
for item in expected_methods:
for method in item['methods']:
try:
if not hasattr(item['type'], method):
not_implemented.append((item['name'], method))
except NameError:
not_implemented.append((item['name'], method))
for r in not_implemented:
print(r[0], ".", r[1])
else:
print("Not much \\o/")
+68 -32
View File
@@ -1,4 +1,3 @@
# This is a python unittest class automatically populating with all tests
# in the tests folder.
@@ -11,6 +10,8 @@ import logging
import subprocess
import contextlib
import enum
from pathlib import Path
import shutil
import compile_code
@@ -19,15 +20,13 @@ class _TestType(enum.Enum):
functional = 1
logger = logging.getLogger('tests')
ROOT_DIR = '..'
TEST_ROOT = os.path.abspath(os.path.join(ROOT_DIR, 'tests'))
TEST_DIRS = {
_TestType.functional: os.path.join(TEST_ROOT, 'snippets'),
}
CPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR, 'py_code_object'))
logger = logging.getLogger("tests")
ROOT_DIR = ".."
TEST_ROOT = os.path.abspath(os.path.join(ROOT_DIR, "tests"))
TEST_DIRS = {_TestType.functional: os.path.join(TEST_ROOT, "snippets")}
CPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR, "py_code_object"))
RUSTPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR))
RUSTPYTHON_LIB_DIR = os.path.abspath(os.path.join(ROOT_DIR, "Lib"))
@contextlib.contextmanager
def pushd(path):
@@ -38,12 +37,12 @@ def pushd(path):
def perform_test(filename, method, test_type):
logger.info('Running %s via %s', filename, method)
if method == 'cpython':
logger.info("Running %s via %s", filename, method)
if method == "cpython":
run_via_cpython(filename)
elif method == 'cpython_bytecode':
elif method == "cpython_bytecode":
run_via_cpython_bytecode(filename, test_type)
elif method == 'rustpython':
elif method == "rustpython":
run_via_rustpython(filename, test_type)
else:
raise NotImplementedError(method)
@@ -57,27 +56,28 @@ def run_via_cpython(filename):
def run_via_cpython_bytecode(filename, test_type):
# Step1: Create bytecode file:
bytecode_filename = filename + '.bytecode'
with open(bytecode_filename, 'w') as f:
bytecode_filename = filename + ".bytecode"
with open(bytecode_filename, "w") as f:
compile_code.compile_to_bytecode(filename, out_file=f)
# Step2: run cpython bytecode:
env = os.environ.copy()
env['RUST_LOG'] = 'info,cargo=error,jobserver=error'
env['RUST_BACKTRACE'] = '1'
env["RUST_LOG"] = "info,cargo=error,jobserver=error"
env["RUST_BACKTRACE"] = "1"
with pushd(CPYTHON_RUNNER_DIR):
subprocess.check_call(['cargo', 'run', bytecode_filename], env=env)
subprocess.check_call(["cargo", "run", bytecode_filename], env=env)
def run_via_rustpython(filename, test_type):
env = os.environ.copy()
env['RUST_LOG'] = 'info,cargo=error,jobserver=error'
env['RUST_BACKTRACE'] = '1'
env['PYTHONPATH'] = RUSTPYTHON_LIB_DIR
target = 'release'
if env.get('CODE_COVERAGE', 'false') == 'true':
target = 'debug'
binary = os.path.abspath(os.path.join(ROOT_DIR, 'target', target, 'rustpython'))
target = "release"
if env.get("CODE_COVERAGE", "false") == "true":
target = "debug"
binary = os.path.abspath(os.path.join(ROOT_DIR, "target", target, "rustpython"))
subprocess.check_call([binary, filename], env=env)
@@ -85,15 +85,15 @@ def run_via_rustpython(filename, test_type):
def create_test_function(cls, filename, method, test_type):
""" Create a test function for a single snippet """
core_test_directory, snippet_filename = os.path.split(filename)
test_function_name = 'test_{}_'.format(method) \
+ os.path.splitext(snippet_filename)[0] \
.replace('.', '_').replace('-', '_')
test_function_name = "test_{}_".format(method) + os.path.splitext(snippet_filename)[
0
].replace(".", "_").replace("-", "_")
def test_function(self):
perform_test(filename, method, test_type)
if hasattr(cls, test_function_name):
raise ValueError('Duplicate test case {}'.format(test_function_name))
raise ValueError("Duplicate test case {}".format(test_function_name))
setattr(cls, test_function_name, test_function)
@@ -103,25 +103,61 @@ def populate(method):
for test_type, filename in get_test_files():
create_test_function(cls, filename, method, test_type)
return cls
return wrapper
def get_test_files():
""" Retrieve test files """
for test_type, test_dir in TEST_DIRS.items():
for filepath in sorted(glob.iglob(os.path.join(test_dir, '*.py'))):
for filepath in sorted(glob.iglob(os.path.join(test_dir, "*.py"))):
filename = os.path.split(filepath)[1]
if filename.startswith('xfail_'):
if filename.startswith("xfail_"):
continue
yield test_type, os.path.abspath(filepath)
@populate('cpython')
def generate_slices(path):
# loop used to build slices_res.py with cpython
ll = [0, 1, 2, 3]
start = list(range(-7, 7))
end = list(range(-7, 7))
step = list(range(-5, 5))
step.pop(step.index(0))
for i in [start, end, step]:
i.append(None)
slices_res = []
for s in start:
for e in end:
for t in step:
slices_res.append(ll[s:e:t])
path.write_text(
"SLICES_RES={}\nSTART= {}\nEND= {}\nSTEP= {}\nLL={}\n".format(
slices_res, start, end, step, ll
)
)
@populate("cpython")
# @populate('cpython_bytecode')
@populate('rustpython')
@populate("rustpython")
class SampleTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
subprocess.check_call(['cargo', 'build'])
subprocess.check_call(['cargo', 'build', '--release'])
# Here add resource files
cls.slices_resource_path = Path(TEST_DIRS[_TestType.functional]) / "cpython_generated_slices.py"
if cls.slices_resource_path.exists():
cls.slices_resource_path.unlink()
generate_slices(cls.slices_resource_path)
# cargo stuff
subprocess.check_call(["cargo", "build"])
subprocess.check_call(["cargo", "build", "--release"])
@classmethod
def tearDownClass(cls):
cls.slices_resource_path.unlink()
+1
View File
@@ -26,3 +26,4 @@ caseless = "0.2.1"
unicode-segmentation = "1.2.1"
lazy_static = "^1.0.1"
lexical = "2.0.0"
itertools = "^0.8.0"
+51 -30
View File
@@ -18,9 +18,9 @@ use crate::obj::objstr::{self, PyString, PyStringRef};
use crate::obj::objtype::{self, PyClassRef};
use crate::frame::Scope;
use crate::function::{Args, OptionalArg, PyFuncArgs};
use crate::function::{Args, KwArgs, OptionalArg, PyFuncArgs};
use crate::pyobject::{
DictProtocol, IdProtocol, PyContext, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject,
IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject,
TypeProtocol,
};
use crate::vm::VirtualMachine;
@@ -102,10 +102,8 @@ fn builtin_compile(
}
};
compile::compile(vm, &source, &mode, filename.value.to_string()).map_err(|err| {
let syntax_error = vm.context().exceptions.syntax_error.clone();
vm.new_exception(syntax_error, err.to_string())
})
compile::compile(vm, &source, &mode, filename.value.to_string())
.map_err(|err| vm.new_syntax_error(&err))
}
fn builtin_delattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> PyResult<()> {
@@ -151,10 +149,8 @@ fn builtin_eval(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
let source = objstr::get_value(source);
// TODO: fix this newline bug:
let source = format!("{}\n", source);
compile::compile(vm, &source, &mode, "<string>".to_string()).map_err(|err| {
let syntax_error = vm.context().exceptions.syntax_error.clone();
vm.new_exception(syntax_error, err.to_string())
})?
compile::compile(vm, &source, &mode, "<string>".to_string())
.map_err(|err| vm.new_syntax_error(&err))?
} else {
return Err(vm.new_type_error("code argument must be str or code object".to_string()));
};
@@ -181,10 +177,8 @@ fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
let source = objstr::get_value(source);
// TODO: fix this newline bug:
let source = format!("{}\n", source);
compile::compile(vm, &source, &mode, "<string>".to_string()).map_err(|err| {
let syntax_error = vm.context().exceptions.syntax_error.clone();
vm.new_exception(syntax_error, err.to_string())
})?
compile::compile(vm, &source, &mode, "<string>".to_string())
.map_err(|err| vm.new_syntax_error(&err))?
} else if let Ok(code_obj) = PyCodeRef::try_from_object(vm, source.clone()) {
code_obj
} else {
@@ -667,8 +661,15 @@ fn builtin_import(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
// builtin_vars
pub fn make_module(ctx: &PyContext) -> PyObjectRef {
let py_mod = py_module!(ctx, "__builtins__", {
pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) {
let ctx = &vm.ctx;
#[cfg(target_arch = "wasm32")]
let open = vm.ctx.none();
#[cfg(not(target_arch = "wasm32"))]
let open = vm.ctx.new_rustfunc(io_open);
extend_module!(vm, module, {
//set __name__ fixes: https://github.com/RustPython/RustPython/issues/146
"__name__" => ctx.new_str(String::from("__main__")),
@@ -714,6 +715,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef {
"min" => ctx.new_rustfunc(builtin_min),
"object" => ctx.object(),
"oct" => ctx.new_rustfunc(builtin_oct),
"open" => open,
"ord" => ctx.new_rustfunc(builtin_ord),
"next" => ctx.new_rustfunc(builtin_next),
"pow" => ctx.new_rustfunc(builtin_pow),
@@ -748,6 +750,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef {
"NameError" => ctx.exceptions.name_error.clone(),
"OverflowError" => ctx.exceptions.overflow_error.clone(),
"RuntimeError" => ctx.exceptions.runtime_error.clone(),
"ReferenceError" => ctx.exceptions.reference_error.clone(),
"NotImplementedError" => ctx.exceptions.not_implemented_error.clone(),
"TypeError" => ctx.exceptions.type_error.clone(),
"ValueError" => ctx.exceptions.value_error.clone(),
@@ -758,19 +761,33 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef {
"ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(),
"KeyError" => ctx.exceptions.key_error.clone(),
"OSError" => ctx.exceptions.os_error.clone(),
// Warnings
"Warning" => ctx.exceptions.warning.clone(),
"BytesWarning" => ctx.exceptions.bytes_warning.clone(),
"UnicodeWarning" => ctx.exceptions.unicode_warning.clone(),
"DeprecationWarning" => ctx.exceptions.deprecation_warning.clone(),
"PendingDeprecationWarning" => ctx.exceptions.pending_deprecation_warning.clone(),
"FutureWarning" => ctx.exceptions.future_warning.clone(),
"ImportWarning" => ctx.exceptions.import_warning.clone(),
"SyntaxWarning" => ctx.exceptions.syntax_warning.clone(),
"ResourceWarning" => ctx.exceptions.resource_warning.clone(),
"RuntimeWarning" => ctx.exceptions.runtime_warning.clone(),
"UserWarning" => ctx.exceptions.user_warning.clone(),
});
#[cfg(not(target_arch = "wasm32"))]
ctx.set_attr(&py_mod, "open", ctx.new_rustfunc(io_open));
py_mod
}
pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult {
let function = args.shift();
let name_arg = args.shift();
let bases = args.args.clone();
let mut metaclass = if let Some(metaclass) = args.get_optional_kwarg("metaclass") {
pub fn builtin_build_class_(
function: PyObjectRef,
qualified_name: PyStringRef,
bases: Args<PyClassRef>,
mut kwargs: KwArgs,
vm: &VirtualMachine,
) -> PyResult {
let name = qualified_name.value.split('.').next_back().unwrap();
let name_obj = vm.new_str(name.to_string());
let mut metaclass = if let Some(metaclass) = kwargs.pop_kwarg("metaclass") {
PyClassRef::try_from_object(vm, metaclass)?
} else {
vm.get_type()
@@ -784,22 +801,26 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu
}
}
let bases = vm.context().new_tuple(bases);
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_arg.clone(), bases.clone()])?;
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();
vm.invoke_with_locals(function, cells.clone(), namespace.clone())?;
namespace.set_item("__name__", name_obj.clone(), vm)?;
namespace.set_item("__qualname__", qualified_name.into_object(), vm)?;
let class = vm.call_method(
metaclass.as_object(),
"__call__",
vec![name_arg, bases, namespace.into_object()],
vec![name_obj, bases, namespace.into_object()],
)?;
cells.set_item(&vm.ctx, "__class__", class.clone());
cells.set_item("__class__", class.clone(), vm)?;
Ok(class)
}
+13 -3
View File
@@ -31,12 +31,19 @@ pub struct CodeObject {
bitflags! {
pub struct FunctionOpArg: u8 {
const HAS_DEFAULTS = 0x01;
const HAS_KW_ONLY_DEFAULTS = 0x02;
const HAS_ANNOTATIONS = 0x04;
}
}
pub type Label = usize;
#[derive(Debug, Clone, PartialEq)]
pub enum NameScope {
Local,
Global,
}
/// A Single bytecode instruction.
#[derive(Debug, Clone, PartialEq)]
pub enum Instruction {
@@ -49,9 +56,11 @@ pub enum Instruction {
},
LoadName {
name: String,
scope: NameScope,
},
StoreName {
name: String,
scope: NameScope,
},
DeleteName {
name: String,
@@ -126,6 +135,7 @@ pub enum Instruction {
PopBlock,
Raise {
argc: usize,
in_exc: bool,
},
BuildString {
size: usize,
@@ -321,8 +331,8 @@ impl Instruction {
match self {
Import { name, symbol } => w!(Import, name, format!("{:?}", symbol)),
ImportStar { name } => w!(ImportStar, name),
LoadName { name } => w!(LoadName, name),
StoreName { name } => w!(StoreName, name),
LoadName { name, scope } => w!(LoadName, name, format!("{:?}", scope)),
StoreName { name, scope } => w!(StoreName, name, format!("{:?}", scope)),
DeleteName { name } => w!(DeleteName, name),
StoreSubscript => w!(StoreSubscript),
DeleteSubscript => w!(DeleteSubscript),
@@ -354,7 +364,7 @@ impl Instruction {
SetupWith { end } => w!(SetupWith, end),
CleanupWith { end } => w!(CleanupWith, end),
PopBlock => w!(PopBlock),
Raise { argc } => w!(Raise, argc),
Raise { argc, in_exc } => w!(Raise, argc, in_exc),
BuildString { size } => w!(BuildString, size),
BuildTuple { size, unpack } => w!(BuildTuple, size, unpack),
BuildList { size, unpack } => w!(BuildList, size, unpack),
+317 -98
View File
@@ -10,17 +10,21 @@ use crate::error::CompileError;
use crate::obj::objcode;
use crate::obj::objcode::PyCodeRef;
use crate::pyobject::PyValue;
use crate::symboltable::{make_symbol_table, statements_to_symbol_table, SymbolRole, SymbolScope};
use crate::VirtualMachine;
use num_complex::Complex64;
use rustpython_parser::{ast, parser};
struct Compiler {
code_object_stack: Vec<CodeObject>,
scope_stack: Vec<SymbolScope>,
nxt_label: usize,
source_path: Option<String>,
current_source_location: ast::Location,
current_qualified_path: Option<String>,
in_loop: bool,
in_function_def: bool,
in_exc_handler: bool,
}
/// Compile a given sourcecode into a bytecode object.
@@ -37,15 +41,18 @@ pub fn compile(
match mode {
Mode::Exec => {
let ast = parser::parse_program(source).map_err(CompileError::Parse)?;
compiler.compile_program(&ast)
let symbol_table = make_symbol_table(&ast);
compiler.compile_program(&ast, symbol_table)
}
Mode::Eval => {
let statement = parser::parse_statement(source).map_err(CompileError::Parse)?;
compiler.compile_statement_eval(&statement)
let symbol_table = statements_to_symbol_table(&statement);
compiler.compile_statement_eval(&statement, symbol_table)
}
Mode::Single => {
let ast = parser::parse_program(source).map_err(CompileError::Parse)?;
compiler.compile_program_single(&ast)
let symbol_table = make_symbol_table(&ast);
compiler.compile_program_single(&ast, symbol_table)
}
}?;
@@ -72,11 +79,14 @@ impl Compiler {
fn new() -> Self {
Compiler {
code_object_stack: Vec::new(),
scope_stack: Vec::new(),
nxt_label: 0,
source_path: None,
current_source_location: ast::Location::default(),
current_qualified_path: None,
in_loop: false,
in_function_def: false,
in_exc_handler: false,
}
}
@@ -94,11 +104,17 @@ impl Compiler {
}
fn pop_code_object(&mut self) -> CodeObject {
// self.scope_stack.pop().unwrap();
self.code_object_stack.pop().unwrap()
}
fn compile_program(&mut self, program: &ast::Program) -> Result<(), CompileError> {
fn compile_program(
&mut self,
program: &ast::Program,
symbol_scope: SymbolScope,
) -> Result<(), CompileError> {
let size_before = self.code_object_stack.len();
self.scope_stack.push(symbol_scope);
self.compile_statements(&program.statements)?;
assert!(self.code_object_stack.len() == size_before);
@@ -110,7 +126,12 @@ impl Compiler {
Ok(())
}
fn compile_program_single(&mut self, program: &ast::Program) -> Result<(), CompileError> {
fn compile_program_single(
&mut self,
program: &ast::Program,
symbol_scope: SymbolScope,
) -> Result<(), CompileError> {
self.scope_stack.push(symbol_scope);
for statement in &program.statements {
if let ast::Statement::Expression { ref expression } = statement.node {
self.compile_expression(expression)?;
@@ -129,15 +150,19 @@ impl Compiler {
// Compile statement in eval mode:
fn compile_statement_eval(
&mut self,
statement: &ast::LocatedStatement,
statements: &[ast::LocatedStatement],
symbol_table: SymbolScope,
) -> Result<(), CompileError> {
if let ast::Statement::Expression { ref expression } = statement.node {
self.compile_expression(expression)?;
self.emit(Instruction::ReturnValue);
Ok(())
} else {
Err(CompileError::ExpectExpr)
self.scope_stack.push(symbol_table);
for statement in statements {
if let ast::Statement::Expression { ref expression } = statement.node {
self.compile_expression(expression)?;
} else {
return Err(CompileError::ExpectExpr);
}
}
self.emit(Instruction::ReturnValue);
Ok(())
}
fn compile_statements(
@@ -150,6 +175,31 @@ impl Compiler {
Ok(())
}
fn scope_for_name(&self, name: &str) -> bytecode::NameScope {
let role = self.lookup_name(name);
match role {
SymbolRole::Global => bytecode::NameScope::Global,
_ => bytecode::NameScope::Local,
}
}
fn load_name(&mut self, name: &str) {
// TODO: if global, do something else!
let scope = self.scope_for_name(name);
self.emit(Instruction::LoadName {
name: name.to_string(),
scope,
});
}
fn store_name(&mut self, name: &str) {
let scope = self.scope_for_name(name);
self.emit(Instruction::StoreName {
name: name.to_string(),
scope,
});
}
fn compile_statement(&mut self, statement: &ast::LocatedStatement) -> Result<(), CompileError> {
trace!("Compiling {:?}", statement);
self.set_source_location(&statement.location);
@@ -173,15 +223,14 @@ impl Compiler {
name: module.clone(),
symbol: symbol.clone(),
});
self.emit(Instruction::StoreName {
name: match alias {
Some(alias) => alias.clone(),
None => match symbol {
Some(symbol) => symbol.clone(),
None => module.clone(),
},
let name = match alias {
Some(alias) => alias.clone(),
None => match symbol {
Some(symbol) => symbol.clone(),
None => module.clone(),
},
});
};
self.store_name(&name);
}
}
}
@@ -192,11 +241,8 @@ impl Compiler {
// Pop result of stack, since we not use it:
self.emit(Instruction::Pop);
}
ast::Statement::Global { names } => {
unimplemented!("global {:?}", names);
}
ast::Statement::Nonlocal { names } => {
unimplemented!("nonlocal {:?}", names);
ast::Statement::Global { .. } | ast::Statement::Nonlocal { .. } => {
// Handled during symbol table construction.
}
ast::Statement::If { test, body, orelse } => {
let end_label = self.new_label();
@@ -281,15 +327,24 @@ impl Compiler {
match cause {
Some(cause) => {
self.compile_expression(cause)?;
self.emit(Instruction::Raise { argc: 2 });
self.emit(Instruction::Raise {
argc: 2,
in_exc: self.in_exc_handler,
});
}
None => {
self.emit(Instruction::Raise { argc: 1 });
self.emit(Instruction::Raise {
argc: 1,
in_exc: self.in_exc_handler,
});
}
}
}
None => {
self.emit(Instruction::Raise { argc: 0 });
self.emit(Instruction::Raise {
argc: 0,
in_exc: self.in_exc_handler,
});
}
},
ast::Statement::Try {
@@ -319,6 +374,7 @@ impl Compiler {
self.compile_test(test, Some(end_label), None, EvalContext::Statement)?;
self.emit(Instruction::LoadName {
name: String::from("AssertionError"),
scope: bytecode::NameScope::Local,
});
match msg {
Some(e) => {
@@ -333,7 +389,10 @@ impl Compiler {
});
}
}
self.emit(Instruction::Raise { argc: 1 });
self.emit(Instruction::Raise {
argc: 1,
in_exc: self.in_exc_handler,
});
self.set_label(end_label);
}
ast::Statement::Break => {
@@ -353,19 +412,8 @@ impl Compiler {
return Err(CompileError::InvalidReturn);
}
match value {
Some(e) => {
let size = e.len();
for v in e {
self.compile_expression(v)?;
}
// If we have more than 1 return value, make it a tuple:
if size > 1 {
self.emit(Instruction::BuildTuple {
size,
unpack: false,
});
}
Some(v) => {
self.compile_expression(v)?;
}
None => {
self.emit(Instruction::LoadConst {
@@ -431,8 +479,8 @@ impl Compiler {
name: &str,
args: &ast::Parameters,
) -> Result<bytecode::FunctionOpArg, CompileError> {
let have_kwargs = !args.defaults.is_empty();
if have_kwargs {
let have_defaults = !args.defaults.is_empty();
if have_defaults {
// Construct a tuple:
let size = args.defaults.len();
for element in &args.defaults {
@@ -444,6 +492,25 @@ impl Compiler {
});
}
let mut num_kw_only_defaults = 0;
for (kw, default) in args.kwonlyargs.iter().zip(&args.kw_defaults) {
if let Some(default) = default {
self.emit(Instruction::LoadConst {
value: bytecode::Constant::String {
value: kw.arg.clone(),
},
});
self.compile_expression(default)?;
num_kw_only_defaults += 1;
}
}
if num_kw_only_defaults > 0 {
self.emit(Instruction::BuildMap {
size: num_kw_only_defaults,
unpack: false,
});
}
let line_number = self.get_source_line_number();
self.code_object_stack.push(CodeObject::new(
args.args.iter().map(|a| a.arg.clone()).collect(),
@@ -454,11 +521,15 @@ impl Compiler {
line_number,
name.to_string(),
));
self.enter_scope();
let mut flags = bytecode::FunctionOpArg::empty();
if have_kwargs {
if have_defaults {
flags |= bytecode::FunctionOpArg::HAS_DEFAULTS;
}
if num_kw_only_defaults > 0 {
flags |= bytecode::FunctionOpArg::HAS_KW_ONLY_DEFAULTS;
}
Ok(flags)
}
@@ -501,6 +572,8 @@ impl Compiler {
self.emit(Instruction::Jump { target: else_label });
// except handlers:
let was_in_exc_handler = self.in_exc_handler;
self.in_exc_handler = true;
self.set_label(handler_label);
// Exception is on top of stack now
handler_label = self.new_label();
@@ -514,6 +587,7 @@ impl Compiler {
// Check exception type:
self.emit(Instruction::LoadName {
name: String::from("isinstance"),
scope: bytecode::NameScope::Local,
});
self.emit(Instruction::Rotate { amount: 2 });
self.compile_expression(exc_type)?;
@@ -528,21 +602,16 @@ impl Compiler {
// We have a match, store in name (except x as y)
if let Some(alias) = &handler.name {
self.emit(Instruction::StoreName {
name: alias.clone(),
});
} else {
// Drop exception from top of stack:
self.emit(Instruction::Pop);
// Duplicate exception for context:
self.emit(Instruction::Duplicate);
self.store_name(alias);
}
} else {
// Catch all!
// Drop exception from top of stack:
self.emit(Instruction::Pop);
}
// Handler code:
self.compile_statements(&handler.body)?;
// Drop exception from top of stack:
self.emit(Instruction::Pop);
self.emit(Instruction::Jump {
target: finally_label,
});
@@ -563,7 +632,12 @@ impl Compiler {
if let Some(statements) = finalbody {
self.compile_statements(statements)?;
}
self.emit(Instruction::Raise { argc: 1 });
self.emit(Instruction::Raise {
argc: 0,
in_exc: true,
});
self.in_exc_handler = was_in_exc_handler;
// We successfully ran the try block:
// else:
@@ -577,7 +651,6 @@ impl Compiler {
if let Some(statements) = finalbody {
self.compile_statements(statements)?;
}
// unimplemented!();
Ok(())
}
@@ -596,8 +669,18 @@ impl Compiler {
let was_in_function_def = self.in_function_def;
self.in_loop = false;
self.in_function_def = true;
let old_qualified_path = self.current_qualified_path.clone();
let qualified_name = self.create_qualified_name(name, "");
self.current_qualified_path = Some(self.create_qualified_name(name, ".<locals>"));
self.prepare_decorators(decorator_list)?;
let mut flags = self.enter_function(name, args)?;
self.compile_statements(body)?;
let (new_body, doc_str) = get_doc(body);
self.compile_statements(new_body)?;
// Emit None at end:
self.emit(Instruction::LoadConst {
@@ -605,8 +688,7 @@ impl Compiler {
});
self.emit(Instruction::ReturnValue);
let code = self.pop_code_object();
self.prepare_decorators(decorator_list)?;
self.leave_scope();
// Prepare type annotations:
let mut num_annotations = 0;
@@ -651,17 +733,18 @@ impl Compiler {
});
self.emit(Instruction::LoadConst {
value: bytecode::Constant::String {
value: name.to_string(),
value: qualified_name,
},
});
// Turn code object into function object:
self.emit(Instruction::MakeFunction { flags });
self.store_docstring(doc_str);
self.apply_decorators(decorator_list);
self.emit(Instruction::StoreName {
name: name.to_string(),
});
self.store_name(name);
self.current_qualified_path = old_qualified_path;
self.in_loop = was_in_loop;
self.in_function_def = was_in_function_def;
Ok(())
@@ -677,6 +760,11 @@ impl Compiler {
) -> Result<(), CompileError> {
let was_in_loop = self.in_loop;
self.in_loop = false;
let old_qualified_path = self.current_qualified_path.clone();
let qualified_name = self.create_qualified_name(name, "");
self.current_qualified_path = Some(qualified_name.clone());
self.prepare_decorators(decorator_list)?;
self.emit(Instruction::LoadBuildClass);
let line_number = self.get_source_line_number();
@@ -689,13 +777,27 @@ impl Compiler {
line_number,
name.to_string(),
));
self.compile_statements(body)?;
self.enter_scope();
let (new_body, doc_str) = get_doc(body);
self.emit(Instruction::LoadName {
name: "__name__".to_string(),
scope: bytecode::NameScope::Local,
});
self.emit(Instruction::StoreName {
name: "__module__".to_string(),
scope: bytecode::NameScope::Local,
});
self.compile_statements(new_body)?;
self.emit(Instruction::LoadConst {
value: bytecode::Constant::None,
});
self.emit(Instruction::ReturnValue);
let code = self.pop_code_object();
self.leave_scope();
self.emit(Instruction::LoadConst {
value: bytecode::Constant::Code {
code: Box::new(code),
@@ -714,7 +816,7 @@ impl Compiler {
self.emit(Instruction::LoadConst {
value: bytecode::Constant::String {
value: name.to_string(),
value: qualified_name,
},
});
@@ -750,19 +852,38 @@ impl Compiler {
});
}
self.store_docstring(doc_str);
self.apply_decorators(decorator_list);
self.emit(Instruction::StoreName {
name: name.to_string(),
});
self.store_name(name);
self.current_qualified_path = old_qualified_path;
self.in_loop = was_in_loop;
Ok(())
}
fn store_docstring(&mut self, doc_str: Option<String>) {
if let Some(doc_string) = doc_str {
// Duplicate top of stack (the function or class object)
self.emit(Instruction::Duplicate);
// Doc string value:
self.emit(Instruction::LoadConst {
value: bytecode::Constant::String {
value: doc_string.to_string(),
},
});
self.emit(Instruction::Rotate { amount: 2 });
self.emit(Instruction::StoreAttr {
name: "__doc__".to_string(),
});
}
}
fn compile_for(
&mut self,
target: &ast::Expression,
iter: &[ast::Expression],
iter: &ast::Expression,
body: &[ast::LocatedStatement],
orelse: &Option<Vec<ast::LocatedStatement>>,
) -> Result<(), CompileError> {
@@ -776,9 +897,7 @@ impl Compiler {
});
// The thing iterated:
for i in iter {
self.compile_expression(i)?;
}
self.compile_expression(iter)?;
// Retrieve Iterator
self.emit(Instruction::GetIter);
@@ -806,12 +925,83 @@ impl Compiler {
Ok(())
}
fn compile_chained_comparison(
&mut self,
vals: &[ast::Expression],
ops: &[ast::Comparison],
) -> Result<(), CompileError> {
assert!(!ops.is_empty());
assert_eq!(vals.len(), ops.len() + 1);
let to_operator = |op: &ast::Comparison| match op {
ast::Comparison::Equal => bytecode::ComparisonOperator::Equal,
ast::Comparison::NotEqual => bytecode::ComparisonOperator::NotEqual,
ast::Comparison::Less => bytecode::ComparisonOperator::Less,
ast::Comparison::LessOrEqual => bytecode::ComparisonOperator::LessOrEqual,
ast::Comparison::Greater => bytecode::ComparisonOperator::Greater,
ast::Comparison::GreaterOrEqual => bytecode::ComparisonOperator::GreaterOrEqual,
ast::Comparison::In => bytecode::ComparisonOperator::In,
ast::Comparison::NotIn => bytecode::ComparisonOperator::NotIn,
ast::Comparison::Is => bytecode::ComparisonOperator::Is,
ast::Comparison::IsNot => bytecode::ComparisonOperator::IsNot,
};
// a == b == c == d
// compile into (pseudocode):
// result = a == b
// if result:
// result = b == c
// if result:
// result = c == d
// initialize lhs outside of loop
self.compile_expression(&vals[0])?;
let break_label = self.new_label();
let last_label = self.new_label();
// for all comparisons except the last (as the last one doesn't need a conditional jump)
let ops_slice = &ops[0..ops.len()];
let vals_slice = &vals[1..ops.len()];
for (op, val) in ops_slice.iter().zip(vals_slice.iter()) {
self.compile_expression(val)?;
// store rhs for the next comparison in chain
self.emit(Instruction::Duplicate);
self.emit(Instruction::Rotate { amount: 3 });
self.emit(Instruction::CompareOperation {
op: to_operator(op),
});
// if comparison result is false, we break with this value; if true, try the next one.
// (CPython compresses these three opcodes into JUMP_IF_FALSE_OR_POP)
self.emit(Instruction::Duplicate);
self.emit(Instruction::JumpIfFalse {
target: break_label,
});
self.emit(Instruction::Pop);
}
// handle the last comparison
self.compile_expression(vals.last().unwrap())?;
self.emit(Instruction::CompareOperation {
op: to_operator(ops.last().unwrap()),
});
self.emit(Instruction::Jump { target: last_label });
// early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs.
self.set_label(break_label);
self.emit(Instruction::Rotate { amount: 2 });
self.emit(Instruction::Pop);
self.set_label(last_label);
Ok(())
}
fn compile_store(&mut self, target: &ast::Expression) -> Result<(), CompileError> {
match target {
ast::Expression::Identifier { name } => {
self.emit(Instruction::StoreName {
name: name.to_string(),
});
self.store_name(name);
}
ast::Expression::Subscript { a, b } => {
self.compile_expression(a)?;
@@ -988,24 +1178,8 @@ impl Compiler {
name: name.to_string(),
});
}
ast::Expression::Compare { a, op, b } => {
self.compile_expression(a)?;
self.compile_expression(b)?;
let i = match op {
ast::Comparison::Equal => bytecode::ComparisonOperator::Equal,
ast::Comparison::NotEqual => bytecode::ComparisonOperator::NotEqual,
ast::Comparison::Less => bytecode::ComparisonOperator::Less,
ast::Comparison::LessOrEqual => bytecode::ComparisonOperator::LessOrEqual,
ast::Comparison::Greater => bytecode::ComparisonOperator::Greater,
ast::Comparison::GreaterOrEqual => bytecode::ComparisonOperator::GreaterOrEqual,
ast::Comparison::In => bytecode::ComparisonOperator::In,
ast::Comparison::NotIn => bytecode::ComparisonOperator::NotIn,
ast::Comparison::Is => bytecode::ComparisonOperator::Is,
ast::Comparison::IsNot => bytecode::ComparisonOperator::IsNot,
};
let i = Instruction::CompareOperation { op: i };
self.emit(i);
ast::Expression::Compare { vals, ops } => {
self.compile_chained_comparison(vals, ops)?;
}
ast::Expression::Number { value } => {
let const_value = match value {
@@ -1114,9 +1288,7 @@ impl Compiler {
});
}
ast::Expression::Identifier { name } => {
self.emit(Instruction::LoadName {
name: name.to_string(),
});
self.load_name(name);
}
ast::Expression::Lambda { args, body } => {
let name = "<lambda>".to_string();
@@ -1125,6 +1297,7 @@ impl Compiler {
self.compile_expression(body)?;
self.emit(Instruction::ReturnValue);
let code = self.pop_code_object();
self.leave_scope();
self.emit(Instruction::LoadConst {
value: bytecode::Constant::Code {
code: Box::new(code),
@@ -1332,6 +1505,7 @@ impl Compiler {
// Load iterator onto stack (passed as first argument):
self.emit(Instruction::LoadName {
name: String::from(".0"),
scope: bytecode::NameScope::Local,
});
} else {
// Evaluate iterated item:
@@ -1471,6 +1645,26 @@ impl Compiler {
Ok(())
}
// Scope helpers:
fn enter_scope(&mut self) {
// println!("Enter scope {:?}", self.scope_stack);
// Enter first subscope!
let scope = self.scope_stack.last_mut().unwrap().sub_scopes.remove(0);
self.scope_stack.push(scope);
}
fn leave_scope(&mut self) {
// println!("Leave scope {:?}", self.scope_stack);
let scope = self.scope_stack.pop().unwrap();
assert!(scope.sub_scopes.is_empty());
}
fn lookup_name(&self, name: &str) -> &SymbolRole {
// println!("Looking up {:?}", name);
let scope = self.scope_stack.last().unwrap();
scope.lookup(name).unwrap()
}
// Low level helper functions:
fn emit(&mut self, instruction: Instruction) {
let location = self.current_source_location.clone();
@@ -1506,17 +1700,41 @@ impl Compiler {
self.current_source_location.get_row()
}
fn create_qualified_name(&self, name: &str, suffix: &str) -> String {
if let Some(ref qualified_path) = self.current_qualified_path {
format!("{}.{}{}", qualified_path, name, suffix)
} else {
format!("{}{}", name, suffix)
}
}
fn mark_generator(&mut self) {
self.current_code_object().is_generator = true;
}
}
fn get_doc(body: &[ast::LocatedStatement]) -> (&[ast::LocatedStatement], Option<String>) {
if let Some(val) = body.get(0) {
if let ast::Statement::Expression { ref expression } = val.node {
if let ast::Expression::String { ref value } = expression {
if let ast::StringGroup::Constant { ref value } = value {
if let Some((_, body_rest)) = body.split_first() {
return (body_rest, Some(value.to_string()));
}
}
}
}
}
(body, None)
}
#[cfg(test)]
mod tests {
use super::Compiler;
use crate::bytecode::CodeObject;
use crate::bytecode::Constant::*;
use crate::bytecode::Instruction::*;
use crate::symboltable::make_symbol_table;
use rustpython_parser::parser;
fn compile_exec(source: &str) -> CodeObject {
@@ -1524,7 +1742,8 @@ mod tests {
compiler.source_path = Some("source_path".to_string());
compiler.push_new_code_object("<module>".to_string());
let ast = parser::parse_program(&source.to_string()).unwrap();
compiler.compile_program(&ast).unwrap();
let symbol_scope = make_symbol_table(&ast);
compiler.compile_program(&ast, symbol_scope).unwrap();
compiler.pop_code_object()
}
+56 -29
View File
@@ -9,19 +9,37 @@ use num_traits::ToPrimitive;
/// And: http://code.activestate.com/recipes/578375/
use std::collections::HashMap;
pub struct Dict {
#[derive(Clone)]
pub struct Dict<T = PyObjectRef> {
size: usize,
indices: HashMap<usize, usize>,
entries: Vec<Option<DictEntry>>,
entries: Vec<Option<DictEntry<T>>>,
}
struct DictEntry {
impl<T> Default for Dict<T> {
fn default() -> Self {
Dict {
size: 0,
indices: HashMap::new(),
entries: Vec::new(),
}
}
}
#[derive(Clone)]
struct DictEntry<T> {
hash: usize,
key: PyObjectRef,
value: PyObjectRef,
value: T,
}
impl Dict {
#[derive(Debug)]
pub struct DictSize {
size: usize,
entries_size: usize,
}
impl<T: Clone> Dict<T> {
pub fn new() -> Self {
Dict {
size: 0,
@@ -31,12 +49,7 @@ impl Dict {
}
/// Store a key
pub fn insert(
&mut self,
vm: &VirtualMachine,
key: &PyObjectRef,
value: PyObjectRef,
) -> PyResult<()> {
pub fn insert(&mut self, vm: &VirtualMachine, key: &PyObjectRef, value: T) -> PyResult<()> {
match self.lookup(vm, key)? {
LookupResult::Existing(index) => {
// Update existing key
@@ -75,19 +88,24 @@ impl Dict {
}
/// Retrieve a key
pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult {
pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult<Option<T>> {
if let LookupResult::Existing(index) = self.lookup(vm, key)? {
if let Some(entry) = &self.entries[index] {
Ok(entry.value.clone())
Ok(Some(entry.value.clone()))
} else {
panic!("Lookup returned invalid index into entries!");
}
} else {
let key_repr = vm.to_pystr(key)?;
Err(vm.new_value_error(format!("Key not found: {}", key_repr)))
Ok(None)
}
}
pub fn clear(&mut self) {
self.entries.clear();
self.indices.clear();
self.size = 0
}
/// Delete a key
pub fn delete(&mut self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult<()> {
if let LookupResult::Existing(index) = self.lookup(vm, key)? {
@@ -96,7 +114,7 @@ impl Dict {
Ok(())
} else {
let key_repr = vm.to_pystr(key)?;
Err(vm.new_value_error(format!("Key not found: {}", key_repr)))
Err(vm.new_key_error(format!("Key not found: {}", key_repr)))
}
}
@@ -108,13 +126,26 @@ impl Dict {
self.len() == 0
}
pub fn get_items(&self) -> Vec<(PyObjectRef, PyObjectRef)> {
self.entries
.iter()
.filter(|e| e.is_some())
.map(|e| e.as_ref().unwrap())
.map(|e| (e.key.clone(), e.value.clone()))
.collect()
pub fn size(&self) -> DictSize {
DictSize {
size: self.size,
entries_size: self.entries.len(),
}
}
pub fn next_entry(&self, position: &mut usize) -> Option<(&PyObjectRef, &T)> {
while *position < self.entries.len() {
if let Some(DictEntry { key, value, .. }) = &self.entries[*position] {
*position += 1;
return Some((key, value));
}
*position += 1;
}
None
}
pub fn has_changed_size(&self, position: &DictSize) -> bool {
position.size != self.size || self.entries.len() != position.entries_size
}
/// Lookup the index for the given key.
@@ -175,12 +206,8 @@ fn calc_hash(vm: &VirtualMachine, key: &PyObjectRef) -> PyResult<usize> {
}
/// Invoke __eq__ on two keys
fn do_eq(
vm: &VirtualMachine,
key1: &PyObjectRef,
key2: &PyObjectRef,
) -> Result<bool, PyObjectRef> {
let result = vm._eq(key1, key2.clone())?;
fn do_eq(vm: &VirtualMachine, key1: &PyObjectRef, key2: &PyObjectRef) -> Result<bool, PyObjectRef> {
let result = vm._eq(key1.clone(), key2.clone())?;
Ok(objbool::get_value(&result))
}
+1 -6
View File
@@ -1,7 +1,5 @@
extern crate rustpython_parser;
use std::error::Error;
use crate::compile;
use crate::frame::Scope;
use crate::pyobject::PyResult;
@@ -13,10 +11,7 @@ pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str)
debug!("Code object: {:?}", bytecode);
vm.run_code_obj(bytecode, scope)
}
Err(err) => {
let syntax_error = vm.context().exceptions.syntax_error.clone();
Err(vm.new_exception(syntax_error, err.description().to_string()))
}
Err(err) => Err(vm.new_syntax_error(&err)),
}
}
+64 -4
View File
@@ -2,7 +2,7 @@ use crate::function::PyFuncArgs;
use crate::obj::objsequence;
use crate::obj::objtype;
use crate::obj::objtype::PyClassRef;
use crate::pyobject::{create_type, PyContext, PyObjectRef, PyResult, TypeProtocol};
use crate::pyobject::{create_type, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol};
use crate::vm::VirtualMachine;
fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
@@ -13,13 +13,34 @@ fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
vm.new_str("No msg".to_string())
};
let traceback = vm.ctx.new_list(Vec::new());
vm.ctx.set_attr(&zelf, "msg", msg);
vm.ctx.set_attr(&zelf, "__traceback__", traceback);
vm.set_attr(&zelf, "msg", msg)?;
vm.set_attr(&zelf, "__traceback__", traceback)?;
Ok(vm.get_none())
}
// Print exception including traceback:
// print excption chain
pub fn print_exception(vm: &VirtualMachine, exc: &PyObjectRef) {
let mut had_cause = false;
if let Ok(cause) = vm.get_attribute(exc.clone(), "__cause__") {
if !vm.get_none().is(&cause) {
had_cause = true;
print_exception(vm, &cause);
println!("\nThe above exception was the direct cause of the following exception:\n");
}
}
if !had_cause {
if let Ok(context) = vm.get_attribute(exc.clone(), "__context__") {
if !vm.get_none().is(&context) {
print_exception(vm, &context);
println!("\nDuring handling of the above exception, another exception occurred:\n");
}
}
}
print_exception_inner(vm, exc)
}
// Print exception including traceback:
pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) {
if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") {
println!("Traceback (most recent call last):");
if objtype::isinstance(&tb, &vm.ctx.list_type()) {
@@ -97,12 +118,25 @@ pub struct ExceptionZoo {
pub os_error: PyClassRef,
pub overflow_error: PyClassRef,
pub permission_error: PyClassRef,
pub reference_error: PyClassRef,
pub runtime_error: PyClassRef,
pub stop_iteration: PyClassRef,
pub syntax_error: PyClassRef,
pub type_error: PyClassRef,
pub value_error: PyClassRef,
pub zero_division_error: PyClassRef,
pub warning: PyClassRef,
pub bytes_warning: PyClassRef,
pub unicode_warning: PyClassRef,
pub deprecation_warning: PyClassRef,
pub pending_deprecation_warning: PyClassRef,
pub future_warning: PyClassRef,
pub import_warning: PyClassRef,
pub syntax_warning: PyClassRef,
pub resource_warning: PyClassRef,
pub runtime_warning: PyClassRef,
pub user_warning: PyClassRef,
}
impl ExceptionZoo {
@@ -119,6 +153,7 @@ impl ExceptionZoo {
let name_error = create_type("NameError", &type_type, &exception_type);
let os_error = create_type("OSError", &type_type, &exception_type);
let runtime_error = create_type("RuntimeError", &type_type, &exception_type);
let reference_error = create_type("ReferenceError", &type_type, &exception_type);
let stop_iteration = create_type("StopIteration", &type_type, &exception_type);
let syntax_error = create_type("SyntaxError", &type_type, &exception_type);
let type_error = create_type("TypeError", &type_type, &exception_type);
@@ -130,6 +165,19 @@ impl ExceptionZoo {
let file_not_found_error = create_type("FileNotFoundError", &type_type, &os_error);
let permission_error = create_type("PermissionError", &type_type, &os_error);
let warning = create_type("Warning", &type_type, &exception_type);
let bytes_warning = create_type("BytesWarning", &type_type, &warning);
let unicode_warning = create_type("UnicodeWarning", &type_type, &warning);
let deprecation_warning = create_type("DeprecationWarning", &type_type, &warning);
let pending_deprecation_warning =
create_type("PendingDeprecationWarning", &type_type, &warning);
let future_warning = create_type("FutureWarning", &type_type, &warning);
let import_warning = create_type("ImportWarning", &type_type, &warning);
let syntax_warning = create_type("SyntaxWarning", &type_type, &warning);
let resource_warning = create_type("ResourceWarning", &type_type, &warning);
let runtime_warning = create_type("RuntimeWarning", &type_type, &warning);
let user_warning = create_type("UserWarning", &type_type, &warning);
ExceptionZoo {
arithmetic_error,
assertion_error,
@@ -152,6 +200,18 @@ impl ExceptionZoo {
type_error,
value_error,
zero_division_error,
warning,
bytes_warning,
unicode_warning,
deprecation_warning,
pending_deprecation_warning,
future_warning,
import_warning,
syntax_warning,
resource_warning,
runtime_warning,
reference_error,
user_warning,
}
}
}
+187 -104
View File
@@ -8,20 +8,22 @@ use crate::builtins;
use crate::bytecode;
use crate::function::PyFuncArgs;
use crate::obj::objbool;
use crate::obj::objbuiltinfunc::PyBuiltinFunction;
use crate::obj::objcode::PyCodeRef;
use crate::obj::objdict::{self, PyDictRef};
use crate::obj::objdict::{PyDict, PyDictRef};
use crate::obj::objiter;
use crate::obj::objlist;
use crate::obj::objslice::PySlice;
use crate::obj::objstr;
use crate::obj::objstr::PyString;
use crate::obj::objtuple::PyTuple;
use crate::obj::objtype;
use crate::obj::objtype::PyClassRef;
use crate::pyobject::{
DictProtocol, IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
TypeProtocol,
};
use crate::vm::VirtualMachine;
use itertools::Itertools;
/*
* So a scope is a linked list of scopes.
@@ -124,26 +126,28 @@ pub trait NameProtocol {
fn store_name(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef);
fn delete_name(&self, vm: &VirtualMachine, name: &str);
fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option<PyObjectRef>;
fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option<PyObjectRef>;
fn store_global(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef);
}
impl NameProtocol for Scope {
fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option<PyObjectRef> {
for dict in self.locals.iter() {
if let Some(value) = dict.get_item(name) {
if let Some(value) = dict.get_item_option(name, vm).unwrap() {
return Some(value);
}
}
if let Some(value) = self.globals.get_item(name) {
if let Some(value) = self.globals.get_item_option(name, vm).unwrap() {
return Some(value);
}
vm.get_attribute(vm.builtins.clone(), name).ok()
}
fn load_cell(&self, _vm: &VirtualMachine, name: &str) -> Option<PyObjectRef> {
fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option<PyObjectRef> {
for dict in self.locals.iter().skip(1) {
if let Some(value) = dict.get_item(name) {
if let Some(value) = dict.get_item_option(name, vm).unwrap() {
return Some(value);
}
}
@@ -151,11 +155,19 @@ impl NameProtocol for Scope {
}
fn store_name(&self, vm: &VirtualMachine, key: &str, value: PyObjectRef) {
self.get_locals().set_item(&vm.ctx, key, value)
self.get_locals().set_item(key, value, vm).unwrap();
}
fn delete_name(&self, _vm: &VirtualMachine, key: &str) {
self.get_locals().del_item(key)
fn delete_name(&self, vm: &VirtualMachine, key: &str) {
self.get_locals().del_item(key, vm).unwrap();
}
fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option<PyObjectRef> {
self.globals.get_item_option(name, vm).unwrap()
}
fn store_global(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) {
self.globals.set_item(name, value, vm).unwrap();
}
}
@@ -278,6 +290,17 @@ impl Frame {
}
}
pub fn throw(
&self,
vm: &VirtualMachine,
exception: PyObjectRef,
) -> Result<ExecutionResult, PyObjectRef> {
match self.unwind_exception(vm, exception) {
None => self.run(vm),
Some(exception) => Err(exception),
}
}
pub fn fetch_instruction(&self) -> &bytecode::Instruction {
let ins2 = &self.code.instructions[*self.lasti.borrow()];
*self.lasti.borrow_mut() += 1;
@@ -310,8 +333,14 @@ impl Frame {
ref symbol,
} => self.import(vm, name, symbol),
bytecode::Instruction::ImportStar { ref name } => self.import_star(vm, name),
bytecode::Instruction::LoadName { ref name } => self.load_name(vm, name),
bytecode::Instruction::StoreName { ref name } => self.store_name(vm, name),
bytecode::Instruction::LoadName {
ref name,
ref scope,
} => self.load_name(vm, name, scope),
bytecode::Instruction::StoreName {
ref name,
ref scope,
} => self.store_name(vm, name, scope),
bytecode::Instruction::DeleteName { ref name } => self.delete_name(vm, name),
bytecode::Instruction::StoreSubscript => self.execute_store_subscript(vm),
bytecode::Instruction::DeleteSubscript => self.execute_delete_subscript(vm),
@@ -382,21 +411,23 @@ impl Frame {
Ok(None)
}
bytecode::Instruction::BuildMap { size, unpack } => {
let map_obj = vm.ctx.new_dict().into_object();
for _x in 0..*size {
let obj = self.pop_value();
if *unpack {
let map_obj = vm.ctx.new_dict();
if *unpack {
for obj in self.pop_multiple(*size) {
// Take all key-value pairs from the dict:
let dict_elements = objdict::get_key_value_pairs(&obj);
for (key, value) in dict_elements.iter() {
objdict::set_item(&map_obj, vm, key, value);
let dict: PyDictRef =
obj.downcast().expect("Need a dictionary to build a map.");
for (key, value) in dict {
map_obj.set_item(key, value, vm).unwrap();
}
} else {
let key = self.pop_value();
objdict::set_item(&map_obj, vm, &key, &obj);
}
} else {
for (key, value) in self.pop_multiple(2 * size).into_iter().tuples() {
map_obj.set_item(key, value, vm).unwrap();
}
}
self.push_value(map_obj);
self.push_value(map_obj.into_object());
Ok(None)
}
bytecode::Instruction::BuildSlice { size } => {
@@ -554,7 +585,10 @@ impl Frame {
}
}
bytecode::Instruction::MakeFunction { flags } => {
let _qualified_name = self.pop_value();
let qualified_name = self
.pop_value()
.downcast::<PyString>()
.expect("qualified name to be a string");
let code_obj = self
.pop_value()
.downcast()
@@ -566,18 +600,44 @@ impl Frame {
vm.ctx.new_dict().into_object()
};
let kw_only_defaults =
if flags.contains(bytecode::FunctionOpArg::HAS_KW_ONLY_DEFAULTS) {
Some(
self.pop_value().downcast::<PyDict>().expect(
"Stack value for keyword only defaults expected to be a dict",
),
)
} else {
None
};
let defaults = if flags.contains(bytecode::FunctionOpArg::HAS_DEFAULTS) {
self.pop_value()
Some(
self.pop_value()
.downcast::<PyTuple>()
.expect("Stack value for defaults expected to be a tuple"),
)
} else {
vm.get_none()
None
};
// pop argc arguments
// argument: name, args, globals
let scope = self.scope.clone();
let obj = vm.ctx.new_function(code_obj, scope, defaults);
let obj = vm
.ctx
.new_function(code_obj, scope, defaults, kw_only_defaults);
vm.ctx.set_attr(&obj, "__annotations__", annotations);
let name = qualified_name.value.split('.').next_back().unwrap();
vm.set_attr(&obj, "__name__", vm.new_str(name.to_string()))?;
vm.set_attr(&obj, "__qualname__", qualified_name)?;
let module = self
.scope
.globals
.get_item_option("__name__", vm)?
.unwrap_or_else(|| vm.get_none());
vm.set_attr(&obj, "__module__", module)?;
vm.set_attr(&obj, "__annotations__", annotations)?;
self.push_value(obj);
Ok(None)
@@ -604,11 +664,11 @@ impl Frame {
}
bytecode::CallType::Ex(has_kwargs) => {
let kwargs = if *has_kwargs {
let kw_dict = self.pop_value();
let dict_elements = objdict::get_elements(&kw_dict).clone();
dict_elements
let kw_dict: PyDictRef =
self.pop_value().downcast().expect("Kwargs must be a dict.");
kw_dict
.into_iter()
.map(|elem| (elem.0, (elem.1).1))
.map(|elem| (objstr::get_value(&elem.0), elem.1))
.collect()
} else {
vec![]
@@ -647,32 +707,39 @@ impl Frame {
Ok(None)
}
bytecode::Instruction::Raise { argc } => {
bytecode::Instruction::Raise { argc, in_exc } => {
let cause = match argc {
2 => self.get_exception(vm, true)?,
_ => vm.get_none(),
};
let exception = match argc {
1 => self.pop_value(),
0 | 2 | 3 => panic!("Not implemented!"),
0 => match in_exc {
true => self.get_exception(vm, false)?,
false => {
return Err(vm.new_exception(
vm.ctx.exceptions.runtime_error.clone(),
"No active exception to reraise".to_string(),
));
}
},
1 | 2 => self.get_exception(vm, false)?,
3 => panic!("Not implemented!"),
_ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"),
};
if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) {
info!("Exception raised: {:?}", exception);
Err(exception)
} else if let Ok(exception) = PyClassRef::try_from_object(vm, exception) {
if objtype::issubclass(&exception, &vm.ctx.exceptions.base_exception_type) {
let exception = vm.new_empty_exception(exception)?;
info!("Exception raised: {:?}", exception);
Err(exception)
} else {
let msg = format!(
"Can only raise BaseException derived types, not {}",
exception
);
let type_error_type = vm.ctx.exceptions.type_error.clone();
let type_error = vm.new_exception(type_error_type, msg);
Err(type_error)
}
} else {
Err(vm.new_type_error("exceptions must derive from BaseException".to_string()))
}
let context = match argc {
0 => vm.get_none(), // We have already got the exception,
_ => match in_exc {
true => self.get_exception(vm, false)?,
false => vm.get_none(),
},
};
info!(
"Exception raised: {:?} with cause: {:?} and context: {:?}",
exception, cause, context
);
vm.set_attr(&exception, vm.new_str("__cause__".to_string()), cause)?;
vm.set_attr(&exception, vm.new_str("__context__".to_string()), context)?;
Err(exception)
}
bytecode::Instruction::Break => {
@@ -710,9 +777,7 @@ impl Frame {
Ok(None)
}
bytecode::Instruction::LoadBuildClass => {
let rustfunc =
PyBuiltinFunction::new(Box::new(builtins::builtin_build_class_)).into_ref(vm);
self.push_value(rustfunc.into_object());
self.push_value(vm.ctx.new_rustfunc(builtins::builtin_build_class_));
Ok(None)
}
bytecode::Instruction::UnpackSequence { size } => {
@@ -831,8 +896,8 @@ impl Frame {
// Grab all the names from the module and put them in the context
if let Some(dict) = &module.dict {
for (k, v) in dict.get_key_value_pairs().iter() {
self.scope.store_name(&vm, &objstr::get_value(k), v.clone());
for (k, v) in dict {
self.scope.store_name(&vm, &objstr::get_value(&k), v);
}
}
Ok(None)
@@ -953,9 +1018,21 @@ impl Frame {
vm.call_method(context_manager, "__exit__", args)
}
fn store_name(&self, vm: &VirtualMachine, name: &str) -> FrameResult {
fn store_name(
&self,
vm: &VirtualMachine,
name: &str,
name_scope: &bytecode::NameScope,
) -> FrameResult {
let obj = self.pop_value();
self.scope.store_name(&vm, name, obj);
match name_scope {
bytecode::NameScope::Global => {
self.scope.store_global(vm, name, obj);
}
bytecode::NameScope::Local => {
self.scope.store_name(&vm, name, obj);
}
}
Ok(None)
}
@@ -964,37 +1041,43 @@ impl Frame {
Ok(None)
}
fn load_name(&self, vm: &VirtualMachine, name: &str) -> FrameResult {
match self.scope.load_name(&vm, name) {
Some(value) => {
self.push_value(value);
Ok(None)
}
fn load_name(
&self,
vm: &VirtualMachine,
name: &str,
name_scope: &bytecode::NameScope,
) -> FrameResult {
let optional_value = match name_scope {
bytecode::NameScope::Global => self.scope.load_global(vm, name),
bytecode::NameScope::Local => self.scope.load_name(&vm, name),
};
let value = match optional_value {
Some(value) => value,
None => {
let name_error_type = vm.ctx.exceptions.name_error.clone();
let msg = format!("name '{}' is not defined", name);
let name_error = vm.new_exception(name_error_type, msg);
Err(name_error)
return Err(name_error);
}
}
}
};
fn subscript(&self, vm: &VirtualMachine, a: PyObjectRef, b: PyObjectRef) -> PyResult {
vm.call_method(&a, "__getitem__", vec![b])
self.push_value(value);
Ok(None)
}
fn execute_store_subscript(&self, vm: &VirtualMachine) -> FrameResult {
let idx = self.pop_value();
let obj = self.pop_value();
let value = self.pop_value();
vm.call_method(&obj, "__setitem__", vec![idx, value])?;
obj.set_item(idx, value, vm)?;
Ok(None)
}
fn execute_delete_subscript(&self, vm: &VirtualMachine) -> FrameResult {
let idx = self.pop_value();
let obj = self.pop_value();
vm.call_method(&obj, "__delitem__", vec![idx])?;
obj.del_item(idx, vm)?;
Ok(None)
}
@@ -1029,7 +1112,7 @@ impl Frame {
bytecode::BinaryOperator::FloorDivide => vm._floordiv(a_ref, b_ref),
// TODO: Subscript should probably have its own op
bytecode::BinaryOperator::Subscript if inplace => unreachable!(),
bytecode::BinaryOperator::Subscript => self.subscript(vm, a_ref, b_ref),
bytecode::BinaryOperator::Subscript => a_ref.get_item(b_ref, vm),
bytecode::BinaryOperator::Modulo if inplace => vm._imod(a_ref, b_ref),
bytecode::BinaryOperator::Modulo => vm._mod(a_ref, b_ref),
bytecode::BinaryOperator::Lshift if inplace => vm._ilshift(a_ref, b_ref),
@@ -1067,36 +1150,14 @@ impl Frame {
a.get_id()
}
// https://docs.python.org/3/reference/expressions.html#membership-test-operations
fn _membership(
&self,
vm: &VirtualMachine,
needle: PyObjectRef,
haystack: &PyObjectRef,
) -> PyResult {
vm.call_method(&haystack, "__contains__", vec![needle])
// TODO: implement __iter__ and __getitem__ cases when __contains__ is
// not implemented.
}
fn _in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult {
match self._membership(vm, needle, &haystack) {
Ok(found) => Ok(found),
Err(_) => Err(vm.new_type_error(format!(
"{} has no __contains__ method",
haystack.class().name
))),
}
let found = vm._membership(haystack.clone(), needle)?;
Ok(vm.ctx.new_bool(objbool::boolval(vm, found)?))
}
fn _not_in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult {
match self._membership(vm, needle, &haystack) {
Ok(found) => Ok(vm.ctx.new_bool(!objbool::get_value(&found))),
Err(_) => Err(vm.new_type_error(format!(
"{} has no __contains__ method",
haystack.class().name
))),
}
let found = vm._membership(haystack.clone(), needle)?;
Ok(vm.ctx.new_bool(!objbool::boolval(vm, found)?))
}
fn _is(&self, a: PyObjectRef, b: PyObjectRef) -> bool {
@@ -1201,6 +1262,28 @@ impl Frame {
let stack = self.stack.borrow_mut();
stack[stack.len() - depth - 1].clone()
}
fn get_exception(&self, vm: &VirtualMachine, none_allowed: bool) -> PyResult {
let exception = self.pop_value();
if none_allowed && vm.get_none().is(&exception)
|| objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type)
{
Ok(exception)
} else if let Ok(exception) = PyClassRef::try_from_object(vm, exception) {
if objtype::issubclass(&exception, &vm.ctx.exceptions.base_exception_type) {
let exception = vm.new_empty_exception(exception)?;
Ok(exception)
} else {
let msg = format!(
"Can only raise BaseException derived types, not {}",
exception
);
Err(vm.new_type_error(msg))
}
} else {
Err(vm.new_type_error("exceptions must derive from BaseException".to_string()))
}
}
}
impl fmt::Debug for Frame {
@@ -1224,8 +1307,8 @@ impl fmt::Debug for Frame {
.map(|elem| format!("\n > {:?}", elem))
.collect::<String>();
let dict = self.scope.get_locals();
let local_str = objdict::get_key_value_pairs_from_content(&dict.entries.borrow())
.iter()
let local_str = dict
.into_iter()
.map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1))
.collect::<String>();
write!(
+37 -8
View File
@@ -3,7 +3,9 @@ use std::mem;
use std::ops::RangeInclusive;
use crate::obj::objtype::{isinstance, PyClassRef};
use crate::pyobject::{IntoPyObject, PyObjectRef, PyResult, TryFromObject, TypeProtocol};
use crate::pyobject::{
IntoPyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
};
use crate::vm::VirtualMachine;
use self::OptionalArg::*;
@@ -249,6 +251,12 @@ pub trait FromArgs: Sized {
/// an appropriate FromArgs implementation must be created.
pub struct KwArgs<T = PyObjectRef>(HashMap<String, T>);
impl<T> KwArgs<T> {
pub fn pop_kwarg(&mut self, name: &str) -> Option<T> {
self.0.remove(name)
}
}
impl<T> FromArgs for KwArgs<T>
where
T: TryFromObject,
@@ -279,8 +287,16 @@ impl<T> IntoIterator for KwArgs<T> {
///
/// `Args` optionally accepts a generic type parameter to allow type checks
/// or conversions of each argument.
#[derive(Clone)]
pub struct Args<T = PyObjectRef>(Vec<T>);
impl<T: PyValue> Args<PyRef<T>> {
pub fn into_tuple(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx
.new_tuple(self.0.into_iter().map(PyRef::into_object).collect())
}
}
impl<T> FromArgs for Args<T>
where
T: TryFromObject,
@@ -410,6 +426,7 @@ tuple_from_py_func_args!(A, B);
tuple_from_py_func_args!(A, B, C);
tuple_from_py_func_args!(A, B, C, D);
tuple_from_py_func_args!(A, B, C, D, E);
tuple_from_py_func_args!(A, B, C, D, E, F);
/// A built-in Python function.
pub type PyNativeFunc = Box<dyn Fn(&VirtualMachine, PyFuncArgs) -> PyResult + 'static>;
@@ -441,11 +458,8 @@ where
}
}
impl IntoPyNativeFunc<PyFuncArgs, PyResult> for PyNativeFunc {
fn into_func(self) -> PyNativeFunc {
self
}
}
pub struct OwnedParam<T>(std::marker::PhantomData<T>);
pub struct RefParam<T>(std::marker::PhantomData<T>);
// This is the "magic" that allows rust functions of varying signatures to
// generate native python functions.
@@ -453,11 +467,10 @@ impl IntoPyNativeFunc<PyFuncArgs, PyResult> for PyNativeFunc {
// Note that this could be done without a macro - it is simply to avoid repetition.
macro_rules! into_py_native_func_tuple {
($(($n:tt, $T:ident)),*) => {
impl<F, $($T,)* R> IntoPyNativeFunc<($($T,)*), R> for F
impl<F, $($T,)* R> IntoPyNativeFunc<($(OwnedParam<$T>,)*), R> for F
where
F: Fn($($T,)* &VirtualMachine) -> R + 'static,
$($T: FromArgs,)*
($($T,)*): FromArgs,
R: IntoPyObject,
{
fn into_func(self) -> PyNativeFunc {
@@ -468,6 +481,22 @@ macro_rules! into_py_native_func_tuple {
})
}
}
impl<F, S, $($T,)* R> IntoPyNativeFunc<(RefParam<S>, $(OwnedParam<$T>,)*), R> for F
where
F: Fn(&S, $($T,)* &VirtualMachine) -> R + 'static,
S: PyValue,
$($T: FromArgs,)*
R: IntoPyObject,
{
fn into_func(self) -> PyNativeFunc {
Box::new(move |vm, args| {
let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
(self)(&zelf, $($n,)* vm).into_pyobject(vm)
})
}
}
};
}
+9 -12
View File
@@ -2,20 +2,19 @@
* Import mechanics
*/
use std::error::Error;
use std::path::PathBuf;
use crate::compile;
use crate::frame::Scope;
use crate::obj::{objsequence, objstr};
use crate::pyobject::{DictProtocol, PyResult};
use crate::pyobject::{ItemProtocol, PyResult};
use crate::util;
use crate::vm::VirtualMachine;
fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &str) -> PyResult {
// Check for Rust-native modules
if let Some(module) = vm.stdlib_inits.borrow().get(module) {
return Ok(module(&vm.ctx).clone());
return Ok(module(vm).clone());
}
let notfound_error = vm.context().exceptions.module_not_found_error.clone();
@@ -25,21 +24,18 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s
let file_path = find_source(vm, current_path, module)
.map_err(|e| vm.new_exception(notfound_error.clone(), e))?;
let source = util::read_file(file_path.as_path())
.map_err(|e| vm.new_exception(import_error.clone(), e.description().to_string()))?;
.map_err(|e| vm.new_exception(import_error.clone(), e.to_string()))?;
let code_obj = compile::compile(
vm,
&source,
&compile::Mode::Exec,
file_path.to_str().unwrap().to_string(),
)
.map_err(|err| {
let syntax_error = vm.context().exceptions.syntax_error.clone();
vm.new_exception(syntax_error, err.description().to_string())
})?;
.map_err(|err| vm.new_syntax_error(&err))?;
// trace!("Code object: {:?}", code_obj);
let attrs = vm.ctx.new_dict();
attrs.set_item(&vm.ctx, "__name__", vm.new_str(module.to_string()));
attrs.set_item("__name__", vm.new_str(module.to_string()), vm)?;
vm.run_code_obj(code_obj, Scope::new(None, attrs.clone()))?;
Ok(vm.ctx.new_module(module, attrs))
}
@@ -47,11 +43,11 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s
pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &str) -> PyResult {
// First, see if we already loaded the module:
let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules")?;
if let Some(module) = sys_modules.get_item(module_name) {
if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) {
return Ok(module);
}
let module = import_uncached_module(vm, current_path, module_name)?;
sys_modules.set_item(&vm.ctx, module_name, module.clone());
sys_modules.set_item(module_name, module.clone(), vm)?;
Ok(module)
}
@@ -64,12 +60,13 @@ fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result
paths.insert(0, current_path);
let rel_name = name.replace('.', "/");
let suffixes = [".py", "/__init__.py"];
let mut file_paths = vec![];
for path in paths {
for suffix in suffixes.iter() {
let mut file_path = path.clone();
file_path.push(format!("{}{}", name, suffix));
file_path.push(format!("{}{}", rel_name, suffix));
file_paths.push(file_path);
}
}
+6
View File
@@ -32,6 +32,10 @@ extern crate rustpython_parser;
#[macro_use]
extern crate rustpython_derive;
extern crate self as rustpython_vm;
pub use rustpython_derive::*;
//extern crate eval; use eval::eval::*;
// use py_code_object::{Function, NativeType, PyCodeObject};
@@ -42,6 +46,7 @@ pub mod macros;
mod builtins;
pub mod bytecode;
pub mod compile;
mod dictdatatype;
pub mod error;
pub mod eval;
mod exceptions;
@@ -52,6 +57,7 @@ pub mod import;
pub mod obj;
pub mod pyobject;
pub mod stdlib;
mod symboltable;
mod sysmodule;
mod traceback;
pub mod util;
+111 -16
View File
@@ -9,7 +9,7 @@ macro_rules! replace_expr {
#[macro_export]
macro_rules! count_tts {
($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*};
($($tts:tt)*) => {0usize $(+ $crate::replace_expr!($tts 1usize))*};
}
#[macro_export]
@@ -20,6 +20,8 @@ macro_rules! type_check {
let arg = &$args.args[$arg_count];
if !$crate::obj::objtype::isinstance(arg, &expected_type) {
use $crate::pyobject::TypeProtocol;
let arg_typ = arg.class();
let expected_type_name = $vm.to_pystr(&expected_type)?;
let actual_type = $vm.to_pystr(&arg_typ)?;
@@ -45,14 +47,14 @@ macro_rules! arg_check {
}
};
( $vm: ident, $args:ident, required=[$( ($arg_name:ident, $arg_type:expr) ),*] ) => {
arg_check!($vm, $args, required=[$( ($arg_name, $arg_type) ),*], optional=[]);
$crate::arg_check!($vm, $args, required=[$( ($arg_name, $arg_type) ),*], optional=[]);
};
( $vm: ident, $args:ident, required=[$( ($arg_name:ident, $arg_type:expr) ),*], optional=[$( ($optional_arg_name:ident, $optional_arg_type:expr) ),*] ) => {
let mut arg_count = 0;
// use macro magic to compile-time count number of required and optional arguments
let minimum_arg_count = count_tts!($($arg_name)*);
let maximum_arg_count = minimum_arg_count + count_tts!($($optional_arg_name)*);
let minimum_arg_count = $crate::count_tts!($($arg_name)*);
let maximum_arg_count = minimum_arg_count + $crate::count_tts!($($optional_arg_name)*);
// verify that the number of given arguments is right
if $args.args.len() < minimum_arg_count || $args.args.len() > maximum_arg_count {
@@ -72,7 +74,7 @@ macro_rules! arg_check {
// check if the type matches. If not, return with error
// assign the arg to a variable
$(
type_check!($vm, $args, arg_count, $arg_name, $arg_type);
$crate::type_check!($vm, $args, arg_count, $arg_name, $arg_type);
let $arg_name = &$args.args[arg_count];
#[allow(unused_assignments)]
{
@@ -85,7 +87,7 @@ macro_rules! arg_check {
// assign the arg to a variable
$(
let $optional_arg_name = if arg_count < $args.args.len() {
type_check!($vm, $args, arg_count, $optional_arg_name, $optional_arg_type);
$crate::type_check!($vm, $args, arg_count, $optional_arg_name, $optional_arg_type);
let ret = Some(&$args.args[arg_count]);
#[allow(unused_assignments)]
{
@@ -114,14 +116,21 @@ macro_rules! no_kwargs {
#[macro_export]
macro_rules! py_module {
( $ctx:expr, $module_name:expr, { $($name:expr => $value:expr),* $(,)* }) => {
{
let py_mod = $ctx.new_module($module_name, $ctx.new_dict());
$(
$ctx.set_attr(&py_mod, $name, $value);
)*
py_mod
}
( $vm:expr, $module_name:expr, { $($name:expr => $value:expr),* $(,)* }) => {{
let module = $vm.ctx.new_module($module_name, $vm.ctx.new_dict());
$(
$vm.set_attr(&module, $name, $value).unwrap();
)*
module
}};
}
#[macro_export]
macro_rules! extend_module {
( $vm:expr, $module:expr, { $($name:expr => $value:expr),* $(,)* }) => {
$(
$vm.set_attr(&$module, $name, $value).unwrap();
)*
}
}
@@ -131,7 +140,7 @@ macro_rules! py_class {
{
let py_class = $ctx.new_class($class_name, $class_base);
$(
$ctx.set_attr(&py_class, $name, $value);
py_class.set_str_attr($name, $value);
)*
py_class
}
@@ -143,7 +152,93 @@ macro_rules! extend_class {
( $ctx:expr, $class:expr, { $($name:expr => $value:expr),* $(,)* }) => {
let class = $class;
$(
$ctx.set_attr(class, $name, $value);
class.set_str_attr($name, $value);
)*
}
}
/// Macro to match on the built-in class of a Python object.
///
/// Like `match`, `match_class!` must be exhaustive, so a default arm with
/// the uncasted object is required.
///
/// # Examples
///
/// ```
/// use num_bigint::ToBigInt;
/// use num_traits::Zero;
///
/// use rustpython_vm::VirtualMachine;
/// use rustpython_vm::match_class;
/// use rustpython_vm::obj::objfloat::PyFloat;
/// use rustpython_vm::obj::objint::PyInt;
/// use rustpython_vm::pyobject::PyValue;
///
/// let vm = VirtualMachine::new();
/// let obj = PyInt::new(0).into_ref(&vm).into_object();
/// assert_eq!(
/// "int",
/// match_class!(obj.clone(),
/// PyInt => "int",
/// PyFloat => "float",
/// _ => "neither",
/// )
/// );
///
/// ```
///
/// With a binding to the downcasted type:
///
/// ```
/// use num_bigint::ToBigInt;
/// use num_traits::Zero;
///
/// use rustpython_vm::VirtualMachine;
/// use rustpython_vm::match_class;
/// use rustpython_vm::obj::objfloat::PyFloat;
/// use rustpython_vm::obj::objint::PyInt;
/// use rustpython_vm::pyobject::PyValue;
///
/// let vm = VirtualMachine::new();
/// let obj = PyInt::new(0).into_ref(&vm).into_object();
///
/// let int_value = match_class!(obj,
/// i @ PyInt => i.as_bigint().clone(),
/// f @ PyFloat => f.to_f64().to_bigint().unwrap(),
/// obj => panic!("non-numeric object {}", obj),
/// );
///
/// assert!(int_value.is_zero());
/// ```
#[macro_export]
macro_rules! match_class {
// The default arm.
($obj:expr, _ => $default:expr $(,)?) => {
$default
};
// The default arm, binding the original object to the specified identifier.
($obj:expr, $binding:ident => $default:expr $(,)?) => {{
let $binding = $obj;
$default
}};
// An arm taken when the object is an instance of the specified built-in
// class and binding the downcasted object to the specified identifier.
($obj:expr, $binding:ident @ $class:ty => $expr:expr, $($rest:tt)*) => {
match $obj.downcast::<$class>() {
Ok($binding) => $expr,
Err(_obj) => $crate::match_class!(_obj, $($rest)*),
}
};
// An arm taken when the object is an instance of the specified built-in
// class.
($obj:expr, $class:ty => $expr:expr, $($rest:tt)*) => {
if $obj.payload_is::<$class>() {
$expr
} else {
$crate::match_class!($obj, $($rest)*)
}
};
}
+2
View File
@@ -3,6 +3,7 @@
pub mod objbool;
pub mod objbuiltinfunc;
pub mod objbytearray;
pub mod objbyteinner;
pub mod objbytes;
pub mod objclassmethod;
pub mod objcode;
@@ -33,5 +34,6 @@ pub mod objstr;
pub mod objsuper;
pub mod objtuple;
pub mod objtype;
pub mod objweakproxy;
pub mod objweakref;
pub mod objzip;
+1 -3
View File
@@ -1,9 +1,7 @@
use num_traits::Zero;
use crate::function::PyFuncArgs;
use crate::pyobject::{
IntoPyObject, PyContext, PyObjectRef, PyResult, TryFromObject, TypeProtocol,
};
use crate::pyobject::{IntoPyObject, PyContext, PyObjectRef, PyResult, TryFromObject};
use crate::vm::VirtualMachine;
use super::objint::PyInt;
+46 -1
View File
@@ -1,6 +1,6 @@
//! Implementation of the python bytearray object.
use std::cell::RefCell;
use std::cell::{Cell, RefCell};
use std::fmt::Write;
use std::ops::{Deref, DerefMut};
@@ -11,6 +11,7 @@ use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::vm::VirtualMachine;
use super::objint;
use super::objiter;
use super::objtype::PyClassRef;
#[derive(Debug)]
@@ -67,6 +68,7 @@ pub fn init(context: &PyContext) {
"__eq__" => context.new_rustfunc(PyByteArrayRef::eq),
"__len__" => context.new_rustfunc(PyByteArrayRef::len),
"__repr__" => context.new_rustfunc(PyByteArrayRef::repr),
"__iter__" => context.new_rustfunc(PyByteArrayRef::iter),
"clear" => context.new_rustfunc(PyByteArrayRef::clear),
"isalnum" => context.new_rustfunc(PyByteArrayRef::isalnum),
"isalpha" => context.new_rustfunc(PyByteArrayRef::isalpha),
@@ -80,6 +82,12 @@ pub fn init(context: &PyContext) {
"pop" => context.new_rustfunc(PyByteArrayRef::pop),
"upper" => context.new_rustfunc(PyByteArrayRef::upper)
});
let bytearrayiterator_type = &context.bytearrayiterator_type;
extend_class!(context, bytearrayiterator_type, {
"__next__" => context.new_rustfunc(PyByteArrayIteratorRef::next),
"__iter__" => context.new_rustfunc(PyByteArrayIteratorRef::iter),
});
}
fn bytearray_new(
@@ -225,6 +233,13 @@ impl PyByteArrayRef {
value: RefCell::new(bytes),
}
}
fn iter(self, _vm: &VirtualMachine) -> PyByteArrayIterator {
PyByteArrayIterator {
position: Cell::new(0),
bytearray: self,
}
}
}
// helper function for istitle
@@ -266,3 +281,33 @@ mod tests {
assert_eq!(&to_hex(&[11u8, 222u8]), "\\x0b\\xde");
}
}
#[derive(Debug)]
pub struct PyByteArrayIterator {
position: Cell<usize>,
bytearray: PyByteArrayRef,
}
impl PyValue for PyByteArrayIterator {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.bytearrayiterator_type()
}
}
type PyByteArrayIteratorRef = PyRef<PyByteArrayIterator>;
impl PyByteArrayIteratorRef {
fn next(self, vm: &VirtualMachine) -> PyResult<u8> {
if self.position.get() < self.bytearray.value.borrow().len() {
let ret = self.bytearray.value.borrow()[self.position.get()];
self.position.set(self.position.get() + 1);
Ok(ret)
} else {
Err(objiter::new_stop_iteration(vm))
}
}
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
+397
View File
@@ -0,0 +1,397 @@
use crate::pyobject::PyObjectRef;
use num_bigint::BigInt;
use crate::function::OptionalArg;
use crate::vm::VirtualMachine;
use crate::pyobject::{PyResult, TypeProtocol};
use crate::obj::objstr::PyString;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use super::objint;
use super::objsequence::PySliceableSequence;
use crate::obj::objint::PyInt;
use num_integer::Integer;
use num_traits::ToPrimitive;
use super::objbytearray::{get_value as get_value_bytearray, PyByteArray};
use super::objbytes::PyBytes;
#[derive(Debug, Default, Clone)]
pub struct PyByteInner {
pub elements: Vec<u8>,
}
impl PyByteInner {
pub fn new(
val_option: OptionalArg<PyObjectRef>,
enc_option: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyByteInner> {
// First handle bytes(string, encoding[, errors])
if let OptionalArg::Present(enc) = enc_option {
if let OptionalArg::Present(eval) = val_option {
if let Ok(input) = eval.downcast::<PyString>() {
if let Ok(encoding) = enc.clone().downcast::<PyString>() {
if &encoding.value.to_lowercase() == "utf8"
|| &encoding.value.to_lowercase() == "utf-8"
// TODO: different encoding
{
return Ok(PyByteInner {
elements: input.value.as_bytes().to_vec(),
});
} else {
return Err(
vm.new_value_error(format!("unknown encoding: {}", encoding.value)), //should be lookup error
);
}
} else {
return Err(vm.new_type_error(format!(
"bytes() argument 2 must be str, not {}",
enc.class().name
)));
}
} else {
return Err(vm.new_type_error("encoding without a string argument".to_string()));
}
} else {
return Err(vm.new_type_error("encoding without a string argument".to_string()));
}
// Only one argument
} else {
let value = if let OptionalArg::Present(ival) = val_option {
match_class!(ival.clone(),
i @ PyInt => {
let size = objint::get_value(&i.into_object()).to_usize().unwrap();
Ok(vec![0; size])},
_l @ PyString=> {return Err(vm.new_type_error("string argument without an encoding".to_string()));},
obj => {
let elements = vm.extract_elements(&obj).or_else(|_| {Err(vm.new_type_error(format!(
"cannot convert {} object to bytes", obj.class().name)))});
let mut data_bytes = vec![];
for elem in elements.unwrap(){
let v = objint::to_int(vm, &elem, 10)?;
if let Some(i) = v.to_u8() {
data_bytes.push(i);
} else {
return Err(vm.new_value_error("bytes must be in range(0, 256)".to_string()));
}
}
Ok(data_bytes)
}
)
} else {
Ok(vec![])
};
match value {
Ok(val) => Ok(PyByteInner { elements: val }),
Err(err) => Err(err),
}
}
}
pub fn repr(&self) -> PyResult<String> {
let mut res = String::with_capacity(self.elements.len());
for i in self.elements.iter() {
match i {
0..=8 => res.push_str(&format!("\\x0{}", i)),
9 => res.push_str("\\t"),
10 => res.push_str("\\n"),
13 => res.push_str("\\r"),
32..=126 => res.push(*(i) as char),
_ => res.push_str(&format!("\\x{:x}", i)),
}
}
Ok(res)
}
pub fn len(&self) -> usize {
self.elements.len()
}
pub fn is_empty(&self) -> bool {
self.elements.len() == 0
}
pub fn eq(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult {
if self.elements == other.elements {
Ok(vm.new_bool(true))
} else {
Ok(vm.new_bool(false))
}
}
pub fn ge(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult {
if self.elements >= other.elements {
Ok(vm.new_bool(true))
} else {
Ok(vm.new_bool(false))
}
}
pub fn le(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult {
if self.elements <= other.elements {
Ok(vm.new_bool(true))
} else {
Ok(vm.new_bool(false))
}
}
pub fn gt(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult {
if self.elements > other.elements {
Ok(vm.new_bool(true))
} else {
Ok(vm.new_bool(false))
}
}
pub fn lt(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult {
if self.elements < other.elements {
Ok(vm.new_bool(true))
} else {
Ok(vm.new_bool(false))
}
}
pub fn hash(&self) -> usize {
let mut hasher = DefaultHasher::new();
self.elements.hash(&mut hasher);
hasher.finish() as usize
}
pub fn add(&self, other: &PyByteInner, _vm: &VirtualMachine) -> Vec<u8> {
let elements: Vec<u8> = self
.elements
.iter()
.chain(other.elements.iter())
.cloned()
.collect();
elements
}
pub fn contains_bytes(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult {
for (n, i) in self.elements.iter().enumerate() {
if n + other.len() <= self.len()
&& *i == other.elements[0]
&& &self.elements[n..n + other.len()] == other.elements.as_slice()
{
return Ok(vm.new_bool(true));
}
}
Ok(vm.new_bool(false))
}
pub fn contains_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
if let Some(int) = int.as_bigint().to_u8() {
if self.elements.contains(&int) {
Ok(vm.new_bool(true))
} else {
Ok(vm.new_bool(false))
}
} else {
Err(vm.new_value_error("byte must be in range(0, 256)".to_string()))
}
}
pub fn getitem_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult {
if let Some(idx) = self.elements.get_pos(int.as_bigint().to_i32().unwrap()) {
Ok(vm.new_int(self.elements[idx]))
} else {
Err(vm.new_index_error("index out of range".to_string()))
}
}
pub fn getitem_slice(&self, slice: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
Ok(vm
.ctx
.new_bytes(self.elements.get_slice_items(vm, slice).unwrap()))
}
pub fn isalnum(&self, vm: &VirtualMachine) -> PyResult {
Ok(vm.new_bool(
!self.elements.is_empty()
&& self
.elements
.iter()
.all(|x| char::from(*x).is_alphanumeric()),
))
}
pub fn isalpha(&self, vm: &VirtualMachine) -> PyResult {
Ok(vm.new_bool(
!self.elements.is_empty()
&& self.elements.iter().all(|x| char::from(*x).is_alphabetic()),
))
}
pub fn isascii(&self, vm: &VirtualMachine) -> PyResult {
Ok(vm.new_bool(
!self.elements.is_empty() && self.elements.iter().all(|x| char::from(*x).is_ascii()),
))
}
pub fn isdigit(&self, vm: &VirtualMachine) -> PyResult {
Ok(vm.new_bool(
!self.elements.is_empty() && self.elements.iter().all(|x| char::from(*x).is_digit(10)),
))
}
pub fn islower(&self, vm: &VirtualMachine) -> PyResult {
Ok(vm.new_bool(
!self.elements.is_empty()
&& self
.elements
.iter()
.filter(|x| !char::from(**x).is_whitespace())
.all(|x| char::from(*x).is_lowercase()),
))
}
pub fn isspace(&self, vm: &VirtualMachine) -> PyResult {
Ok(vm.new_bool(
!self.elements.is_empty()
&& self.elements.iter().all(|x| char::from(*x).is_whitespace()),
))
}
pub fn isupper(&self, vm: &VirtualMachine) -> PyResult {
Ok(vm.new_bool(
!self.elements.is_empty()
&& self
.elements
.iter()
.filter(|x| !char::from(**x).is_whitespace())
.all(|x| char::from(*x).is_uppercase()),
))
}
pub fn istitle(&self, vm: &VirtualMachine) -> PyResult {
if self.elements.is_empty() {
return Ok(vm.new_bool(false));
}
let mut iter = self.elements.iter().peekable();
let mut prev_cased = false;
while let Some(c) = iter.next() {
let current = char::from(*c);
let next = if let Some(k) = iter.peek() {
char::from(**k)
} else if current.is_uppercase() {
return Ok(vm.new_bool(!prev_cased));
} else {
return Ok(vm.new_bool(prev_cased));
};
let is_cased = current.to_uppercase().next().unwrap() != current
|| current.to_lowercase().next().unwrap() != current;
if (is_cased && next.is_uppercase() && !prev_cased)
|| (!is_cased && next.is_lowercase())
{
return Ok(vm.new_bool(false));
}
prev_cased = is_cased;
}
Ok(vm.new_bool(true))
}
pub fn lower(&self, _vm: &VirtualMachine) -> Vec<u8> {
self.elements.to_ascii_lowercase()
}
pub fn upper(&self, _vm: &VirtualMachine) -> Vec<u8> {
self.elements.to_ascii_uppercase()
}
pub fn capitalize(&self, _vm: &VirtualMachine) -> Vec<u8> {
let mut new: Vec<u8> = Vec::new();
if let Some((first, second)) = self.elements.split_first() {
new.push(first.to_ascii_uppercase());
second.iter().for_each(|x| new.push(x.to_ascii_lowercase()));
}
new
}
pub fn hex(&self, vm: &VirtualMachine) -> PyResult {
let bla = self
.elements
.iter()
.map(|x| format!("{:02x}", x))
.collect::<String>();
Ok(vm.ctx.new_str(bla))
}
pub fn fromhex(string: String, vm: &VirtualMachine) -> Result<Vec<u8>, PyObjectRef> {
// first check for invalid character
for (i, c) in string.char_indices() {
if !c.is_digit(16) && !c.is_whitespace() {
return Err(vm.new_value_error(format!(
"non-hexadecimal number found in fromhex() arg at position {}",
i
)));
}
}
// strip white spaces
let stripped = string.split_whitespace().collect::<String>();
// Hex is evaluated on 2 digits
if stripped.len() % 2 != 0 {
return Err(vm.new_value_error(format!(
"non-hexadecimal number found in fromhex() arg at position {}",
stripped.len() - 1
)));
}
// parse even string
Ok(stripped
.chars()
.collect::<Vec<char>>()
.chunks(2)
.map(|x| x.to_vec().iter().collect::<String>())
.map(|x| u8::from_str_radix(&x, 16).unwrap())
.collect::<Vec<u8>>())
}
pub fn center(&self, width: &BigInt, fillbyte: u8, _vm: &VirtualMachine) -> Vec<u8> {
let width = width.to_usize().unwrap();
// adjust right et left side
if width <= self.len() {
return self.elements.clone();
}
let diff: usize = width - self.len();
let mut ln: usize = diff / 2;
let mut rn: usize = ln;
if diff.is_odd() && self.len() % 2 == 0 {
ln += 1
}
if diff.is_odd() && self.len() % 2 != 0 {
rn += 1
}
// merge all
let mut res = vec![fillbyte; ln];
res.extend_from_slice(&self.elements[..]);
res.extend_from_slice(&vec![fillbyte; rn][..]);
res
}
}
pub fn is_byte(obj: &PyObjectRef) -> Option<Vec<u8>> {
match_class!(obj.clone(),
i @ PyBytes => Some(i.get_value().to_vec()),
j @ PyByteArray => Some(get_value_bytearray(&j.as_object()).to_vec()),
_ => None)
}
+252 -153
View File
@@ -1,27 +1,42 @@
use std::cell::Cell;
use std::hash::{Hash, Hasher};
use crate::obj::objint::PyInt;
use crate::obj::objstr::PyString;
use crate::vm::VirtualMachine;
use core::cell::Cell;
use std::ops::Deref;
use num_traits::ToPrimitive;
use crate::function::OptionalArg;
use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::function::{OptionalArg, PyFuncArgs};
use crate::pyobject::{
PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol,
};
use crate::vm::VirtualMachine;
use super::objbyteinner::{is_byte, PyByteInner};
use super::objiter;
use super::objslice::PySlice;
use super::objtype::PyClassRef;
use super::objint;
use super::objtype::{self, PyClassRef};
#[derive(Debug)]
/// "bytes(iterable_of_ints) -> bytes\n\
/// bytes(string, encoding[, errors]) -> bytes\n\
/// bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\
/// bytes(int) -> bytes object of size given by the parameter initialized with null bytes\n\
/// bytes() -> empty bytes object\n\nConstruct an immutable array of bytes from:\n \
/// - an iterable yielding integers in range(256)\n \
/// - a text string encoded using the specified encoding\n \
/// - any object implementing the buffer API.\n \
/// - an integer";
#[pyclass(name = "bytes")]
#[derive(Clone, Debug)]
pub struct PyBytes {
value: Vec<u8>,
inner: PyByteInner,
}
type PyBytesRef = PyRef<PyBytes>;
pub type PyBytesRef = PyRef<PyBytes>;
impl PyBytes {
pub fn new(data: Vec<u8>) -> Self {
PyBytes { value: data }
pub fn new(elements: Vec<u8>) -> Self {
PyBytes {
inner: PyByteInner { elements },
}
}
pub fn get_value(&self) -> &[u8] {
&self.inner.elements
}
}
@@ -29,7 +44,7 @@ impl Deref for PyBytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.value
&self.inner.elements
}
}
@@ -39,165 +54,249 @@ impl PyValue for PyBytes {
}
}
// Binary data support
pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref<Target = Vec<u8>> + 'a {
&obj.payload::<PyBytes>().unwrap().inner.elements
}
// Fill bytes class methods:
pub fn init(context: &PyContext) {
let bytes_type = context.bytes_type.as_object();
let bytes_doc =
"bytes(iterable_of_ints) -> bytes\n\
bytes(string, encoding[, errors]) -> bytes\n\
bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\
bytes(int) -> bytes object of size given by the parameter initialized with null bytes\n\
bytes() -> empty bytes object\n\nConstruct an immutable array of bytes from:\n \
- an iterable yielding integers in range(256)\n \
- a text string encoded using the specified encoding\n \
- any object implementing the buffer API.\n \
- an integer";
PyBytesRef::extend_class(context, &context.bytes_type);
let bytes_type = &context.bytes_type;
extend_class!(context, bytes_type, {
"__eq__" => context.new_rustfunc(bytes_eq),
"__lt__" => context.new_rustfunc(bytes_lt),
"__le__" => context.new_rustfunc(bytes_le),
"__gt__" => context.new_rustfunc(bytes_gt),
"__ge__" => context.new_rustfunc(bytes_ge),
"__hash__" => context.new_rustfunc(bytes_hash),
"__new__" => context.new_rustfunc(bytes_new),
"__repr__" => context.new_rustfunc(bytes_repr),
"__len__" => context.new_rustfunc(bytes_len),
"__iter__" => context.new_rustfunc(bytes_iter),
"__doc__" => context.new_str(bytes_doc.to_string())
"fromhex" => context.new_rustfunc(PyBytesRef::fromhex),
});
let bytesiterator_type = &context.bytesiterator_type;
extend_class!(context, bytesiterator_type, {
"__next__" => context.new_rustfunc(PyBytesIteratorRef::next),
"__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter),
});
}
fn bytes_new(
cls: PyClassRef,
val_option: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyBytesRef> {
// Create bytes data:
let value = if let OptionalArg::Present(ival) = val_option {
let elements = vm.extract_elements(&ival)?;
let mut data_bytes = vec![];
for elem in elements.iter() {
let v = objint::to_int(vm, elem, 10)?;
data_bytes.push(v.to_u8().unwrap());
#[pyimpl]
impl PyBytesRef {
#[pymethod(name = "__new__")]
fn bytes_new(
cls: PyClassRef,
val_option: OptionalArg<PyObjectRef>,
enc_option: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyBytesRef> {
PyBytes {
inner: PyByteInner::new(val_option, enc_option, vm)?,
}
data_bytes
// return Err(vm.new_type_error("Cannot construct bytes".to_string()));
} else {
vec![]
};
.into_ref_with_type(vm, cls)
}
PyBytes::new(value).into_ref_with_type(vm, cls)
}
#[pymethod(name = "__repr__")]
fn repr(self, vm: &VirtualMachine) -> PyResult {
Ok(vm.new_str(format!("b'{}'", self.inner.repr()?)))
}
fn bytes_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(
vm,
args,
required = [(a, Some(vm.ctx.bytes_type())), (b, None)]
);
#[pymethod(name = "__len__")]
fn len(self, _vm: &VirtualMachine) -> usize {
self.inner.len()
}
let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) {
get_value(a).to_vec() == get_value(b).to_vec()
} else {
false
};
Ok(vm.ctx.new_bool(result))
}
#[pymethod(name = "__eq__")]
fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match_class!(other,
bytes @ PyBytes => self.inner.eq(&bytes.inner, vm),
_ => Ok(vm.ctx.not_implemented()))
}
fn bytes_ge(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(
vm,
args,
required = [(a, Some(vm.ctx.bytes_type())), (b, None)]
);
#[pymethod(name = "__ge__")]
fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match_class!(other,
bytes @ PyBytes => self.inner.ge(&bytes.inner, vm),
_ => Ok(vm.ctx.not_implemented()))
}
#[pymethod(name = "__le__")]
fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match_class!(other,
bytes @ PyBytes => self.inner.le(&bytes.inner, vm),
_ => Ok(vm.ctx.not_implemented()))
}
#[pymethod(name = "__gt__")]
fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match_class!(other,
bytes @ PyBytes => self.inner.gt(&bytes.inner, vm),
_ => Ok(vm.ctx.not_implemented()))
}
#[pymethod(name = "__lt__")]
fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match_class!(other,
bytes @ PyBytes => self.inner.lt(&bytes.inner, vm),
_ => Ok(vm.ctx.not_implemented()))
}
#[pymethod(name = "__hash__")]
fn hash(self, _vm: &VirtualMachine) -> usize {
self.inner.hash()
}
let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) {
get_value(a).to_vec() >= get_value(b).to_vec()
} else {
return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>'", a, b)));
};
Ok(vm.ctx.new_bool(result))
}
#[pymethod(name = "__iter__")]
fn iter(self, _vm: &VirtualMachine) -> PyBytesIterator {
PyBytesIterator {
position: Cell::new(0),
bytes: self,
}
}
fn bytes_gt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(
vm,
args,
required = [(a, Some(vm.ctx.bytes_type())), (b, None)]
);
#[pymethod(name = "__add__")]
fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match_class!(other,
bytes @ PyBytes => Ok(vm.ctx.new_bytes(self.inner.add(&bytes.inner, vm))),
_ => Ok(vm.ctx.not_implemented()))
}
let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) {
get_value(a).to_vec() > get_value(b).to_vec()
} else {
return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>='", a, b)));
};
Ok(vm.ctx.new_bool(result))
}
#[pymethod(name = "__contains__")]
fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match_class!(needle,
bytes @ PyBytes => self.inner.contains_bytes(&bytes.inner, vm),
int @ PyInt => self.inner.contains_int(&int, vm),
obj => Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj))))
}
fn bytes_le(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(
vm,
args,
required = [(a, Some(vm.ctx.bytes_type())), (b, None)]
);
#[pymethod(name = "__getitem__")]
fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match_class!(needle,
int @ PyInt => self.inner.getitem_int(&int, vm),
slice @ PySlice => self.inner.getitem_slice(slice.as_object(), vm),
obj => Err(vm.new_type_error(format!("byte indices must be integers or slices, not {}", obj))))
}
let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) {
get_value(a).to_vec() <= get_value(b).to_vec()
} else {
return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<'", a, b)));
};
Ok(vm.ctx.new_bool(result))
}
#[pymethod(name = "isalnum")]
fn isalnum(self, vm: &VirtualMachine) -> PyResult {
self.inner.isalnum(vm)
}
fn bytes_lt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(
vm,
args,
required = [(a, Some(vm.ctx.bytes_type())), (b, None)]
);
#[pymethod(name = "isalpha")]
fn isalpha(self, vm: &VirtualMachine) -> PyResult {
self.inner.isalpha(vm)
}
let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) {
get_value(a).to_vec() < get_value(b).to_vec()
} else {
return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<='", a, b)));
};
Ok(vm.ctx.new_bool(result))
}
#[pymethod(name = "isascii")]
fn isascii(self, vm: &VirtualMachine) -> PyResult {
self.inner.isascii(vm)
}
fn bytes_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(a, Some(vm.ctx.bytes_type()))]);
#[pymethod(name = "isdigit")]
fn isdigit(self, vm: &VirtualMachine) -> PyResult {
self.inner.isdigit(vm)
}
let byte_vec = get_value(a).to_vec();
Ok(vm.ctx.new_int(byte_vec.len()))
}
#[pymethod(name = "islower")]
fn islower(self, vm: &VirtualMachine) -> PyResult {
self.inner.islower(vm)
}
fn bytes_hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytes_type()))]);
let data = get_value(zelf);
let mut hasher = std::collections::hash_map::DefaultHasher::new();
data.hash(&mut hasher);
let hash = hasher.finish();
Ok(vm.ctx.new_int(hash))
}
#[pymethod(name = "isspace")]
fn isspace(self, vm: &VirtualMachine) -> PyResult {
self.inner.isspace(vm)
}
pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref<Target = Vec<u8>> + 'a {
&obj.payload::<PyBytes>().unwrap().value
}
#[pymethod(name = "isupper")]
fn isupper(self, vm: &VirtualMachine) -> PyResult {
self.inner.isupper(vm)
}
fn bytes_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytes_type()))]);
let value = get_value(obj);
let data = String::from_utf8(value.to_vec()).unwrap();
Ok(vm.new_str(format!("b'{}'", data)))
}
#[pymethod(name = "istitle")]
fn istitle(self, vm: &VirtualMachine) -> PyResult {
self.inner.istitle(vm)
}
fn bytes_iter(obj: PyBytesRef, _vm: &VirtualMachine) -> PyIteratorValue {
PyIteratorValue {
position: Cell::new(0),
iterated_obj: obj.into_object(),
#[pymethod(name = "lower")]
fn lower(self, vm: &VirtualMachine) -> PyResult {
Ok(vm.ctx.new_bytes(self.inner.lower(vm)))
}
#[pymethod(name = "upper")]
fn upper(self, vm: &VirtualMachine) -> PyResult {
Ok(vm.ctx.new_bytes(self.inner.upper(vm)))
}
#[pymethod(name = "capitalize")]
fn capitalize(self, vm: &VirtualMachine) -> PyResult {
Ok(vm.ctx.new_bytes(self.inner.capitalize(vm)))
}
#[pymethod(name = "hex")]
fn hex(self, vm: &VirtualMachine) -> PyResult {
self.inner.hex(vm)
}
// #[pymethod(name = "fromhex")]
fn fromhex(string: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match_class!(string,
s @ PyString => {
match PyByteInner::fromhex(s.to_string(), vm) {
Ok(x) => Ok(vm.ctx.new_bytes(x)),
Err(y) => Err(y)}},
obj => Err(vm.new_type_error(format!("fromhex() argument must be str, not {}", obj )))
)
}
#[pymethod(name = "center")]
fn center(
self,
width: PyObjectRef,
fillbyte: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult {
let sym = if let OptionalArg::Present(v) = fillbyte {
match is_byte(&v) {
Some(x) => {
if x.len() == 1 {
x[0]
} else {
return Err(vm.new_type_error(format!(
"center() argument 2 must be a byte string of length 1, not {}",
&v
)));
}
}
None => {
return Err(vm.new_type_error(format!(
"center() argument 2 must be a byte string of length 1, not {}",
&v
)));
}
}
} else {
32 // default is space
};
match_class!(width,
i @PyInt => Ok(vm.ctx.new_bytes(self.inner.center(i.as_bigint(), sym, vm))),
obj => {Err(vm.new_type_error(format!("{} cannot be interpreted as an integer", obj)))}
)
}
}
#[derive(Debug)]
pub struct PyBytesIterator {
position: Cell<usize>,
bytes: PyBytesRef,
}
impl PyValue for PyBytesIterator {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.bytesiterator_type()
}
}
type PyBytesIteratorRef = PyRef<PyBytesIterator>;
impl PyBytesIteratorRef {
fn next(self, vm: &VirtualMachine) -> PyResult<u8> {
if self.position.get() < self.bytes.inner.len() {
let ret = self.bytes[self.position.get()];
self.position.set(self.position.get() + 1);
Ok(ret)
} else {
Err(objiter::new_stop_iteration(vm))
}
}
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
+2
View File
@@ -9,6 +9,8 @@ pub struct PyClassMethod {
pub type PyClassMethodRef = PyRef<PyClassMethod>;
impl PyValue for PyClassMethod {
const HAVE_DICT: bool = true;
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.classmethod_type()
}
+1 -2
View File
@@ -80,8 +80,7 @@ impl PyCodeRef {
}
pub fn init(context: &PyContext) {
let code_type = context.code_type.as_object();
extend_class!(context, code_type, {
extend_class!(context, &context.code_type, {
"__new__" => context.new_rustfunc(PyCodeRef::new),
"__repr__" => context.new_rustfunc(PyCodeRef::repr),
+92 -125
View File
@@ -1,11 +1,11 @@
use num_complex::Complex64;
use num_traits::ToPrimitive;
use crate::function::{OptionalArg, PyFuncArgs};
use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol};
use crate::function::OptionalArg;
use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::vm::VirtualMachine;
use super::objfloat;
use super::objfloat::{self, PyFloat};
use super::objint;
use super::objtype::{self, PyClassRef};
@@ -35,17 +35,17 @@ pub fn init(context: &PyContext) {
This is equivalent to (real + imag*1j) where imag defaults to 0.";
extend_class!(context, complex_type, {
"__abs__" => context.new_rustfunc(complex_abs),
"__add__" => context.new_rustfunc(complex_add),
"__doc__" => context.new_str(complex_doc.to_string()),
"__eq__" => context.new_rustfunc(complex_eq),
"__neg__" => context.new_rustfunc(complex_neg),
"__new__" => context.new_rustfunc(complex_new),
"__radd__" => context.new_rustfunc(complex_radd),
"__repr__" => context.new_rustfunc(complex_repr),
"conjugate" => context.new_rustfunc(complex_conjugate),
"imag" => context.new_property(complex_imag),
"real" => context.new_property(complex_real)
"__abs__" => context.new_rustfunc(PyComplexRef::abs),
"__add__" => context.new_rustfunc(PyComplexRef::add),
"__eq__" => context.new_rustfunc(PyComplexRef::eq),
"__neg__" => context.new_rustfunc(PyComplexRef::neg),
"__new__" => context.new_rustfunc(PyComplexRef::new),
"__radd__" => context.new_rustfunc(PyComplexRef::radd),
"__repr__" => context.new_rustfunc(PyComplexRef::repr),
"conjugate" => context.new_rustfunc(PyComplexRef::conjugate),
"imag" => context.new_property(PyComplexRef::imag),
"real" => context.new_property(PyComplexRef::real)
});
}
@@ -53,128 +53,95 @@ pub fn get_value(obj: &PyObjectRef) -> Complex64 {
obj.payload::<PyComplex>().unwrap().value
}
fn complex_new(
cls: PyClassRef,
real: OptionalArg<PyObjectRef>,
imag: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyComplexRef> {
let real = match real {
OptionalArg::Missing => 0.0,
OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?,
};
impl PyComplexRef {
fn new(
cls: PyClassRef,
real: OptionalArg<PyObjectRef>,
imag: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyComplexRef> {
let real = match real {
OptionalArg::Missing => 0.0,
OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?,
};
let imag = match imag {
OptionalArg::Missing => 0.0,
OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?,
};
let imag = match imag {
OptionalArg::Missing => 0.0,
OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?,
};
let value = Complex64::new(real, imag);
PyComplex { value }.into_ref_with_type(vm, cls)
}
fn complex_real(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]);
let Complex64 { re, .. } = get_value(zelf);
Ok(vm.ctx.new_float(re))
}
fn complex_imag(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]);
let Complex64 { im, .. } = get_value(zelf);
Ok(vm.ctx.new_float(im))
}
fn complex_abs(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]);
let Complex64 { re, im } = get_value(zelf);
Ok(vm.ctx.new_float(re.hypot(im)))
}
fn complex_add(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(
vm,
args,
required = [(i, Some(vm.ctx.complex_type())), (i2, None)]
);
let v1 = get_value(i);
if objtype::isinstance(i2, &vm.ctx.complex_type()) {
Ok(vm.ctx.new_complex(v1 + get_value(i2)))
} else if objtype::isinstance(i2, &vm.ctx.int_type()) {
Ok(vm.ctx.new_complex(Complex64::new(
v1.re + objint::get_value(i2).to_f64().unwrap(),
v1.im,
)))
} else {
Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2)))
let value = Complex64::new(real, imag);
PyComplex { value }.into_ref_with_type(vm, cls)
}
}
fn complex_radd(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(
vm,
args,
required = [(i, Some(vm.ctx.complex_type())), (i2, None)]
);
let v1 = get_value(i);
if objtype::isinstance(i2, &vm.ctx.int_type()) {
Ok(vm.ctx.new_complex(Complex64::new(
v1.re + objint::get_value(i2).to_f64().unwrap(),
v1.im,
)))
} else {
Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2)))
fn real(self, _vm: &VirtualMachine) -> PyFloat {
self.value.re.into()
}
}
fn complex_conjugate(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(i, Some(vm.ctx.complex_type()))]);
fn imag(self, _vm: &VirtualMachine) -> PyFloat {
self.value.im.into()
}
let v1 = get_value(i);
Ok(vm.ctx.new_complex(v1.conj()))
}
fn abs(self, _vm: &VirtualMachine) -> PyFloat {
let Complex64 { im, re } = self.value;
re.hypot(im).into()
}
fn complex_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(
vm,
args,
required = [(zelf, Some(vm.ctx.complex_type())), (other, None)]
);
let z = get_value(zelf);
let result = if objtype::isinstance(other, &vm.ctx.complex_type()) {
z == get_value(other)
} else if objtype::isinstance(other, &vm.ctx.int_type()) {
match objint::get_value(other).to_f64() {
Some(f) => z.im == 0.0f64 && z.re == f,
None => false,
fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.complex_type()) {
vm.ctx.new_complex(self.value + get_value(&other))
} else if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_complex(Complex64::new(
self.value.re + objint::get_value(&other).to_f64().unwrap(),
self.value.im,
))
} else {
vm.ctx.not_implemented()
}
} else if objtype::isinstance(other, &vm.ctx.float_type()) {
z.im == 0.0 && z.re == objfloat::get_value(other)
} else {
return Ok(vm.ctx.not_implemented());
};
}
Ok(vm.ctx.new_bool(result))
}
fn radd(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_complex(Complex64::new(
self.value.re + objint::get_value(&other).to_f64().unwrap(),
self.value.im,
))
} else {
vm.ctx.not_implemented()
}
}
fn complex_neg(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]);
Ok(vm.ctx.new_complex(-get_value(zelf)))
}
fn conjugate(self, _vm: &VirtualMachine) -> PyComplex {
self.value.conj().into()
}
fn complex_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(obj, Some(vm.ctx.complex_type()))]);
let v = get_value(obj);
let repr = if v.re == 0. {
format!("{}j", v.im)
} else {
format!("({}+{}j)", v.re, v.im)
};
Ok(vm.new_str(repr))
fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
let result = if objtype::isinstance(&other, &vm.ctx.complex_type()) {
self.value == get_value(&other)
} else if objtype::isinstance(&other, &vm.ctx.int_type()) {
match objint::get_value(&other).to_f64() {
Some(f) => self.value.im == 0.0f64 && self.value.re == f,
None => false,
}
} else if objtype::isinstance(&other, &vm.ctx.float_type()) {
self.value.im == 0.0 && self.value.re == objfloat::get_value(&other)
} else {
return vm.ctx.not_implemented();
};
vm.ctx.new_bool(result)
}
fn neg(self, _vm: &VirtualMachine) -> PyComplex {
PyComplex::from(-self.value)
}
fn repr(self, _vm: &VirtualMachine) -> String {
let Complex64 { re, im } = self.value;
if re == 0.0 {
format!("{}j", im)
} else {
format!("({}+{}j)", re, im)
}
}
}
+339 -208
View File
@@ -1,25 +1,25 @@
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::fmt;
use std::ops::{Deref, DerefMut};
use crate::function::{KwArgs, OptionalArg};
use crate::pyobject::{
DictProtocol, PyAttributes, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue,
IdProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult,
PyValue,
};
use crate::vm::{ReprGuard, VirtualMachine};
use super::objbool;
use super::objiter;
use super::objstr::{self, PyStringRef};
use super::objtype;
use super::objstr;
use crate::dictdatatype;
use crate::obj::objtype::PyClassRef;
use crate::pyobject::PyClassImpl;
pub type DictContentType = HashMap<String, (PyObjectRef, PyObjectRef)>;
pub type DictContentType = dictdatatype::Dict;
#[derive(Default)]
pub struct PyDict {
// TODO: should be private
pub entries: RefCell<DictContentType>,
entries: RefCell<DictContentType>,
}
pub type PyDictRef = PyRef<PyDict>;
@@ -36,107 +36,35 @@ impl PyValue for PyDict {
}
}
pub fn get_elements<'a>(obj: &'a PyObjectRef) -> impl Deref<Target = DictContentType> + 'a {
obj.payload::<PyDict>().unwrap().entries.borrow()
}
pub fn get_mut_elements<'a>(obj: &'a PyObjectRef) -> impl DerefMut<Target = DictContentType> + 'a {
obj.payload::<PyDict>().unwrap().entries.borrow_mut()
}
pub fn set_item(
dict: &PyObjectRef,
_vm: &VirtualMachine,
needle: &PyObjectRef,
value: &PyObjectRef,
) {
// TODO: use vm to call eventual __hash__ and __eq__methods.
let mut elements = get_mut_elements(dict);
set_item_in_content(&mut elements, needle, value);
}
pub fn set_item_in_content(
elements: &mut DictContentType,
needle: &PyObjectRef,
value: &PyObjectRef,
) {
// XXX: Currently, we only support String keys, so we have to unwrap the
// PyObject (and ensure it is a String).
// TODO: invoke __hash__ function here!
let needle_str = objstr::get_value(needle);
elements.insert(needle_str, (needle.clone(), value.clone()));
}
pub fn get_key_value_pairs(dict: &PyObjectRef) -> Vec<(PyObjectRef, PyObjectRef)> {
let dict_elements = get_elements(dict);
get_key_value_pairs_from_content(&dict_elements)
}
pub fn get_key_value_pairs_from_content(
dict_content: &DictContentType,
) -> Vec<(PyObjectRef, PyObjectRef)> {
let mut pairs: Vec<(PyObjectRef, PyObjectRef)> = Vec::new();
for (_str_key, pair) in dict_content.iter() {
let (key, obj) = pair;
pairs.push((key.clone(), obj.clone()));
}
pairs
}
pub fn get_item(dict: &PyObjectRef, key: &PyObjectRef) -> Option<PyObjectRef> {
let needle_str = objstr::get_value(key);
get_key_str(dict, &needle_str)
}
// Special case for the case when requesting a str key from a dict:
pub fn get_key_str(dict: &PyObjectRef, key: &str) -> Option<PyObjectRef> {
let elements = get_elements(dict);
content_get_key_str(&elements, key)
}
/// Retrieve a key from dict contents:
pub fn content_get_key_str(elements: &DictContentType, key: &str) -> Option<PyObjectRef> {
// TODO: let hash: usize = key;
match elements.get(key) {
Some(v) => Some(v.1.clone()),
None => None,
}
}
pub fn contains_key_str(dict: &PyObjectRef, key: &str) -> bool {
let elements = get_elements(dict);
content_contains_key_str(&elements, key)
}
pub fn content_contains_key_str(elements: &DictContentType, key: &str) -> bool {
// TODO: let hash: usize = key;
elements.get(key).is_some()
}
/// Take a python dictionary and convert it to attributes.
pub fn py_dict_to_attributes(dict: &PyObjectRef) -> PyAttributes {
let mut attrs = PyAttributes::new();
for (key, value) in get_key_value_pairs(dict) {
let key = objstr::get_value(&key);
attrs.insert(key, value);
}
attrs
}
// Python dict methods:
impl PyDictRef {
fn new(
_class: PyClassRef, // TODO Support subclasses of int.
class: PyClassRef,
dict_obj: OptionalArg<PyObjectRef>,
kwargs: KwArgs,
vm: &VirtualMachine,
) -> PyResult<PyDictRef> {
let dict = vm.ctx.new_dict();
let dict = DictContentType::default();
let entries = RefCell::new(dict);
// it's unfortunate that we can't abstract over RefCall, as we should be able to use dict
// directly here, but that would require generic associated types
PyDictRef::merge(&entries, dict_obj, kwargs, vm)?;
PyDict { entries }.into_ref_with_type(vm, class)
}
fn merge(
dict: &RefCell<DictContentType>,
dict_obj: OptionalArg<PyObjectRef>,
kwargs: KwArgs,
vm: &VirtualMachine,
) -> PyResult<()> {
if let OptionalArg::Present(dict_obj) = dict_obj {
if objtype::isinstance(&dict_obj, &vm.ctx.dict_type()) {
for (needle, value) in get_key_value_pairs(&dict_obj) {
set_item(dict.as_object(), vm, &needle, &value);
let dicted: PyResult<PyDictRef> = dict_obj.clone().downcast();
if let Ok(dict_obj) = dicted {
for (key, value) in dict_obj {
dict.borrow_mut().insert(vm, &key, value)?;
}
} else {
let iter = objiter::get_iter(vm, &dict_obj)?;
@@ -149,36 +77,66 @@ impl PyDictRef {
None => break,
};
let elem_iter = objiter::get_iter(vm, &element)?;
let needle =
objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?;
let key = objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?;
let value = objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?;
if objiter::get_next_object(vm, &elem_iter)?.is_some() {
return Err(err(vm));
}
set_item(dict.as_object(), vm, &needle, &value);
dict.borrow_mut().insert(vm, &key, value)?;
}
}
}
for (needle, value) in kwargs.into_iter() {
let py_needle = vm.new_str(needle);
set_item(&dict.as_object(), vm, &py_needle, &value);
let mut dict_borrowed = dict.borrow_mut();
for (key, value) in kwargs.into_iter() {
dict_borrowed.insert(vm, &vm.new_str(key), value)?;
}
Ok(dict)
Ok(())
}
fn bool(self, _vm: &VirtualMachine) -> bool {
!self.entries.borrow().is_empty()
}
fn inner_eq(self, other: &PyDict, vm: &VirtualMachine) -> PyResult<bool> {
if other.entries.borrow().len() != self.entries.borrow().len() {
return Ok(false);
}
for (k, v1) in self {
match other.entries.borrow().get(vm, &k)? {
Some(v2) => {
if v1.is(&v2) {
continue;
}
let value = objbool::boolval(vm, vm._eq(v1, v2)?)?;
if !value {
return Ok(false);
}
}
None => {
return Ok(false);
}
}
}
Ok(true)
}
fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if let Some(other) = other.payload::<PyDict>() {
let eq = self.inner_eq(other, vm)?;
Ok(vm.ctx.new_bool(eq))
} else {
Ok(vm.ctx.not_implemented())
}
}
fn len(self, _vm: &VirtualMachine) -> usize {
self.entries.borrow().len()
}
fn repr(self, vm: &VirtualMachine) -> PyResult {
fn repr(self, vm: &VirtualMachine) -> PyResult<String> {
let s = if let Some(_guard) = ReprGuard::enter(self.as_object()) {
let elements = get_key_value_pairs(self.as_object());
let mut str_parts = vec![];
for (key, value) in elements {
for (key, value) in self {
let key_repr = vm.to_repr(&key)?;
let value_repr = vm.to_repr(&value)?;
str_parts.push(format!("{}: {}", key_repr.value, value_repr.value));
@@ -188,134 +146,296 @@ impl PyDictRef {
} else {
"{...}".to_string()
};
Ok(vm.new_str(s))
Ok(s)
}
fn contains(self, key: PyStringRef, _vm: &VirtualMachine) -> bool {
self.entries.borrow().contains_key(&key.value)
fn contains(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
self.entries.borrow().contains(vm, &key)
}
fn delitem(self, key: PyStringRef, vm: &VirtualMachine) -> PyResult<()> {
let key = &key.value;
// Delete the item:
let mut elements = self.entries.borrow_mut();
match elements.remove(key) {
Some(_) => Ok(()),
None => Err(vm.new_value_error(format!("Key not found: {}", key))),
}
fn inner_delitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
self.entries.borrow_mut().delete(vm, &key)
}
fn clear(self, _vm: &VirtualMachine) {
self.entries.borrow_mut().clear()
}
/// When iterating over a dictionary, we iterate over the keys of it.
fn iter(self, vm: &VirtualMachine) -> PyIteratorValue {
let keys = self
.entries
.borrow()
.values()
.map(|(k, _v)| k.clone())
.collect();
let key_list = vm.ctx.new_list(keys);
PyIteratorValue {
position: Cell::new(0),
iterated_obj: key_list,
}
fn iter(self, _vm: &VirtualMachine) -> PyDictKeyIterator {
PyDictKeyIterator::new(self)
}
fn values(self, vm: &VirtualMachine) -> PyIteratorValue {
let values = self
.entries
.borrow()
.values()
.map(|(_k, v)| v.clone())
.collect();
let values_list = vm.ctx.new_list(values);
PyIteratorValue {
position: Cell::new(0),
iterated_obj: values_list,
}
fn keys(self, _vm: &VirtualMachine) -> PyDictKeys {
PyDictKeys::new(self)
}
fn items(self, vm: &VirtualMachine) -> PyIteratorValue {
let items = self
.entries
.borrow()
.values()
.map(|(k, v)| vm.ctx.new_tuple(vec![k.clone(), v.clone()]))
.collect();
let items_list = vm.ctx.new_list(items);
PyIteratorValue {
position: Cell::new(0),
iterated_obj: items_list,
}
fn values(self, _vm: &VirtualMachine) -> PyDictValues {
PyDictValues::new(self)
}
fn setitem(self, needle: PyObjectRef, value: PyObjectRef, _vm: &VirtualMachine) {
let mut elements = self.entries.borrow_mut();
set_item_in_content(&mut elements, &needle, &value)
fn items(self, _vm: &VirtualMachine) -> PyDictItems {
PyDictItems::new(self)
}
fn getitem(self, key: PyStringRef, vm: &VirtualMachine) -> PyResult {
let key = &key.value;
fn inner_setitem(
self,
key: PyObjectRef,
value: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<()> {
self.entries.borrow_mut().insert(vm, &key, value)
}
// What we are looking for:
let elements = self.entries.borrow();
if elements.contains_key(key) {
Ok(elements[key].1.clone())
} else {
Err(vm.new_value_error(format!("Key not found: {}", key)))
fn inner_getitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if let Some(value) = self.entries.borrow().get(vm, &key)? {
return Ok(value);
}
if let Ok(method) = vm.get_method(self.clone().into_object(), "__missing__") {
return vm.invoke(method, vec![key]);
}
Err(vm.new_key_error(format!("Key not found: {}", vm.to_pystr(&key)?)))
}
fn get(
self,
key: PyStringRef,
key: PyObjectRef,
default: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyObjectRef {
// What we are looking for:
let key = &key.value;
) -> PyResult {
match self.entries.borrow().get(vm, &key)? {
Some(value) => Ok(value),
None => match default {
OptionalArg::Present(value) => Ok(value),
OptionalArg::Missing => Ok(vm.ctx.none()),
},
}
}
let elements = self.entries.borrow();
if elements.contains_key(key) {
elements[key].1.clone()
} else {
match default {
OptionalArg::Present(value) => value,
OptionalArg::Missing => vm.ctx.none(),
}
fn copy(self, _vm: &VirtualMachine) -> PyDict {
PyDict {
entries: self.entries.clone(),
}
}
fn update(
self,
dict_obj: OptionalArg<PyObjectRef>,
kwargs: KwArgs,
vm: &VirtualMachine,
) -> PyResult<()> {
PyDictRef::merge(&self.entries, dict_obj, kwargs, vm)
}
/// Take a python dictionary and convert it to attributes.
pub fn to_attributes(self) -> PyAttributes {
let mut attrs = PyAttributes::new();
for (key, value) in self {
let key = objstr::get_value(&key);
attrs.insert(key, value);
}
attrs
}
pub fn from_attributes(attrs: PyAttributes, vm: &VirtualMachine) -> PyResult<Self> {
let dict = DictContentType::default();
let entries = RefCell::new(dict);
for (key, value) in attrs {
entries
.borrow_mut()
.insert(vm, &vm.ctx.new_str(key), value)?;
}
Ok(PyDict { entries }.into_ref(vm))
}
fn hash(self, vm: &VirtualMachine) -> PyResult {
Err(vm.new_type_error("unhashable type".to_string()))
}
pub fn contains_key<T: IntoPyObject>(&self, key: T, vm: &VirtualMachine) -> bool {
let key = key.into_pyobject(vm).unwrap();
self.entries.borrow().contains(vm, &key).unwrap()
}
pub fn size(&self) -> dictdatatype::DictSize {
self.entries.borrow().size()
}
}
impl ItemProtocol for PyDictRef {
fn get_item<T: IntoPyObject>(&self, key: T, vm: &VirtualMachine) -> PyResult {
self.as_object().get_item(key, vm)
}
fn set_item<T: IntoPyObject>(
&self,
key: T,
value: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
self.as_object().set_item(key, value, vm)
}
fn del_item<T: IntoPyObject>(&self, key: T, vm: &VirtualMachine) -> PyResult {
self.as_object().del_item(key, vm)
}
}
// Implement IntoIterator so that we can easily iterate dictionaries from rust code.
impl IntoIterator for PyDictRef {
type Item = (PyObjectRef, PyObjectRef);
type IntoIter = DictIter;
fn into_iter(self) -> Self::IntoIter {
DictIter::new(self)
}
}
impl IntoIterator for &PyDictRef {
type Item = (PyObjectRef, PyObjectRef);
type IntoIter = DictIter;
fn into_iter(self) -> Self::IntoIter {
DictIter::new(self.clone())
}
}
pub struct DictIter {
dict: PyDictRef,
position: usize,
}
impl DictIter {
pub fn new(dict: PyDictRef) -> DictIter {
DictIter { dict, position: 0 }
}
}
impl Iterator for DictIter {
type Item = (PyObjectRef, PyObjectRef);
fn next(&mut self) -> Option<Self::Item> {
match self.dict.entries.borrow().next_entry(&mut self.position) {
Some((key, value)) => Some((key.clone(), value.clone())),
None => None,
}
}
}
impl DictProtocol for PyDictRef {
fn contains_key(&self, k: &str) -> bool {
content_contains_key_str(&self.entries.borrow(), k)
}
macro_rules! dict_iterator {
( $name: ident, $iter_name: ident, $class: ident, $iter_class: ident, $class_name: literal, $iter_class_name: literal, $result_fn: expr) => {
#[pyclass(name = $class_name)]
#[derive(Debug)]
struct $name {
pub dict: PyDictRef,
}
fn get_item(&self, k: &str) -> Option<PyObjectRef> {
content_get_key_str(&self.entries.borrow(), k)
}
#[pyimpl]
impl $name {
fn new(dict: PyDictRef) -> Self {
$name { dict: dict }
}
fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> {
get_key_value_pairs(self.as_object())
}
#[pymethod(name = "__iter__")]
fn iter(&self, _vm: &VirtualMachine) -> $iter_name {
$iter_name::new(self.dict.clone())
}
// Item set/get:
fn set_item(&self, ctx: &PyContext, key: &str, v: PyObjectRef) {
let key = ctx.new_str(key.to_string());
set_item_in_content(&mut self.entries.borrow_mut(), &key, &v);
}
#[pymethod(name = "__len__")]
fn len(&self, vm: &VirtualMachine) -> usize {
self.dict.clone().len(vm)
}
}
fn del_item(&self, key: &str) {
let mut elements = get_mut_elements(self.as_object());
elements.remove(key).unwrap();
}
impl PyValue for $name {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.$class.clone()
}
}
#[pyclass(name = $iter_class_name)]
#[derive(Debug)]
struct $iter_name {
pub dict: PyDictRef,
pub size: dictdatatype::DictSize,
pub position: Cell<usize>,
}
#[pyimpl]
impl $iter_name {
fn new(dict: PyDictRef) -> Self {
$iter_name {
position: Cell::new(0),
size: dict.size(),
dict,
}
}
#[pymethod(name = "__next__")]
fn next(&self, vm: &VirtualMachine) -> PyResult {
let mut position = self.position.get();
let dict = self.dict.entries.borrow();
if dict.has_changed_size(&self.size) {
return Err(vm.new_exception(
vm.ctx.exceptions.runtime_error.clone(),
"dictionary changed size during iteration".to_string(),
));
}
match dict.next_entry(&mut position) {
Some((key, value)) => {
self.position.set(position);
Ok($result_fn(vm, key, value))
}
None => Err(objiter::new_stop_iteration(vm)),
}
}
#[pymethod(name = "__iter__")]
fn iter(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyRef<Self> {
zelf
}
}
impl PyValue for $iter_name {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.$iter_class.clone()
}
}
};
}
dict_iterator! {
PyDictKeys,
PyDictKeyIterator,
dictkeys_type,
dictkeyiterator_type,
"dictkeys",
"dictkeyiterator",
|_vm: &VirtualMachine, key: &PyObjectRef, _value: &PyObjectRef| key.clone()
}
dict_iterator! {
PyDictValues,
PyDictValueIterator,
dictvalues_type,
dictvalueiterator_type,
"dictvalues",
"dictvalueiterator",
|_vm: &VirtualMachine, _key: &PyObjectRef, value: &PyObjectRef| value.clone()
}
dict_iterator! {
PyDictItems,
PyDictItemIterator,
dictitems_type,
dictitemiterator_type,
"dictitems",
"dictitemiterator",
|vm: &VirtualMachine, key: &PyObjectRef, value: &PyObjectRef|
vm.ctx.new_tuple(vec![key.clone(), value.clone()])
}
pub fn init(context: &PyContext) {
@@ -323,16 +443,27 @@ pub fn init(context: &PyContext) {
"__bool__" => context.new_rustfunc(PyDictRef::bool),
"__len__" => context.new_rustfunc(PyDictRef::len),
"__contains__" => context.new_rustfunc(PyDictRef::contains),
"__delitem__" => context.new_rustfunc(PyDictRef::delitem),
"__getitem__" => context.new_rustfunc(PyDictRef::getitem),
"__delitem__" => context.new_rustfunc(PyDictRef::inner_delitem),
"__eq__" => context.new_rustfunc(PyDictRef::eq),
"__getitem__" => context.new_rustfunc(PyDictRef::inner_getitem),
"__iter__" => context.new_rustfunc(PyDictRef::iter),
"__new__" => context.new_rustfunc(PyDictRef::new),
"__repr__" => context.new_rustfunc(PyDictRef::repr),
"__setitem__" => context.new_rustfunc(PyDictRef::setitem),
"__setitem__" => context.new_rustfunc(PyDictRef::inner_setitem),
"__hash__" => context.new_rustfunc(PyDictRef::hash),
"clear" => context.new_rustfunc(PyDictRef::clear),
"values" => context.new_rustfunc(PyDictRef::values),
"items" => context.new_rustfunc(PyDictRef::items),
"keys" => context.new_rustfunc(PyDictRef::iter),
"keys" => context.new_rustfunc(PyDictRef::keys),
"get" => context.new_rustfunc(PyDictRef::get),
"copy" => context.new_rustfunc(PyDictRef::copy),
"update" => context.new_rustfunc(PyDictRef::update),
});
PyDictKeys::extend_class(context, &context.dictkeys_type);
PyDictKeyIterator::extend_class(context, &context.dictkeyiterator_type);
PyDictValues::extend_class(context, &context.dictvalues_type);
PyDictValueIterator::extend_class(context, &context.dictvalueiterator_type);
PyDictItems::extend_class(context, &context.dictitems_type);
PyDictItemIterator::extend_class(context, &context.dictitemiterator_type);
}
+2 -3
View File
@@ -1,10 +1,9 @@
use crate::function::PyFuncArgs;
use crate::pyobject::{PyContext, PyResult, TypeProtocol};
use crate::pyobject::{PyContext, PyResult};
use crate::vm::VirtualMachine;
pub fn init(context: &PyContext) {
let ellipsis_type = context.ellipsis_type.as_object();
extend_class!(context, ellipsis_type, {
extend_class!(context, &context.ellipsis_type, {
"__new__" => context.new_rustfunc(ellipsis_new),
"__repr__" => context.new_rustfunc(ellipsis_repr)
});
+6 -2
View File
@@ -57,13 +57,17 @@ impl PyEnumerateRef {
Ok(result)
}
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
pub fn init(context: &PyContext) {
let enumerate_type = &context.enumerate_type;
objiter::iter_type_init(context, enumerate_type);
extend_class!(context, enumerate_type, {
"__new__" => context.new_rustfunc(enumerate_new),
"__next__" => context.new_rustfunc(PyEnumerateRef::next)
"__next__" => context.new_rustfunc(PyEnumerateRef::next),
"__iter__" => context.new_rustfunc(PyEnumerateRef::iter),
});
}
+6 -3
View File
@@ -52,13 +52,15 @@ impl PyFilterRef {
}
}
}
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
pub fn init(context: &PyContext) {
let filter_type = &context.filter_type;
objiter::iter_type_init(context, filter_type);
let filter_doc =
"filter(function or None, iterable) --> filter object\n\n\
Return an iterator yielding those items of iterable for which function(item)\n\
@@ -67,6 +69,7 @@ pub fn init(context: &PyContext) {
extend_class!(context, filter_type, {
"__new__" => context.new_rustfunc(filter_new),
"__doc__" => context.new_str(filter_doc.to_string()),
"__next__" => context.new_rustfunc(PyFilterRef::next)
"__next__" => context.new_rustfunc(PyFilterRef::next),
"__iter__" => context.new_rustfunc(PyFilterRef::iter),
});
}
+61 -55
View File
@@ -16,6 +16,12 @@ pub struct PyFloat {
value: f64,
}
impl PyFloat {
pub fn to_f64(&self) -> f64 {
self.value
}
}
impl PyValue for PyFloat {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.float_type()
@@ -34,10 +40,8 @@ impl From<f64> for PyFloat {
}
}
pub type PyFloatRef = PyRef<PyFloat>;
impl PyFloatRef {
fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
impl PyFloat {
fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
let value = self.value;
let result = if objtype::isinstance(&other, &vm.ctx.float_type()) {
let other = get_value(&other);
@@ -56,7 +60,7 @@ impl PyFloatRef {
vm.ctx.new_bool(result)
}
fn lt(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
fn lt(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
let v1 = self.value;
if objtype::isinstance(&i2, &vm.ctx.float_type()) {
vm.ctx.new_bool(v1 < get_value(&i2))
@@ -68,7 +72,7 @@ impl PyFloatRef {
}
}
fn le(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
fn le(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
let v1 = self.value;
if objtype::isinstance(&i2, &vm.ctx.float_type()) {
vm.ctx.new_bool(v1 <= get_value(&i2))
@@ -80,7 +84,7 @@ impl PyFloatRef {
}
}
fn gt(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
fn gt(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
let v1 = self.value;
if objtype::isinstance(&i2, &vm.ctx.float_type()) {
vm.ctx.new_bool(v1 > get_value(&i2))
@@ -92,7 +96,7 @@ impl PyFloatRef {
}
}
fn ge(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
fn ge(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
let v1 = self.value;
if objtype::isinstance(&i2, &vm.ctx.float_type()) {
vm.ctx.new_bool(v1 >= get_value(&i2))
@@ -104,11 +108,11 @@ impl PyFloatRef {
}
}
fn abs(self, _vm: &VirtualMachine) -> f64 {
fn abs(&self, _vm: &VirtualMachine) -> f64 {
self.value.abs()
}
fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
let v1 = self.value;
if objtype::isinstance(&other, &vm.ctx.float_type()) {
vm.ctx.new_float(v1 + get_value(&other))
@@ -120,23 +124,23 @@ impl PyFloatRef {
}
}
fn bool(self, _vm: &VirtualMachine) -> bool {
fn bool(&self, _vm: &VirtualMachine) -> bool {
self.value != 0.0
}
fn divmod(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
fn divmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if objtype::isinstance(&other, &vm.ctx.float_type())
|| objtype::isinstance(&other, &vm.ctx.int_type())
{
let r1 = PyFloatRef::floordiv(self.clone(), other.clone(), vm)?;
let r2 = PyFloatRef::mod_(self, other, vm)?;
let r1 = self.floordiv(other.clone(), vm)?;
let r2 = self.mod_(other, vm)?;
Ok(vm.ctx.new_tuple(vec![r1, r2]))
} else {
Ok(vm.ctx.not_implemented())
}
}
fn floordiv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
fn floordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let v1 = self.value;
let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) {
get_value(&other)
@@ -195,7 +199,7 @@ impl PyFloatRef {
PyFloat { value }.into_ref_with_type(vm, cls)
}
fn mod_(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let v1 = self.value;
let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) {
get_value(&other)
@@ -214,11 +218,11 @@ impl PyFloatRef {
}
}
fn neg(self, _vm: &VirtualMachine) -> f64 {
fn neg(&self, _vm: &VirtualMachine) -> f64 {
-self.value
}
fn pow(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
let v1 = self.value;
if objtype::isinstance(&other, &vm.ctx.float_type()) {
vm.ctx.new_float(v1.powf(get_value(&other)))
@@ -230,7 +234,7 @@ impl PyFloatRef {
}
}
fn sub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let v1 = self.value;
if objtype::isinstance(&other, &vm.ctx.float_type()) {
Ok(vm.ctx.new_float(v1 - get_value(&other)))
@@ -243,7 +247,7 @@ impl PyFloatRef {
}
}
fn rsub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let v1 = self.value;
if objtype::isinstance(&other, &vm.ctx.float_type()) {
Ok(vm.ctx.new_float(get_value(&other) - v1))
@@ -256,11 +260,11 @@ impl PyFloatRef {
}
}
fn repr(self, _vm: &VirtualMachine) -> String {
fn repr(&self, _vm: &VirtualMachine) -> String {
self.value.to_string()
}
fn truediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let v1 = self.value;
let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) {
get_value(&other)
@@ -279,7 +283,7 @@ impl PyFloatRef {
}
}
fn rtruediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
fn rtruediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let v1 = self.value;
let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) {
get_value(&other)
@@ -298,7 +302,7 @@ impl PyFloatRef {
}
}
fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let v1 = self.value;
if objtype::isinstance(&other, &vm.ctx.float_type) {
Ok(vm.ctx.new_float(v1 * get_value(&other)))
@@ -311,16 +315,16 @@ impl PyFloatRef {
}
}
fn is_integer(self, _vm: &VirtualMachine) -> bool {
fn real(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyFloatRef {
zelf
}
fn is_integer(&self, _vm: &VirtualMachine) -> bool {
let v = self.value;
(v - v.round()).abs() < std::f64::EPSILON
}
fn real(self, _vm: &VirtualMachine) -> Self {
self
}
fn as_integer_ratio(self, vm: &VirtualMachine) -> PyResult {
fn as_integer_ratio(&self, vm: &VirtualMachine) -> PyResult {
let value = self.value;
if value.is_infinite() {
return Err(
@@ -338,6 +342,8 @@ impl PyFloatRef {
}
}
pub type PyFloatRef = PyRef<PyFloat>;
// Retrieve inner float value:
pub fn get_value(obj: &PyObjectRef) -> f64 {
obj.payload::<PyFloat>().unwrap().value
@@ -361,31 +367,31 @@ pub fn init(context: &PyContext) {
let float_doc = "Convert a string or number to a floating point number, if possible.";
extend_class!(context, float_type, {
"__eq__" => context.new_rustfunc(PyFloatRef::eq),
"__lt__" => context.new_rustfunc(PyFloatRef::lt),
"__le__" => context.new_rustfunc(PyFloatRef::le),
"__gt__" => context.new_rustfunc(PyFloatRef::gt),
"__ge__" => context.new_rustfunc(PyFloatRef::ge),
"__abs__" => context.new_rustfunc(PyFloatRef::abs),
"__add__" => context.new_rustfunc(PyFloatRef::add),
"__radd__" => context.new_rustfunc(PyFloatRef::add),
"__bool__" => context.new_rustfunc(PyFloatRef::bool),
"__divmod__" => context.new_rustfunc(PyFloatRef::divmod),
"__floordiv__" => context.new_rustfunc(PyFloatRef::floordiv),
"__new__" => context.new_rustfunc(PyFloatRef::new_float),
"__mod__" => context.new_rustfunc(PyFloatRef::mod_),
"__neg__" => context.new_rustfunc(PyFloatRef::neg),
"__pow__" => context.new_rustfunc(PyFloatRef::pow),
"__sub__" => context.new_rustfunc(PyFloatRef::sub),
"__rsub__" => context.new_rustfunc(PyFloatRef::rsub),
"__repr__" => context.new_rustfunc(PyFloatRef::repr),
"__eq__" => context.new_rustfunc(PyFloat::eq),
"__lt__" => context.new_rustfunc(PyFloat::lt),
"__le__" => context.new_rustfunc(PyFloat::le),
"__gt__" => context.new_rustfunc(PyFloat::gt),
"__ge__" => context.new_rustfunc(PyFloat::ge),
"__abs__" => context.new_rustfunc(PyFloat::abs),
"__add__" => context.new_rustfunc(PyFloat::add),
"__radd__" => context.new_rustfunc(PyFloat::add),
"__bool__" => context.new_rustfunc(PyFloat::bool),
"__divmod__" => context.new_rustfunc(PyFloat::divmod),
"__floordiv__" => context.new_rustfunc(PyFloat::floordiv),
"__new__" => context.new_rustfunc(PyFloat::new_float),
"__mod__" => context.new_rustfunc(PyFloat::mod_),
"__neg__" => context.new_rustfunc(PyFloat::neg),
"__pow__" => context.new_rustfunc(PyFloat::pow),
"__sub__" => context.new_rustfunc(PyFloat::sub),
"__rsub__" => context.new_rustfunc(PyFloat::rsub),
"__repr__" => context.new_rustfunc(PyFloat::repr),
"__doc__" => context.new_str(float_doc.to_string()),
"__truediv__" => context.new_rustfunc(PyFloatRef::truediv),
"__rtruediv__" => context.new_rustfunc(PyFloatRef::rtruediv),
"__mul__" => context.new_rustfunc(PyFloatRef::mul),
"__rmul__" => context.new_rustfunc(PyFloatRef::mul),
"real" => context.new_property(PyFloatRef::real),
"is_integer" => context.new_rustfunc(PyFloatRef::is_integer),
"as_integer_ratio" => context.new_rustfunc(PyFloatRef::as_integer_ratio)
"__truediv__" => context.new_rustfunc(PyFloat::truediv),
"__rtruediv__" => context.new_rustfunc(PyFloat::rtruediv),
"__mul__" => context.new_rustfunc(PyFloat::mul),
"__rmul__" => context.new_rustfunc(PyFloat::mul),
"real" => context.new_property(PyFloat::real),
"is_integer" => context.new_rustfunc(PyFloat::is_integer),
"as_integer_ratio" => context.new_rustfunc(PyFloat::as_integer_ratio)
});
}
+28 -3
View File
@@ -1,5 +1,8 @@
use crate::frame::Scope;
use crate::function::{Args, KwArgs};
use crate::obj::objcode::PyCodeRef;
use crate::obj::objdict::PyDictRef;
use crate::obj::objtuple::PyTupleRef;
use crate::obj::objtype::PyClassRef;
use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol};
use crate::vm::VirtualMachine;
@@ -11,15 +14,22 @@ pub struct PyFunction {
// TODO: these shouldn't be public
pub code: PyCodeRef,
pub scope: Scope,
pub defaults: PyObjectRef,
pub defaults: Option<PyTupleRef>,
pub kw_only_defaults: Option<PyDictRef>,
}
impl PyFunction {
pub fn new(code: PyCodeRef, scope: Scope, defaults: PyObjectRef) -> Self {
pub fn new(
code: PyCodeRef,
scope: Scope,
defaults: Option<PyTupleRef>,
kw_only_defaults: Option<PyDictRef>,
) -> Self {
PyFunction {
code,
scope,
defaults,
kw_only_defaults,
}
}
}
@@ -31,9 +41,21 @@ impl PyValue for PyFunction {
}
impl PyFunctionRef {
fn call(self, args: Args, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult {
vm.invoke(self.into_object(), (&args, &kwargs))
}
fn code(self, _vm: &VirtualMachine) -> PyCodeRef {
self.code.clone()
}
fn defaults(self, _vm: &VirtualMachine) -> Option<PyTupleRef> {
self.defaults.clone()
}
fn kwdefaults(self, _vm: &VirtualMachine) -> Option<PyDictRef> {
self.kw_only_defaults.clone()
}
}
#[derive(Debug)]
@@ -59,7 +81,10 @@ pub fn init(context: &PyContext) {
let function_type = &context.function_type;
extend_class!(context, function_type, {
"__get__" => context.new_rustfunc(bind_method),
"__code__" => context.new_property(PyFunctionRef::code)
"__call__" => context.new_rustfunc(PyFunctionRef::call),
"__code__" => context.new_property(PyFunctionRef::code),
"__defaults__" => context.new_property(PyFunctionRef::defaults),
"__kwdefaults__" => context.new_property(PyFunctionRef::kwdefaults),
});
let builtin_function_or_method_type = &context.builtin_function_or_method_type;
+43 -21
View File
@@ -3,12 +3,13 @@
*/
use crate::frame::{ExecutionResult, FrameRef};
use crate::obj::objtype::PyClassRef;
use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::obj::objtype::{isinstance, PyClassRef};
use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::vm::VirtualMachine;
pub type PyGeneratorRef = PyRef<PyGenerator>;
#[pyclass(name = "generator")]
#[derive(Debug)]
pub struct PyGenerator {
frame: FrameRef,
@@ -20,38 +21,59 @@ impl PyValue for PyGenerator {
}
}
impl PyGeneratorRef {
#[pyimpl]
impl PyGenerator {
pub fn new(frame: FrameRef, vm: &VirtualMachine) -> PyGeneratorRef {
PyGenerator { frame }.into_ref(vm)
}
fn iter(self, _vm: &VirtualMachine) -> PyGeneratorRef {
self
#[pymethod(name = "__iter__")]
fn iter(zelf: PyGeneratorRef, _vm: &VirtualMachine) -> PyGeneratorRef {
zelf
}
fn next(self, vm: &VirtualMachine) -> PyResult {
#[pymethod(name = "__next__")]
fn next(&self, vm: &VirtualMachine) -> PyResult {
self.send(vm.get_none(), vm)
}
fn send(self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
#[pymethod]
fn send(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
self.frame.push_value(value.clone());
match vm.run_frame(self.frame.clone())? {
ExecutionResult::Yield(value) => Ok(value),
ExecutionResult::Return(_value) => {
// Stop iteration!
let stop_iteration = vm.ctx.exceptions.stop_iteration.clone();
Err(vm.new_exception(stop_iteration, "End of generator".to_string()))
}
let result = vm.run_frame(self.frame.clone())?;
handle_execution_result(result, vm)
}
#[pymethod]
fn throw(
&self,
_exc_type: PyObjectRef,
exc_val: PyObjectRef,
_exc_tb: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
// TODO what should we do with the other parameters? CPython normalises them with
// PyErr_NormalizeException, do we want to do the same.
if !isinstance(&exc_val, &vm.ctx.exceptions.base_exception_type) {
return Err(vm.new_type_error("Can't throw non exception".to_string()));
}
let result = vm.frame_throw(self.frame.clone(), exc_val)?;
handle_execution_result(result, vm)
}
}
fn handle_execution_result(result: ExecutionResult, vm: &VirtualMachine) -> PyResult {
match result {
ExecutionResult::Yield(value) => Ok(value),
ExecutionResult::Return(_value) => {
// Stop iteration!
let stop_iteration = vm.ctx.exceptions.stop_iteration.clone();
Err(vm.new_exception(stop_iteration, "End of generator".to_string()))
}
}
}
pub fn init(context: &PyContext) {
let generator_type = &context.generator_type;
extend_class!(context, generator_type, {
"__iter__" => context.new_rustfunc(PyGeneratorRef::iter),
"__next__" => context.new_rustfunc(PyGeneratorRef::next),
"send" => context.new_rustfunc(PyGeneratorRef::send)
});
pub fn init(ctx: &PyContext) {
PyGenerator::extend_class(ctx, &ctx.generator_type);
}
+161 -123
View File
@@ -8,15 +8,31 @@ use num_traits::{Pow, Signed, ToPrimitive, Zero};
use crate::format::FormatSpec;
use crate::function::OptionalArg;
use crate::pyobject::{
IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
TypeProtocol,
};
use crate::vm::VirtualMachine;
use super::objfloat;
use super::objstr;
use super::objfloat::{self, PyFloat};
use super::objstr::{PyString, PyStringRef};
use super::objtype;
use crate::obj::objtype::PyClassRef;
/// int(x=0) -> integer
/// int(x, base=10) -> integer
///
/// Convert a number or string to an integer, or return 0 if no arguments
/// are given. If x is a number, return x.__int__(). For floating point
/// numbers, this truncates towards zero.
///
/// If x is not a number or if base is given, then x must be a string,
/// bytes, or bytearray instance representing an integer literal in the
/// given base. The literal can be preceded by '+' or '-' and be surrounded
/// by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.
/// Base 0 means to interpret the base from the string as an integer literal.
/// >>> int('0b100', base=0)
/// 4
#[pyclass]
#[derive(Debug)]
pub struct PyInt {
value: BigInt,
@@ -95,12 +111,10 @@ impl_try_from_object_int!(
(u64, to_u64),
);
impl PyIntRef {
fn pass_value(self, _vm: &VirtualMachine) -> Self {
self
}
fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pyimpl]
impl PyInt {
#[pymethod(name = "__eq__")]
fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_bool(self.value == *get_value(&other))
} else {
@@ -108,7 +122,8 @@ impl PyIntRef {
}
}
fn ne(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__ne__")]
fn ne(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_bool(self.value != *get_value(&other))
} else {
@@ -116,7 +131,8 @@ impl PyIntRef {
}
}
fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__lt__")]
fn lt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_bool(self.value < *get_value(&other))
} else {
@@ -124,7 +140,8 @@ impl PyIntRef {
}
}
fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__le__")]
fn le(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_bool(self.value <= *get_value(&other))
} else {
@@ -132,7 +149,8 @@ impl PyIntRef {
}
}
fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__gt__")]
fn gt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_bool(self.value > *get_value(&other))
} else {
@@ -140,7 +158,8 @@ impl PyIntRef {
}
}
fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__ge__")]
fn ge(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_bool(self.value >= *get_value(&other))
} else {
@@ -148,7 +167,8 @@ impl PyIntRef {
}
}
fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__add__")]
fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_int((&self.value) + get_value(&other))
} else {
@@ -156,7 +176,13 @@ impl PyIntRef {
}
}
fn sub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__radd__")]
fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
self.add(other, vm)
}
#[pymethod(name = "__sub__")]
fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_int((&self.value) - get_value(&other))
} else {
@@ -164,7 +190,8 @@ impl PyIntRef {
}
}
fn rsub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__rsub__")]
fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_int(get_value(&other) - (&self.value))
} else {
@@ -172,7 +199,8 @@ impl PyIntRef {
}
}
fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__mul__")]
fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_int((&self.value) * get_value(&other))
} else {
@@ -180,7 +208,13 @@ impl PyIntRef {
}
}
fn truediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
#[pymethod(name = "__rmul__")]
fn rmul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
self.mul(other, vm)
}
#[pymethod(name = "__truediv__")]
fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
div_ints(vm, &self.value, &get_value(&other))
} else {
@@ -188,7 +222,8 @@ impl PyIntRef {
}
}
fn rtruediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
#[pymethod(name = "__rtruediv__")]
fn rtruediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
div_ints(vm, &get_value(&other), &self.value)
} else {
@@ -196,7 +231,8 @@ impl PyIntRef {
}
}
fn floordiv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
#[pymethod(name = "__floordiv__")]
fn floordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
let v2 = get_value(&other);
if *v2 != BigInt::zero() {
@@ -209,7 +245,8 @@ impl PyIntRef {
}
}
fn lshift(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
#[pymethod(name = "__lshift__")]
fn lshift(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if !objtype::isinstance(&other, &vm.ctx.int_type()) {
return Ok(vm.ctx.not_implemented());
}
@@ -228,7 +265,8 @@ impl PyIntRef {
}
}
fn rshift(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
#[pymethod(name = "__rshift__")]
fn rshift(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if !objtype::isinstance(&other, &vm.ctx.int_type()) {
return Ok(vm.ctx.not_implemented());
}
@@ -247,7 +285,8 @@ impl PyIntRef {
}
}
fn xor(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__xor__")]
fn xor(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_int((&self.value) ^ get_value(&other))
} else {
@@ -255,7 +294,8 @@ impl PyIntRef {
}
}
fn rxor(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__rxor__")]
fn rxor(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_int(get_value(&other) ^ (&self.value))
} else {
@@ -263,7 +303,8 @@ impl PyIntRef {
}
}
fn or(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__or__")]
fn or(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
vm.ctx.new_int((&self.value) | get_value(&other))
} else {
@@ -271,7 +312,8 @@ impl PyIntRef {
}
}
fn and(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__and__")]
fn and(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
let v2 = get_value(&other);
vm.ctx.new_int((&self.value) & v2)
@@ -280,7 +322,8 @@ impl PyIntRef {
}
}
fn pow(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod(name = "__pow__")]
fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
let v2 = get_value(&other).to_u32().unwrap();
vm.ctx.new_int(self.value.pow(v2))
@@ -292,7 +335,8 @@ impl PyIntRef {
}
}
fn mod_(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
#[pymethod(name = "__mod__")]
fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
let v2 = get_value(&other);
if *v2 != BigInt::zero() {
@@ -305,7 +349,8 @@ impl PyIntRef {
}
}
fn divmod(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
#[pymethod(name = "__divmod__")]
fn divmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if objtype::isinstance(&other, &vm.ctx.int_type()) {
let v2 = get_value(&other);
if *v2 != BigInt::zero() {
@@ -321,37 +366,82 @@ impl PyIntRef {
}
}
fn neg(self, _vm: &VirtualMachine) -> BigInt {
#[pymethod(name = "__neg__")]
fn neg(&self, _vm: &VirtualMachine) -> BigInt {
-(&self.value)
}
fn hash(self, _vm: &VirtualMachine) -> u64 {
#[pymethod(name = "__hash__")]
fn hash(&self, _vm: &VirtualMachine) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.value.hash(&mut hasher);
hasher.finish()
}
fn abs(self, _vm: &VirtualMachine) -> BigInt {
#[pymethod(name = "__abs__")]
fn abs(&self, _vm: &VirtualMachine) -> BigInt {
self.value.abs()
}
fn round(self, _precision: OptionalArg<PyObjectRef>, _vm: &VirtualMachine) -> Self {
self
#[pymethod(name = "__round__")]
fn round(
zelf: PyRef<Self>,
_precision: OptionalArg<PyObjectRef>,
_vm: &VirtualMachine,
) -> PyIntRef {
zelf
}
fn float(self, _vm: &VirtualMachine) -> f64 {
self.value.to_f64().unwrap()
#[pymethod(name = "__int__")]
fn int(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyIntRef {
zelf
}
fn invert(self, _vm: &VirtualMachine) -> BigInt {
#[pymethod(name = "__pos__")]
fn pos(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyIntRef {
zelf
}
#[pymethod(name = "__float__")]
fn float(&self, vm: &VirtualMachine) -> PyResult<PyFloat> {
self.value
.to_f64()
.map(PyFloat::from)
.ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))
}
#[pymethod(name = "__trunc__")]
fn trunc(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyIntRef {
zelf
}
#[pymethod(name = "__floor__")]
fn floor(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyIntRef {
zelf
}
#[pymethod(name = "__ceil__")]
fn ceil(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyIntRef {
zelf
}
#[pymethod(name = "__index__")]
fn index(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyIntRef {
zelf
}
#[pymethod(name = "__invert__")]
fn invert(&self, _vm: &VirtualMachine) -> BigInt {
!(&self.value)
}
fn repr(self, _vm: &VirtualMachine) -> String {
#[pymethod(name = "__repr__")]
fn repr(&self, _vm: &VirtualMachine) -> String {
self.value.to_string()
}
fn format(self, spec: PyRef<objstr::PyString>, vm: &VirtualMachine) -> PyResult<String> {
#[pymethod(name = "__format__")]
fn format(&self, spec: PyStringRef, vm: &VirtualMachine) -> PyResult<String> {
let format_spec = FormatSpec::parse(&spec.value);
match format_spec.format_int(&self.value) {
Ok(string) => Ok(string),
@@ -359,15 +449,28 @@ impl PyIntRef {
}
}
fn bool(self, _vm: &VirtualMachine) -> bool {
#[pymethod(name = "__bool__")]
fn bool(&self, _vm: &VirtualMachine) -> bool {
!self.value.is_zero()
}
fn bit_length(self, _vm: &VirtualMachine) -> usize {
#[pymethod]
fn bit_length(&self, _vm: &VirtualMachine) -> usize {
self.value.bits()
}
fn imag(self, _vm: &VirtualMachine) -> usize {
#[pymethod]
fn conjugate(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyIntRef {
zelf
}
#[pyproperty]
fn real(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyIntRef {
zelf
}
#[pyproperty]
fn imag(&self, _vm: &VirtualMachine) -> usize {
0
}
}
@@ -407,30 +510,24 @@ fn int_new(cls: PyClassRef, options: IntOptions, vm: &VirtualMachine) -> PyResul
}
// Casting function:
// TODO: this should just call `__int__` on the object
pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult<BigInt> {
let val = if objtype::isinstance(obj, &vm.ctx.int_type()) {
get_value(obj).clone()
} else if objtype::isinstance(obj, &vm.ctx.float_type()) {
objfloat::get_value(obj).to_bigint().unwrap()
} else if objtype::isinstance(obj, &vm.ctx.str_type()) {
let s = objstr::get_value(obj);
match i32::from_str_radix(&s, base) {
Ok(v) => v.to_bigint().unwrap(),
Err(err) => {
trace!("Error occurred during int conversion {:?}", err);
return Err(vm.new_value_error(format!(
match_class!(obj.clone(),
i @ PyInt => Ok(i.as_bigint().clone()),
f @ PyFloat => Ok(f.to_f64().to_bigint().unwrap()),
s @ PyString => {
i32::from_str_radix(s.as_str(), base)
.map(BigInt::from)
.map_err(|_|vm.new_value_error(format!(
"invalid literal for int() with base {}: '{}'",
base, s
)));
}
}
} else {
return Err(vm.new_type_error(format!(
)))
},
obj => Err(vm.new_type_error(format!(
"int() argument must be a string or a number, not '{}'",
obj.class().name
)));
};
Ok(val)
)))
)
}
// Retrieve inner int value:
@@ -470,68 +567,9 @@ fn div_ints(vm: &VirtualMachine, i1: &BigInt, i2: &BigInt) -> PyResult {
}
}
#[rustfmt::skip] // to avoid line splitting
pub fn init(context: &PyContext) {
let int_doc = "int(x=0) -> integer
int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments
are given. If x is a number, return x.__int__(). For floating point
numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string,
bytes, or bytearray instance representing an integer literal in the
given base. The literal can be preceded by '+' or '-' and be surrounded
by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.
Base 0 means to interpret the base from the string as an integer literal.
>>> int('0b100', base=0)
4";
let int_type = &context.int_type;
extend_class!(context, int_type, {
"__doc__" => context.new_str(int_doc.to_string()),
"__eq__" => context.new_rustfunc(PyIntRef::eq),
"__ne__" => context.new_rustfunc(PyIntRef::ne),
"__lt__" => context.new_rustfunc(PyIntRef::lt),
"__le__" => context.new_rustfunc(PyIntRef::le),
"__gt__" => context.new_rustfunc(PyIntRef::gt),
"__ge__" => context.new_rustfunc(PyIntRef::ge),
"__abs__" => context.new_rustfunc(PyIntRef::abs),
"__add__" => context.new_rustfunc(PyIntRef::add),
"__radd__" => context.new_rustfunc(PyIntRef::add),
"__and__" => context.new_rustfunc(PyIntRef::and),
"__divmod__" => context.new_rustfunc(PyIntRef::divmod),
"__float__" => context.new_rustfunc(PyIntRef::float),
"__round__" => context.new_rustfunc(PyIntRef::round),
"__ceil__" => context.new_rustfunc(PyIntRef::pass_value),
"__floor__" => context.new_rustfunc(PyIntRef::pass_value),
"__index__" => context.new_rustfunc(PyIntRef::pass_value),
"__trunc__" => context.new_rustfunc(PyIntRef::pass_value),
"__int__" => context.new_rustfunc(PyIntRef::pass_value),
"__floordiv__" => context.new_rustfunc(PyIntRef::floordiv),
"__hash__" => context.new_rustfunc(PyIntRef::hash),
"__lshift__" => context.new_rustfunc(PyIntRef::lshift),
"__rshift__" => context.new_rustfunc(PyIntRef::rshift),
PyInt::extend_class(context, &context.int_type);
extend_class!(context, &context.int_type, {
"__new__" => context.new_rustfunc(int_new),
"__mod__" => context.new_rustfunc(PyIntRef::mod_),
"__mul__" => context.new_rustfunc(PyIntRef::mul),
"__rmul__" => context.new_rustfunc(PyIntRef::mul),
"__or__" => context.new_rustfunc(PyIntRef::or),
"__neg__" => context.new_rustfunc(PyIntRef::neg),
"__pos__" => context.new_rustfunc(PyIntRef::pass_value),
"__pow__" => context.new_rustfunc(PyIntRef::pow),
"__repr__" => context.new_rustfunc(PyIntRef::repr),
"__sub__" => context.new_rustfunc(PyIntRef::sub),
"__rsub__" => context.new_rustfunc(PyIntRef::rsub),
"__format__" => context.new_rustfunc(PyIntRef::format),
"__truediv__" => context.new_rustfunc(PyIntRef::truediv),
"__rtruediv__" => context.new_rustfunc(PyIntRef::rtruediv),
"__xor__" => context.new_rustfunc(PyIntRef::xor),
"__rxor__" => context.new_rustfunc(PyIntRef::rxor),
"__bool__" => context.new_rustfunc(PyIntRef::bool),
"__invert__" => context.new_rustfunc(PyIntRef::invert),
"bit_length" => context.new_rustfunc(PyIntRef::bit_length),
"conjugate" => context.new_rustfunc(PyIntRef::pass_value),
"real" => context.new_property(PyIntRef::pass_value),
"imag" => context.new_property(PyIntRef::imag)
});
}
+45 -112
View File
@@ -2,17 +2,13 @@
* Various types to support iteration.
*/
use crate::function::PyFuncArgs;
use crate::pyobject::{PyContext, PyIteratorValue, PyObjectRef, PyResult, TypeProtocol};
use std::cell::Cell;
use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol};
use crate::vm::VirtualMachine;
use super::objbool;
use super::objbytearray::PyByteArray;
use super::objbytes::PyBytes;
use super::objrange::PyRange;
use super::objsequence;
use super::objtype;
use crate::obj::objtype::PyClassRef;
use super::objtype::PyClassRef;
/*
* This helper function is called at multiple places. First, it is called
@@ -20,10 +16,19 @@ use crate::obj::objtype::PyClassRef;
* function 'iter' is called.
*/
pub fn get_iter(vm: &VirtualMachine, iter_target: &PyObjectRef) -> PyResult {
vm.call_method(iter_target, "__iter__", vec![])
// let type_str = objstr::get_value(&vm.to_str(iter_target.class()).unwrap());
// let type_error = vm.new_type_error(format!("Cannot iterate over {}", type_str));
// return Err(type_error);
if let Ok(method) = vm.get_method(iter_target.clone(), "__iter__") {
vm.invoke(method, vec![])
} else if vm.get_method(iter_target.clone(), "__getitem__").is_ok() {
Ok(PySequenceIterator {
position: Cell::new(0),
obj: iter_target.clone(),
}
.into_ref(vm)
.into_object())
} else {
let message = format!("Cannot iterate over {}", iter_target.class().name);
return Err(vm.new_type_error(message));
}
}
pub fn call_next(vm: &VirtualMachine, iter_obj: &PyObjectRef) -> PyResult {
@@ -70,118 +75,46 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef {
vm.new_exception(stop_iteration_type, "End of iterator".to_string())
}
fn contains(vm: &VirtualMachine, args: PyFuncArgs, iter_type: PyClassRef) -> PyResult {
arg_check!(
vm,
args,
required = [(iter, Some(iter_type)), (needle, None)]
);
loop {
if let Some(element) = get_next_object(vm, iter)? {
let equal = vm._eq(needle.clone(), element.clone())?;
if objbool::get_value(&equal) {
return Ok(vm.new_bool(true));
} else {
continue;
}
} else {
return Ok(vm.new_bool(false));
}
#[derive(Debug)]
pub struct PySequenceIterator {
pub position: Cell<usize>,
pub obj: PyObjectRef,
}
impl PyValue for PySequenceIterator {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.iter_type()
}
}
/// Common setup for iter types, adds __iter__ and __contains__ methods
pub fn iter_type_init(context: &PyContext, iter_type: &PyClassRef) {
let contains_func = {
let cloned_iter_type = iter_type.clone();
move |vm: &VirtualMachine, args: PyFuncArgs| contains(vm, args, cloned_iter_type.clone())
};
type PySequenceIteratorRef = PyRef<PySequenceIterator>;
let iter_func = {
let cloned_iter_type = iter_type.clone();
move |vm: &VirtualMachine, args: PyFuncArgs| {
arg_check!(
vm,
args,
required = [(iter, Some(cloned_iter_type.clone()))]
);
// Return self:
Ok(iter.clone())
impl PySequenceIteratorRef {
fn next(self, vm: &VirtualMachine) -> PyResult {
let number = vm.ctx.new_int(self.position.get());
match vm.call_method(&self.obj, "__getitem__", vec![number]) {
Ok(val) => {
self.position.set(self.position.get() + 1);
Ok(val)
}
Err(ref e) if objtype::isinstance(&e, &vm.ctx.exceptions.index_error) => {
Err(new_stop_iteration(vm))
}
// also catches stop_iteration => stop_iteration
Err(e) => Err(e),
}
};
}
extend_class!(context, iter_type, {
"__contains__" => context.new_rustfunc(contains_func),
"__iter__" => context.new_rustfunc(iter_func)
});
}
// Sequence iterator:
fn iter_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(iter_target, None)]);
get_iter(vm, iter_target)
}
fn iter_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(iter, Some(vm.ctx.iter_type()))]);
if let Some(PyIteratorValue {
ref position,
iterated_obj: ref iterated_obj_ref,
}) = iter.payload()
{
if let Some(range) = iterated_obj_ref.payload::<PyRange>() {
if let Some(int) = range.get(position.get()) {
position.set(position.get() + 1);
Ok(vm.ctx.new_int(int))
} else {
Err(new_stop_iteration(vm))
}
} else if let Some(bytes) = iterated_obj_ref.payload::<PyBytes>() {
if position.get() < bytes.len() {
let obj_ref = vm.ctx.new_int(bytes[position.get()]);
position.set(position.get() + 1);
Ok(obj_ref)
} else {
Err(new_stop_iteration(vm))
}
} else if let Some(bytes) = iterated_obj_ref.payload::<PyByteArray>() {
if position.get() < bytes.value.borrow().len() {
let obj_ref = vm.ctx.new_int(bytes.value.borrow()[position.get()]);
position.set(position.get() + 1);
Ok(obj_ref)
} else {
Err(new_stop_iteration(vm))
}
} else {
let elements = objsequence::get_elements(iterated_obj_ref);
if position.get() < elements.len() {
let obj_ref = elements[position.get()].clone();
position.set(position.get() + 1);
Ok(obj_ref)
} else {
Err(new_stop_iteration(vm))
}
}
} else {
panic!("NOT IMPL");
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
pub fn init(context: &PyContext) {
let iter_type = &context.iter_type;
let iter_doc = "iter(iterable) -> iterator\n\
iter(callable, sentinel) -> iterator\n\n\
Get an iterator from an object. In the first form, the argument must\n\
supply its own iterator, or be a sequence.\n\
In the second form, the callable is called until it returns the sentinel.";
iter_type_init(context, iter_type);
extend_class!(context, iter_type, {
"__new__" => context.new_rustfunc(iter_new),
"__next__" => context.new_rustfunc(iter_next),
"__doc__" => context.new_str(iter_doc.to_string())
"__next__" => context.new_rustfunc(PySequenceIteratorRef::next),
"__iter__" => context.new_rustfunc(PySequenceIteratorRef::iter),
});
}
+415 -19
View File
@@ -1,20 +1,25 @@
use std::cell::{Cell, RefCell};
use std::fmt;
use num_traits::ToPrimitive;
use std::ops::Range;
use num_bigint::{BigInt, ToBigInt};
use num_traits::{One, Signed, ToPrimitive, Zero};
use crate::function::{OptionalArg, PyFuncArgs};
use crate::pyobject::{
IdProtocol, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol,
IdProtocol, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
};
use crate::vm::{ReprGuard, VirtualMachine};
use super::objbool;
use super::objint;
//use super::objint;
use super::objiter;
use super::objsequence::{
get_elements, get_elements_cell, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul,
PySliceableSequence,
SequenceIndex,
};
use super::objslice::PySliceRef;
use super::objtype;
use crate::obj::objtype::PyClassRef;
@@ -45,6 +50,54 @@ impl PyValue for PyList {
}
}
impl PyList {
pub fn get_len(&self) -> usize {
self.elements.borrow().len()
}
pub fn get_pos(&self, p: i32) -> Option<usize> {
// convert a (potentially negative) positon into a real index
if p < 0 {
if -p as usize > self.get_len() {
None
} else {
Some(self.get_len() - ((-p) as usize))
}
} else if p as usize >= self.get_len() {
None
} else {
Some(p as usize)
}
}
pub fn get_slice_pos(&self, slice_pos: &BigInt) -> usize {
if let Some(pos) = slice_pos.to_i32() {
if let Some(index) = self.get_pos(pos) {
// within bounds
return index;
}
}
if slice_pos.is_negative() {
// slice past start bound, round to start
0
} else {
// slice past end bound, round to end
self.get_len()
}
}
pub fn get_slice_range(&self, start: &Option<BigInt>, stop: &Option<BigInt>) -> Range<usize> {
let start = start.as_ref().map(|x| self.get_slice_pos(x)).unwrap_or(0);
let stop = stop
.as_ref()
.map(|x| self.get_slice_pos(x))
.unwrap_or_else(|| self.get_len());
start..stop
}
}
pub type PyListRef = PyRef<PyList>;
impl PyListRef {
@@ -123,29 +176,199 @@ impl PyListRef {
)
}
fn iter(self, _vm: &VirtualMachine) -> PyIteratorValue {
PyIteratorValue {
fn iter(self, _vm: &VirtualMachine) -> PyListIterator {
PyListIterator {
position: Cell::new(0),
iterated_obj: self.into_object(),
list: self,
}
}
fn setitem(self, key: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let mut elements = self.elements.borrow_mut();
fn setitem(
self,
subscript: SequenceIndex,
value: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
match subscript {
SequenceIndex::Int(index) => self.setindex(index, value, vm),
SequenceIndex::Slice(slice) => {
if let Ok(sec) = PyIterable::try_from_object(vm, value) {
return self.setslice(slice, sec, vm);
}
Err(vm.new_type_error("can only assign an iterable to a slice".to_string()))
}
}
}
if objtype::isinstance(&key, &vm.ctx.int_type()) {
let idx = objint::get_value(&key).to_i32().unwrap();
if let Some(pos_index) = elements.get_pos(idx) {
elements[pos_index] = value;
Ok(vm.get_none())
fn setindex(self, index: i32, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if let Some(pos_index) = self.get_pos(index) {
self.elements.borrow_mut()[pos_index] = value;
Ok(vm.get_none())
} else {
Err(vm.new_index_error("list assignment index out of range".to_string()))
}
}
fn setslice(self, slice: PySliceRef, sec: PyIterable, vm: &VirtualMachine) -> PyResult {
let step = slice.step.clone().unwrap_or_else(BigInt::one);
if step.is_zero() {
Err(vm.new_value_error("slice step cannot be zero".to_string()))
} else if step.is_positive() {
let range = self.get_slice_range(&slice.start, &slice.stop);
if range.start < range.end {
match step.to_i32() {
Some(1) => self._set_slice(range, sec, vm),
Some(num) => {
// assign to extended slice
self._set_stepped_slice(range, num as usize, sec, vm)
}
None => {
// not sure how this is reached, step too big for i32?
// then step is bigger than the than len of the list, no question
#[allow(clippy::range_plus_one)]
self._set_stepped_slice(range.start..(range.start + 1), 1, sec, vm)
}
}
} else {
Err(vm.new_index_error("list index out of range".to_string()))
// this functions as an insert of sec before range.start
self._set_slice(range.start..range.start, sec, vm)
}
} else {
panic!(
"TypeError: indexing type {:?} with index {:?} is not supported (yet?)",
elements, key
)
// calculate the range for the reverse slice, first the bounds needs to be made
// exclusive around stop, the lower number
let start = &slice.start.as_ref().map(|x| {
if *x == (-1).to_bigint().unwrap() {
self.get_len() + BigInt::one() //.to_bigint().unwrap()
} else {
x + 1
}
});
let stop = &slice.stop.as_ref().map(|x| {
if *x == (-1).to_bigint().unwrap() {
self.get_len().to_bigint().unwrap()
} else {
x + 1
}
});
let range = self.get_slice_range(&stop, &start);
match (-step).to_i32() {
Some(num) => self._set_stepped_slice_reverse(range, num as usize, sec, vm),
None => {
// not sure how this is reached, step too big for i32?
// then step is bigger than the than len of the list no question
self._set_stepped_slice_reverse(range.end - 1..range.end, 1, sec, vm)
}
}
}
}
fn _set_slice(self, range: Range<usize>, sec: PyIterable, vm: &VirtualMachine) -> PyResult {
// consume the iter, we need it's size
// and if it's going to fail we want that to happen *before* we start modifing
let items: Result<Vec<PyObjectRef>, _> = sec.iter(vm)?.collect();
let items = items?;
// replace the range of elements with the full sequence
self.elements.borrow_mut().splice(range, items);
Ok(vm.get_none())
}
fn _set_stepped_slice(
self,
range: Range<usize>,
step: usize,
sec: PyIterable,
vm: &VirtualMachine,
) -> PyResult {
let slicelen = if range.end > range.start {
((range.end - range.start - 1) / step) + 1
} else {
0
};
// consume the iter, we need it's size
// and if it's going to fail we want that to happen *before* we start modifing
let items: Result<Vec<PyObjectRef>, _> = sec.iter(vm)?.collect();
let items = items?;
let n = items.len();
if range.start < range.end {
if n == slicelen {
let indexes = range.step_by(step);
self._replace_indexes(indexes, &items);
Ok(vm.get_none())
} else {
Err(vm.new_value_error(format!(
"attempt to assign sequence of size {} to extended slice of size {}",
n, slicelen
)))
}
} else if n == 0 {
// slice is empty but so is sequence
Ok(vm.get_none())
} else {
// empty slice but this is an error because stepped slice
Err(vm.new_value_error(format!(
"attempt to assign sequence of size {} to extended slice of size 0",
n
)))
}
}
fn _set_stepped_slice_reverse(
self,
range: Range<usize>,
step: usize,
sec: PyIterable,
vm: &VirtualMachine,
) -> PyResult {
let slicelen = if range.end > range.start {
((range.end - range.start - 1) / step) + 1
} else {
0
};
// consume the iter, we need it's size
// and if it's going to fail we want that to happen *before* we start modifing
let items: Result<Vec<PyObjectRef>, _> = sec.iter(vm)?.collect();
let items = items?;
let n = items.len();
if range.start < range.end {
if n == slicelen {
let indexes = range.rev().step_by(step);
self._replace_indexes(indexes, &items);
Ok(vm.get_none())
} else {
Err(vm.new_value_error(format!(
"attempt to assign sequence of size {} to extended slice of size {}",
n, slicelen
)))
}
} else if n == 0 {
// slice is empty but so is sequence
Ok(vm.get_none())
} else {
// empty slice but this is an error because stepped slice
Err(vm.new_value_error(format!(
"attempt to assign sequence of size {} to extended slice of size 0",
n
)))
}
}
fn _replace_indexes<I>(self, indexes: I, items: &[PyObjectRef])
where
I: Iterator<Item = usize>,
{
let mut elements = self.elements.borrow_mut();
for (i, value) in indexes.zip(items) {
// clone for refrence count
elements[i] = value.clone();
}
}
@@ -163,6 +386,10 @@ impl PyListRef {
Ok(s)
}
fn hash(self, vm: &VirtualMachine) -> PyResult {
Err(vm.new_type_error("unhashable type".to_string()))
}
fn mul(self, counter: isize, vm: &VirtualMachine) -> PyObjectRef {
let new_elements = seq_mul(&self.elements.borrow(), counter);
vm.ctx.new_list(new_elements)
@@ -307,6 +534,137 @@ impl PyListRef {
Ok(vm.ctx.not_implemented())
}
}
fn delitem(self, subscript: SequenceIndex, vm: &VirtualMachine) -> PyResult {
match subscript {
SequenceIndex::Int(index) => self.delindex(index, vm),
SequenceIndex::Slice(slice) => self.delslice(slice, vm),
}
}
fn delindex(self, index: i32, vm: &VirtualMachine) -> PyResult {
if let Some(pos_index) = self.get_pos(index) {
self.elements.borrow_mut().remove(pos_index);
Ok(vm.get_none())
} else {
Err(vm.new_index_error("Index out of bounds!".to_string()))
}
}
fn delslice(self, slice: PySliceRef, vm: &VirtualMachine) -> PyResult {
let start = &slice.start;
let stop = &slice.stop;
let step = slice.step.clone().unwrap_or_else(BigInt::one);
if step.is_zero() {
Err(vm.new_value_error("slice step cannot be zero".to_string()))
} else if step.is_positive() {
let range = self.get_slice_range(&start, &stop);
if range.start < range.end {
#[allow(clippy::range_plus_one)]
match step.to_i32() {
Some(1) => {
self._del_slice(range);
Ok(vm.get_none())
}
Some(num) => {
self._del_stepped_slice(range, num as usize);
Ok(vm.get_none())
}
None => {
self._del_slice(range.start..range.start + 1);
Ok(vm.get_none())
}
}
} else {
// no del to do
Ok(vm.get_none())
}
} else {
// calculate the range for the reverse slice, first the bounds needs to be made
// exclusive around stop, the lower number
let start = start.as_ref().map(|x| {
if *x == (-1).to_bigint().unwrap() {
self.get_len() + BigInt::one() //.to_bigint().unwrap()
} else {
x + 1
}
});
let stop = stop.as_ref().map(|x| {
if *x == (-1).to_bigint().unwrap() {
self.get_len().to_bigint().unwrap()
} else {
x + 1
}
});
let range = self.get_slice_range(&stop, &start);
if range.start < range.end {
match (-step).to_i32() {
Some(1) => {
self._del_slice(range);
Ok(vm.get_none())
}
Some(num) => {
self._del_stepped_slice_reverse(range, num as usize);
Ok(vm.get_none())
}
None => {
self._del_slice(range.end - 1..range.end);
Ok(vm.get_none())
}
}
} else {
// no del to do
Ok(vm.get_none())
}
}
}
fn _del_slice(self, range: Range<usize>) {
self.elements.borrow_mut().drain(range);
}
fn _del_stepped_slice(self, range: Range<usize>, step: usize) {
// no easy way to delete stepped indexes so here is what we'll do
let mut deleted = 0;
let mut elements = self.elements.borrow_mut();
let mut indexes = range.clone().step_by(step).peekable();
for i in range.clone() {
// is this an index to delete?
if indexes.peek() == Some(&i) {
// record and move on
indexes.next();
deleted += 1;
} else {
// swap towards front
elements.swap(i - deleted, i);
}
}
// then drain (the values to delete should now be contiguous at the end of the range)
elements.drain((range.end - deleted)..range.end);
}
fn _del_stepped_slice_reverse(self, range: Range<usize>, step: usize) {
// no easy way to delete stepped indexes so here is what we'll do
let mut deleted = 0;
let mut elements = self.elements.borrow_mut();
let mut indexes = range.clone().rev().step_by(step).peekable();
for i in range.clone().rev() {
// is this an index to delete?
if indexes.peek() == Some(&i) {
// record and move on
indexes.next();
deleted += 1;
} else {
// swap towards back
elements.swap(i + deleted, i);
}
}
// then drain (the values to delete should now be contiguous at teh start of the range)
elements.drain(range.start..(range.start + deleted));
}
}
fn list_new(
@@ -412,6 +770,36 @@ fn list_sort(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
Ok(vm.get_none())
}
#[derive(Debug)]
pub struct PyListIterator {
pub position: Cell<usize>,
pub list: PyListRef,
}
impl PyValue for PyListIterator {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.listiterator_type()
}
}
type PyListIteratorRef = PyRef<PyListIterator>;
impl PyListIteratorRef {
fn next(self, vm: &VirtualMachine) -> PyResult {
if self.position.get() < self.list.elements.borrow().len() {
let ret = self.list.elements.borrow()[self.position.get()].clone();
self.position.set(self.position.get() + 1);
Ok(ret)
} else {
Err(objiter::new_stop_iteration(vm))
}
}
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
#[rustfmt::skip] // to avoid line splitting
pub fn init(context: &PyContext) {
let list_type = &context.list_type;
@@ -425,6 +813,7 @@ pub fn init(context: &PyContext) {
"__iadd__" => context.new_rustfunc(PyListRef::iadd),
"__bool__" => context.new_rustfunc(PyListRef::bool),
"__contains__" => context.new_rustfunc(PyListRef::contains),
"__delitem__" => context.new_rustfunc(PyListRef::delitem),
"__eq__" => context.new_rustfunc(PyListRef::eq),
"__lt__" => context.new_rustfunc(PyListRef::lt),
"__gt__" => context.new_rustfunc(PyListRef::gt),
@@ -437,6 +826,7 @@ pub fn init(context: &PyContext) {
"__len__" => context.new_rustfunc(PyListRef::len),
"__new__" => context.new_rustfunc(list_new),
"__repr__" => context.new_rustfunc(PyListRef::repr),
"__hash__" => context.new_rustfunc(PyListRef::hash),
"__doc__" => context.new_str(list_doc.to_string()),
"append" => context.new_rustfunc(PyListRef::append),
"clear" => context.new_rustfunc(PyListRef::clear),
@@ -450,4 +840,10 @@ pub fn init(context: &PyContext) {
"pop" => context.new_rustfunc(PyListRef::pop),
"remove" => context.new_rustfunc(PyListRef::remove)
});
let listiterator_type = &context.listiterator_type;
extend_class!(context, listiterator_type, {
"__next__" => context.new_rustfunc(PyListIteratorRef::next),
"__iter__" => context.new_rustfunc(PyListIteratorRef::iter),
});
}
+5 -1
View File
@@ -46,6 +46,10 @@ impl PyMapRef {
// the mapper itself can raise StopIteration which does stop the map iteration
vm.invoke(self.mapper.clone(), next_objs)
}
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
pub fn init(context: &PyContext) {
@@ -55,10 +59,10 @@ pub fn init(context: &PyContext) {
Make an iterator that computes the function using arguments from\n\
each of the iterables. Stops when the shortest iterable is exhausted.";
objiter::iter_type_init(context, map_type);
extend_class!(context, map_type, {
"__new__" => context.new_rustfunc(map_new),
"__next__" => context.new_rustfunc(PyMapRef::next),
"__iter__" => context.new_rustfunc(PyMapRef::iter),
"__doc__" => context.new_str(map_doc.to_string())
});
}
+1 -1
View File
@@ -20,7 +20,7 @@ pub fn new_memory_view(
bytes_object: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<PyMemoryViewRef> {
vm.ctx.set_attr(&cls, "obj", bytes_object.clone());
vm.set_attr(cls.as_object(), "obj", bytes_object.clone())?;
PyMemoryView { obj: bytes_object }.into_ref_with_type(vm, cls)
}
+2 -6
View File
@@ -1,5 +1,5 @@
use crate::obj::objtype::PyClassRef;
use crate::pyobject::{DictProtocol, PyContext, PyRef, PyResult, PyValue};
use crate::pyobject::{PyContext, PyRef, PyResult, PyValue};
use crate::vm::VirtualMachine;
#[derive(Debug)]
@@ -17,11 +17,7 @@ impl PyValue for PyModule {
impl PyModuleRef {
fn dir(self: PyModuleRef, vm: &VirtualMachine) -> PyResult {
if let Some(dict) = &self.into_object().dict {
let keys = dict
.get_key_value_pairs()
.iter()
.map(|(k, _v)| k.clone())
.collect();
let keys = dict.into_iter().map(|(k, _v)| k.clone()).collect();
Ok(vm.ctx.new_list(keys))
} else {
panic!("Modules should definitely have a dict.");
+45 -32
View File
@@ -1,4 +1,4 @@
use super::objdict::{self, PyDictRef};
use super::objdict::PyDictRef;
use super::objlist::PyList;
use super::objstr::PyStringRef;
use super::objtype;
@@ -6,7 +6,7 @@ use crate::function::PyFuncArgs;
use crate::obj::objproperty::PropertyBuilder;
use crate::obj::objtype::PyClassRef;
use crate::pyobject::{
DictProtocol, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyResult, PyValue,
IdProtocol, ItemProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyResult, PyValue,
TryFromObject, TypeProtocol,
};
use crate::vm::VirtualMachine;
@@ -55,8 +55,8 @@ fn object_ge(_zelf: PyObjectRef, _other: PyObjectRef, vm: &VirtualMachine) -> Py
vm.ctx.not_implemented()
}
fn object_hash(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
Err(vm.new_type_error("unhashable type".to_string()))
fn object_hash(zelf: PyObjectRef, _vm: &VirtualMachine) -> u64 {
zelf.get_id() as u64
}
fn object_setattr(
@@ -77,7 +77,7 @@ fn object_setattr(
}
if let Some(ref dict) = obj.clone().dict {
dict.set_item(&vm.ctx, &attr_name.value, value);
dict.set_item(attr_name, value, vm)?;
Ok(())
} else {
Err(vm.new_attribute_error(format!(
@@ -98,7 +98,7 @@ fn object_delattr(obj: PyObjectRef, attr_name: PyStringRef, vm: &VirtualMachine)
}
if let Some(ref dict) = obj.dict {
dict.del_item(&attr_name.value);
dict.del_item(attr_name, vm)?;
Ok(())
} else {
Err(vm.new_attribute_error(format!(
@@ -117,13 +117,22 @@ fn object_repr(zelf: PyObjectRef, _vm: &VirtualMachine) -> String {
format!("<{} object at 0x{:x}>", zelf.class().name, zelf.get_id())
}
pub fn object_dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyList {
let attributes = get_attributes(&obj);
let attributes: Vec<PyObjectRef> = attributes
.keys()
.map(|k| vm.ctx.new_str(k.to_string()))
.collect();
PyList::from(attributes)
pub fn object_dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyList> {
let attributes: PyAttributes = objtype::get_attributes(obj.class());
let dict = PyDictRef::from_attributes(attributes, vm)?;
// Get instance attributes:
if let Some(object_dict) = &obj.dict {
vm.invoke(
vm.get_attribute(dict.clone().into_object(), "update")?,
object_dict.clone().into_object(),
)?;
}
let attributes: Vec<_> = dict.into_iter().map(|(k, _v)| k.clone()).collect();
Ok(PyList::from(attributes))
}
fn object_format(
@@ -158,7 +167,11 @@ pub fn init(context: &PyContext) {
"__ge__" => context.new_rustfunc(object_ge),
"__setattr__" => context.new_rustfunc(object_setattr),
"__delattr__" => context.new_rustfunc(object_delattr),
"__dict__" => context.new_property(object_dict),
"__dict__" =>
PropertyBuilder::new(context)
.add_getter(object_dict)
.add_setter(object_dict_setter)
.create(),
"__dir__" => context.new_rustfunc(object_dir),
"__hash__" => context.new_rustfunc(object_hash),
"__str__" => context.new_rustfunc(object_str),
@@ -194,6 +207,16 @@ fn object_dict(object: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyDictRef>
}
}
fn object_dict_setter(
_instance: PyObjectRef,
_value: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
Err(vm.new_not_implemented_error(
"Setting __dict__ attribute on am object isn't yet implemented".to_string(),
))
}
fn object_getattribute(obj: PyObjectRef, name_str: PyStringRef, vm: &VirtualMachine) -> PyResult {
let name = &name_str.value;
trace!("object.__getattribute__({:?}, {:?})", obj, name);
@@ -208,7 +231,7 @@ fn object_getattribute(obj: PyObjectRef, name_str: PyStringRef, vm: &VirtualMach
}
}
if let Some(obj_attr) = object_getattr(&obj, &name) {
if let Some(obj_attr) = object_getattr(&obj, &name, &vm)? {
Ok(obj_attr)
} else if let Some(attr) = objtype::class_get_attr(&cls, &name) {
vm.call_get_descriptor(attr, obj)
@@ -219,24 +242,14 @@ fn object_getattribute(obj: PyObjectRef, name_str: PyStringRef, vm: &VirtualMach
}
}
fn object_getattr(obj: &PyObjectRef, attr_name: &str) -> Option<PyObjectRef> {
fn object_getattr(
obj: &PyObjectRef,
attr_name: &str,
vm: &VirtualMachine,
) -> PyResult<Option<PyObjectRef>> {
if let Some(ref dict) = obj.dict {
dict.get_item(attr_name)
dict.get_item_option(attr_name, vm)
} else {
None
Ok(None)
}
}
pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes {
// Get class attributes:
let mut attributes = objtype::get_attributes(obj.class());
// Get instance attributes:
if let Some(dict) = &obj.dict {
for (key, value) in objdict::get_key_value_pairs(dict.as_object()) {
attributes.insert(key.to_string(), value.clone());
}
}
attributes
}
+93 -37
View File
@@ -6,11 +6,14 @@ use num_integer::Integer;
use num_traits::{One, Signed, Zero};
use crate::function::{OptionalArg, PyFuncArgs};
use crate::pyobject::{Either, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue};
use crate::pyobject::{
PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
};
use crate::vm::VirtualMachine;
use super::objint::{PyInt, PyIntRef};
use super::objslice::PySliceRef;
use super::objiter;
use super::objslice::{PySlice, PySliceRef};
use super::objtype::PyClassRef;
#[derive(Debug, Clone)]
@@ -98,26 +101,32 @@ pub fn init(context: &PyContext) {
When step is given, it specifies the increment (or decrement).";
extend_class!(context, range_type, {
"__bool__" => context.new_rustfunc(PyRangeRef::bool),
"__contains__" => context.new_rustfunc(PyRangeRef::contains),
"__bool__" => context.new_rustfunc(PyRange::bool),
"__contains__" => context.new_rustfunc(PyRange::contains),
"__doc__" => context.new_str(range_doc.to_string()),
"__getitem__" => context.new_rustfunc(PyRangeRef::getitem),
"__iter__" => context.new_rustfunc(PyRangeRef::iter),
"__len__" => context.new_rustfunc(PyRangeRef::len),
"__getitem__" => context.new_rustfunc(PyRange::getitem),
"__iter__" => context.new_rustfunc(PyRange::iter),
"__len__" => context.new_rustfunc(PyRange::len),
"__new__" => context.new_rustfunc(range_new),
"__repr__" => context.new_rustfunc(PyRangeRef::repr),
"__reversed__" => context.new_rustfunc(PyRangeRef::reversed),
"count" => context.new_rustfunc(PyRangeRef::count),
"index" => context.new_rustfunc(PyRangeRef::index),
"start" => context.new_property(PyRangeRef::start),
"stop" => context.new_property(PyRangeRef::stop),
"step" => context.new_property(PyRangeRef::step),
"__repr__" => context.new_rustfunc(PyRange::repr),
"__reversed__" => context.new_rustfunc(PyRange::reversed),
"count" => context.new_rustfunc(PyRange::count),
"index" => context.new_rustfunc(PyRange::index),
"start" => context.new_property(PyRange::start),
"stop" => context.new_property(PyRange::stop),
"step" => context.new_property(PyRange::step),
});
let rangeiterator_type = &context.rangeiterator_type;
extend_class!(context, rangeiterator_type, {
"__next__" => context.new_rustfunc(PyRangeIteratorRef::next),
"__iter__" => context.new_rustfunc(PyRangeIteratorRef::iter),
});
}
type PyRangeRef = PyRef<PyRange>;
impl PyRangeRef {
impl PyRange {
fn new(cls: PyClassRef, stop: PyIntRef, vm: &VirtualMachine) -> PyResult<PyRangeRef> {
PyRange {
start: PyInt::new(BigInt::zero()).into_ref(vm),
@@ -135,8 +144,8 @@ impl PyRangeRef {
vm: &VirtualMachine,
) -> PyResult<PyRangeRef> {
PyRange {
start: start,
stop: stop,
start,
stop,
step: step
.into_option()
.unwrap_or_else(|| PyInt::new(BigInt::one()).into_ref(vm)),
@@ -144,26 +153,26 @@ impl PyRangeRef {
.into_ref_with_type(vm, cls)
}
fn start(self, _vm: &VirtualMachine) -> PyIntRef {
fn start(&self, _vm: &VirtualMachine) -> PyIntRef {
self.start.clone()
}
fn stop(self, _vm: &VirtualMachine) -> PyIntRef {
fn stop(&self, _vm: &VirtualMachine) -> PyIntRef {
self.stop.clone()
}
fn step(self, _vm: &VirtualMachine) -> PyIntRef {
fn step(&self, _vm: &VirtualMachine) -> PyIntRef {
self.step.clone()
}
fn iter(self: PyRangeRef, _vm: &VirtualMachine) -> PyIteratorValue {
PyIteratorValue {
fn iter(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyRangeIterator {
PyRangeIterator {
position: Cell::new(0),
iterated_obj: self.into_object(),
range: zelf,
}
}
fn reversed(self: PyRangeRef, vm: &VirtualMachine) -> PyIteratorValue {
fn reversed(&self, vm: &VirtualMachine) -> PyRangeIterator {
let start = self.start.as_bigint();
let stop = self.stop.as_bigint();
let step = self.step.as_bigint();
@@ -189,13 +198,13 @@ impl PyRangeRef {
step: PyInt::new(-step).into_ref(vm),
};
PyIteratorValue {
PyRangeIterator {
position: Cell::new(0),
iterated_obj: reversed.into_ref(vm).into_object(),
range: reversed.into_ref(vm),
}
}
fn len(self, _vm: &VirtualMachine) -> PyInt {
fn len(&self, _vm: &VirtualMachine) -> PyInt {
let start = self.start.as_bigint();
let stop = self.stop.as_bigint();
let step = self.step.as_bigint();
@@ -208,7 +217,7 @@ impl PyRangeRef {
}
}
fn repr(self, _vm: &VirtualMachine) -> String {
fn repr(&self, _vm: &VirtualMachine) -> String {
if self.step.as_bigint().is_one() {
format!("range({}, {})", self.start, self.stop)
} else {
@@ -216,11 +225,11 @@ impl PyRangeRef {
}
}
fn bool(self, _vm: &VirtualMachine) -> bool {
fn bool(&self, _vm: &VirtualMachine) -> bool {
!self.is_empty()
}
fn contains(self, needle: PyObjectRef, _vm: &VirtualMachine) -> bool {
fn contains(&self, needle: PyObjectRef, _vm: &VirtualMachine) -> bool {
if let Ok(int) = needle.downcast::<PyInt>() {
match self.offset(int.as_bigint()) {
Some(ref offset) => offset.is_multiple_of(self.step.as_bigint()),
@@ -231,7 +240,7 @@ impl PyRangeRef {
}
}
fn index(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyInt> {
fn index(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyInt> {
if let Ok(int) = needle.downcast::<PyInt>() {
match self.index_of(int.as_bigint()) {
Some(idx) => Ok(PyInt::new(idx)),
@@ -242,7 +251,7 @@ impl PyRangeRef {
}
}
fn count(self, item: PyObjectRef, _vm: &VirtualMachine) -> PyInt {
fn count(&self, item: PyObjectRef, _vm: &VirtualMachine) -> PyInt {
if let Ok(int) = item.downcast::<PyInt>() {
if self.index_of(int.as_bigint()).is_some() {
PyInt::new(1)
@@ -254,16 +263,16 @@ impl PyRangeRef {
}
}
fn getitem(self, subscript: Either<PyIntRef, PySliceRef>, vm: &VirtualMachine) -> PyResult {
fn getitem(&self, subscript: RangeIndex, vm: &VirtualMachine) -> PyResult {
match subscript {
Either::A(index) => {
RangeIndex::Int(index) => {
if let Some(value) = self.get(index.as_bigint()) {
Ok(PyInt::new(value).into_ref(vm).into_object())
} else {
Err(vm.new_index_error("range object index out of range".to_string()))
}
}
Either::B(slice) => {
RangeIndex::Slice(slice) => {
let new_start = if let Some(int) = slice.start_index(vm)? {
if let Some(i) = self.get(int) {
PyInt::new(i).into_ref(vm)
@@ -305,11 +314,58 @@ impl PyRangeRef {
fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
let range = if args.args.len() <= 2 {
let (cls, stop) = args.bind(vm)?;
PyRangeRef::new(cls, stop, vm)
PyRange::new(cls, stop, vm)
} else {
let (cls, start, stop, step) = args.bind(vm)?;
PyRangeRef::new_from(cls, start, stop, step, vm)
PyRange::new_from(cls, start, stop, step, vm)
}?;
Ok(range.into_object())
}
#[derive(Debug)]
pub struct PyRangeIterator {
position: Cell<usize>,
range: PyRangeRef,
}
impl PyValue for PyRangeIterator {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.rangeiterator_type()
}
}
type PyRangeIteratorRef = PyRef<PyRangeIterator>;
impl PyRangeIteratorRef {
fn next(self, vm: &VirtualMachine) -> PyResult<BigInt> {
if let Some(int) = self.range.get(self.position.get()) {
self.position.set(self.position.get() + 1);
Ok(int)
} else {
Err(objiter::new_stop_iteration(vm))
}
}
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
pub enum RangeIndex {
Int(PyIntRef),
Slice(PySliceRef),
}
impl TryFromObject for RangeIndex {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
match_class!(obj,
i @ PyInt => Ok(RangeIndex::Int(i)),
s @ PySlice => Ok(RangeIndex::Slice(s)),
obj => Err(vm.new_type_error(format!(
"sequence indices be integers or slices, not {}",
obj.class(),
)))
)
}
}
+36 -6
View File
@@ -2,16 +2,16 @@ use std::cell::RefCell;
use std::marker::Sized;
use std::ops::{Deref, DerefMut, Range};
use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};
use crate::pyobject::{IdProtocol, PyObject, PyObjectRef, PyResult, TryFromObject, TypeProtocol};
use crate::pyobject::{IdProtocol, PyObject, PyObjectRef, PyResult, TypeProtocol};
use crate::vm::VirtualMachine;
use num_bigint::{BigInt, ToBigInt};
use num_traits::{One, Signed, ToPrimitive, Zero};
use super::objbool;
use super::objint::PyInt;
use super::objlist::PyList;
use super::objslice::PySlice;
use super::objslice::{PySlice, PySliceRef};
use super::objtuple::PyTuple;
pub trait PySliceableSequence {
@@ -87,8 +87,20 @@ pub trait PySliceableSequence {
} else {
// calculate the range for the reverse slice, first the bounds needs to be made
// exclusive around stop, the lower number
let start = start.as_ref().map(|x| x + 1);
let stop = stop.as_ref().map(|x| x + 1);
let start = start.as_ref().map(|x| {
if *x == (-1).to_bigint().unwrap() {
self.len() + BigInt::one() //.to_bigint().unwrap()
} else {
x + 1
}
});
let stop = stop.as_ref().map(|x| {
if *x == (-1).to_bigint().unwrap() {
self.len().to_bigint().unwrap()
} else {
x + 1
}
});
let range = self.get_slice_range(&stop, &start);
if range.start < range.end {
match (-step).to_i32() {
@@ -138,6 +150,24 @@ impl<T: Clone> PySliceableSequence for Vec<T> {
}
}
pub enum SequenceIndex {
Int(i32),
Slice(PySliceRef),
}
impl TryFromObject for SequenceIndex {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
match_class!(obj,
i @ PyInt => Ok(SequenceIndex::Int(i32::try_from_object(vm, i.into_object())?)),
s @ PySlice => Ok(SequenceIndex::Slice(s)),
obj => Err(vm.new_type_error(format!(
"sequence indices be integers or slices, not {}",
obj.class(),
)))
)
}
}
pub fn get_item(
vm: &VirtualMachine,
sequence: &PyObjectRef,
+692 -533
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::function::{OptionalArg, PyFuncArgs};
use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol};
use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::vm::VirtualMachine;
use crate::obj::objint::PyInt;
+344 -211
View File
@@ -10,8 +10,8 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::format::{FormatParseError, FormatPart, FormatString};
use crate::function::{OptionalArg, PyFuncArgs};
use crate::pyobject::{
IdProtocol, IntoPyObject, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue,
TryFromObject, TryIntoRef, TypeProtocol,
IdProtocol, IntoPyObject, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult,
PyValue, TryFromObject, TryIntoRef, TypeProtocol,
};
use crate::vm::VirtualMachine;
@@ -20,11 +20,37 @@ use super::objsequence::PySliceableSequence;
use super::objslice::PySlice;
use super::objtype::{self, PyClassRef};
/// str(object='') -> str
/// str(bytes_or_buffer[, encoding[, errors]]) -> str
///
/// Create a new string object from the given object. If encoding or
/// errors is specified, then the object must expose a data buffer
/// that will be decoded using the given encoding and error handler.
/// Otherwise, returns the result of object.__str__() (if defined)
/// or repr(object).
/// encoding defaults to sys.getdefaultencoding().
/// errors defaults to 'strict'."
#[pyclass(name = "str")]
#[derive(Clone, Debug)]
pub struct PyString {
// TODO: shouldn't be public
pub value: String,
}
impl PyString {
pub fn as_str(&self) -> &str {
&self.value
}
}
impl From<&str> for PyString {
fn from(s: &str) -> PyString {
PyString {
value: s.to_string(),
}
}
}
pub type PyStringRef = PyRef<PyString>;
impl fmt::Display for PyString {
@@ -48,8 +74,30 @@ impl TryIntoRef<PyString> for &str {
}
}
impl PyStringRef {
fn add(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
#[pyimpl]
impl PyString {
// TODO: should with following format
// class str(object='')
// class str(object=b'', encoding='utf-8', errors='strict')
#[pymethod(name = "__new__")]
fn new(
cls: PyClassRef,
object: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyStringRef> {
let string = match object {
OptionalArg::Present(ref input) => vm.to_str(input)?.into_object(),
OptionalArg::Missing => vm.new_str("".to_string()),
};
if string.class().is(&cls) {
TryFromObject::try_from_object(vm, string)
} else {
let payload = string.payload::<PyString>().unwrap();
payload.clone().into_ref_with_type(vm, cls)
}
}
#[pymethod(name = "__add__")]
fn add(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(format!("{}{}", self.value, get_value(&rhs)))
} else {
@@ -57,11 +105,13 @@ impl PyStringRef {
}
}
fn bool(self, _vm: &VirtualMachine) -> bool {
#[pymethod(name = "__bool__")]
fn bool(&self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty()
}
fn eq(self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool {
#[pymethod(name = "__eq__")]
fn eq(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
self.value == get_value(&rhs)
} else {
@@ -69,15 +119,18 @@ impl PyStringRef {
}
}
fn contains(self, needle: PyStringRef, _vm: &VirtualMachine) -> bool {
#[pymethod(name = "__contains__")]
fn contains(&self, needle: PyStringRef, _vm: &VirtualMachine) -> bool {
self.value.contains(&needle.value)
}
fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
#[pymethod(name = "__getitem__")]
fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
subscript(vm, &self.value, needle)
}
fn gt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
#[pymethod(name = "__gt__")]
fn gt(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(self.value > get_value(&rhs))
} else {
@@ -85,7 +138,8 @@ impl PyStringRef {
}
}
fn ge(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
#[pymethod(name = "__ge__")]
fn ge(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(self.value >= get_value(&rhs))
} else {
@@ -93,7 +147,8 @@ impl PyStringRef {
}
}
fn lt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
#[pymethod(name = "__lt__")]
fn lt(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(self.value < get_value(&rhs))
} else {
@@ -101,7 +156,8 @@ impl PyStringRef {
}
}
fn le(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
#[pymethod(name = "__le__")]
fn le(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(self.value <= get_value(&rhs))
} else {
@@ -109,17 +165,20 @@ impl PyStringRef {
}
}
fn hash(self, _vm: &VirtualMachine) -> usize {
#[pymethod(name = "__hash__")]
fn hash(&self, _vm: &VirtualMachine) -> usize {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.value.hash(&mut hasher);
hasher.finish() as usize
}
fn len(self, _vm: &VirtualMachine) -> usize {
#[pymethod(name = "__len__")]
fn len(&self, _vm: &VirtualMachine) -> usize {
self.value.chars().count()
}
fn mul(self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
#[pymethod(name = "__mul__")]
fn mul(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
if objtype::isinstance(&val, &vm.ctx.int_type()) {
let value = &self.value;
let multiplier = objint::get_value(&val).to_i32().unwrap();
@@ -133,11 +192,13 @@ impl PyStringRef {
}
}
fn str(self, _vm: &VirtualMachine) -> PyStringRef {
self
#[pymethod(name = "__str__")]
fn str(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyStringRef {
zelf
}
fn repr(self, _vm: &VirtualMachine) -> String {
#[pymethod(name = "__repr__")]
fn repr(&self, _vm: &VirtualMachine) -> String {
let value = &self.value;
let quote_char = if count_char(value, '\'') > count_char(value, '"') {
'"'
@@ -167,27 +228,32 @@ impl PyStringRef {
formatted
}
fn lower(self, _vm: &VirtualMachine) -> String {
#[pymethod]
fn lower(&self, _vm: &VirtualMachine) -> String {
self.value.to_lowercase()
}
// casefold is much more aggressive than lower
fn casefold(self, _vm: &VirtualMachine) -> String {
#[pymethod]
fn casefold(&self, _vm: &VirtualMachine) -> String {
caseless::default_case_fold_str(&self.value)
}
fn upper(self, _vm: &VirtualMachine) -> String {
#[pymethod]
fn upper(&self, _vm: &VirtualMachine) -> String {
self.value.to_uppercase()
}
fn capitalize(self, _vm: &VirtualMachine) -> String {
#[pymethod]
fn capitalize(&self, _vm: &VirtualMachine) -> String {
let (first_part, lower_str) = self.value.split_at(1);
format!("{}{}", first_part.to_uppercase(), lower_str)
}
#[pymethod]
fn split(
self,
pattern: OptionalArg<Self>,
&self,
pattern: OptionalArg<PyStringRef>,
num: OptionalArg<usize>,
vm: &VirtualMachine,
) -> PyObjectRef {
@@ -206,9 +272,10 @@ impl PyStringRef {
vm.ctx.new_list(elements)
}
#[pymethod]
fn rsplit(
self,
pattern: OptionalArg<Self>,
&self,
pattern: OptionalArg<PyStringRef>,
num: OptionalArg<usize>,
vm: &VirtualMachine,
) -> PyObjectRef {
@@ -227,20 +294,24 @@ impl PyStringRef {
vm.ctx.new_list(elements)
}
fn strip(self, _vm: &VirtualMachine) -> String {
#[pymethod]
fn strip(&self, _vm: &VirtualMachine) -> String {
self.value.trim().to_string()
}
fn lstrip(self, _vm: &VirtualMachine) -> String {
#[pymethod]
fn lstrip(&self, _vm: &VirtualMachine) -> String {
self.value.trim_start().to_string()
}
fn rstrip(self, _vm: &VirtualMachine) -> String {
#[pymethod]
fn rstrip(&self, _vm: &VirtualMachine) -> String {
self.value.trim_end().to_string()
}
#[pymethod]
fn endswith(
self,
&self,
suffix: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
@@ -253,8 +324,9 @@ impl PyStringRef {
}
}
#[pymethod]
fn startswith(
self,
&self,
prefix: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
@@ -267,15 +339,18 @@ impl PyStringRef {
}
}
fn isalnum(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn isalnum(&self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(char::is_alphanumeric)
}
fn isnumeric(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn isnumeric(&self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(char::is_numeric)
}
fn isdigit(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn isdigit(&self, _vm: &VirtualMachine) -> bool {
// python's isdigit also checks if exponents are digits, these are the unicodes for exponents
let valid_unicodes: [u16; 10] = [
0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079,
@@ -291,7 +366,8 @@ impl PyStringRef {
}
}
fn isdecimal(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn isdecimal(&self, _vm: &VirtualMachine) -> bool {
if self.value.is_empty() {
false
} else {
@@ -299,11 +375,66 @@ impl PyStringRef {
}
}
fn title(self, _vm: &VirtualMachine) -> String {
make_title(&self.value)
#[pymethod]
fn format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
if args.args.is_empty() {
return Err(vm.new_type_error(
"descriptor 'format' of 'str' object needs an argument".to_string(),
));
}
let zelf = &args.args[0];
if !objtype::isinstance(&zelf, &vm.ctx.str_type()) {
let zelf_typ = zelf.class();
let actual_type = vm.to_pystr(&zelf_typ)?;
return Err(vm.new_type_error(format!(
"descriptor 'format' requires a 'str' object but received a '{}'",
actual_type
)));
}
let format_string_text = get_value(zelf);
match FormatString::from_str(format_string_text.as_str()) {
Ok(format_string) => perform_format(vm, &format_string, &args),
Err(err) => match err {
FormatParseError::UnmatchedBracket => {
Err(vm.new_value_error("expected '}' before end of string".to_string()))
}
_ => Err(vm.new_value_error("Unexpected error parsing format string".to_string())),
},
}
}
fn swapcase(self, _vm: &VirtualMachine) -> String {
/// Return a titlecased version of the string where words start with an
/// uppercase character and the remaining characters are lowercase.
#[pymethod]
fn title(&self, _vm: &VirtualMachine) -> String {
let mut title = String::new();
let mut previous_is_cased = false;
for c in self.value.chars() {
if c.is_lowercase() {
if !previous_is_cased {
title.extend(c.to_uppercase());
} else {
title.push(c);
}
previous_is_cased = true;
} else if c.is_uppercase() {
if previous_is_cased {
title.extend(c.to_lowercase());
} else {
title.push(c);
}
previous_is_cased = true;
} else {
previous_is_cased = false;
title.push(c);
}
}
title
}
#[pymethod]
fn swapcase(&self, _vm: &VirtualMachine) -> String {
let mut swapped_str = String::with_capacity(self.value.len());
for c in self.value.chars() {
// to_uppercase returns an iterator, to_ascii_uppercase returns the char
@@ -318,14 +449,16 @@ impl PyStringRef {
swapped_str
}
fn isalpha(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn isalpha(&self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(char::is_alphanumeric)
}
#[pymethod]
fn replace(
self,
old: Self,
new: Self,
&self,
old: PyStringRef,
new: PyStringRef,
num: OptionalArg<usize>,
_vm: &VirtualMachine,
) -> String {
@@ -337,11 +470,13 @@ impl PyStringRef {
// cpython's isspace ignores whitespace, including \t and \n, etc, unless the whole string is empty
// which is why isspace is using is_ascii_whitespace. Same for isupper & islower
fn isspace(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn isspace(&self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(|c| c.is_ascii_whitespace())
}
fn isupper(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn isupper(&self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty()
&& self
.value
@@ -350,7 +485,8 @@ impl PyStringRef {
.all(char::is_uppercase)
}
fn islower(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn islower(&self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty()
&& self
.value
@@ -359,12 +495,14 @@ impl PyStringRef {
.all(char::is_lowercase)
}
fn isascii(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn isascii(&self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(|c| c.is_ascii())
}
// doesn't implement keep new line delimiter just yet
fn splitlines(self, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod]
fn splitlines(&self, vm: &VirtualMachine) -> PyObjectRef {
let elements = self
.value
.split('\n')
@@ -373,7 +511,8 @@ impl PyStringRef {
vm.ctx.new_list(elements)
}
fn join(self, iterable: PyIterable<PyStringRef>, vm: &VirtualMachine) -> PyResult<String> {
#[pymethod]
fn join(&self, iterable: PyIterable<PyStringRef>, vm: &VirtualMachine) -> PyResult<String> {
let mut joined = String::new();
for (idx, elem) in iterable.iter(vm)?.enumerate() {
@@ -387,9 +526,10 @@ impl PyStringRef {
Ok(joined)
}
#[pymethod]
fn find(
self,
sub: Self,
&self,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
_vm: &VirtualMachine,
@@ -405,9 +545,10 @@ impl PyStringRef {
}
}
#[pymethod]
fn rfind(
self,
sub: Self,
&self,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
_vm: &VirtualMachine,
@@ -423,9 +564,10 @@ impl PyStringRef {
}
}
#[pymethod]
fn index(
self,
sub: Self,
&self,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
vm: &VirtualMachine,
@@ -441,9 +583,10 @@ impl PyStringRef {
}
}
#[pymethod]
fn rindex(
self,
sub: Self,
&self,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
vm: &VirtualMachine,
@@ -459,7 +602,8 @@ impl PyStringRef {
}
}
fn partition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod]
fn partition(&self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef {
let value = &self.value;
let sub = &sub.value;
let mut new_tup = Vec::new();
@@ -477,7 +621,8 @@ impl PyStringRef {
vm.ctx.new_tuple(new_tup)
}
fn rpartition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef {
#[pymethod]
fn rpartition(&self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef {
let value = &self.value;
let sub = &sub.value;
let mut new_tup = Vec::new();
@@ -496,17 +641,40 @@ impl PyStringRef {
vm.ctx.new_tuple(new_tup)
}
fn istitle(self, _vm: &VirtualMachine) -> bool {
/// Return `true` if the sequence is ASCII titlecase and the sequence is not
/// empty, `false` otherwise.
#[pymethod]
fn istitle(&self, _vm: &VirtualMachine) -> bool {
if self.value.is_empty() {
false
} else {
self.value.split(' ').all(|word| word == make_title(word))
return false;
}
let mut cased = false;
let mut previous_is_cased = false;
for c in self.value.chars() {
if c.is_uppercase() {
if previous_is_cased {
return false;
}
previous_is_cased = true;
cased = true;
} else if c.is_lowercase() {
if !previous_is_cased {
return false;
}
previous_is_cased = true;
cased = true;
} else {
previous_is_cased = false;
}
}
cased
}
#[pymethod]
fn count(
self,
sub: Self,
&self,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
_vm: &VirtualMachine,
@@ -519,7 +687,8 @@ impl PyStringRef {
}
}
fn zfill(self, len: usize, _vm: &VirtualMachine) -> String {
#[pymethod]
fn zfill(&self, len: usize, _vm: &VirtualMachine) -> String {
let value = &self.value;
if len <= value.len() {
value.to_string()
@@ -528,7 +697,10 @@ impl PyStringRef {
}
}
fn get_fill_char<'a>(rep: &'a OptionalArg<Self>, vm: &VirtualMachine) -> PyResult<&'a str> {
fn get_fill_char<'a>(
rep: &'a OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<&'a str> {
let rep_str = match rep {
OptionalArg::Present(ref st) => &st.value,
OptionalArg::Missing => " ",
@@ -542,23 +714,55 @@ impl PyStringRef {
}
}
fn ljust(self, len: usize, rep: OptionalArg<Self>, vm: &VirtualMachine) -> PyResult<String> {
#[pymethod]
fn ljust(
&self,
len: usize,
rep: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
let value = &self.value;
let rep_char = PyStringRef::get_fill_char(&rep, vm)?;
let rep_char = Self::get_fill_char(&rep, vm)?;
Ok(format!("{}{}", value, rep_char.repeat(len)))
}
fn rjust(self, len: usize, rep: OptionalArg<Self>, vm: &VirtualMachine) -> PyResult<String> {
#[pymethod]
fn rjust(
&self,
len: usize,
rep: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
let value = &self.value;
let rep_char = PyStringRef::get_fill_char(&rep, vm)?;
let rep_char = Self::get_fill_char(&rep, vm)?;
Ok(format!("{}{}", rep_char.repeat(len), value))
}
fn center(self, len: usize, rep: OptionalArg<Self>, vm: &VirtualMachine) -> PyResult<String> {
#[pymethod]
fn center(
&self,
len: usize,
rep: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
let value = &self.value;
let rep_char = PyStringRef::get_fill_char(&rep, vm)?;
let left_buff: usize = (len - value.len()) / 2;
let right_buff = len - value.len() - left_buff;
let rep_char = Self::get_fill_char(&rep, vm)?;
let value_len = self.value.chars().count();
if len <= value_len {
return Ok(value.to_string());
}
let diff: usize = len - value_len;
let mut left_buff: usize = diff / 2;
let mut right_buff: usize = left_buff;
if diff % 2 != 0 && value_len % 2 == 0 {
left_buff += 1
}
if diff % 2 != 0 && value_len % 2 != 0 {
right_buff += 1
}
Ok(format!(
"{}{}{}",
rep_char.repeat(left_buff),
@@ -567,7 +771,8 @@ impl PyStringRef {
))
}
fn expandtabs(self, tab_stop: OptionalArg<usize>, _vm: &VirtualMachine) -> String {
#[pymethod]
fn expandtabs(&self, tab_stop: OptionalArg<usize>, _vm: &VirtualMachine) -> String {
let tab_stop = tab_stop.into_option().unwrap_or(8 as usize);
let mut expanded_str = String::new();
let mut tab_size = tab_stop;
@@ -590,7 +795,8 @@ impl PyStringRef {
expanded_str
}
fn isidentifier(self, _vm: &VirtualMachine) -> bool {
#[pymethod]
fn isidentifier(&self, _vm: &VirtualMachine) -> bool {
let value = &self.value;
// a string is not an identifier if it has whitespace or starts with a number
if !value.chars().any(|c| c.is_ascii_whitespace())
@@ -620,78 +826,20 @@ impl IntoPyObject for String {
}
}
#[rustfmt::skip] // to avoid line splitting
pub fn init(context: &PyContext) {
let str_type = &context.str_type;
let str_doc = "str(object='') -> str\n\
str(bytes_or_buffer[, encoding[, errors]]) -> str\n\
\n\
Create a new string object from the given object. If encoding or\n\
errors is specified, then the object must expose a data buffer\n\
that will be decoded using the given encoding and error handler.\n\
Otherwise, returns the result of object.__str__() (if defined)\n\
or repr(object).\n\
encoding defaults to sys.getdefaultencoding().\n\
errors defaults to 'strict'.";
impl IntoPyObject for &str {
fn into_pyobject(self, vm: &VirtualMachine) -> PyResult {
Ok(vm.ctx.new_str(self.to_string()))
}
}
extend_class!(context, str_type, {
"__add__" => context.new_rustfunc(PyStringRef::add),
"__bool__" => context.new_rustfunc(PyStringRef::bool),
"__contains__" => context.new_rustfunc(PyStringRef::contains),
"__doc__" => context.new_str(str_doc.to_string()),
"__eq__" => context.new_rustfunc(PyStringRef::eq),
"__ge__" => context.new_rustfunc(PyStringRef::ge),
"__getitem__" => context.new_rustfunc(PyStringRef::getitem),
"__gt__" => context.new_rustfunc(PyStringRef::gt),
"__hash__" => context.new_rustfunc(PyStringRef::hash),
"__lt__" => context.new_rustfunc(PyStringRef::lt),
"__le__" => context.new_rustfunc(PyStringRef::le),
"__len__" => context.new_rustfunc(PyStringRef::len),
"__mul__" => context.new_rustfunc(PyStringRef::mul),
"__new__" => context.new_rustfunc(str_new),
"__repr__" => context.new_rustfunc(PyStringRef::repr),
"__str__" => context.new_rustfunc(PyStringRef::str),
"capitalize" => context.new_rustfunc(PyStringRef::capitalize),
"casefold" => context.new_rustfunc(PyStringRef::casefold),
"center" => context.new_rustfunc(PyStringRef::center),
"count" => context.new_rustfunc(PyStringRef::count),
"endswith" => context.new_rustfunc(PyStringRef::endswith),
"expandtabs" => context.new_rustfunc(PyStringRef::expandtabs),
"find" => context.new_rustfunc(PyStringRef::find),
"format" => context.new_rustfunc(str_format),
"index" => context.new_rustfunc(PyStringRef::index),
"isalnum" => context.new_rustfunc(PyStringRef::isalnum),
"isalpha" => context.new_rustfunc(PyStringRef::isalpha),
"isascii" => context.new_rustfunc(PyStringRef::isascii),
"isdecimal" => context.new_rustfunc(PyStringRef::isdecimal),
"isdigit" => context.new_rustfunc(PyStringRef::isdigit),
"isidentifier" => context.new_rustfunc(PyStringRef::isidentifier),
"islower" => context.new_rustfunc(PyStringRef::islower),
"isnumeric" => context.new_rustfunc(PyStringRef::isnumeric),
"isspace" => context.new_rustfunc(PyStringRef::isspace),
"isupper" => context.new_rustfunc(PyStringRef::isupper),
"istitle" => context.new_rustfunc(PyStringRef::istitle),
"join" => context.new_rustfunc(PyStringRef::join),
"lower" => context.new_rustfunc(PyStringRef::lower),
"ljust" => context.new_rustfunc(PyStringRef::ljust),
"lstrip" => context.new_rustfunc(PyStringRef::lstrip),
"partition" => context.new_rustfunc(PyStringRef::partition),
"replace" => context.new_rustfunc(PyStringRef::replace),
"rfind" => context.new_rustfunc(PyStringRef::rfind),
"rindex" => context.new_rustfunc(PyStringRef::rindex),
"rjust" => context.new_rustfunc(PyStringRef::rjust),
"rpartition" => context.new_rustfunc(PyStringRef::rpartition),
"rsplit" => context.new_rustfunc(PyStringRef::rsplit),
"rstrip" => context.new_rustfunc(PyStringRef::rstrip),
"split" => context.new_rustfunc(PyStringRef::split),
"splitlines" => context.new_rustfunc(PyStringRef::splitlines),
"startswith" => context.new_rustfunc(PyStringRef::startswith),
"strip" => context.new_rustfunc(PyStringRef::strip),
"swapcase" => context.new_rustfunc(PyStringRef::swapcase),
"title" => context.new_rustfunc(PyStringRef::title),
"upper" => context.new_rustfunc(PyStringRef::upper),
"zfill" => context.new_rustfunc(PyStringRef::zfill),
});
impl IntoPyObject for &String {
fn into_pyobject(self, vm: &VirtualMachine) -> PyResult {
Ok(vm.ctx.new_str(self.clone()))
}
}
pub fn init(ctx: &PyContext) {
PyString::extend_class(ctx, &ctx.str_type);
}
pub fn get_value(obj: &PyObjectRef) -> String {
@@ -706,34 +854,6 @@ fn count_char(s: &str, c: char) -> usize {
s.chars().filter(|x| *x == c).count()
}
fn str_format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
if args.args.is_empty() {
return Err(
vm.new_type_error("descriptor 'format' of 'str' object needs an argument".to_string())
);
}
let zelf = &args.args[0];
if !objtype::isinstance(&zelf, &vm.ctx.str_type()) {
let zelf_typ = zelf.class();
let actual_type = vm.to_pystr(&zelf_typ)?;
return Err(vm.new_type_error(format!(
"descriptor 'format' requires a 'str' object but received a '{}'",
actual_type
)));
}
let format_string_text = get_value(zelf);
match FormatString::from_str(format_string_text.as_str()) {
Ok(format_string) => perform_format(vm, &format_string, &args),
Err(err) => match err {
FormatParseError::UnmatchedBracket => {
Err(vm.new_value_error("expected '}' before end of string".to_string()))
}
_ => Err(vm.new_value_error("Unexpected error parsing format string".to_string())),
},
}
}
fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: &str) -> PyResult {
let returned_type = vm.ctx.new_str(format_spec.to_string());
let result = vm.call_method(&argument, "__format__", vec![returned_type])?;
@@ -797,26 +917,6 @@ fn perform_format(
Ok(vm.ctx.new_str(final_string))
}
// TODO: should with following format
// class str(object='')
// class str(object=b'', encoding='utf-8', errors='strict')
fn str_new(
cls: PyClassRef,
object: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyStringRef> {
let string = match object {
OptionalArg::Present(ref input) => vm.to_str(input)?.into_object(),
OptionalArg::Missing => vm.new_str("".to_string()),
};
if string.class().is(&cls) {
TryFromObject::try_from_object(vm, string)
} else {
let payload = string.payload::<PyString>().unwrap();
payload.clone().into_ref_with_type(vm, cls)
}
}
impl PySliceableSequence for String {
fn do_slice(&self, range: Range<usize>) -> Self {
to_graphemes(self)
@@ -934,22 +1034,55 @@ fn adjust_indices(
}
}
// helper function to title strings
fn make_title(s: &str) -> String {
let mut titled_str = String::new();
let mut capitalize_char: bool = true;
for c in s.chars() {
if c.is_alphabetic() {
if !capitalize_char {
titled_str.push(c);
} else if capitalize_char {
titled_str.push(c.to_ascii_uppercase());
capitalize_char = false;
}
} else {
titled_str.push(c);
capitalize_char = true;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn str_title() {
let vm = VirtualMachine::new();
let tests = vec![
(" Hello ", " hello "),
("Hello ", "hello "),
("Hello ", "Hello "),
("Format This As Title String", "fOrMaT thIs aS titLe String"),
("Format,This-As*Title;String", "fOrMaT,thIs-aS*titLe;String"),
("Getint", "getInt"),
("Greek Ωppercases ...", "greek ωppercases ..."),
];
for (title, input) in tests {
assert_eq!(PyString::from(input).title(&vm).as_str(), title);
}
}
#[test]
fn str_istitle() {
let vm = VirtualMachine::new();
let pos = vec![
"A",
"A Titlecased Line",
"A\nTitlecased Line",
"A Titlecased, Line",
"Greek Ωppercases ...",
];
for s in pos {
assert!(PyString::from(s).istitle(&vm));
}
let neg = vec![
"",
"a",
"\n",
"Not a capitalized String",
"Not\ta Titlecase String",
"Not--a Titlecase String",
"NOT",
];
for s in neg {
assert!(!PyString::from(s).istitle(&vm));
}
}
titled_str
}
+6 -4
View File
@@ -12,7 +12,7 @@ use crate::obj::objfunction::PyMethod;
use crate::obj::objstr;
use crate::obj::objtype::{PyClass, PyClassRef};
use crate::pyobject::{
DictProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
ItemProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
};
use crate::vm::VirtualMachine;
@@ -124,11 +124,13 @@ fn super_new(
} else {
let frame = vm.current_frame().expect("no current frame for super()");
if let Some(first_arg) = frame.code.arg_names.get(0) {
match vm.get_locals().get_item(first_arg) {
match vm.get_locals().get_item_option(first_arg, vm)? {
Some(obj) => obj.clone(),
_ => {
return Err(vm
.new_type_error(format!("super arguement {} was not supplied", first_arg)));
return Err(vm.new_type_error(format!(
"super arguement {} was not supplied",
first_arg
)));
}
}
} else {
+41 -6
View File
@@ -3,13 +3,12 @@ use std::fmt;
use std::hash::{Hash, Hasher};
use crate::function::OptionalArg;
use crate::pyobject::{
IdProtocol, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue,
};
use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::vm::{ReprGuard, VirtualMachine};
use super::objbool;
use super::objint;
use super::objiter;
use super::objsequence::{
get_elements, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul,
};
@@ -140,10 +139,10 @@ impl PyTupleRef {
Ok(hasher.finish())
}
fn iter(self, _vm: &VirtualMachine) -> PyIteratorValue {
PyIteratorValue {
fn iter(self, _vm: &VirtualMachine) -> PyTupleIterator {
PyTupleIterator {
position: Cell::new(0),
iterated_obj: self.into_object(),
tuple: self,
}
}
@@ -225,6 +224,36 @@ fn tuple_new(
PyTuple::from(elements).into_ref_with_type(vm, cls)
}
#[derive(Debug)]
pub struct PyTupleIterator {
position: Cell<usize>,
tuple: PyTupleRef,
}
impl PyValue for PyTupleIterator {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.tupleiterator_type()
}
}
type PyTupleIteratorRef = PyRef<PyTupleIterator>;
impl PyTupleIteratorRef {
fn next(self, vm: &VirtualMachine) -> PyResult {
if self.position.get() < self.tuple.elements.borrow().len() {
let ret = self.tuple.elements.borrow()[self.position.get()].clone();
self.position.set(self.position.get() + 1);
Ok(ret)
} else {
Err(objiter::new_stop_iteration(vm))
}
}
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
#[rustfmt::skip] // to avoid line splitting
pub fn init(context: &PyContext) {
let tuple_type = &context.tuple_type;
@@ -252,4 +281,10 @@ If the argument is a tuple, the return value is the same object.";
"__doc__" => context.new_str(tuple_doc.to_string()),
"index" => context.new_rustfunc(PyTupleRef::index)
});
let tupleiterator_type = &context.tupleiterator_type;
extend_class!(context, tupleiterator_type, {
"__next__" => context.new_rustfunc(PyTupleIteratorRef::next),
"__iter__" => context.new_rustfunc(PyTupleIteratorRef::iter),
});
}
+9 -8
View File
@@ -9,7 +9,6 @@ use crate::pyobject::{
};
use crate::vm::VirtualMachine;
use super::objdict;
use super::objdict::PyDictRef;
use super::objlist::PyList;
use super::objproperty::PropertyBuilder;
@@ -163,6 +162,13 @@ impl PyClassRef {
Ok(())
}
// This is used for class initialisation where the vm is not yet available.
pub fn set_str_attr<V: Into<PyObjectRef>>(&self, attr_name: &str, value: V) {
self.attributes
.borrow_mut()
.insert(attr_name.to_string(), value.into());
}
fn subclasses(self, _vm: &VirtualMachine) -> PyList {
let mut subclasses = self.subclasses.borrow_mut();
subclasses.retain(|x| x.upgrade().is_some());
@@ -229,7 +235,7 @@ pub fn type_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
Ok(args.args[1].class().into_object())
} else if args.args.len() == 4 {
let (typ, name, bases, dict) = args.bind(vm)?;
type_new_class(vm, typ, name, bases, dict).map(|x| x.into_object())
type_new_class(vm, typ, name, bases, dict).map(PyRef::into_object)
} else {
Err(vm.new_type_error(format!(": type_new: {:?}", args)))
}
@@ -244,12 +250,7 @@ pub fn type_new_class(
) -> PyResult<PyClassRef> {
let mut bases: Vec<PyClassRef> = bases.iter(vm)?.collect::<Result<Vec<_>, _>>()?;
bases.push(vm.ctx.object());
new(
typ.clone(),
&name.value,
bases,
objdict::py_dict_to_attributes(dict.as_object()),
)
new(typ.clone(), &name.value, bases, dict.to_attributes())
}
pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult {
+50
View File
@@ -0,0 +1,50 @@
use super::objweakref::PyWeak;
use crate::function::OptionalArg;
use crate::obj::objtype::PyClassRef;
use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::vm::VirtualMachine;
#[derive(Debug)]
pub struct PyWeakProxy {
weak: PyWeak,
}
impl PyValue for PyWeakProxy {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.weakproxy_type()
}
}
pub type PyWeakProxyRef = PyRef<PyWeakProxy>;
impl PyWeakProxyRef {
// TODO callbacks
fn create(
cls: PyClassRef,
referent: PyObjectRef,
_callback: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<Self> {
PyWeakProxy {
weak: PyWeak::downgrade(&referent),
}
.into_ref_with_type(vm, cls)
}
fn getattr(self, attr_name: PyObjectRef, vm: &VirtualMachine) -> PyResult {
match self.weak.upgrade() {
Some(obj) => vm.get_attribute(obj, attr_name),
None => Err(vm.new_exception(
vm.ctx.exceptions.reference_error.clone(),
"weakly-referenced object no longer exists".to_string(),
)),
}
}
}
pub fn init(context: &PyContext) {
extend_class!(context, &context.weakproxy_type, {
"__new__" => context.new_rustfunc(PyWeakProxyRef::create),
"__getattr__" => context.new_rustfunc(PyWeakProxyRef::getattr),
});
}
+7 -1
View File
@@ -1,3 +1,4 @@
use crate::function::OptionalArg;
use crate::obj::objtype::PyClassRef;
use crate::pyobject::PyValue;
use crate::pyobject::{PyContext, PyObject, PyObjectPayload, PyObjectRef, PyRef, PyResult};
@@ -32,7 +33,12 @@ pub type PyWeakRef = PyRef<PyWeak>;
impl PyWeakRef {
// TODO callbacks
fn create(cls: PyClassRef, referent: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> {
fn create(
cls: PyClassRef,
referent: PyObjectRef,
_callback: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<Self> {
PyWeak::downgrade(&referent).into_ref_with_type(vm, cls)
}
+6 -2
View File
@@ -40,13 +40,17 @@ impl PyZipRef {
Ok(vm.ctx.new_tuple(next_objs))
}
}
fn iter(self, _vm: &VirtualMachine) -> Self {
self
}
}
pub fn init(context: &PyContext) {
let zip_type = &context.zip_type;
objiter::iter_type_init(context, zip_type);
extend_class!(context, zip_type, {
"__new__" => context.new_rustfunc(zip_new),
"__next__" => context.new_rustfunc(PyZipRef::next)
"__next__" => context.new_rustfunc(PyZipRef::next),
"__iter__" => context.new_rustfunc(PyZipRef::iter),
});
}
Loaded 100 of 130 files, more files were not shown because too many files have changed in this diff. Show more