forked from Rust-related/RustPython
Compare commits
37 Commits
impl_del_o
...
2025-02-24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7a72b5755 | ||
|
|
1f3a9672c3 | ||
|
|
31c5c3eb9d | ||
|
|
7fada8b97e | ||
|
|
429754fd33 | ||
|
|
b4f0a589ed | ||
|
|
331a3c108f | ||
|
|
d698b28ce5 | ||
|
|
23236aa8c7 | ||
|
|
a9331bb34d | ||
|
|
65dcf1ce1c | ||
|
|
e2b0fe4266 | ||
|
|
fa2acd7cde | ||
|
|
a71c16f8cb | ||
|
|
f466971312 | ||
|
|
69b1a9910f | ||
|
|
4ed735b424 | ||
|
|
175afd97d8 | ||
|
|
72338d578b | ||
|
|
9856d94f2d | ||
|
|
517ffed401 | ||
|
|
38a6a8d984 | ||
|
|
630c1ff8d1 | ||
|
|
7e1568a1ff | ||
|
|
6788010f7d | ||
|
|
9e310934d3 | ||
|
|
e8a3406624 | ||
|
|
fde87a340c | ||
|
|
19050afc3f | ||
|
|
e96557b3e1 | ||
|
|
a5364973d9 | ||
|
|
a46ce8ec3a | ||
|
|
6e35e20e49 | ||
|
|
2d5e4d89b0 | ||
|
|
c9e62002ec | ||
|
|
465627f104 | ||
|
|
3de1c2ab56 |
18
.github/workflows/ci.yaml
vendored
18
.github/workflows/ci.yaml
vendored
@@ -17,12 +17,17 @@ concurrency:
|
||||
env:
|
||||
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,sqlite,ssl
|
||||
# Skip additional tests on Windows. They are checked on Linux and MacOS.
|
||||
# test_glob: many failing tests
|
||||
# test_io: many failing tests
|
||||
# test_os: many failing tests
|
||||
# test_pathlib: support.rmtree() failing
|
||||
# test_posixpath: OSError: (22, 'The filename, directory name, or volume label syntax is incorrect. (os error 123)')
|
||||
# test_venv: couple of failing tests
|
||||
WINDOWS_SKIPS: >-
|
||||
test_datetime
|
||||
test_glob
|
||||
test_importlib
|
||||
test_io
|
||||
test_os
|
||||
test_rlcompleter
|
||||
test_pathlib
|
||||
test_posixpath
|
||||
test_venv
|
||||
@@ -34,7 +39,7 @@ env:
|
||||
# PLATFORM_INDEPENDENT_TESTS are tests that do not depend on the underlying OS. They are currently
|
||||
# only run on Linux to speed up the CI.
|
||||
PLATFORM_INDEPENDENT_TESTS: >-
|
||||
test_argparse
|
||||
test__colorize
|
||||
test_array
|
||||
test_asyncgen
|
||||
test_binop
|
||||
@@ -59,7 +64,6 @@ env:
|
||||
test_dis
|
||||
test_enumerate
|
||||
test_exception_variations
|
||||
test_exceptions
|
||||
test_float
|
||||
test_format
|
||||
test_fractions
|
||||
@@ -100,12 +104,11 @@ env:
|
||||
test_tuple
|
||||
test_types
|
||||
test_unary
|
||||
test_unicode
|
||||
test_unpack
|
||||
test_weakref
|
||||
test_yield_from
|
||||
# Python version targeted by the CI.
|
||||
PYTHON_VERSION: "3.12.3"
|
||||
PYTHON_VERSION: "3.13.1"
|
||||
|
||||
jobs:
|
||||
rust_tests:
|
||||
@@ -378,7 +381,8 @@ jobs:
|
||||
with: { wabt-version: "1.0.30" }
|
||||
- name: check wasm32-unknown without js
|
||||
run: |
|
||||
cargo build --release --manifest-path wasm/wasm-unknown-test/Cargo.toml --target wasm32-unknown-unknown --verbose
|
||||
cd wasm/wasm-unknown-test
|
||||
cargo build --release --verbose
|
||||
if wasm-objdump -xj Import target/wasm32-unknown-unknown/release/wasm_unknown_test.wasm; then
|
||||
echo "ERROR: wasm32-unknown module expects imports from the host environment" >2
|
||||
fi
|
||||
|
||||
2
.github/workflows/cron-ci.yaml
vendored
2
.github/workflows/cron-ci.yaml
vendored
@@ -7,7 +7,7 @@ name: Periodic checks/tasks
|
||||
|
||||
env:
|
||||
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit
|
||||
PYTHON_VERSION: "3.12.0"
|
||||
PYTHON_VERSION: "3.13.1"
|
||||
|
||||
jobs:
|
||||
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
|
||||
|
||||
766
Cargo.lock
generated
766
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
71
Cargo.toml
71
Cargo.toml
@@ -47,7 +47,7 @@ libc = { workspace = true }
|
||||
rustyline = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3.5", features = ["html_reports"] }
|
||||
criterion = { workspace = true }
|
||||
pyo3 = { version = "0.22", features = ["auto-initialize"] }
|
||||
|
||||
[[bench]]
|
||||
@@ -104,7 +104,7 @@ members = [
|
||||
version = "0.4.0"
|
||||
authors = ["RustPython Team"]
|
||||
edition = "2021"
|
||||
rust-version = "1.83.0"
|
||||
rust-version = "1.85.0"
|
||||
repository = "https://github.com/RustPython/RustPython"
|
||||
license = "MIT"
|
||||
|
||||
@@ -127,11 +127,11 @@ rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0",
|
||||
# rustpython-parser = { version = "0.4.0" }
|
||||
# rustpython-ast = { version = "0.4.0" }
|
||||
# rustpython-format= { version = "0.4.0" }
|
||||
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||
rustpython-parser-core = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||
rustpython-ast = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||
rustpython-format = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
|
||||
rustpython-parser-core = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
|
||||
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
|
||||
rustpython-ast = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
|
||||
rustpython-format = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "d2f137b372ec08ce4a243564a80f8f9153c45a23" }
|
||||
# rustpython-literal = { path = "../RustPython-parser/literal" }
|
||||
# rustpython-parser-core = { path = "../RustPython-parser/core" }
|
||||
# rustpython-parser = { path = "../RustPython-parser/parser" }
|
||||
@@ -139,50 +139,51 @@ rustpython-format = { git = "https://github.com/RustPython/Parser.git", version
|
||||
# rustpython-format = { path = "../RustPython-parser/format" }
|
||||
|
||||
ahash = "0.8.11"
|
||||
ascii = "1.0"
|
||||
bitflags = "2.4.1"
|
||||
ascii = "1.1"
|
||||
bitflags = "2.4.2"
|
||||
bstr = "1"
|
||||
cfg-if = "1.0"
|
||||
chrono = "0.4.37"
|
||||
crossbeam-utils = "0.8.19"
|
||||
chrono = "0.4.39"
|
||||
criterion = { version = "0.3.5", features = ["html_reports"] }
|
||||
crossbeam-utils = "0.8.21"
|
||||
flame = "0.2.2"
|
||||
getrandom = "0.2.12"
|
||||
getrandom = "0.3"
|
||||
glob = "0.3"
|
||||
hex = "0.4.3"
|
||||
indexmap = { version = "2.2.6", features = ["std"] }
|
||||
insta = "1.38.0"
|
||||
itertools = "0.11.0"
|
||||
is-macro = "0.3.0"
|
||||
junction = "1.0.0"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.16"
|
||||
itertools = "0.14.0"
|
||||
is-macro = "0.3.7"
|
||||
junction = "1.2.0"
|
||||
libc = "0.2.169"
|
||||
log = "0.4.25"
|
||||
nix = { version = "0.29", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
|
||||
malachite-bigint = "0.2.2"
|
||||
malachite-q = "<=0.4.18"
|
||||
malachite-base = "<=0.4.18"
|
||||
memchr = "2.7.2"
|
||||
num-complex = "0.4.0"
|
||||
num-integer = "0.1.44"
|
||||
malachite-bigint = "0.2.3"
|
||||
malachite-q = "0.4.22"
|
||||
malachite-base = "0.4.22"
|
||||
memchr = "2.7.4"
|
||||
num-complex = "0.4.6"
|
||||
num-integer = "0.1.46"
|
||||
num-traits = "0.2"
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
once_cell = "1.19.0"
|
||||
parking_lot = "0.12.1"
|
||||
paste = "1.0.7"
|
||||
rand = "0.8.5"
|
||||
once_cell = "1.20.3"
|
||||
parking_lot = "0.12.3"
|
||||
paste = "1.0.15"
|
||||
rand = "0.9"
|
||||
rustix = { version = "0.38", features = ["event"] }
|
||||
rustyline = "14.0.0"
|
||||
rustyline = "15.0.0"
|
||||
serde = { version = "1.0.133", default-features = false }
|
||||
schannel = "0.1.22"
|
||||
schannel = "0.1.27"
|
||||
static_assertions = "1.1"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
strum = "0.27"
|
||||
strum_macros = "0.27"
|
||||
syn = "1.0.109"
|
||||
thiserror = "1.0"
|
||||
thread_local = "1.1.4"
|
||||
unicode_names2 = "1.2.0"
|
||||
thiserror = "2.0"
|
||||
thread_local = "1.1.8"
|
||||
unicode_names2 = "1.3.0"
|
||||
widestring = "1.1.0"
|
||||
windows-sys = "0.52.0"
|
||||
wasm-bindgen = "0.2.92"
|
||||
wasm-bindgen = "0.2.100"
|
||||
|
||||
# Lints
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ RustPython requires the following:
|
||||
stable version: `rustup update stable`
|
||||
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to
|
||||
do so.
|
||||
- CPython version 3.12 or higher
|
||||
- CPython version 3.13 or higher
|
||||
- CPython can be installed by your operating system's package manager,
|
||||
from the [Python website](https://www.python.org/downloads/), or
|
||||
using a third-party distribution, such as
|
||||
|
||||
67
Lib/_colorize.py
vendored
Normal file
67
Lib/_colorize.py
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
COLORIZE = True
|
||||
|
||||
|
||||
class ANSIColors:
|
||||
BOLD_GREEN = "\x1b[1;32m"
|
||||
BOLD_MAGENTA = "\x1b[1;35m"
|
||||
BOLD_RED = "\x1b[1;31m"
|
||||
GREEN = "\x1b[32m"
|
||||
GREY = "\x1b[90m"
|
||||
MAGENTA = "\x1b[35m"
|
||||
RED = "\x1b[31m"
|
||||
RESET = "\x1b[0m"
|
||||
YELLOW = "\x1b[33m"
|
||||
|
||||
|
||||
NoColors = ANSIColors()
|
||||
|
||||
for attr in dir(NoColors):
|
||||
if not attr.startswith("__"):
|
||||
setattr(NoColors, attr, "")
|
||||
|
||||
|
||||
def get_colors(colorize: bool = False, *, file=None) -> ANSIColors:
|
||||
if colorize or can_colorize(file=file):
|
||||
return ANSIColors()
|
||||
else:
|
||||
return NoColors
|
||||
|
||||
|
||||
def can_colorize(*, file=None) -> bool:
|
||||
if file is None:
|
||||
file = sys.stdout
|
||||
|
||||
if not sys.flags.ignore_environment:
|
||||
if os.environ.get("PYTHON_COLORS") == "0":
|
||||
return False
|
||||
if os.environ.get("PYTHON_COLORS") == "1":
|
||||
return True
|
||||
if os.environ.get("NO_COLOR"):
|
||||
return False
|
||||
if not COLORIZE:
|
||||
return False
|
||||
if os.environ.get("FORCE_COLOR"):
|
||||
return True
|
||||
if os.environ.get("TERM") == "dumb":
|
||||
return False
|
||||
|
||||
if not hasattr(file, "fileno"):
|
||||
return False
|
||||
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import nt
|
||||
|
||||
if not nt._supports_virtual_terminal():
|
||||
return False
|
||||
except (ImportError, AttributeError):
|
||||
return False
|
||||
|
||||
try:
|
||||
return os.isatty(file.fileno())
|
||||
except io.UnsupportedOperation:
|
||||
return file.isatty()
|
||||
19
Lib/difflib.py
vendored
19
Lib/difflib.py
vendored
@@ -1200,25 +1200,6 @@ def context_diff(a, b, fromfile='', tofile='',
|
||||
strings for 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
|
||||
The modification times are normally expressed in the ISO 8601 format.
|
||||
If not specified, the strings default to blanks.
|
||||
|
||||
Example:
|
||||
|
||||
>>> print(''.join(context_diff('one\ntwo\nthree\nfour\n'.splitlines(True),
|
||||
... 'zero\none\ntree\nfour\n'.splitlines(True), 'Original', 'Current')),
|
||||
... end="")
|
||||
*** Original
|
||||
--- Current
|
||||
***************
|
||||
*** 1,4 ****
|
||||
one
|
||||
! two
|
||||
! three
|
||||
four
|
||||
--- 1,4 ----
|
||||
+ zero
|
||||
one
|
||||
! tree
|
||||
four
|
||||
"""
|
||||
|
||||
_check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm)
|
||||
|
||||
183
Lib/test/ieee754.txt
vendored
Normal file
183
Lib/test/ieee754.txt
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
======================================
|
||||
Python IEEE 754 floating point support
|
||||
======================================
|
||||
|
||||
>>> from sys import float_info as FI
|
||||
>>> from math import *
|
||||
>>> PI = pi
|
||||
>>> E = e
|
||||
|
||||
You must never compare two floats with == because you are not going to get
|
||||
what you expect. We treat two floats as equal if the difference between them
|
||||
is small than epsilon.
|
||||
>>> EPS = 1E-15
|
||||
>>> def equal(x, y):
|
||||
... """Almost equal helper for floats"""
|
||||
... return abs(x - y) < EPS
|
||||
|
||||
|
||||
NaNs and INFs
|
||||
=============
|
||||
|
||||
In Python 2.6 and newer NaNs (not a number) and infinity can be constructed
|
||||
from the strings 'inf' and 'nan'.
|
||||
|
||||
>>> INF = float('inf')
|
||||
>>> NINF = float('-inf')
|
||||
>>> NAN = float('nan')
|
||||
|
||||
>>> INF
|
||||
inf
|
||||
>>> NINF
|
||||
-inf
|
||||
>>> NAN
|
||||
nan
|
||||
|
||||
The math module's ``isnan`` and ``isinf`` functions can be used to detect INF
|
||||
and NAN:
|
||||
>>> isinf(INF), isinf(NINF), isnan(NAN)
|
||||
(True, True, True)
|
||||
>>> INF == -NINF
|
||||
True
|
||||
|
||||
Infinity
|
||||
--------
|
||||
|
||||
Ambiguous operations like ``0 * inf`` or ``inf - inf`` result in NaN.
|
||||
>>> INF * 0
|
||||
nan
|
||||
>>> INF - INF
|
||||
nan
|
||||
>>> INF / INF
|
||||
nan
|
||||
|
||||
However unambiguous operations with inf return inf:
|
||||
>>> INF * INF
|
||||
inf
|
||||
>>> 1.5 * INF
|
||||
inf
|
||||
>>> 0.5 * INF
|
||||
inf
|
||||
>>> INF / 1000
|
||||
inf
|
||||
|
||||
Not a Number
|
||||
------------
|
||||
|
||||
NaNs are never equal to another number, even itself
|
||||
>>> NAN == NAN
|
||||
False
|
||||
>>> NAN < 0
|
||||
False
|
||||
>>> NAN >= 0
|
||||
False
|
||||
|
||||
All operations involving a NaN return a NaN except for nan**0 and 1**nan.
|
||||
>>> 1 + NAN
|
||||
nan
|
||||
>>> 1 * NAN
|
||||
nan
|
||||
>>> 0 * NAN
|
||||
nan
|
||||
>>> 1 ** NAN
|
||||
1.0
|
||||
>>> NAN ** 0
|
||||
1.0
|
||||
>>> 0 ** NAN
|
||||
nan
|
||||
>>> (1.0 + FI.epsilon) * NAN
|
||||
nan
|
||||
|
||||
Misc Functions
|
||||
==============
|
||||
|
||||
The power of 1 raised to x is always 1.0, even for special values like 0,
|
||||
infinity and NaN.
|
||||
|
||||
>>> pow(1, 0)
|
||||
1.0
|
||||
>>> pow(1, INF)
|
||||
1.0
|
||||
>>> pow(1, -INF)
|
||||
1.0
|
||||
>>> pow(1, NAN)
|
||||
1.0
|
||||
|
||||
The power of 0 raised to x is defined as 0, if x is positive. Negative
|
||||
finite values are a domain error or zero division error and NaN result in a
|
||||
silent NaN.
|
||||
|
||||
>>> pow(0, 0)
|
||||
1.0
|
||||
>>> pow(0, INF)
|
||||
0.0
|
||||
>>> pow(0, -INF)
|
||||
inf
|
||||
>>> 0 ** -1
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ZeroDivisionError: 0.0 cannot be raised to a negative power
|
||||
>>> pow(0, NAN)
|
||||
nan
|
||||
|
||||
|
||||
Trigonometric Functions
|
||||
=======================
|
||||
|
||||
>>> sin(INF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> sin(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> sin(NAN)
|
||||
nan
|
||||
>>> cos(INF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> cos(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> cos(NAN)
|
||||
nan
|
||||
>>> tan(INF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> tan(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> tan(NAN)
|
||||
nan
|
||||
|
||||
Neither pi nor tan are exact, but you can assume that tan(pi/2) is a large value
|
||||
and tan(pi) is a very small value:
|
||||
>>> tan(PI/2) > 1E10
|
||||
True
|
||||
>>> -tan(-PI/2) > 1E10
|
||||
True
|
||||
>>> tan(PI) < 1E-15
|
||||
True
|
||||
|
||||
>>> asin(NAN), acos(NAN), atan(NAN)
|
||||
(nan, nan, nan)
|
||||
>>> asin(INF), asin(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> acos(INF), acos(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> equal(atan(INF), PI/2), equal(atan(NINF), -PI/2)
|
||||
(True, True)
|
||||
|
||||
|
||||
Hyberbolic Functions
|
||||
====================
|
||||
|
||||
135
Lib/test/test__colorize.py
vendored
Normal file
135
Lib/test/test__colorize.py
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
import contextlib
|
||||
import io
|
||||
import sys
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import _colorize
|
||||
from test.support.os_helper import EnvironmentVarGuard
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def clear_env():
|
||||
with EnvironmentVarGuard() as mock_env:
|
||||
for var in "FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS":
|
||||
mock_env.unset(var)
|
||||
yield mock_env
|
||||
|
||||
|
||||
def supports_virtual_terminal():
|
||||
if sys.platform == "win32":
|
||||
return unittest.mock.patch("nt._supports_virtual_terminal", return_value=True)
|
||||
else:
|
||||
return contextlib.nullcontext()
|
||||
|
||||
|
||||
class TestColorizeFunction(unittest.TestCase):
|
||||
def test_colorized_detection_checks_for_environment_variables(self):
|
||||
def check(env, fallback, expected):
|
||||
with (self.subTest(env=env, fallback=fallback),
|
||||
clear_env() as mock_env):
|
||||
mock_env.update(env)
|
||||
isatty_mock.return_value = fallback
|
||||
stdout_mock.isatty.return_value = fallback
|
||||
self.assertEqual(_colorize.can_colorize(), expected)
|
||||
|
||||
with (unittest.mock.patch("os.isatty") as isatty_mock,
|
||||
unittest.mock.patch("sys.stdout") as stdout_mock,
|
||||
supports_virtual_terminal()):
|
||||
stdout_mock.fileno.return_value = 1
|
||||
|
||||
for fallback in False, True:
|
||||
check({}, fallback, fallback)
|
||||
check({'TERM': 'dumb'}, fallback, False)
|
||||
check({'TERM': 'xterm'}, fallback, fallback)
|
||||
check({'TERM': ''}, fallback, fallback)
|
||||
check({'FORCE_COLOR': '1'}, fallback, True)
|
||||
check({'FORCE_COLOR': '0'}, fallback, True)
|
||||
check({'FORCE_COLOR': ''}, fallback, fallback)
|
||||
check({'NO_COLOR': '1'}, fallback, False)
|
||||
check({'NO_COLOR': '0'}, fallback, False)
|
||||
check({'NO_COLOR': ''}, fallback, fallback)
|
||||
|
||||
check({'TERM': 'dumb', 'FORCE_COLOR': '1'}, False, True)
|
||||
check({'FORCE_COLOR': '1', 'NO_COLOR': '1'}, True, False)
|
||||
|
||||
for ignore_environment in False, True:
|
||||
# Simulate running with or without `-E`.
|
||||
flags = unittest.mock.MagicMock(ignore_environment=ignore_environment)
|
||||
with unittest.mock.patch("sys.flags", flags):
|
||||
check({'PYTHON_COLORS': '1'}, True, True)
|
||||
check({'PYTHON_COLORS': '1'}, False, not ignore_environment)
|
||||
check({'PYTHON_COLORS': '0'}, True, ignore_environment)
|
||||
check({'PYTHON_COLORS': '0'}, False, False)
|
||||
for fallback in False, True:
|
||||
check({'PYTHON_COLORS': 'x'}, fallback, fallback)
|
||||
check({'PYTHON_COLORS': ''}, fallback, fallback)
|
||||
|
||||
check({'TERM': 'dumb', 'PYTHON_COLORS': '1'}, False, not ignore_environment)
|
||||
check({'NO_COLOR': '1', 'PYTHON_COLORS': '1'}, False, not ignore_environment)
|
||||
check({'FORCE_COLOR': '1', 'PYTHON_COLORS': '0'}, True, ignore_environment)
|
||||
|
||||
@unittest.skipUnless(sys.platform == "win32", "requires Windows")
|
||||
def test_colorized_detection_checks_on_windows(self):
|
||||
with (clear_env(),
|
||||
unittest.mock.patch("os.isatty") as isatty_mock,
|
||||
unittest.mock.patch("sys.stdout") as stdout_mock,
|
||||
supports_virtual_terminal() as vt_mock):
|
||||
stdout_mock.fileno.return_value = 1
|
||||
isatty_mock.return_value = True
|
||||
stdout_mock.isatty.return_value = True
|
||||
|
||||
vt_mock.return_value = True
|
||||
self.assertEqual(_colorize.can_colorize(), True)
|
||||
vt_mock.return_value = False
|
||||
self.assertEqual(_colorize.can_colorize(), False)
|
||||
import nt
|
||||
del nt._supports_virtual_terminal
|
||||
self.assertEqual(_colorize.can_colorize(), False)
|
||||
|
||||
def test_colorized_detection_checks_for_std_streams(self):
|
||||
with (clear_env(),
|
||||
unittest.mock.patch("os.isatty") as isatty_mock,
|
||||
unittest.mock.patch("sys.stdout") as stdout_mock,
|
||||
unittest.mock.patch("sys.stderr") as stderr_mock,
|
||||
supports_virtual_terminal()):
|
||||
stdout_mock.fileno.return_value = 1
|
||||
stderr_mock.fileno.side_effect = ZeroDivisionError
|
||||
stderr_mock.isatty.side_effect = ZeroDivisionError
|
||||
|
||||
isatty_mock.return_value = True
|
||||
stdout_mock.isatty.return_value = True
|
||||
self.assertEqual(_colorize.can_colorize(), True)
|
||||
|
||||
isatty_mock.return_value = False
|
||||
stdout_mock.isatty.return_value = False
|
||||
self.assertEqual(_colorize.can_colorize(), False)
|
||||
|
||||
def test_colorized_detection_checks_for_file(self):
|
||||
with clear_env(), supports_virtual_terminal():
|
||||
|
||||
with unittest.mock.patch("os.isatty") as isatty_mock:
|
||||
file = unittest.mock.MagicMock()
|
||||
file.fileno.return_value = 1
|
||||
isatty_mock.return_value = True
|
||||
self.assertEqual(_colorize.can_colorize(file=file), True)
|
||||
isatty_mock.return_value = False
|
||||
self.assertEqual(_colorize.can_colorize(file=file), False)
|
||||
|
||||
# No file.fileno.
|
||||
with unittest.mock.patch("os.isatty", side_effect=ZeroDivisionError):
|
||||
file = unittest.mock.MagicMock(spec=['isatty'])
|
||||
file.isatty.return_value = True
|
||||
self.assertEqual(_colorize.can_colorize(file=file), False)
|
||||
|
||||
# file.fileno() raises io.UnsupportedOperation.
|
||||
with unittest.mock.patch("os.isatty", side_effect=ZeroDivisionError):
|
||||
file = unittest.mock.MagicMock()
|
||||
file.fileno.side_effect = io.UnsupportedOperation
|
||||
file.isatty.return_value = True
|
||||
self.assertEqual(_colorize.can_colorize(file=file), True)
|
||||
file.isatty.return_value = False
|
||||
self.assertEqual(_colorize.can_colorize(file=file), False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
3
Lib/test/test_charmapcodec.py
vendored
3
Lib/test/test_charmapcodec.py
vendored
@@ -26,7 +26,6 @@ codecs.register(codec_search_function)
|
||||
codecname = 'testcodec'
|
||||
|
||||
class CharmapCodecTest(unittest.TestCase):
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_constructorx(self):
|
||||
self.assertEqual(str(b'abc', codecname), 'abc')
|
||||
self.assertEqual(str(b'xdef', codecname), 'abcdef')
|
||||
@@ -43,14 +42,12 @@ class CharmapCodecTest(unittest.TestCase):
|
||||
self.assertEqual('dxf'.encode(codecname), b'dabcf')
|
||||
self.assertEqual('dxfx'.encode(codecname), b'dabcfabc')
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_constructory(self):
|
||||
self.assertEqual(str(b'ydef', codecname), 'def')
|
||||
self.assertEqual(str(b'defy', codecname), 'def')
|
||||
self.assertEqual(str(b'dyf', codecname), 'df')
|
||||
self.assertEqual(str(b'dyfy', codecname), 'df')
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_maptoundefined(self):
|
||||
self.assertRaises(UnicodeError, str, b'abc\001', codecname)
|
||||
|
||||
|
||||
11
Lib/test/test_codecs.py
vendored
11
Lib/test/test_codecs.py
vendored
@@ -1827,7 +1827,6 @@ class CodecsModuleTest(unittest.TestCase):
|
||||
self.assertEqual(codecs.decode(b'[\xff]', 'ascii', errors='ignore'),
|
||||
'[]')
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_encode(self):
|
||||
self.assertEqual(codecs.encode('\xe4\xf6\xfc', 'latin-1'),
|
||||
b'\xe4\xf6\xfc')
|
||||
@@ -1846,7 +1845,6 @@ class CodecsModuleTest(unittest.TestCase):
|
||||
self.assertRaises(TypeError, codecs.register)
|
||||
self.assertRaises(TypeError, codecs.register, 42)
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AttributeError: module '_winapi' has no attribute 'GetACP'")
|
||||
def test_unregister(self):
|
||||
name = "nonexistent_codec_name"
|
||||
search_function = mock.Mock()
|
||||
@@ -1859,28 +1857,23 @@ class CodecsModuleTest(unittest.TestCase):
|
||||
self.assertRaises(LookupError, codecs.lookup, name)
|
||||
search_function.assert_not_called()
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_lookup(self):
|
||||
self.assertRaises(TypeError, codecs.lookup)
|
||||
self.assertRaises(LookupError, codecs.lookup, "__spam__")
|
||||
self.assertRaises(LookupError, codecs.lookup, " ")
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_getencoder(self):
|
||||
self.assertRaises(TypeError, codecs.getencoder)
|
||||
self.assertRaises(LookupError, codecs.getencoder, "__spam__")
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_getdecoder(self):
|
||||
self.assertRaises(TypeError, codecs.getdecoder)
|
||||
self.assertRaises(LookupError, codecs.getdecoder, "__spam__")
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_getreader(self):
|
||||
self.assertRaises(TypeError, codecs.getreader)
|
||||
self.assertRaises(LookupError, codecs.getreader, "__spam__")
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_getwriter(self):
|
||||
self.assertRaises(TypeError, codecs.getwriter)
|
||||
self.assertRaises(LookupError, codecs.getwriter, "__spam__")
|
||||
@@ -1939,7 +1932,6 @@ class CodecsModuleTest(unittest.TestCase):
|
||||
self.assertRaises(UnicodeError,
|
||||
codecs.decode, b'abc', 'undefined', errors)
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_file_closes_if_lookup_error_raised(self):
|
||||
mock_open = mock.mock_open()
|
||||
with mock.patch('builtins.open', mock_open) as file:
|
||||
@@ -3287,7 +3279,6 @@ class ExceptionNotesTest(unittest.TestCase):
|
||||
self.check_note(RuntimeError('a', 'b', 'c'), msg_re)
|
||||
|
||||
# http://bugs.python.org/issue19609
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_codec_lookup_failure(self):
|
||||
msg = "^unknown encoding: {}$".format(self.codec_name)
|
||||
with self.assertRaisesRegex(LookupError, msg):
|
||||
@@ -3523,8 +3514,6 @@ class CodePageTest(unittest.TestCase):
|
||||
False)
|
||||
self.assertEqual(decoded, ('abc', 3))
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_mbcs_alias(self):
|
||||
# Check that looking up our 'default' codepage will return
|
||||
# mbcs when we don't have a more specific one available
|
||||
|
||||
30
Lib/test/test_enum.py
vendored
30
Lib/test/test_enum.py
vendored
@@ -1369,10 +1369,12 @@ class TestSpecial(unittest.TestCase):
|
||||
[Outer.a, Outer.b, Outer.Inner],
|
||||
)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
@unittest.skipIf(
|
||||
python_version < (3, 13),
|
||||
'inner classes are still members',
|
||||
)
|
||||
python_version < (3, 13),
|
||||
'inner classes are still members',
|
||||
)
|
||||
def test_nested_classes_in_enum_are_not_members(self):
|
||||
"""Support locally-defined nested classes."""
|
||||
class Outer(Enum):
|
||||
@@ -4555,20 +4557,24 @@ class TestInternals(unittest.TestCase):
|
||||
self.assertEqual(Color.green.value, 3)
|
||||
self.assertEqual(Color.yellow.value, 4)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
@unittest.skipIf(
|
||||
python_version < (3, 13),
|
||||
'mixed types with auto() will raise in 3.13',
|
||||
)
|
||||
python_version < (3, 13),
|
||||
'inner classes are still members',
|
||||
)
|
||||
def test_auto_garbage_fail(self):
|
||||
with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'):
|
||||
class Color(Enum):
|
||||
red = 'red'
|
||||
blue = auto()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
@unittest.skipIf(
|
||||
python_version < (3, 13),
|
||||
'mixed types with auto() will raise in 3.13',
|
||||
)
|
||||
python_version < (3, 13),
|
||||
'inner classes are still members',
|
||||
)
|
||||
def test_auto_garbage_corrected_fail(self):
|
||||
with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'):
|
||||
class Color(Enum):
|
||||
@@ -4598,9 +4604,9 @@ class TestInternals(unittest.TestCase):
|
||||
self.assertEqual(Color.blue.value, 'blue')
|
||||
|
||||
@unittest.skipIf(
|
||||
python_version < (3, 13),
|
||||
'auto() will return highest value + 1 in 3.13',
|
||||
)
|
||||
python_version < (3, 13),
|
||||
'inner classes are still members',
|
||||
)
|
||||
def test_auto_with_aliases(self):
|
||||
class Color(Enum):
|
||||
red = auto()
|
||||
|
||||
@@ -7,6 +7,7 @@ import _imp
|
||||
import contextlib
|
||||
import marshal
|
||||
import os.path
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
import warnings
|
||||
@@ -77,6 +78,7 @@ class ExecModuleTests(abc.LoaderTests):
|
||||
self.assertTrue(hasattr(module, '__spec__'))
|
||||
self.assertEqual(module.__spec__.loader_state.origname, name)
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON")
|
||||
def test_package(self):
|
||||
name = '__phello__'
|
||||
module, output = self.exec_module(name)
|
||||
@@ -90,6 +92,7 @@ class ExecModuleTests(abc.LoaderTests):
|
||||
self.assertEqual(output, 'Hello world!\n')
|
||||
self.assertEqual(module.__spec__.loader_state.origname, name)
|
||||
|
||||
@unittest.skipIf(sys.platform == 'win32', "TODO:RUSTPYTHON Flaky on Windows")
|
||||
def test_lacking_parent(self):
|
||||
name = '__phello__.spam'
|
||||
with util.uncache('__phello__'):
|
||||
@@ -147,6 +150,7 @@ class InspectLoaderTests:
|
||||
result = self.machinery.FrozenImporter.get_source('__hello__')
|
||||
self.assertIsNone(result)
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON")
|
||||
def test_is_package(self):
|
||||
# Should be able to tell what is a package.
|
||||
test_for = (('__hello__', False), ('__phello__', True),
|
||||
|
||||
@@ -52,6 +52,10 @@ class OpenDiskTests(FilesTests, unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.data = data01
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_read_bytes(self):
|
||||
super().test_read_bytes()
|
||||
|
||||
|
||||
class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
||||
pass
|
||||
@@ -63,6 +67,9 @@ class OpenNamespaceTests(FilesTests, unittest.TestCase):
|
||||
|
||||
self.data = namespacedata01
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_read_bytes(self):
|
||||
super().test_read_bytes()
|
||||
|
||||
class SiteDir:
|
||||
def setUp(self):
|
||||
|
||||
@@ -168,7 +168,6 @@ class FinderTests(abc.FinderTests):
|
||||
found = self._find(finder, 'doesnotexist')
|
||||
self.assertEqual(found, self.NOT_FOUND)
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_ignore_file(self):
|
||||
# If a directory got changed to a file from underneath us, then don't
|
||||
# worry about looking for submodules.
|
||||
|
||||
402
Lib/test/test_math.py
vendored
402
Lib/test/test_math.py
vendored
@@ -4,6 +4,7 @@
|
||||
from test.support import verbose, requires_IEEE_754
|
||||
from test import support
|
||||
import unittest
|
||||
import fractions
|
||||
import itertools
|
||||
import decimal
|
||||
import math
|
||||
@@ -186,6 +187,9 @@ def result_check(expected, got, ulp_tol=5, abs_tol=0.0):
|
||||
|
||||
# Check exactly equal (applies also to strings representing exceptions)
|
||||
if got == expected:
|
||||
if not got and not expected:
|
||||
if math.copysign(1, got) != math.copysign(1, expected):
|
||||
return f"expected {expected}, got {got} (zero has wrong sign)"
|
||||
return None
|
||||
|
||||
failure = "not equal"
|
||||
@@ -234,6 +238,10 @@ class MyIndexable(object):
|
||||
def __index__(self):
|
||||
return self.value
|
||||
|
||||
class BadDescr:
|
||||
def __get__(self, obj, objtype=None):
|
||||
raise ValueError
|
||||
|
||||
class MathTests(unittest.TestCase):
|
||||
|
||||
def ftest(self, name, got, expected, ulp_tol=5, abs_tol=0.0):
|
||||
@@ -323,6 +331,7 @@ class MathTests(unittest.TestCase):
|
||||
self.ftest('atan2(0, 1)', math.atan2(0, 1), 0)
|
||||
self.ftest('atan2(1, 1)', math.atan2(1, 1), math.pi/4)
|
||||
self.ftest('atan2(1, 0)', math.atan2(1, 0), math.pi/2)
|
||||
self.ftest('atan2(1, -1)', math.atan2(1, -1), 3*math.pi/4)
|
||||
|
||||
# math.atan2(0, x)
|
||||
self.ftest('atan2(0., -inf)', math.atan2(0., NINF), math.pi)
|
||||
@@ -416,16 +425,22 @@ class MathTests(unittest.TestCase):
|
||||
return 42
|
||||
class TestNoCeil:
|
||||
pass
|
||||
class TestBadCeil:
|
||||
__ceil__ = BadDescr()
|
||||
self.assertEqual(math.ceil(TestCeil()), 42)
|
||||
self.assertEqual(math.ceil(FloatCeil()), 42)
|
||||
self.assertEqual(math.ceil(FloatLike(42.5)), 43)
|
||||
self.assertRaises(TypeError, math.ceil, TestNoCeil())
|
||||
self.assertRaises(ValueError, math.ceil, TestBadCeil())
|
||||
|
||||
t = TestNoCeil()
|
||||
t.__ceil__ = lambda *args: args
|
||||
self.assertRaises(TypeError, math.ceil, t)
|
||||
self.assertRaises(TypeError, math.ceil, t, 0)
|
||||
|
||||
self.assertEqual(math.ceil(FloatLike(+1.0)), +1.0)
|
||||
self.assertEqual(math.ceil(FloatLike(-1.0)), -1.0)
|
||||
|
||||
@requires_IEEE_754
|
||||
def testCopysign(self):
|
||||
self.assertEqual(math.copysign(1, 42), 1.0)
|
||||
@@ -566,16 +581,22 @@ class MathTests(unittest.TestCase):
|
||||
return 42
|
||||
class TestNoFloor:
|
||||
pass
|
||||
class TestBadFloor:
|
||||
__floor__ = BadDescr()
|
||||
self.assertEqual(math.floor(TestFloor()), 42)
|
||||
self.assertEqual(math.floor(FloatFloor()), 42)
|
||||
self.assertEqual(math.floor(FloatLike(41.9)), 41)
|
||||
self.assertRaises(TypeError, math.floor, TestNoFloor())
|
||||
self.assertRaises(ValueError, math.floor, TestBadFloor())
|
||||
|
||||
t = TestNoFloor()
|
||||
t.__floor__ = lambda *args: args
|
||||
self.assertRaises(TypeError, math.floor, t)
|
||||
self.assertRaises(TypeError, math.floor, t, 0)
|
||||
|
||||
self.assertEqual(math.floor(FloatLike(+1.0)), +1.0)
|
||||
self.assertEqual(math.floor(FloatLike(-1.0)), -1.0)
|
||||
|
||||
def testFmod(self):
|
||||
self.assertRaises(TypeError, math.fmod)
|
||||
self.ftest('fmod(10, 1)', math.fmod(10, 1), 0.0)
|
||||
@@ -597,6 +618,7 @@ class MathTests(unittest.TestCase):
|
||||
self.assertEqual(math.fmod(-3.0, NINF), -3.0)
|
||||
self.assertEqual(math.fmod(0.0, 3.0), 0.0)
|
||||
self.assertEqual(math.fmod(0.0, NINF), 0.0)
|
||||
self.assertRaises(ValueError, math.fmod, INF, INF)
|
||||
|
||||
def testFrexp(self):
|
||||
self.assertRaises(TypeError, math.frexp)
|
||||
@@ -638,7 +660,7 @@ class MathTests(unittest.TestCase):
|
||||
def msum(iterable):
|
||||
"""Full precision summation. Compute sum(iterable) without any
|
||||
intermediate accumulation of error. Based on the 'lsum' function
|
||||
at http://code.activestate.com/recipes/393090/
|
||||
at https://code.activestate.com/recipes/393090-binary-floating-point-summation-accurate-to-full-p/
|
||||
|
||||
"""
|
||||
tmant, texp = 0, 0
|
||||
@@ -666,6 +688,7 @@ class MathTests(unittest.TestCase):
|
||||
([], 0.0),
|
||||
([0.0], 0.0),
|
||||
([1e100, 1.0, -1e100, 1e-100, 1e50, -1.0, -1e50], 1e-100),
|
||||
([1e100, 1.0, -1e100, 1e-100, 1e50, -1, -1e50], 1e-100),
|
||||
([2.0**53, -0.5, -2.0**-54], 2.0**53-1.0),
|
||||
([2.0**53, 1.0, 2.0**-100], 2.0**53+2.0),
|
||||
([2.0**53+10.0, 1.0, 2.0**-100], 2.0**53+12.0),
|
||||
@@ -713,6 +736,22 @@ class MathTests(unittest.TestCase):
|
||||
s = msum(vals)
|
||||
self.assertEqual(msum(vals), math.fsum(vals))
|
||||
|
||||
self.assertEqual(math.fsum([1.0, math.inf]), math.inf)
|
||||
self.assertTrue(math.isnan(math.fsum([math.nan, 1.0])))
|
||||
self.assertEqual(math.fsum([1e100, FloatLike(1.0), -1e100, 1e-100,
|
||||
1e50, FloatLike(-1.0), -1e50]), 1e-100)
|
||||
self.assertRaises(OverflowError, math.fsum, [1e+308, 1e+308])
|
||||
self.assertRaises(ValueError, math.fsum, [math.inf, -math.inf])
|
||||
self.assertRaises(TypeError, math.fsum, ['spam'])
|
||||
self.assertRaises(TypeError, math.fsum, 1)
|
||||
self.assertRaises(OverflowError, math.fsum, [10**1000])
|
||||
|
||||
def bad_iter():
|
||||
yield 1.0
|
||||
raise ZeroDivisionError
|
||||
|
||||
self.assertRaises(ZeroDivisionError, math.fsum, bad_iter())
|
||||
|
||||
def testGcd(self):
|
||||
gcd = math.gcd
|
||||
self.assertEqual(gcd(0, 0), 0)
|
||||
@@ -773,9 +812,13 @@ class MathTests(unittest.TestCase):
|
||||
# Test allowable types (those with __float__)
|
||||
self.assertEqual(hypot(12.0, 5.0), 13.0)
|
||||
self.assertEqual(hypot(12, 5), 13)
|
||||
self.assertEqual(hypot(0.75, -1), 1.25)
|
||||
self.assertEqual(hypot(-1, 0.75), 1.25)
|
||||
self.assertEqual(hypot(0.75, FloatLike(-1.)), 1.25)
|
||||
self.assertEqual(hypot(FloatLike(-1.), 0.75), 1.25)
|
||||
self.assertEqual(hypot(Decimal(12), Decimal(5)), 13)
|
||||
self.assertEqual(hypot(Fraction(12, 32), Fraction(5, 32)), Fraction(13, 32))
|
||||
self.assertEqual(hypot(bool(1), bool(0), bool(1), bool(1)), math.sqrt(3))
|
||||
self.assertEqual(hypot(True, False, True, True, True), 2.0)
|
||||
|
||||
# Test corner cases
|
||||
self.assertEqual(hypot(0.0, 0.0), 0.0) # Max input is zero
|
||||
@@ -830,6 +873,8 @@ class MathTests(unittest.TestCase):
|
||||
scale = FLOAT_MIN / 2.0 ** exp
|
||||
self.assertEqual(math.hypot(4*scale, 3*scale), 5*scale)
|
||||
|
||||
self.assertRaises(TypeError, math.hypot, *([1.0]*18), 'spam')
|
||||
|
||||
@requires_IEEE_754
|
||||
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
|
||||
"hypot() loses accuracy on machines with double rounding")
|
||||
@@ -922,12 +967,16 @@ class MathTests(unittest.TestCase):
|
||||
# Test allowable types (those with __float__)
|
||||
self.assertEqual(dist((14.0, 1.0), (2.0, -4.0)), 13.0)
|
||||
self.assertEqual(dist((14, 1), (2, -4)), 13)
|
||||
self.assertEqual(dist((FloatLike(14.), 1), (2, -4)), 13)
|
||||
self.assertEqual(dist((11, 1), (FloatLike(-1.), -4)), 13)
|
||||
self.assertEqual(dist((14, FloatLike(-1.)), (2, -6)), 13)
|
||||
self.assertEqual(dist((14, -1), (2, -6)), 13)
|
||||
self.assertEqual(dist((D(14), D(1)), (D(2), D(-4))), D(13))
|
||||
self.assertEqual(dist((F(14, 32), F(1, 32)), (F(2, 32), F(-4, 32))),
|
||||
F(13, 32))
|
||||
self.assertEqual(dist((True, True, False, True, False),
|
||||
(True, False, True, True, False)),
|
||||
sqrt(2.0))
|
||||
self.assertEqual(dist((True, True, False, False, True, True),
|
||||
(True, False, True, False, False, False)),
|
||||
2.0)
|
||||
|
||||
# Test corner cases
|
||||
self.assertEqual(dist((13.25, 12.5, -3.25),
|
||||
@@ -965,6 +1014,8 @@ class MathTests(unittest.TestCase):
|
||||
dist((1, 2, 3, 4), (5, 6, 7))
|
||||
with self.assertRaises(ValueError): # Check dimension agree
|
||||
dist((1, 2, 3), (4, 5, 6, 7))
|
||||
with self.assertRaises(TypeError):
|
||||
dist((1,)*17 + ("spam",), (1,)*18)
|
||||
with self.assertRaises(TypeError): # Rejects invalid types
|
||||
dist("abc", "xyz")
|
||||
int_too_big_for_float = 10 ** (sys.float_info.max_10_exp + 5)
|
||||
@@ -972,6 +1023,16 @@ class MathTests(unittest.TestCase):
|
||||
dist((1, int_too_big_for_float), (2, 3))
|
||||
with self.assertRaises((ValueError, OverflowError)):
|
||||
dist((2, 3), (1, int_too_big_for_float))
|
||||
with self.assertRaises(TypeError):
|
||||
dist((1,), 2)
|
||||
with self.assertRaises(TypeError):
|
||||
dist([1], 2)
|
||||
|
||||
class BadFloat:
|
||||
__float__ = BadDescr()
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
dist([1], [BadFloat()])
|
||||
|
||||
# Verify that the one dimensional case is equivalent to abs()
|
||||
for i in range(20):
|
||||
@@ -1110,6 +1171,7 @@ class MathTests(unittest.TestCase):
|
||||
|
||||
def testLdexp(self):
|
||||
self.assertRaises(TypeError, math.ldexp)
|
||||
self.assertRaises(TypeError, math.ldexp, 2.0, 1.1)
|
||||
self.ftest('ldexp(0,1)', math.ldexp(0,1), 0)
|
||||
self.ftest('ldexp(1,1)', math.ldexp(1,1), 2)
|
||||
self.ftest('ldexp(1,-1)', math.ldexp(1,-1), 0.5)
|
||||
@@ -1142,6 +1204,7 @@ class MathTests(unittest.TestCase):
|
||||
|
||||
def testLog(self):
|
||||
self.assertRaises(TypeError, math.log)
|
||||
self.assertRaises(TypeError, math.log, 1, 2, 3)
|
||||
self.ftest('log(1/e)', math.log(1/math.e), -1)
|
||||
self.ftest('log(1)', math.log(1), 0)
|
||||
self.ftest('log(e)', math.log(math.e), 1)
|
||||
@@ -1152,6 +1215,7 @@ class MathTests(unittest.TestCase):
|
||||
2302.5850929940457)
|
||||
self.assertRaises(ValueError, math.log, -1.5)
|
||||
self.assertRaises(ValueError, math.log, -10**1000)
|
||||
self.assertRaises(ValueError, math.log, 10, -10)
|
||||
self.assertRaises(ValueError, math.log, NINF)
|
||||
self.assertEqual(math.log(INF), INF)
|
||||
self.assertTrue(math.isnan(math.log(NAN)))
|
||||
@@ -1202,6 +1266,277 @@ class MathTests(unittest.TestCase):
|
||||
self.assertEqual(math.log(INF), INF)
|
||||
self.assertTrue(math.isnan(math.log10(NAN)))
|
||||
|
||||
def testSumProd(self):
|
||||
sumprod = math.sumprod
|
||||
Decimal = decimal.Decimal
|
||||
Fraction = fractions.Fraction
|
||||
|
||||
# Core functionality
|
||||
self.assertEqual(sumprod(iter([10, 20, 30]), (1, 2, 3)), 140)
|
||||
self.assertEqual(sumprod([1.5, 2.5], [3.5, 4.5]), 16.5)
|
||||
self.assertEqual(sumprod([], []), 0)
|
||||
self.assertEqual(sumprod([-1], [1.]), -1)
|
||||
self.assertEqual(sumprod([1.], [-1]), -1)
|
||||
|
||||
# Type preservation and coercion
|
||||
for v in [
|
||||
(10, 20, 30),
|
||||
(1.5, -2.5),
|
||||
(Fraction(3, 5), Fraction(4, 5)),
|
||||
(Decimal(3.5), Decimal(4.5)),
|
||||
(2.5, 10), # float/int
|
||||
(2.5, Fraction(3, 5)), # float/fraction
|
||||
(25, Fraction(3, 5)), # int/fraction
|
||||
(25, Decimal(4.5)), # int/decimal
|
||||
]:
|
||||
for p, q in [(v, v), (v, v[::-1])]:
|
||||
with self.subTest(p=p, q=q):
|
||||
expected = sum(p_i * q_i for p_i, q_i in zip(p, q, strict=True))
|
||||
actual = sumprod(p, q)
|
||||
self.assertEqual(expected, actual)
|
||||
self.assertEqual(type(expected), type(actual))
|
||||
|
||||
# Bad arguments
|
||||
self.assertRaises(TypeError, sumprod) # No args
|
||||
self.assertRaises(TypeError, sumprod, []) # One arg
|
||||
self.assertRaises(TypeError, sumprod, [], [], []) # Three args
|
||||
self.assertRaises(TypeError, sumprod, None, [10]) # Non-iterable
|
||||
self.assertRaises(TypeError, sumprod, [10], None) # Non-iterable
|
||||
self.assertRaises(TypeError, sumprod, ['x'], [1.0])
|
||||
|
||||
# Uneven lengths
|
||||
self.assertRaises(ValueError, sumprod, [10, 20], [30])
|
||||
self.assertRaises(ValueError, sumprod, [10], [20, 30])
|
||||
|
||||
# Overflows
|
||||
self.assertEqual(sumprod([10**20], [1]), 10**20)
|
||||
self.assertEqual(sumprod([1], [10**20]), 10**20)
|
||||
self.assertEqual(sumprod([10**10], [10**10]), 10**20)
|
||||
self.assertEqual(sumprod([10**7]*10**5, [10**7]*10**5), 10**19)
|
||||
self.assertRaises(OverflowError, sumprod, [10**1000], [1.0])
|
||||
self.assertRaises(OverflowError, sumprod, [1.0], [10**1000])
|
||||
|
||||
# Error in iterator
|
||||
def raise_after(n):
|
||||
for i in range(n):
|
||||
yield i
|
||||
raise RuntimeError
|
||||
with self.assertRaises(RuntimeError):
|
||||
sumprod(range(10), raise_after(5))
|
||||
with self.assertRaises(RuntimeError):
|
||||
sumprod(raise_after(5), range(10))
|
||||
|
||||
from test.test_iter import BasicIterClass
|
||||
|
||||
self.assertEqual(sumprod(BasicIterClass(1), [1]), 0)
|
||||
self.assertEqual(sumprod([1], BasicIterClass(1)), 0)
|
||||
|
||||
# Error in multiplication
|
||||
class BadMultiply:
|
||||
def __mul__(self, other):
|
||||
raise RuntimeError
|
||||
def __rmul__(self, other):
|
||||
raise RuntimeError
|
||||
with self.assertRaises(RuntimeError):
|
||||
sumprod([10, BadMultiply(), 30], [1, 2, 3])
|
||||
with self.assertRaises(RuntimeError):
|
||||
sumprod([1, 2, 3], [10, BadMultiply(), 30])
|
||||
|
||||
# Error in addition
|
||||
with self.assertRaises(TypeError):
|
||||
sumprod(['abc', 3], [5, 10])
|
||||
with self.assertRaises(TypeError):
|
||||
sumprod([5, 10], ['abc', 3])
|
||||
|
||||
# Special values should give the same as the pure python recipe
|
||||
self.assertEqual(sumprod([10.1, math.inf], [20.2, 30.3]), math.inf)
|
||||
self.assertEqual(sumprod([10.1, math.inf], [math.inf, 30.3]), math.inf)
|
||||
self.assertEqual(sumprod([10.1, math.inf], [math.inf, math.inf]), math.inf)
|
||||
self.assertEqual(sumprod([10.1, -math.inf], [20.2, 30.3]), -math.inf)
|
||||
self.assertTrue(math.isnan(sumprod([10.1, math.inf], [-math.inf, math.inf])))
|
||||
self.assertTrue(math.isnan(sumprod([10.1, math.nan], [20.2, 30.3])))
|
||||
self.assertTrue(math.isnan(sumprod([10.1, math.inf], [math.nan, 30.3])))
|
||||
self.assertTrue(math.isnan(sumprod([10.1, math.inf], [20.3, math.nan])))
|
||||
|
||||
# Error cases that arose during development
|
||||
args = ((-5, -5, 10), (1.5, 4611686018427387904, 2305843009213693952))
|
||||
self.assertEqual(sumprod(*args), 0.0)
|
||||
|
||||
|
||||
@requires_IEEE_754
|
||||
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
|
||||
"sumprod() accuracy not guaranteed on machines with double rounding")
|
||||
@support.cpython_only # Other implementations may choose a different algorithm
|
||||
def test_sumprod_accuracy(self):
|
||||
sumprod = math.sumprod
|
||||
self.assertEqual(sumprod([0.1] * 10, [1]*10), 1.0)
|
||||
self.assertEqual(sumprod([0.1] * 20, [True, False] * 10), 1.0)
|
||||
self.assertEqual(sumprod([True, False] * 10, [0.1] * 20), 1.0)
|
||||
self.assertEqual(sumprod([1.0, 10E100, 1.0, -10E100], [1.0]*4), 2.0)
|
||||
|
||||
@support.requires_resource('cpu')
|
||||
def test_sumprod_stress(self):
|
||||
sumprod = math.sumprod
|
||||
product = itertools.product
|
||||
Decimal = decimal.Decimal
|
||||
Fraction = fractions.Fraction
|
||||
|
||||
class Int(int):
|
||||
def __add__(self, other):
|
||||
return Int(int(self) + int(other))
|
||||
def __mul__(self, other):
|
||||
return Int(int(self) * int(other))
|
||||
__radd__ = __add__
|
||||
__rmul__ = __mul__
|
||||
def __repr__(self):
|
||||
return f'Int({int(self)})'
|
||||
|
||||
class Flt(float):
|
||||
def __add__(self, other):
|
||||
return Int(int(self) + int(other))
|
||||
def __mul__(self, other):
|
||||
return Int(int(self) * int(other))
|
||||
__radd__ = __add__
|
||||
__rmul__ = __mul__
|
||||
def __repr__(self):
|
||||
return f'Flt({int(self)})'
|
||||
|
||||
def baseline_sumprod(p, q):
|
||||
"""This defines the target behavior including exceptions and special values.
|
||||
However, it is subject to rounding errors, so float inputs should be exactly
|
||||
representable with only a few bits.
|
||||
"""
|
||||
total = 0
|
||||
for p_i, q_i in zip(p, q, strict=True):
|
||||
total += p_i * q_i
|
||||
return total
|
||||
|
||||
def run(func, *args):
|
||||
"Make comparing functions easier. Returns error status, type, and result."
|
||||
try:
|
||||
result = func(*args)
|
||||
except (AssertionError, NameError):
|
||||
raise
|
||||
except Exception as e:
|
||||
return type(e), None, 'None'
|
||||
return None, type(result), repr(result)
|
||||
|
||||
pools = [
|
||||
(-5, 10, -2**20, 2**31, 2**40, 2**61, 2**62, 2**80, 1.5, Int(7)),
|
||||
(5.25, -3.5, 4.75, 11.25, 400.5, 0.046875, 0.25, -1.0, -0.078125),
|
||||
(-19.0*2**500, 11*2**1000, -3*2**1500, 17*2*333,
|
||||
5.25, -3.25, -3.0*2**(-333), 3, 2**513),
|
||||
(3.75, 2.5, -1.5, float('inf'), -float('inf'), float('NaN'), 14,
|
||||
9, 3+4j, Flt(13), 0.0),
|
||||
(13.25, -4.25, Decimal('10.5'), Decimal('-2.25'), Fraction(13, 8),
|
||||
Fraction(-11, 16), 4.75 + 0.125j, 97, -41, Int(3)),
|
||||
(Decimal('6.125'), Decimal('12.375'), Decimal('-2.75'), Decimal(0),
|
||||
Decimal('Inf'), -Decimal('Inf'), Decimal('NaN'), 12, 13.5),
|
||||
(-2.0 ** -1000, 11*2**1000, 3, 7, -37*2**32, -2*2**-537, -2*2**-538,
|
||||
2*2**-513),
|
||||
(-7 * 2.0 ** -510, 5 * 2.0 ** -520, 17, -19.0, -6.25),
|
||||
(11.25, -3.75, -0.625, 23.375, True, False, 7, Int(5)),
|
||||
]
|
||||
|
||||
for pool in pools:
|
||||
for size in range(4):
|
||||
for args1 in product(pool, repeat=size):
|
||||
for args2 in product(pool, repeat=size):
|
||||
args = (args1, args2)
|
||||
self.assertEqual(
|
||||
run(baseline_sumprod, *args),
|
||||
run(sumprod, *args),
|
||||
args,
|
||||
)
|
||||
|
||||
@requires_IEEE_754
|
||||
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
|
||||
"sumprod() accuracy not guaranteed on machines with double rounding")
|
||||
@support.cpython_only # Other implementations may choose a different algorithm
|
||||
@support.requires_resource('cpu')
|
||||
def test_sumprod_extended_precision_accuracy(self):
|
||||
import operator
|
||||
from fractions import Fraction
|
||||
from itertools import starmap
|
||||
from collections import namedtuple
|
||||
from math import log2, exp2, fabs
|
||||
from random import choices, uniform, shuffle
|
||||
from statistics import median
|
||||
|
||||
DotExample = namedtuple('DotExample', ('x', 'y', 'target_sumprod', 'condition'))
|
||||
|
||||
def DotExact(x, y):
|
||||
vec1 = map(Fraction, x)
|
||||
vec2 = map(Fraction, y)
|
||||
return sum(starmap(operator.mul, zip(vec1, vec2, strict=True)))
|
||||
|
||||
def Condition(x, y):
|
||||
return 2.0 * DotExact(map(abs, x), map(abs, y)) / abs(DotExact(x, y))
|
||||
|
||||
def linspace(lo, hi, n):
|
||||
width = (hi - lo) / (n - 1)
|
||||
return [lo + width * i for i in range(n)]
|
||||
|
||||
def GenDot(n, c):
|
||||
""" Algorithm 6.1 (GenDot) works as follows. The condition number (5.7) of
|
||||
the dot product xT y is proportional to the degree of cancellation. In
|
||||
order to achieve a prescribed cancellation, we generate the first half of
|
||||
the vectors x and y randomly within a large exponent range. This range is
|
||||
chosen according to the anticipated condition number. The second half of x
|
||||
and y is then constructed choosing xi randomly with decreasing exponent,
|
||||
and calculating yi such that some cancellation occurs. Finally, we permute
|
||||
the vectors x, y randomly and calculate the achieved condition number.
|
||||
"""
|
||||
|
||||
assert n >= 6
|
||||
n2 = n // 2
|
||||
x = [0.0] * n
|
||||
y = [0.0] * n
|
||||
b = log2(c)
|
||||
|
||||
# First half with exponents from 0 to |_b/2_| and random ints in between
|
||||
e = choices(range(int(b/2)), k=n2)
|
||||
e[0] = int(b / 2) + 1
|
||||
e[-1] = 0.0
|
||||
|
||||
x[:n2] = [uniform(-1.0, 1.0) * exp2(p) for p in e]
|
||||
y[:n2] = [uniform(-1.0, 1.0) * exp2(p) for p in e]
|
||||
|
||||
# Second half
|
||||
e = list(map(round, linspace(b/2, 0.0 , n-n2)))
|
||||
for i in range(n2, n):
|
||||
x[i] = uniform(-1.0, 1.0) * exp2(e[i - n2])
|
||||
y[i] = (uniform(-1.0, 1.0) * exp2(e[i - n2]) - DotExact(x, y)) / x[i]
|
||||
|
||||
# Shuffle
|
||||
pairs = list(zip(x, y))
|
||||
shuffle(pairs)
|
||||
x, y = zip(*pairs)
|
||||
|
||||
return DotExample(x, y, DotExact(x, y), Condition(x, y))
|
||||
|
||||
def RelativeError(res, ex):
|
||||
x, y, target_sumprod, condition = ex
|
||||
n = DotExact(list(x) + [-res], list(y) + [1])
|
||||
return fabs(n / target_sumprod)
|
||||
|
||||
def Trial(dotfunc, c, n):
|
||||
ex = GenDot(10, c)
|
||||
res = dotfunc(ex.x, ex.y)
|
||||
return RelativeError(res, ex)
|
||||
|
||||
times = 1000 # Number of trials
|
||||
n = 20 # Length of vectors
|
||||
c = 1e30 # Target condition number
|
||||
|
||||
# If the following test fails, it means that the C math library
|
||||
# implementation of fma() is not compliant with the C99 standard
|
||||
# and is inaccurate. To solve this problem, make a new build
|
||||
# with the symbol UNRELIABLE_FMA defined. That will enable a
|
||||
# slower but accurate code path that avoids the fma() call.
|
||||
relative_err = median(Trial(math.sumprod, c, n) for i in range(times))
|
||||
self.assertLess(relative_err, 1e-16)
|
||||
|
||||
def testModf(self):
|
||||
self.assertRaises(TypeError, math.modf)
|
||||
|
||||
@@ -1235,6 +1570,7 @@ class MathTests(unittest.TestCase):
|
||||
self.assertTrue(math.isnan(math.pow(2, NAN)))
|
||||
self.assertTrue(math.isnan(math.pow(0, NAN)))
|
||||
self.assertEqual(math.pow(1, NAN), 1)
|
||||
self.assertRaises(OverflowError, math.pow, 1e+100, 1e+100)
|
||||
|
||||
# pow(0., x)
|
||||
self.assertEqual(math.pow(0., INF), 0.)
|
||||
@@ -1550,7 +1886,7 @@ class MathTests(unittest.TestCase):
|
||||
try:
|
||||
self.assertTrue(math.isnan(math.tan(INF)))
|
||||
self.assertTrue(math.isnan(math.tan(NINF)))
|
||||
except:
|
||||
except ValueError:
|
||||
self.assertRaises(ValueError, math.tan, INF)
|
||||
self.assertRaises(ValueError, math.tan, NINF)
|
||||
self.assertTrue(math.isnan(math.tan(NAN)))
|
||||
@@ -1591,6 +1927,8 @@ class MathTests(unittest.TestCase):
|
||||
return 23
|
||||
class TestNoTrunc:
|
||||
pass
|
||||
class TestBadTrunc:
|
||||
__trunc__ = BadDescr()
|
||||
|
||||
self.assertEqual(math.trunc(TestTrunc()), 23)
|
||||
self.assertEqual(math.trunc(FloatTrunc()), 23)
|
||||
@@ -1599,6 +1937,7 @@ class MathTests(unittest.TestCase):
|
||||
self.assertRaises(TypeError, math.trunc, 1, 2)
|
||||
self.assertRaises(TypeError, math.trunc, FloatLike(23.5))
|
||||
self.assertRaises(TypeError, math.trunc, TestNoTrunc())
|
||||
self.assertRaises(ValueError, math.trunc, TestBadTrunc())
|
||||
|
||||
def testIsfinite(self):
|
||||
self.assertTrue(math.isfinite(0.0))
|
||||
@@ -1626,11 +1965,11 @@ class MathTests(unittest.TestCase):
|
||||
self.assertFalse(math.isinf(0.))
|
||||
self.assertFalse(math.isinf(1.))
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_nan_constant(self):
|
||||
# `math.nan` must be a quiet NaN with positive sign bit
|
||||
self.assertTrue(math.isnan(math.nan))
|
||||
self.assertEqual(math.copysign(1., math.nan), 1.)
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_inf_constant(self):
|
||||
self.assertTrue(math.isinf(math.inf))
|
||||
self.assertGreater(math.inf, 0.0)
|
||||
@@ -1719,6 +2058,13 @@ class MathTests(unittest.TestCase):
|
||||
except OverflowError:
|
||||
result = 'OverflowError'
|
||||
|
||||
# C99+ says for math.h's sqrt: If the argument is +∞ or ±0, it is
|
||||
# returned, unmodified. On another hand, for csqrt: If z is ±0+0i,
|
||||
# the result is +0+0i. Lets correct zero sign of er to follow
|
||||
# first convention.
|
||||
if id in ['sqrt0002', 'sqrt0003', 'sqrt1001', 'sqrt1023']:
|
||||
er = math.copysign(er, ar)
|
||||
|
||||
# Default tolerances
|
||||
ulp_tol, abs_tol = 5, 0.0
|
||||
|
||||
@@ -1802,6 +2148,8 @@ class MathTests(unittest.TestCase):
|
||||
'\n '.join(failures))
|
||||
|
||||
def test_prod(self):
|
||||
from fractions import Fraction as F
|
||||
|
||||
prod = math.prod
|
||||
self.assertEqual(prod([]), 1)
|
||||
self.assertEqual(prod([], start=5), 5)
|
||||
@@ -1813,6 +2161,14 @@ class MathTests(unittest.TestCase):
|
||||
self.assertEqual(prod([1.0, 2.0, 3.0, 4.0, 5.0]), 120.0)
|
||||
self.assertEqual(prod([1, 2, 3, 4.0, 5.0]), 120.0)
|
||||
self.assertEqual(prod([1.0, 2.0, 3.0, 4, 5]), 120.0)
|
||||
self.assertEqual(prod([1., F(3, 2)]), 1.5)
|
||||
|
||||
# Error in multiplication
|
||||
class BadMultiply:
|
||||
def __rmul__(self, other):
|
||||
raise RuntimeError
|
||||
with self.assertRaises(RuntimeError):
|
||||
prod([10., BadMultiply()])
|
||||
|
||||
# Test overflow in fast-path for integers
|
||||
self.assertEqual(prod([1, 1, 2**32, 1, 1]), 2**32)
|
||||
@@ -2044,11 +2400,20 @@ class MathTests(unittest.TestCase):
|
||||
float.fromhex('0x1.fffffffffffffp-1'))
|
||||
self.assertEqual(math.nextafter(1.0, INF),
|
||||
float.fromhex('0x1.0000000000001p+0'))
|
||||
self.assertEqual(math.nextafter(1.0, -INF, steps=1),
|
||||
float.fromhex('0x1.fffffffffffffp-1'))
|
||||
self.assertEqual(math.nextafter(1.0, INF, steps=1),
|
||||
float.fromhex('0x1.0000000000001p+0'))
|
||||
self.assertEqual(math.nextafter(1.0, -INF, steps=3),
|
||||
float.fromhex('0x1.ffffffffffffdp-1'))
|
||||
self.assertEqual(math.nextafter(1.0, INF, steps=3),
|
||||
float.fromhex('0x1.0000000000003p+0'))
|
||||
|
||||
# x == y: y is returned
|
||||
self.assertEqual(math.nextafter(2.0, 2.0), 2.0)
|
||||
self.assertEqualSign(math.nextafter(-0.0, +0.0), +0.0)
|
||||
self.assertEqualSign(math.nextafter(+0.0, -0.0), -0.0)
|
||||
for steps in range(1, 5):
|
||||
self.assertEqual(math.nextafter(2.0, 2.0, steps=steps), 2.0)
|
||||
self.assertEqualSign(math.nextafter(-0.0, +0.0, steps=steps), +0.0)
|
||||
self.assertEqualSign(math.nextafter(+0.0, -0.0, steps=steps), -0.0)
|
||||
|
||||
# around 0.0
|
||||
smallest_subnormal = sys.float_info.min * sys.float_info.epsilon
|
||||
@@ -2073,6 +2438,11 @@ class MathTests(unittest.TestCase):
|
||||
self.assertIsNaN(math.nextafter(1.0, NAN))
|
||||
self.assertIsNaN(math.nextafter(NAN, NAN))
|
||||
|
||||
self.assertEqual(1.0, math.nextafter(1.0, INF, steps=0))
|
||||
with self.assertRaises(ValueError):
|
||||
math.nextafter(1.0, INF, steps=-1)
|
||||
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_ulp(self):
|
||||
self.assertEqual(math.ulp(1.0), sys.float_info.epsilon)
|
||||
@@ -2112,6 +2482,14 @@ class MathTests(unittest.TestCase):
|
||||
# argument to a float.
|
||||
self.assertFalse(getattr(y, "converted", False))
|
||||
|
||||
def test_input_exceptions(self):
|
||||
self.assertRaises(TypeError, math.exp, "spam")
|
||||
self.assertRaises(TypeError, math.erf, "spam")
|
||||
self.assertRaises(TypeError, math.atan2, "spam", 1.0)
|
||||
self.assertRaises(TypeError, math.atan2, 1.0, "spam")
|
||||
self.assertRaises(TypeError, math.atan2, 1.0)
|
||||
self.assertRaises(TypeError, math.atan2, 1.0, 2.0, 3.0)
|
||||
|
||||
# Custom assertions.
|
||||
|
||||
def assertIsNaN(self, value):
|
||||
@@ -2252,7 +2630,7 @@ class IsCloseTests(unittest.TestCase):
|
||||
|
||||
def load_tests(loader, tests, pattern):
|
||||
from doctest import DocFileSuite
|
||||
# tests.addTest(DocFileSuite("ieee754.txt"))
|
||||
tests.addTest(DocFileSuite("ieee754.txt"))
|
||||
return tests
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
2
Lib/test/test_random.py
vendored
2
Lib/test/test_random.py
vendored
@@ -1012,8 +1012,6 @@ def gamma(z, sqrt2pi=(2.0*pi)**0.5):
|
||||
])
|
||||
|
||||
class TestDistributions(unittest.TestCase):
|
||||
# TODO: RUSTPYTHON ValueError: math domain error
|
||||
@unittest.expectedFailure
|
||||
def test_zeroinputs(self):
|
||||
# Verify that distributions can handle a series of zero inputs'
|
||||
g = random.Random()
|
||||
|
||||
2
Lib/test/test_tokenize.py
vendored
2
Lib/test/test_tokenize.py
vendored
@@ -1237,7 +1237,6 @@ class TestDetectEncoding(TestCase):
|
||||
found, consumed_lines = detect_encoding(rl)
|
||||
self.assertEqual(found, "utf-8")
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_short_files(self):
|
||||
readline = self.get_readline((b'print(something)\n',))
|
||||
encoding, consumed_lines = detect_encoding(readline)
|
||||
@@ -1316,7 +1315,6 @@ class TestDetectEncoding(TestCase):
|
||||
ins = Bunk(lines, path)
|
||||
detect_encoding(ins.readline)
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_open_error(self):
|
||||
# Issue #23840: open() must close the binary file on error
|
||||
m = BytesIO(b'#coding:xxx')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# [RustPython](https://rustpython.github.io/)
|
||||
|
||||
A Python-3 (CPython >= 3.12.0) Interpreter written in Rust :snake: :scream:
|
||||
A Python-3 (CPython >= 3.13.0) Interpreter written in Rust :snake: :scream:
|
||||
:metal:.
|
||||
|
||||
[](https://github.com/RustPython/RustPython/actions?query=workflow%3ACI)
|
||||
|
||||
@@ -133,6 +133,69 @@ pub fn nextafter(x: f64, y: f64) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
pub fn nextafter_with_steps(x: f64, y: f64, steps: u64) -> f64 {
|
||||
if x == y {
|
||||
y
|
||||
} else if x.is_nan() || y.is_nan() {
|
||||
f64::NAN
|
||||
} else if x >= f64::INFINITY {
|
||||
f64::MAX
|
||||
} else if x <= f64::NEG_INFINITY {
|
||||
f64::MIN
|
||||
} else if x == 0.0 {
|
||||
f64::from_bits(1).copysign(y)
|
||||
} else {
|
||||
if steps == 0 {
|
||||
return x;
|
||||
}
|
||||
|
||||
if x.is_nan() {
|
||||
return x;
|
||||
}
|
||||
|
||||
if y.is_nan() {
|
||||
return y;
|
||||
}
|
||||
|
||||
let sign_bit: u64 = 1 << 63;
|
||||
|
||||
let mut ux = x.to_bits();
|
||||
let uy = y.to_bits();
|
||||
|
||||
let ax = ux & !sign_bit;
|
||||
let ay = uy & !sign_bit;
|
||||
|
||||
// If signs are different
|
||||
if ((ux ^ uy) & sign_bit) != 0 {
|
||||
return if ax + ay <= steps {
|
||||
f64::from_bits(uy)
|
||||
} else if ax < steps {
|
||||
let result = (uy & sign_bit) | (steps - ax);
|
||||
f64::from_bits(result)
|
||||
} else {
|
||||
ux -= steps;
|
||||
f64::from_bits(ux)
|
||||
};
|
||||
}
|
||||
|
||||
// If signs are the same
|
||||
if ax > ay {
|
||||
if ax - ay >= steps {
|
||||
ux -= steps;
|
||||
f64::from_bits(ux)
|
||||
} else {
|
||||
f64::from_bits(uy)
|
||||
}
|
||||
} else if ay - ax >= steps {
|
||||
ux += steps;
|
||||
f64::from_bits(ux)
|
||||
} else {
|
||||
f64::from_bits(uy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ulp(x: f64) -> f64 {
|
||||
if x.is_nan() {
|
||||
return x;
|
||||
|
||||
@@ -37,11 +37,11 @@ impl BuildHasher for HashSecret {
|
||||
}
|
||||
}
|
||||
|
||||
impl rand::distributions::Distribution<HashSecret> for rand::distributions::Standard {
|
||||
impl rand::distr::Distribution<HashSecret> for rand::distr::StandardUniform {
|
||||
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> HashSecret {
|
||||
HashSecret {
|
||||
k0: rng.gen(),
|
||||
k1: rng.gen(),
|
||||
k0: rng.random(),
|
||||
k1: rng.random(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ pub fn hash_float(value: f64) -> Option<PyHash> {
|
||||
let mut e = frexp.1;
|
||||
let mut x: PyUHash = 0;
|
||||
while m != 0.0 {
|
||||
x = ((x << 28) & MODULUS) | x >> (BITS - 28);
|
||||
x = ((x << 28) & MODULUS) | (x >> (BITS - 28));
|
||||
m *= 268_435_456.0; // 2**28
|
||||
e -= 28;
|
||||
let y = m as PyUHash; // pull out integer part
|
||||
@@ -132,7 +132,7 @@ pub fn hash_float(value: f64) -> Option<PyHash> {
|
||||
} else {
|
||||
BITS32 - 1 - ((-1 - e) % BITS32)
|
||||
};
|
||||
x = ((x << e) & MODULUS) | x >> (BITS32 - e);
|
||||
x = ((x << e) & MODULUS) | (x >> (BITS32 - e));
|
||||
|
||||
Some(fix_sentinel(x as PyHash * value.signum() as PyHash))
|
||||
}
|
||||
|
||||
@@ -322,6 +322,37 @@ pub mod levenshtein {
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace all tabs in a string with spaces, using the given tab size.
|
||||
pub fn expandtabs(input: &str, tab_size: usize) -> String {
|
||||
let tab_stop = tab_size;
|
||||
let mut expanded_str = String::with_capacity(input.len());
|
||||
let mut tab_size = tab_stop;
|
||||
let mut col_count = 0usize;
|
||||
for ch in input.chars() {
|
||||
match ch {
|
||||
'\t' => {
|
||||
let num_spaces = tab_size - col_count;
|
||||
col_count += num_spaces;
|
||||
let expand = " ".repeat(num_spaces);
|
||||
expanded_str.push_str(&expand);
|
||||
}
|
||||
'\r' | '\n' => {
|
||||
expanded_str.push(ch);
|
||||
col_count = 0;
|
||||
tab_size = 0;
|
||||
}
|
||||
_ => {
|
||||
expanded_str.push(ch);
|
||||
col_count += 1;
|
||||
}
|
||||
}
|
||||
if col_count >= tab_size {
|
||||
tab_size += tab_stop;
|
||||
}
|
||||
}
|
||||
expanded_str
|
||||
}
|
||||
|
||||
/// Creates an [`AsciiStr`][ascii::AsciiStr] from a string literal, throwing a compile error if the
|
||||
/// literal isn't actually ascii.
|
||||
///
|
||||
|
||||
@@ -11,6 +11,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
rustpython-ast = { workspace = true, features=["unparse", "constant-optimization"] }
|
||||
rustpython-common = { workspace = true }
|
||||
rustpython-parser-core = { workspace = true }
|
||||
rustpython-compiler-core = { workspace = true }
|
||||
|
||||
|
||||
@@ -1095,8 +1095,27 @@ impl Compiler {
|
||||
self.store_name(name.as_ref())?;
|
||||
}
|
||||
}
|
||||
located_ast::TypeParam::ParamSpec(_) => todo!(),
|
||||
located_ast::TypeParam::TypeVarTuple(_) => todo!(),
|
||||
located_ast::TypeParam::ParamSpec(located_ast::TypeParamParamSpec {
|
||||
name, ..
|
||||
}) => {
|
||||
self.emit_load_const(ConstantData::Str {
|
||||
value: name.to_string(),
|
||||
});
|
||||
emit!(self, Instruction::ParamSpec);
|
||||
emit!(self, Instruction::Duplicate);
|
||||
self.store_name(name.as_ref())?;
|
||||
}
|
||||
located_ast::TypeParam::TypeVarTuple(located_ast::TypeParamTypeVarTuple {
|
||||
name,
|
||||
..
|
||||
}) => {
|
||||
self.emit_load_const(ConstantData::Str {
|
||||
value: name.to_string(),
|
||||
});
|
||||
emit!(self, Instruction::TypeVarTuple);
|
||||
emit!(self, Instruction::Duplicate);
|
||||
self.store_name(name.as_ref())?;
|
||||
}
|
||||
};
|
||||
}
|
||||
emit!(
|
||||
@@ -2704,7 +2723,7 @@ impl Compiler {
|
||||
|
||||
fn compile_keywords(&mut self, keywords: &[located_ast::Keyword]) -> CompileResult<()> {
|
||||
let mut size = 0;
|
||||
let groupby = keywords.iter().group_by(|e| e.arg.is_none());
|
||||
let groupby = keywords.iter().chunk_by(|e| e.arg.is_none());
|
||||
for (is_unpacking, sub_keywords) in &groupby {
|
||||
if is_unpacking {
|
||||
for keyword in sub_keywords {
|
||||
@@ -2867,7 +2886,7 @@ impl Compiler {
|
||||
(false, element)
|
||||
}
|
||||
})
|
||||
.group_by(|(starred, _)| *starred);
|
||||
.chunk_by(|(starred, _)| *starred);
|
||||
|
||||
for (starred, run) in &groups {
|
||||
let mut run_size = 0;
|
||||
@@ -3349,17 +3368,51 @@ impl EmitArg<bytecode::Label> for ir::BlockIdx {
|
||||
}
|
||||
}
|
||||
|
||||
/// Strips leading whitespace from a docstring.
|
||||
///
|
||||
/// The code has been ported from `_PyCompile_CleanDoc` in cpython.
|
||||
/// `inspect.cleandoc` is also a good reference, but has a few incompatibilities.
|
||||
fn clean_doc(doc: &str) -> String {
|
||||
let doc = rustpython_common::str::expandtabs(doc, 8);
|
||||
// First pass: find minimum indentation of any non-blank lines
|
||||
// after first line.
|
||||
let margin = doc
|
||||
.lines()
|
||||
// Find the non-blank lines
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
// get the one with the least indentation
|
||||
.map(|line| line.chars().take_while(|c| c == &' ').count())
|
||||
.min();
|
||||
if let Some(margin) = margin {
|
||||
let mut cleaned = String::with_capacity(doc.len());
|
||||
// copy first line without leading whitespace
|
||||
if let Some(first_line) = doc.lines().next() {
|
||||
cleaned.push_str(first_line.trim_start());
|
||||
}
|
||||
// copy subsequent lines without margin.
|
||||
for line in doc.split('\n').skip(1) {
|
||||
cleaned.push('\n');
|
||||
let cleaned_line = line.chars().skip(margin).collect::<String>();
|
||||
cleaned.push_str(&cleaned_line);
|
||||
}
|
||||
|
||||
cleaned
|
||||
} else {
|
||||
doc.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
fn split_doc<'a>(
|
||||
body: &'a [located_ast::Stmt],
|
||||
opts: &CompileOpts,
|
||||
) -> (Option<String>, &'a [located_ast::Stmt]) {
|
||||
if let Some((located_ast::Stmt::Expr(expr), body_rest)) = body.split_first() {
|
||||
if let Some(doc) = try_get_constant_string(std::slice::from_ref(&expr.value)) {
|
||||
if opts.optimize < 2 {
|
||||
return (Some(doc), body_rest);
|
||||
return if opts.optimize < 2 {
|
||||
(Some(clean_doc(&doc)), body_rest)
|
||||
} else {
|
||||
return (None, body_rest);
|
||||
}
|
||||
(None, body_rest)
|
||||
};
|
||||
}
|
||||
}
|
||||
(None, body)
|
||||
|
||||
@@ -1257,8 +1257,18 @@ impl SymbolTableBuilder {
|
||||
self.scan_expression(binding, ExpressionContext::Load)?;
|
||||
}
|
||||
}
|
||||
ast::located::TypeParam::ParamSpec(_) => todo!(),
|
||||
ast::located::TypeParam::TypeVarTuple(_) => todo!(),
|
||||
ast::located::TypeParam::ParamSpec(ast::TypeParamParamSpec {
|
||||
name,
|
||||
range: param_spec_range,
|
||||
}) => {
|
||||
self.register_name(name, SymbolUsage::Assigned, param_spec_range.start)?;
|
||||
}
|
||||
ast::located::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple {
|
||||
name,
|
||||
range: type_var_tuple_range,
|
||||
}) => {
|
||||
self.register_name(name, SymbolUsage::Assigned, type_var_tuple_range.start)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -15,7 +15,7 @@ bitflags = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
malachite-bigint = { workspace = true }
|
||||
num-complex = { workspace = true }
|
||||
serde = { version = "1.0.133", optional = true, default-features = false, features = ["derive"] }
|
||||
serde = { version = "1.0.217", optional = true, default-features = false, features = ["derive"] }
|
||||
|
||||
lz4_flex = "0.11"
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ impl OpArgState {
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn extend(&mut self, arg: OpArgByte) -> OpArg {
|
||||
self.state = self.state << 8 | u32::from(arg.0);
|
||||
self.state = (self.state << 8) | u32::from(arg.0);
|
||||
OpArg(self.state)
|
||||
}
|
||||
#[inline(always)]
|
||||
@@ -595,10 +595,12 @@ pub enum Instruction {
|
||||
TypeVarWithBound,
|
||||
TypeVarWithConstraint,
|
||||
TypeAlias,
|
||||
TypeVarTuple,
|
||||
ParamSpec,
|
||||
// If you add a new instruction here, be sure to keep LAST_INSTRUCTION updated
|
||||
}
|
||||
// This must be kept up to date to avoid marshaling errors
|
||||
const LAST_INSTRUCTION: Instruction = Instruction::TypeAlias;
|
||||
const LAST_INSTRUCTION: Instruction = Instruction::ParamSpec;
|
||||
const _: () = assert!(mem::size_of::<Instruction>() == 1);
|
||||
|
||||
impl From<Instruction> for u8 {
|
||||
@@ -1291,6 +1293,8 @@ impl Instruction {
|
||||
TypeVarWithBound => -1,
|
||||
TypeVarWithConstraint => -1,
|
||||
TypeAlias => -2,
|
||||
ParamSpec => 0,
|
||||
TypeVarTuple => 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1460,6 +1464,8 @@ impl Instruction {
|
||||
TypeVarWithBound => w!(TypeVarWithBound),
|
||||
TypeVarWithConstraint => w!(TypeVarWithConstraint),
|
||||
TypeAlias => w!(TypeAlias),
|
||||
ParamSpec => w!(ParamSpec),
|
||||
TypeVarTuple => w!(TypeVarTuple),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ once_cell = { workspace = true }
|
||||
syn = { workspace = true, features = ["full", "extra-traits"] }
|
||||
|
||||
maplit = "1.0.2"
|
||||
proc-macro2 = "1.0.79"
|
||||
quote = "1.0.18"
|
||||
proc-macro2 = "1.0.93"
|
||||
quote = "1.0.38"
|
||||
syn-ext = { version = "0.4.0", features = ["full"] }
|
||||
textwrap = { version = "0.15.0", default-features = false }
|
||||
textwrap = { version = "0.16.1", default-features = false }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1229,6 +1229,13 @@ impl MethodItemMeta {
|
||||
let inner = self.inner();
|
||||
let name = inner._optional_str("name")?;
|
||||
let magic = inner._bool("magic")?;
|
||||
if magic && name.is_some() {
|
||||
bail_span!(
|
||||
&inner.meta_ident,
|
||||
"A #[{}] method cannot be magic and have a specified name, choose one.",
|
||||
inner.meta_name()
|
||||
);
|
||||
}
|
||||
Ok(if let Some(name) = name {
|
||||
name
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,124 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream {
|
||||
derive_impl::derive_from_args(input).into()
|
||||
}
|
||||
|
||||
/// The attribute can be applied either to a struct, trait, or impl.
|
||||
/// # Struct
|
||||
/// This implements `MaybeTraverse`, `PyClassDef`, and `StaticType` for the struct.
|
||||
/// Consider deriving `Traverse` to implement it.
|
||||
/// ## Arguments
|
||||
/// - `module`: the module which contains the class -- can be omitted if in a `#[pymodule]`.
|
||||
/// - `name`: the name of the Python class, by default it is the name of the struct.
|
||||
/// - `base`: the base class of the Python class.
|
||||
/// This does not cause inheritance of functions or attributes that must be done by a separate trait.
|
||||
/// # Impl
|
||||
/// This part implements `PyClassImpl` for the struct.
|
||||
/// This includes methods, getters/setters, etc.; only annotated methods will be included.
|
||||
/// Common functions and abilities like instantiation and `__call__` are often implemented by
|
||||
/// traits rather than in the `impl` itself; see `Constructor` and `Callable` respectively for those.
|
||||
/// ## Arguments
|
||||
/// - `name`: the name of the Python class, when no name is provided the struct name is used.
|
||||
/// - `flags`: the flags of the class, see `PyTypeFlags`.
|
||||
/// - `BASETYPE`: allows the class to be inheritable.
|
||||
/// - `IMMUTABLETYPE`: class attributes are immutable.
|
||||
/// - `with`: which trait implementations are to be included in the python class.
|
||||
/// ```rust, ignore
|
||||
/// #[pyclass(module = "mymodule", name = "MyClass", base = "BaseClass")]
|
||||
/// struct MyStruct {
|
||||
/// x: i32,
|
||||
/// }
|
||||
///
|
||||
/// impl Constructor for MyStruct {
|
||||
/// ...
|
||||
/// }
|
||||
///
|
||||
/// #[pyclass(with(Constructor))]
|
||||
/// impl MyStruct {
|
||||
/// ...
|
||||
/// }
|
||||
/// ```
|
||||
/// ## Inner markers
|
||||
/// ### pymethod/pyclassmethod/pystaticmethod
|
||||
/// `pymethod` is used to mark a method of the Python class.
|
||||
/// `pyclassmethod` is used to mark a class method.
|
||||
/// `pystaticmethod` is used to mark a static method.
|
||||
/// #### Method signature
|
||||
/// The first parameter can be either `&self` or `<var>: PyRef<Self>` for `pymethod`.
|
||||
/// The first parameter can be `cls: PyTypeRef` for `pyclassmethod`.
|
||||
/// There is no mandatory parameter for `pystaticmethod`.
|
||||
/// Both are valid and essentially the same, but the latter can yield more control.
|
||||
/// The last parameter can optionally be of the type `&VirtualMachine` to access the VM.
|
||||
/// All other values must implement `IntoPyResult`.
|
||||
/// Numeric types, `String`, `bool`, and `PyObjectRef` implement this trait,
|
||||
/// but so does any object that implements `PyValue`.
|
||||
/// Consider using `OptionalArg` for optional arguments.
|
||||
/// #### Arguments
|
||||
/// - `magic`: marks the method as a magic method: the method name is surrounded with double underscores.
|
||||
/// ```rust, ignore
|
||||
/// #[pyclass]
|
||||
/// impl MyStruct {
|
||||
/// // This will be called as the `__add__` method in Python.
|
||||
/// #[pymethod(magic)]
|
||||
/// fn add(&self, other: &Self) -> PyResult<i32> {
|
||||
/// ...
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// - `name`: the name of the method in Python,
|
||||
/// by default it is the same as the Rust method, or surrounded by double underscores if magic is present.
|
||||
/// This overrides `magic` and the default name and cannot be used with `magic` to prevent ambiguity.
|
||||
/// ### pygetset
|
||||
/// This is used to mark a getter/setter pair.
|
||||
/// #### Arguments
|
||||
/// - `setter`: marks the method as a setter, it acts as a getter by default.
|
||||
/// Setter method names should be prefixed with `set_`.
|
||||
/// - `name`: the name of the attribute in Python, by default it is the same as the Rust method.
|
||||
/// - `magic`: marks the method as a magic method: the method name is surrounded with double underscores.
|
||||
/// This cannot be used with `name` to prevent ambiguity.
|
||||
///
|
||||
/// Ensure both the getter and setter are marked with `name` and `magic` in the same manner.
|
||||
/// #### Examples
|
||||
/// ```rust, ignore
|
||||
/// #[pyclass]
|
||||
/// impl MyStruct {
|
||||
/// #[pygetset]
|
||||
/// fn x(&self) -> PyResult<i32> {
|
||||
/// Ok(self.x.lock())
|
||||
/// }
|
||||
/// #[pygetset(setter)]
|
||||
/// fn set_x(&mut self, value: i32) -> PyResult<()> {
|
||||
/// self.x.set(value);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// ### pyslot
|
||||
/// This is used to mark a slot method it should be marked by prefixing the method in rust with `slot_`.
|
||||
/// #### Arguments
|
||||
/// - name: the name of the slot method.
|
||||
/// ### pyattr
|
||||
/// ### extend_class
|
||||
/// This helps inherit attributes from a parent class.
|
||||
/// The method this is applied on should be called `extend_class_with_fields`.
|
||||
/// #### Examples
|
||||
/// ```rust, ignore
|
||||
/// #[extend_class]
|
||||
/// fn extend_class_with_fields(ctx: &Context, class: &'static Py<PyType>) {
|
||||
/// class.set_attr(
|
||||
/// identifier!(ctx, _fields),
|
||||
/// ctx.new_tuple(vec![
|
||||
/// ctx.new_str(ascii!("body")).into(),
|
||||
/// ctx.new_str(ascii!("type_ignores")).into(),
|
||||
/// ])
|
||||
/// .into(),
|
||||
/// );
|
||||
/// class.set_attr(identifier!(ctx, _attributes), ctx.new_list(vec![]).into());
|
||||
/// }
|
||||
/// ```
|
||||
/// ### pymember
|
||||
/// # Trait
|
||||
/// `#[pyclass]` on traits functions a lot like `#[pyclass]` on `impl` blocks.
|
||||
/// Note that associated functions that are annotated with `#[pymethod]` or similar **must**
|
||||
/// have a body, abstract functions should be wrapped before applying an annotation.
|
||||
#[proc_macro_attribute]
|
||||
pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let attr = parse_macro_input!(attr);
|
||||
@@ -34,6 +152,71 @@ pub fn pyexception(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
derive_impl::pyexception(attr, item).into()
|
||||
}
|
||||
|
||||
/// This attribute must be applied to an inline module.
|
||||
/// It defines a Python module in the form a `make_module` function in the module;
|
||||
/// this has to be used in a `get_module_inits` to properly register the module.
|
||||
/// Additionally, this macro defines 'MODULE_NAME' and 'DOC' in the module.
|
||||
/// # Arguments
|
||||
/// - `name`: the name of the python module,
|
||||
/// by default, it is the name of the module, but this can be configured.
|
||||
/// ```rust, ignore
|
||||
/// // This will create a module named `mymodule`
|
||||
/// #[pymodule(name = "mymodule")]
|
||||
/// mod module {
|
||||
/// }
|
||||
/// ```
|
||||
/// - `sub`: declare the module as a submodule of another module.
|
||||
/// ```rust, ignore
|
||||
/// #[pymodule(sub)]
|
||||
/// mod submodule {
|
||||
/// }
|
||||
///
|
||||
/// #[pymodule(with(submodule))]
|
||||
/// mod mymodule {
|
||||
/// }
|
||||
/// ```
|
||||
/// - `with`: declare the list of submodules that this module contains (see `sub` for example).
|
||||
/// ## Inner markers
|
||||
/// ### pyattr
|
||||
/// `pyattr` is a multipurpose marker that can be used in a pymodule.
|
||||
/// The most common use is to mark a function or class as a part of the module.
|
||||
/// This can be done by applying it to a function or struct prior to the `#[pyfunction]` or `#[pyclass]` macro.
|
||||
/// If applied to a constant, it will be added to the module as an attribute.
|
||||
/// If applied to a function not marked with `pyfunction`,
|
||||
/// it will also be added to the module as an attribute but the value is the result of the function.
|
||||
/// If `#[pyattr(once)]` is used in this case, the function will be called once
|
||||
/// and the result will be stored using a `static_cell`.
|
||||
/// #### Examples
|
||||
/// ```rust, ignore
|
||||
/// #[pymodule]
|
||||
/// mod mymodule {
|
||||
/// #[pyattr]
|
||||
/// const MY_CONSTANT: i32 = 42;
|
||||
/// #[pyattr]
|
||||
/// fn another_constant() -> PyResult<i32> {
|
||||
/// Ok(42)
|
||||
/// }
|
||||
/// #[pyattr(once)]
|
||||
/// fn once() -> PyResult<i32> {
|
||||
/// // This will only be called once and the result will be stored.
|
||||
/// Ok(2 ** 24)
|
||||
/// }
|
||||
///
|
||||
/// #[pyattr]
|
||||
/// #[pyfunction]
|
||||
/// fn my_function(vm: &VirtualMachine) -> PyResult<()> {
|
||||
/// ...
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// ### pyfunction
|
||||
/// This is used to create a python function.
|
||||
/// #### Function signature
|
||||
/// The last argument can optionally be of the type `&VirtualMachine` to access the VM.
|
||||
/// Refer to the `pymethod` documentation (located in the `pyclass` macro documentation)
|
||||
/// for more information on what regular argument types are permitted.
|
||||
/// #### Arguments
|
||||
/// - `name`: the name of the function in Python, by default it is the same as the associated Rust function.
|
||||
#[proc_macro_attribute]
|
||||
pub fn pymodule(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let attr = parse_macro_input!(attr);
|
||||
|
||||
@@ -7,9 +7,7 @@ myobj = MyObject()
|
||||
assert myobj == myobj
|
||||
assert not myobj != myobj
|
||||
|
||||
object.__subclasshook__() == NotImplemented
|
||||
object.__subclasshook__(1) == NotImplemented
|
||||
object.__subclasshook__(1, 2) == NotImplemented
|
||||
|
||||
assert MyObject().__eq__(MyObject()) == NotImplemented
|
||||
assert MyObject().__ne__(MyObject()) == NotImplemented
|
||||
|
||||
133
extra_tests/snippets/builtins_ctypes.py
Normal file
133
extra_tests/snippets/builtins_ctypes.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import os as _os, sys as _sys
|
||||
|
||||
from _ctypes import sizeof
|
||||
from _ctypes import _SimpleCData
|
||||
from struct import calcsize as _calcsize
|
||||
|
||||
def create_string_buffer(init, size=None):
|
||||
"""create_string_buffer(aBytes) -> character array
|
||||
create_string_buffer(anInteger) -> character array
|
||||
create_string_buffer(aBytes, anInteger) -> character array
|
||||
"""
|
||||
if isinstance(init, bytes):
|
||||
if size is None:
|
||||
size = len(init)+1
|
||||
_sys.audit("ctypes.create_string_buffer", init, size)
|
||||
buftype = c_char * size
|
||||
buf = buftype()
|
||||
buf.value = init
|
||||
return buf
|
||||
elif isinstance(init, int):
|
||||
_sys.audit("ctypes.create_string_buffer", None, init)
|
||||
buftype = c_char * init
|
||||
buf = buftype()
|
||||
return buf
|
||||
raise TypeError(init)
|
||||
|
||||
def _check_size(typ, typecode=None):
|
||||
# Check if sizeof(ctypes_type) against struct.calcsize. This
|
||||
# should protect somewhat against a misconfigured libffi.
|
||||
from struct import calcsize
|
||||
if typecode is None:
|
||||
# Most _type_ codes are the same as used in struct
|
||||
typecode = typ._type_
|
||||
actual, required = sizeof(typ), calcsize(typecode)
|
||||
if actual != required:
|
||||
raise SystemError("sizeof(%s) wrong: %d instead of %d" % \
|
||||
(typ, actual, required))
|
||||
|
||||
class c_short(_SimpleCData):
|
||||
_type_ = "h"
|
||||
_check_size(c_short)
|
||||
|
||||
class c_ushort(_SimpleCData):
|
||||
_type_ = "H"
|
||||
_check_size(c_ushort)
|
||||
|
||||
class c_long(_SimpleCData):
|
||||
_type_ = "l"
|
||||
_check_size(c_long)
|
||||
|
||||
class c_ulong(_SimpleCData):
|
||||
_type_ = "L"
|
||||
_check_size(c_ulong)
|
||||
|
||||
if _calcsize("i") == _calcsize("l"):
|
||||
# if int and long have the same size, make c_int an alias for c_long
|
||||
c_int = c_long
|
||||
c_uint = c_ulong
|
||||
else:
|
||||
class c_int(_SimpleCData):
|
||||
_type_ = "i"
|
||||
_check_size(c_int)
|
||||
|
||||
class c_uint(_SimpleCData):
|
||||
_type_ = "I"
|
||||
_check_size(c_uint)
|
||||
|
||||
class c_float(_SimpleCData):
|
||||
_type_ = "f"
|
||||
_check_size(c_float)
|
||||
|
||||
class c_double(_SimpleCData):
|
||||
_type_ = "d"
|
||||
_check_size(c_double)
|
||||
|
||||
class c_longdouble(_SimpleCData):
|
||||
_type_ = "g"
|
||||
if sizeof(c_longdouble) == sizeof(c_double):
|
||||
c_longdouble = c_double
|
||||
|
||||
if _calcsize("l") == _calcsize("q"):
|
||||
# if long and long long have the same size, make c_longlong an alias for c_long
|
||||
c_longlong = c_long
|
||||
c_ulonglong = c_ulong
|
||||
else:
|
||||
class c_longlong(_SimpleCData):
|
||||
_type_ = "q"
|
||||
_check_size(c_longlong)
|
||||
|
||||
class c_ulonglong(_SimpleCData):
|
||||
_type_ = "Q"
|
||||
## def from_param(cls, val):
|
||||
## return ('d', float(val), val)
|
||||
## from_param = classmethod(from_param)
|
||||
_check_size(c_ulonglong)
|
||||
|
||||
class c_ubyte(_SimpleCData):
|
||||
_type_ = "B"
|
||||
c_ubyte.__ctype_le__ = c_ubyte.__ctype_be__ = c_ubyte
|
||||
# backward compatibility:
|
||||
##c_uchar = c_ubyte
|
||||
_check_size(c_ubyte)
|
||||
|
||||
class c_byte(_SimpleCData):
|
||||
_type_ = "b"
|
||||
c_byte.__ctype_le__ = c_byte.__ctype_be__ = c_byte
|
||||
_check_size(c_byte)
|
||||
|
||||
class c_char(_SimpleCData):
|
||||
_type_ = "c"
|
||||
c_char.__ctype_le__ = c_char.__ctype_be__ = c_char
|
||||
_check_size(c_char)
|
||||
|
||||
class c_char_p(_SimpleCData):
|
||||
_type_ = "z"
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value)
|
||||
_check_size(c_char_p, "P")
|
||||
|
||||
class c_void_p(_SimpleCData):
|
||||
_type_ = "P"
|
||||
c_voidp = c_void_p # backwards compatibility (to a bug)
|
||||
_check_size(c_void_p)
|
||||
|
||||
class c_bool(_SimpleCData):
|
||||
_type_ = "?"
|
||||
_check_size(c_bool)
|
||||
|
||||
i = c_int(42)
|
||||
f = c_float(3.14)
|
||||
# s = create_string_buffer(b'\000' * 32)
|
||||
assert i.value == 42
|
||||
assert abs(f.value - 3.14) < 1e-06
|
||||
@@ -1,27 +0,0 @@
|
||||
# unittest for modified imghdr.py
|
||||
# Should be replace it into https://github.com/python/cpython/blob/main/Lib/test/test_imghdr.py
|
||||
import os
|
||||
import imghdr
|
||||
|
||||
|
||||
TEST_FILES = (
|
||||
#('python.png', 'png'),
|
||||
('python.gif', 'gif'),
|
||||
('python.bmp', 'bmp'),
|
||||
('python.ppm', 'ppm'),
|
||||
('python.pgm', 'pgm'),
|
||||
('python.pbm', 'pbm'),
|
||||
('python.jpg', 'jpeg'),
|
||||
('python.ras', 'rast'),
|
||||
#('python.sgi', 'rgb'),
|
||||
('python.tiff', 'tiff'),
|
||||
('python.xbm', 'xbm'),
|
||||
('python.webp', 'webp'),
|
||||
('python.exr', 'exr'),
|
||||
)
|
||||
|
||||
resource_dir = os.path.join(os.path.dirname(__file__), 'imghdrdata')
|
||||
|
||||
for fname, expected in TEST_FILES:
|
||||
res = imghdr.what(os.path.join(resource_dir, fname))
|
||||
assert res == expected
|
||||
@@ -32,7 +32,7 @@ p = subprocess.Popen(sleep(2))
|
||||
assert p.poll() is None
|
||||
|
||||
with assert_raises(subprocess.TimeoutExpired):
|
||||
assert p.wait(1)
|
||||
assert p.wait(1)
|
||||
|
||||
p.wait()
|
||||
|
||||
@@ -48,17 +48,17 @@ p = subprocess.Popen(sleep(2))
|
||||
p.terminate()
|
||||
p.wait()
|
||||
if is_unix:
|
||||
assert p.returncode == -signal.SIGTERM
|
||||
assert p.returncode == -signal.SIGTERM
|
||||
else:
|
||||
assert p.returncode == 1
|
||||
assert p.returncode == 1
|
||||
|
||||
p = subprocess.Popen(sleep(2))
|
||||
p.kill()
|
||||
p.wait()
|
||||
if is_unix:
|
||||
assert p.returncode == -signal.SIGKILL
|
||||
assert p.returncode == -signal.SIGKILL
|
||||
else:
|
||||
assert p.returncode == 1
|
||||
assert p.returncode == 1
|
||||
|
||||
p = subprocess.Popen(echo("test"), stdout=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate()
|
||||
@@ -66,4 +66,4 @@ assert stdout.strip() == b"test"
|
||||
|
||||
p = subprocess.Popen(sleep(5), stdout=subprocess.PIPE)
|
||||
with assert_raises(subprocess.TimeoutExpired):
|
||||
p.communicate(timeout=1)
|
||||
p.communicate(timeout=1)
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# This probably will be superceeded by the python unittests when that works.
|
||||
|
||||
import xdrlib
|
||||
|
||||
p = xdrlib.Packer()
|
||||
p.pack_int(1337)
|
||||
|
||||
d = p.get_buffer()
|
||||
|
||||
print(d)
|
||||
|
||||
# assert d == b'\x00\x00\x059'
|
||||
@@ -50,7 +50,7 @@ class Bar:
|
||||
assert x == 3
|
||||
|
||||
|
||||
assert Bar.__doc__ == " W00t "
|
||||
assert Bar.__doc__ == "W00t "
|
||||
|
||||
bar = Bar(42)
|
||||
assert bar.get_x.__doc__ == None
|
||||
@@ -147,7 +147,7 @@ class T3:
|
||||
test3
|
||||
"""
|
||||
|
||||
assert T3.__doc__ == "\n test3\n "
|
||||
assert T3.__doc__ == "\ntest3\n"
|
||||
|
||||
class T4:
|
||||
|
||||
|
||||
15
extra_tests/snippets/syntax_doc.py
Normal file
15
extra_tests/snippets/syntax_doc.py
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
def f1():
|
||||
"""
|
||||
x
|
||||
\ty
|
||||
"""
|
||||
assert f1.__doc__ == '\nx\ny\n'
|
||||
|
||||
def f2():
|
||||
"""
|
||||
\t x
|
||||
\t\ty
|
||||
"""
|
||||
|
||||
assert f2.__doc__ == '\nx\n y\n'
|
||||
@@ -44,7 +44,7 @@ def f3():
|
||||
"""
|
||||
pass
|
||||
|
||||
assert f3.__doc__ == "\n test3\n "
|
||||
assert f3.__doc__ == "\ntest3\n"
|
||||
|
||||
def f4():
|
||||
"test4"
|
||||
|
||||
@@ -107,7 +107,7 @@ impl<'vm> ShellHelper<'vm> {
|
||||
.filter(|res| {
|
||||
res.as_ref()
|
||||
.ok()
|
||||
.map_or(true, |s| s.as_str().starts_with(word_start))
|
||||
.is_none_or(|s| s.as_str().starts_with(word_start))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.ok()?;
|
||||
|
||||
@@ -46,16 +46,15 @@ thread_local = { workspace = true }
|
||||
|
||||
memchr = { workspace = true }
|
||||
base64 = "0.13.0"
|
||||
csv-core = "0.1.10"
|
||||
csv-core = "0.1.11"
|
||||
dyn-clone = "1.0.10"
|
||||
libz-sys = { version = "1.1", default-features = false, optional = true }
|
||||
puruspe = "0.2.4"
|
||||
puruspe = "0.4.0"
|
||||
xml-rs = "0.8.14"
|
||||
|
||||
# random
|
||||
rand = { workspace = true }
|
||||
rand_core = "0.6.3"
|
||||
mt19937 = "2.0.1"
|
||||
mt19937 = "3.1"
|
||||
|
||||
# Crypto:
|
||||
digest = "0.10.3"
|
||||
@@ -88,12 +87,12 @@ bzip2 = { version = "0.4", optional = true }
|
||||
# uuid
|
||||
[target.'cfg(not(any(target_os = "ios", target_os = "android", target_os = "windows", target_arch = "wasm32", target_os = "redox")))'.dependencies]
|
||||
mac_address = "1.1.3"
|
||||
uuid = { version = "1.1.2", features = ["v1", "fast-rng"] }
|
||||
uuid = { version = "1.1.2", features = ["v1"] }
|
||||
|
||||
# mmap
|
||||
[target.'cfg(all(unix, not(target_arch = "wasm32")))'.dependencies]
|
||||
memmap2 = "0.5.4"
|
||||
page_size = "0.4"
|
||||
memmap2 = "0.5.10"
|
||||
page_size = "0.6"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))'.dependencies]
|
||||
termios = "0.3.3"
|
||||
@@ -102,8 +101,8 @@ termios = "0.3.3"
|
||||
rustix = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
gethostname = "0.2.3"
|
||||
socket2 = { version = "0.5.6", features = ["all"] }
|
||||
gethostname = "1.0.0"
|
||||
socket2 = { version = "0.5.8", features = ["all"] }
|
||||
dns-lookup = "2"
|
||||
openssl = { version = "0.10.66", optional = true }
|
||||
openssl-sys = { version = "0.9.80", optional = true }
|
||||
@@ -136,7 +135,7 @@ features = [
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
system-configuration = "0.5.0"
|
||||
system-configuration = "0.5.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -76,7 +76,7 @@ mod decl {
|
||||
let mut unhex = Vec::<u8>::with_capacity(hex_bytes.len() / 2);
|
||||
for (n1, n2) in hex_bytes.iter().tuples() {
|
||||
if let (Some(n1), Some(n2)) = (unhex_nibble(*n1), unhex_nibble(*n2)) {
|
||||
unhex.push(n1 << 4 | n2);
|
||||
unhex.push((n1 << 4) | n2);
|
||||
} else {
|
||||
return Err(super::new_binascii_error(
|
||||
"Non-hexadecimal digit found".to_owned(),
|
||||
@@ -343,7 +343,7 @@ mod decl {
|
||||
if let (Some(ch1), Some(ch2)) =
|
||||
(unhex_nibble(buffer[idx]), unhex_nibble(buffer[idx + 1]))
|
||||
{
|
||||
out_data.push(ch1 << 4 | ch2);
|
||||
out_data.push((ch1 << 4) | ch2);
|
||||
}
|
||||
idx += 2;
|
||||
} else {
|
||||
@@ -661,19 +661,19 @@ mod decl {
|
||||
};
|
||||
|
||||
if res.len() < length {
|
||||
res.push(char_a << 2 | char_b >> 4);
|
||||
res.push((char_a << 2) | (char_b >> 4));
|
||||
} else if char_a != 0 || char_b != 0 {
|
||||
return trailing_garbage_error();
|
||||
}
|
||||
|
||||
if res.len() < length {
|
||||
res.push((char_b & 0xf) << 4 | char_c >> 2);
|
||||
res.push(((char_b & 0xf) << 4) | (char_c >> 2));
|
||||
} else if char_c != 0 {
|
||||
return trailing_garbage_error();
|
||||
}
|
||||
|
||||
if res.len() < length {
|
||||
res.push((char_c & 0x3) << 6 | char_d);
|
||||
res.push(((char_c & 0x3) << 6) | char_d);
|
||||
} else if char_d != 0 {
|
||||
return trailing_garbage_error();
|
||||
}
|
||||
@@ -725,8 +725,8 @@ mod decl {
|
||||
let char_c = *chunk.get(2).unwrap_or(&0);
|
||||
|
||||
res.push(uu_b2a(char_a >> 2, backtick));
|
||||
res.push(uu_b2a((char_a & 0x3) << 4 | char_b >> 4, backtick));
|
||||
res.push(uu_b2a((char_b & 0xf) << 2 | char_c >> 6, backtick));
|
||||
res.push(uu_b2a(((char_a & 0x3) << 4) | (char_b >> 4), backtick));
|
||||
res.push(uu_b2a(((char_b & 0xf) << 2) | (char_c >> 6), backtick));
|
||||
res.push(uu_b2a(char_c & 0x3f, backtick));
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,9 @@ mod math {
|
||||
#[pyfunction]
|
||||
fn log(x: PyObjectRef, base: OptionalArg<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
let base = base.map(|b| *b).unwrap_or(std::f64::consts::E);
|
||||
if base.is_sign_negative() {
|
||||
return Err(vm.new_value_error("math domain error".to_owned()));
|
||||
}
|
||||
log2(x, vm).map(|logx| logx / base.log2())
|
||||
}
|
||||
|
||||
@@ -192,16 +195,18 @@ mod math {
|
||||
let x = *x;
|
||||
let y = *y;
|
||||
|
||||
if x < 0.0 && x.is_finite() && y.fract() != 0.0 && y.is_finite() {
|
||||
return Err(vm.new_value_error("math domain error".to_owned()));
|
||||
}
|
||||
|
||||
if x == 0.0 && y < 0.0 && y != f64::NEG_INFINITY {
|
||||
if x < 0.0 && x.is_finite() && y.fract() != 0.0 && y.is_finite()
|
||||
|| x == 0.0 && y < 0.0 && y != f64::NEG_INFINITY
|
||||
{
|
||||
return Err(vm.new_value_error("math domain error".to_owned()));
|
||||
}
|
||||
|
||||
let value = x.powf(y);
|
||||
|
||||
if x.is_finite() && y.is_finite() && value.is_infinite() {
|
||||
return Err(vm.new_overflow_error("math range error".to_string()));
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
@@ -212,6 +217,9 @@ mod math {
|
||||
return Ok(value);
|
||||
}
|
||||
if value.is_sign_negative() {
|
||||
if value.is_zero() {
|
||||
return Ok(-0.0f64);
|
||||
}
|
||||
return Err(vm.new_value_error("math domain error".to_owned()));
|
||||
}
|
||||
Ok(value.sqrt())
|
||||
@@ -260,6 +268,9 @@ mod math {
|
||||
|
||||
#[pyfunction]
|
||||
fn cos(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
if x.is_infinite() {
|
||||
return Err(vm.new_value_error("math domain error".to_owned()));
|
||||
}
|
||||
call_math_func!(cos, x, vm)
|
||||
}
|
||||
|
||||
@@ -345,7 +356,7 @@ mod math {
|
||||
.map(|x| (x / scale).powi(2))
|
||||
.chain(std::iter::once(-norm * norm))
|
||||
// Pairwise summation of floats gives less rounding error than a naive sum.
|
||||
.tree_fold1(std::ops::Add::add)
|
||||
.tree_reduce(std::ops::Add::add)
|
||||
.expect("expected at least 1 element");
|
||||
norm = norm + correction / (2.0 * norm);
|
||||
}
|
||||
@@ -394,11 +405,17 @@ mod math {
|
||||
|
||||
#[pyfunction]
|
||||
fn sin(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
if x.is_infinite() {
|
||||
return Err(vm.new_value_error("math domain error".to_owned()));
|
||||
}
|
||||
call_math_func!(sin, x, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn tan(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
if x.is_infinite() {
|
||||
return Err(vm.new_value_error("math domain error".to_owned()));
|
||||
}
|
||||
call_math_func!(tan, x, vm)
|
||||
}
|
||||
|
||||
@@ -612,7 +629,7 @@ mod math {
|
||||
|
||||
#[pyfunction]
|
||||
fn fsum(seq: ArgIterable<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
let mut partials = vec![];
|
||||
let mut partials = Vec::with_capacity(32);
|
||||
let mut special_sum = 0.0;
|
||||
let mut inf_sum = 0.0;
|
||||
|
||||
@@ -620,11 +637,11 @@ mod math {
|
||||
let mut x = *obj?;
|
||||
|
||||
let xsave = x;
|
||||
let mut j = 0;
|
||||
let mut i = 0;
|
||||
// This inner loop applies `hi`/`lo` summation to each
|
||||
// partial so that the list of partial sums remains exact.
|
||||
for i in 0..partials.len() {
|
||||
let mut y: f64 = partials[i];
|
||||
for j in 0..partials.len() {
|
||||
let mut y: f64 = partials[j];
|
||||
if x.abs() < y.abs() {
|
||||
std::mem::swap(&mut x, &mut y);
|
||||
}
|
||||
@@ -633,33 +650,33 @@ mod math {
|
||||
let hi = x + y;
|
||||
let lo = y - (hi - x);
|
||||
if lo != 0.0 {
|
||||
partials[j] = lo;
|
||||
j += 1;
|
||||
partials[i] = lo;
|
||||
i += 1;
|
||||
}
|
||||
x = hi;
|
||||
}
|
||||
|
||||
if !x.is_finite() {
|
||||
// a nonfinite x could arise either as
|
||||
// a result of intermediate overflow, or
|
||||
// as a result of a nan or inf in the
|
||||
// summands
|
||||
if xsave.is_finite() {
|
||||
return Err(vm.new_overflow_error("intermediate overflow in fsum".to_owned()));
|
||||
partials.truncate(i);
|
||||
if x != 0.0 {
|
||||
if !x.is_finite() {
|
||||
// a nonfinite x could arise either as
|
||||
// a result of intermediate overflow, or
|
||||
// as a result of a nan or inf in the
|
||||
// summands
|
||||
if xsave.is_finite() {
|
||||
return Err(
|
||||
vm.new_overflow_error("intermediate overflow in fsum".to_owned())
|
||||
);
|
||||
}
|
||||
if xsave.is_infinite() {
|
||||
inf_sum += xsave;
|
||||
}
|
||||
special_sum += xsave;
|
||||
// reset partials
|
||||
partials.clear();
|
||||
} else {
|
||||
partials.push(x);
|
||||
}
|
||||
if xsave.is_infinite() {
|
||||
inf_sum += xsave;
|
||||
}
|
||||
special_sum += xsave;
|
||||
// reset partials
|
||||
partials.clear();
|
||||
}
|
||||
|
||||
if j >= partials.len() {
|
||||
partials.push(x);
|
||||
} else {
|
||||
partials[j] = x;
|
||||
partials.truncate(j + 1);
|
||||
}
|
||||
}
|
||||
if special_sum != 0.0 {
|
||||
@@ -814,9 +831,38 @@ mod math {
|
||||
(x.fract(), x.trunc())
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct NextAfterArgs {
|
||||
#[pyarg(positional)]
|
||||
x: ArgIntoFloat,
|
||||
#[pyarg(positional)]
|
||||
y: ArgIntoFloat,
|
||||
#[pyarg(named, optional)]
|
||||
steps: OptionalArg<ArgIndex>,
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn nextafter(x: ArgIntoFloat, y: ArgIntoFloat) -> f64 {
|
||||
float_ops::nextafter(*x, *y)
|
||||
fn nextafter(arg: NextAfterArgs, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
let steps: Option<i64> = arg
|
||||
.steps
|
||||
.map(|v| v.try_to_primitive(vm))
|
||||
.transpose()?
|
||||
.into_option();
|
||||
match steps {
|
||||
Some(steps) => {
|
||||
if steps < 0 {
|
||||
return Err(
|
||||
vm.new_value_error("steps must be a non-negative integer".to_string())
|
||||
);
|
||||
}
|
||||
Ok(float_ops::nextafter_with_steps(
|
||||
*arg.x,
|
||||
*arg.y,
|
||||
steps as u64,
|
||||
))
|
||||
}
|
||||
None => Ok(float_ops::nextafter(*arg.x, *arg.y)),
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
@@ -900,10 +946,38 @@ mod math {
|
||||
// refer: https://github.com/python/cpython/blob/main/Modules/mathmodule.c#L3093-L3193
|
||||
for obj in iter.iter(vm)? {
|
||||
let obj = obj?;
|
||||
result = vm._mul(&result, &obj)?;
|
||||
}
|
||||
|
||||
result = vm
|
||||
._mul(&result, &obj)
|
||||
.map_err(|_| vm.new_type_error("math type error".to_owned()))?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn sumprod(
|
||||
p: ArgIterable<PyObjectRef>,
|
||||
q: ArgIterable<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyObjectRef> {
|
||||
let mut p_iter = p.iter(vm)?;
|
||||
let mut q_iter = q.iter(vm)?;
|
||||
// We cannot just create a float because the iterator may contain
|
||||
// anything as long as it supports __add__ and __mul__.
|
||||
let mut result = vm.new_pyobj(0);
|
||||
loop {
|
||||
let m_p = p_iter.next();
|
||||
let m_q = q_iter.next();
|
||||
match (m_p, m_q) {
|
||||
(Some(r_p), Some(r_q)) => {
|
||||
let p = r_p?;
|
||||
let q = r_q?;
|
||||
let tmp = vm._mul(&p, &q)?;
|
||||
result = vm._add(&result, &tmp)?;
|
||||
}
|
||||
(None, None) => break,
|
||||
_ => {
|
||||
return Err(vm.new_value_error("Inputs are not the same length".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
|
||||
@@ -23,7 +23,7 @@ mod _random {
|
||||
|
||||
impl Default for PyRng {
|
||||
fn default() -> Self {
|
||||
PyRng::Std(Box::new(StdRng::from_entropy()))
|
||||
PyRng::Std(Box::new(StdRng::from_os_rng()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +46,6 @@ mod _random {
|
||||
Self::MT(m) => m.fill_bytes(dest),
|
||||
}
|
||||
}
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
|
||||
match self {
|
||||
Self::Std(s) => s.try_fill_bytes(dest),
|
||||
Self::MT(m) => m.try_fill_bytes(dest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
|
||||
@@ -612,7 +612,11 @@ mod _ssl {
|
||||
Ok(pbuf.to_vec())
|
||||
})?;
|
||||
ctx.set_alpn_select_callback(move |_, client| {
|
||||
ssl::select_next_proto(&server, client).ok_or(ssl::AlpnError::NOACK)
|
||||
let proto =
|
||||
ssl::select_next_proto(&server, client).ok_or(ssl::AlpnError::NOACK)?;
|
||||
let pos = memchr::memmem::find(client, proto)
|
||||
.expect("selected alpn proto should be present in client protos");
|
||||
Ok(&client[pos..proto.len()])
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,33 +5,19 @@ mod _uuid {
|
||||
use crate::{builtins::PyNone, vm::VirtualMachine};
|
||||
use mac_address::get_mac_address;
|
||||
use once_cell::sync::OnceCell;
|
||||
use rand::Rng;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use uuid::{
|
||||
v1::{Context, Timestamp},
|
||||
Uuid,
|
||||
};
|
||||
use uuid::{timestamp::Timestamp, Context, Uuid};
|
||||
|
||||
fn get_node_id() -> [u8; 6] {
|
||||
match get_mac_address() {
|
||||
Ok(Some(_ma)) => get_mac_address().unwrap().unwrap().bytes(),
|
||||
_ => rand::thread_rng().gen::<[u8; 6]>(),
|
||||
_ => rand::random::<[u8; 6]>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn now_unix_duration() -> Duration {
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
let now = SystemTime::now();
|
||||
now.duration_since(UNIX_EPOCH)
|
||||
.expect("SystemTime before UNIX EPOCH!")
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn generate_time_safe() -> (Vec<u8>, PyNone) {
|
||||
static CONTEXT: Context = Context::new(0);
|
||||
let now = now_unix_duration();
|
||||
let ts = Timestamp::from_unix(&CONTEXT, now.as_secs(), now.subsec_nanos());
|
||||
let ts = Timestamp::now(&CONTEXT);
|
||||
|
||||
static NODE_ID: OnceCell<[u8; 6]> = OnceCell::new();
|
||||
let unique_node_id = NODE_ID.get_or_init(get_node_id);
|
||||
|
||||
@@ -23,7 +23,7 @@ ast = ["rustpython-ast"]
|
||||
codegen = ["rustpython-codegen", "ast"]
|
||||
parser = ["rustpython-parser", "ast"]
|
||||
serde = ["dep:serde"]
|
||||
wasmbind = ["chrono/wasmbind", "getrandom/js", "wasm-bindgen"]
|
||||
wasmbind = ["chrono/wasmbind", "getrandom/wasm_js", "wasm-bindgen"]
|
||||
|
||||
[dependencies]
|
||||
rustpython-compiler = { workspace = true, optional = true }
|
||||
@@ -99,6 +99,9 @@ uname = "0.1.1"
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rustyline = { workspace = true }
|
||||
which = "6"
|
||||
errno = "0.3"
|
||||
libloading = "0.8"
|
||||
widestring = { workspace = true }
|
||||
|
||||
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies]
|
||||
num_cpus = "1.13.1"
|
||||
@@ -106,8 +109,7 @@ num_cpus = "1.13.1"
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
junction = { workspace = true }
|
||||
schannel = { workspace = true }
|
||||
widestring = { workspace = true }
|
||||
winreg = "0.52"
|
||||
winreg = "0.55"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.52.0"
|
||||
@@ -115,6 +117,7 @@ features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_Time",
|
||||
"Win32_UI_Shell",
|
||||
]
|
||||
|
||||
@@ -122,6 +125,7 @@ features = [
|
||||
workspace = true
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Networking_WinSock",
|
||||
"Win32_Security",
|
||||
"Win32_Storage_FileSystem",
|
||||
@@ -143,7 +147,7 @@ features = [
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
getrandom = { workspace = true, features = ["custom"] }
|
||||
getrandom = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
glob = { workspace = true }
|
||||
|
||||
@@ -124,8 +124,8 @@ fn inner_divmod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<(f64, f64)> {
|
||||
|
||||
pub fn float_pow(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult {
|
||||
if v1.is_zero() && v2.is_sign_negative() {
|
||||
let msg = format!("{v1} cannot be raised to a negative power");
|
||||
Err(vm.new_zero_division_error(msg))
|
||||
let msg = "0.0 cannot be raised to a negative power";
|
||||
Err(vm.new_zero_division_error(msg.to_owned()))
|
||||
} else if v1.is_sign_negative() && (v2.floor() - v2).abs() > f64::EPSILON {
|
||||
let v1 = Complex64::new(v1, 0.);
|
||||
let v2 = Complex64::new(v2, 0.);
|
||||
|
||||
@@ -113,7 +113,7 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine)
|
||||
// ));
|
||||
// }
|
||||
|
||||
let state = if obj.dict().map_or(true, |d| d.is_empty()) {
|
||||
let state = if obj.dict().is_none_or(|d| d.is_empty()) {
|
||||
vm.ctx.none()
|
||||
} else {
|
||||
// let state = object_get_dict(obj.clone(), obj.ctx()).unwrap();
|
||||
|
||||
@@ -1125,33 +1125,7 @@ impl PyStr {
|
||||
|
||||
#[pymethod]
|
||||
fn expandtabs(&self, args: anystr::ExpandTabsArgs) -> String {
|
||||
let tab_stop = args.tabsize();
|
||||
let mut expanded_str = String::with_capacity(self.byte_len());
|
||||
let mut tab_size = tab_stop;
|
||||
let mut col_count = 0usize;
|
||||
for ch in self.as_str().chars() {
|
||||
match ch {
|
||||
'\t' => {
|
||||
let num_spaces = tab_size - col_count;
|
||||
col_count += num_spaces;
|
||||
let expand = " ".repeat(num_spaces);
|
||||
expanded_str.push_str(&expand);
|
||||
}
|
||||
'\r' | '\n' => {
|
||||
expanded_str.push(ch);
|
||||
col_count = 0;
|
||||
tab_size = 0;
|
||||
}
|
||||
_ => {
|
||||
expanded_str.push(ch);
|
||||
col_count += 1;
|
||||
}
|
||||
}
|
||||
if col_count >= tab_size {
|
||||
tab_size += tab_stop;
|
||||
}
|
||||
}
|
||||
expanded_str
|
||||
rustpython_common::str::expandtabs(self.as_str(), args.tabsize())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
@@ -1591,80 +1565,6 @@ impl AsRef<str> for PyExact<PyStr> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Interpreter;
|
||||
|
||||
#[test]
|
||||
fn str_title() {
|
||||
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 ..."),
|
||||
("Greek ῼitlecases ...", "greek ῳitlecases ..."),
|
||||
];
|
||||
for (title, input) in tests {
|
||||
assert_eq!(PyStr::from(input).title().as_str(), title);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_istitle() {
|
||||
let pos = vec![
|
||||
"A",
|
||||
"A Titlecased Line",
|
||||
"A\nTitlecased Line",
|
||||
"A Titlecased, Line",
|
||||
"Greek Ωppercases ...",
|
||||
"Greek ῼitlecases ...",
|
||||
];
|
||||
|
||||
for s in pos {
|
||||
assert!(PyStr::from(s).istitle());
|
||||
}
|
||||
|
||||
let neg = vec![
|
||||
"",
|
||||
"a",
|
||||
"\n",
|
||||
"Not a capitalized String",
|
||||
"Not\ta Titlecase String",
|
||||
"Not--a Titlecase String",
|
||||
"NOT",
|
||||
];
|
||||
for s in neg {
|
||||
assert!(!PyStr::from(s).istitle());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_maketrans_and_translate() {
|
||||
Interpreter::without_stdlib(Default::default()).enter(|vm| {
|
||||
let table = vm.ctx.new_dict();
|
||||
table
|
||||
.set_item("a", vm.ctx.new_str("🎅").into(), vm)
|
||||
.unwrap();
|
||||
table.set_item("b", vm.ctx.none(), vm).unwrap();
|
||||
table
|
||||
.set_item("c", vm.ctx.new_str(ascii!("xda")).into(), vm)
|
||||
.unwrap();
|
||||
let translated =
|
||||
PyStr::maketrans(table.into(), OptionalArg::Missing, OptionalArg::Missing, vm)
|
||||
.unwrap();
|
||||
let text = PyStr::from("abc");
|
||||
let translated = text.translate(translated, vm).unwrap();
|
||||
assert_eq!(translated, "🎅xda".to_owned());
|
||||
let translated = text.translate(vm.ctx.new_int(3).into(), vm);
|
||||
assert_eq!("TypeError", &*translated.unwrap_err().class().name(),);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyStrWrapper for PyStrRef {
|
||||
type Str = str;
|
||||
fn as_ref(&self) -> &str {
|
||||
@@ -1806,3 +1706,77 @@ impl AsRef<str> for PyStrInterned {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Interpreter;
|
||||
|
||||
#[test]
|
||||
fn str_title() {
|
||||
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 ..."),
|
||||
("Greek ῼitlecases ...", "greek ῳitlecases ..."),
|
||||
];
|
||||
for (title, input) in tests {
|
||||
assert_eq!(PyStr::from(input).title().as_str(), title);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_istitle() {
|
||||
let pos = vec![
|
||||
"A",
|
||||
"A Titlecased Line",
|
||||
"A\nTitlecased Line",
|
||||
"A Titlecased, Line",
|
||||
"Greek Ωppercases ...",
|
||||
"Greek ῼitlecases ...",
|
||||
];
|
||||
|
||||
for s in pos {
|
||||
assert!(PyStr::from(s).istitle());
|
||||
}
|
||||
|
||||
let neg = vec![
|
||||
"",
|
||||
"a",
|
||||
"\n",
|
||||
"Not a capitalized String",
|
||||
"Not\ta Titlecase String",
|
||||
"Not--a Titlecase String",
|
||||
"NOT",
|
||||
];
|
||||
for s in neg {
|
||||
assert!(!PyStr::from(s).istitle());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_maketrans_and_translate() {
|
||||
Interpreter::without_stdlib(Default::default()).enter(|vm| {
|
||||
let table = vm.ctx.new_dict();
|
||||
table
|
||||
.set_item("a", vm.ctx.new_str("🎅").into(), vm)
|
||||
.unwrap();
|
||||
table.set_item("b", vm.ctx.none(), vm).unwrap();
|
||||
table
|
||||
.set_item("c", vm.ctx.new_str(ascii!("xda")).into(), vm)
|
||||
.unwrap();
|
||||
let translated =
|
||||
PyStr::maketrans(table.into(), OptionalArg::Missing, OptionalArg::Missing, vm)
|
||||
.unwrap();
|
||||
let text = PyStr::from("abc");
|
||||
let translated = text.translate(translated, vm).unwrap();
|
||||
assert_eq!(translated, "🎅xda".to_owned());
|
||||
let translated = text.translate(vm.ctx.new_int(3).into(), vm);
|
||||
assert_eq!("TypeError", &*translated.unwrap_err().class().name(),);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,11 +619,16 @@ fn surrogatepass_errors(err: PyObjectRef, vm: &VirtualMachine) -> PyResult<(PyOb
|
||||
// Not supported, fail with original exception
|
||||
return Err(err.downcast().unwrap());
|
||||
}
|
||||
|
||||
debug_assert!(range.start <= 0.max(s.len() - 1));
|
||||
debug_assert!(range.end >= 1.min(s.len()));
|
||||
debug_assert!(range.end <= s.len());
|
||||
|
||||
let mut c: u32 = 0;
|
||||
// Try decoding a single surrogate character. If there are more,
|
||||
// let the codec call us again.
|
||||
let p = &s.as_bytes()[range.start..];
|
||||
if p.len() - range.start >= byte_length {
|
||||
if p.len().overflowing_sub(range.start).0 >= byte_length {
|
||||
match standard_encoding {
|
||||
StandardEncoding::Utf8 => {
|
||||
if (p[0] as u32 & 0xf0) == 0xe0
|
||||
@@ -637,10 +642,10 @@ fn surrogatepass_errors(err: PyObjectRef, vm: &VirtualMachine) -> PyResult<(PyOb
|
||||
}
|
||||
}
|
||||
StandardEncoding::Utf16Le => {
|
||||
c = (p[1] as u32) << 8 | p[0] as u32;
|
||||
c = ((p[1] as u32) << 8) | p[0] as u32;
|
||||
}
|
||||
StandardEncoding::Utf16Be => {
|
||||
c = (p[0] as u32) << 8 | p[1] as u32;
|
||||
c = ((p[0] as u32) << 8) | p[1] as u32;
|
||||
}
|
||||
StandardEncoding::Utf32Le => {
|
||||
c = ((p[3] as u32) << 24)
|
||||
|
||||
@@ -374,7 +374,9 @@ fn write_traceback_entry<W: Write>(
|
||||
writeln!(
|
||||
output,
|
||||
r##" File "{}", line {}, in {}"##,
|
||||
filename, tb_entry.lineno, tb_entry.frame.code.obj_name
|
||||
filename.trim_start_matches(r"\\?\"),
|
||||
tb_entry.lineno,
|
||||
tb_entry.frame.code.obj_name
|
||||
)?;
|
||||
print_source_line(output, filename, tb_entry.lineno.to_usize())?;
|
||||
|
||||
|
||||
@@ -1202,6 +1202,23 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(type_alias.into_ref(&vm.ctx).into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::ParamSpec => {
|
||||
let param_spec_name = self.pop_value();
|
||||
let param_spec: PyObjectRef = _typing::make_paramspec(param_spec_name.clone())
|
||||
.into_ref(&vm.ctx)
|
||||
.into();
|
||||
self.push_value(param_spec);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::TypeVarTuple => {
|
||||
let type_var_tuple_name = self.pop_value();
|
||||
let type_var_tuple: PyObjectRef =
|
||||
_typing::make_typevartuple(type_var_tuple_name.clone())
|
||||
.into_ref(&vm.ctx)
|
||||
.into();
|
||||
self.push_value(type_var_tuple);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::{
|
||||
vm::{thread, VirtualMachine},
|
||||
AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
|
||||
};
|
||||
use rand::Rng;
|
||||
|
||||
pub(crate) fn init_importlib_base(vm: &mut VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
flame_guard!("init importlib");
|
||||
@@ -50,7 +49,7 @@ pub(crate) fn init_importlib_package(vm: &VirtualMachine, importlib: PyObjectRef
|
||||
let mut magic = get_git_revision().into_bytes();
|
||||
magic.truncate(4);
|
||||
if magic.len() != 4 {
|
||||
magic = rand::thread_rng().gen::<[u8; 4]>().to_vec();
|
||||
magic = rand::random::<[u8; 4]>().to_vec();
|
||||
}
|
||||
let magic: PyObjectRef = vm.ctx.new_bytes(magic).into();
|
||||
importlib_external.set_attr("MAGIC_NUMBER", magic, vm)?;
|
||||
|
||||
226
vm/src/stdlib/ctypes.rs
Normal file
226
vm/src/stdlib/ctypes.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
pub(crate) mod array;
|
||||
pub(crate) mod base;
|
||||
pub(crate) mod function;
|
||||
pub(crate) mod library;
|
||||
pub(crate) mod pointer;
|
||||
pub(crate) mod structure;
|
||||
pub(crate) mod union;
|
||||
|
||||
use crate::builtins::PyModule;
|
||||
use crate::class::PyClassImpl;
|
||||
use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PySimpleMeta};
|
||||
use crate::{Py, PyRef, VirtualMachine};
|
||||
|
||||
pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) {
|
||||
let ctx = &vm.ctx;
|
||||
PySimpleMeta::make_class(ctx);
|
||||
extend_module!(vm, module, {
|
||||
"_CData" => PyCData::make_class(ctx),
|
||||
"_SimpleCData" => PyCSimple::make_class(ctx),
|
||||
"Array" => array::PyCArray::make_class(ctx),
|
||||
"CFuncPtr" => function::PyCFuncPtr::make_class(ctx),
|
||||
"_Pointer" => pointer::PyCPointer::make_class(ctx),
|
||||
"_pointer_type_cache" => ctx.new_dict(),
|
||||
"Structure" => structure::PyCStructure::make_class(ctx),
|
||||
"Union" => union::PyCUnion::make_class(ctx),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
|
||||
let module = _ctypes::make_module(vm);
|
||||
extend_module_nodes(vm, &module);
|
||||
module
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
pub(crate) mod _ctypes {
|
||||
use super::base::PyCSimple;
|
||||
use crate::builtins::PyTypeRef;
|
||||
use crate::class::StaticType;
|
||||
use crate::function::Either;
|
||||
use crate::stdlib::ctypes::library;
|
||||
use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use std::ffi::{
|
||||
c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong,
|
||||
c_ulonglong,
|
||||
};
|
||||
use std::mem;
|
||||
use widestring::WideChar;
|
||||
|
||||
#[pyattr(name = "__version__")]
|
||||
const __VERSION__: &str = "1.1.0";
|
||||
|
||||
// TODO: get properly
|
||||
#[pyattr(name = "RTLD_LOCAL")]
|
||||
const RTLD_LOCAL: i32 = 0;
|
||||
|
||||
// TODO: get properly
|
||||
#[pyattr(name = "RTLD_GLOBAL")]
|
||||
const RTLD_GLOBAL: i32 = 0;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[pyattr(name = "SIZEOF_TIME_T")]
|
||||
pub const SIZEOF_TIME_T: usize = 8;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[pyattr(name = "SIZEOF_TIME_T")]
|
||||
pub const SIZEOF_TIME_T: usize = 4;
|
||||
|
||||
#[pyattr(name = "CTYPES_MAX_ARGCOUNT")]
|
||||
pub const CTYPES_MAX_ARGCOUNT: usize = 1024;
|
||||
|
||||
#[pyattr]
|
||||
pub const FUNCFLAG_STDCALL: u32 = 0x0;
|
||||
#[pyattr]
|
||||
pub const FUNCFLAG_CDECL: u32 = 0x1;
|
||||
#[pyattr]
|
||||
pub const FUNCFLAG_HRESULT: u32 = 0x2;
|
||||
#[pyattr]
|
||||
pub const FUNCFLAG_PYTHONAPI: u32 = 0x4;
|
||||
#[pyattr]
|
||||
pub const FUNCFLAG_USE_ERRNO: u32 = 0x8;
|
||||
#[pyattr]
|
||||
pub const FUNCFLAG_USE_LASTERROR: u32 = 0x10;
|
||||
|
||||
#[pyattr]
|
||||
pub const TYPEFLAG_ISPOINTER: u32 = 0x100;
|
||||
#[pyattr]
|
||||
pub const TYPEFLAG_HASPOINTER: u32 = 0x200;
|
||||
|
||||
#[pyattr]
|
||||
pub const DICTFLAG_FINAL: u32 = 0x1000;
|
||||
|
||||
#[pyattr(name = "ArgumentError", once)]
|
||||
fn argument_error(vm: &VirtualMachine) -> PyTypeRef {
|
||||
vm.ctx.new_exception_type(
|
||||
"_ctypes",
|
||||
"ArgumentError",
|
||||
Some(vec![vm.ctx.exceptions.exception_type.to_owned()]),
|
||||
)
|
||||
}
|
||||
|
||||
#[pyattr(name = "FormatError", once)]
|
||||
fn format_error(vm: &VirtualMachine) -> PyTypeRef {
|
||||
vm.ctx.new_exception_type(
|
||||
"_ctypes",
|
||||
"FormatError",
|
||||
Some(vec![vm.ctx.exceptions.exception_type.to_owned()]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_size(ty: &str) -> usize {
|
||||
match ty {
|
||||
"u" => mem::size_of::<WideChar>(),
|
||||
"c" | "b" => mem::size_of::<c_schar>(),
|
||||
"h" => mem::size_of::<c_short>(),
|
||||
"H" => mem::size_of::<c_short>(),
|
||||
"i" => mem::size_of::<c_int>(),
|
||||
"I" => mem::size_of::<c_uint>(),
|
||||
"l" => mem::size_of::<c_long>(),
|
||||
"q" => mem::size_of::<c_longlong>(),
|
||||
"L" => mem::size_of::<c_ulong>(),
|
||||
"Q" => mem::size_of::<c_ulonglong>(),
|
||||
"f" => mem::size_of::<c_float>(),
|
||||
"d" | "g" => mem::size_of::<c_double>(),
|
||||
"?" | "B" => mem::size_of::<c_uchar>(),
|
||||
"P" | "z" | "Z" => mem::size_of::<usize>(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?";
|
||||
|
||||
pub fn new_simple_type(
|
||||
cls: Either<&PyObjectRef, &PyTypeRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyCSimple> {
|
||||
let cls = match cls {
|
||||
Either::A(obj) => obj,
|
||||
Either::B(typ) => typ.as_object(),
|
||||
};
|
||||
|
||||
if let Ok(_type_) = cls.get_attr("_type_", vm) {
|
||||
if _type_.is_instance((&vm.ctx.types.str_type).as_ref(), vm)? {
|
||||
let tp_str = _type_.str(vm)?.to_string();
|
||||
|
||||
if tp_str.len() != 1 {
|
||||
Err(vm.new_value_error(
|
||||
format!("class must define a '_type_' attribute which must be a string of length 1, str: {tp_str}"),
|
||||
))
|
||||
} else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) {
|
||||
Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\n a single character string containing one of {SIMPLE_TYPE_CHARS}, currently it is {tp_str}.")))
|
||||
} else {
|
||||
Ok(PyCSimple {
|
||||
_type_: tp_str,
|
||||
value: AtomicCell::new(vm.ctx.none()),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(vm.new_type_error("class must define a '_type_' string attribute".to_string()))
|
||||
}
|
||||
} else {
|
||||
Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction(name = "sizeof")]
|
||||
pub fn size_of(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> {
|
||||
match tp {
|
||||
Either::A(type_) if type_.fast_issubclass(PyCSimple::static_type()) => {
|
||||
let zelf = new_simple_type(Either::B(&type_), vm)?;
|
||||
Ok(get_size(zelf._type_.as_str()))
|
||||
}
|
||||
Either::B(obj) if obj.has_attr("size_of_instances", vm)? => {
|
||||
let size_of_method = obj.get_attr("size_of_instances", vm)?;
|
||||
let size_of_return = size_of_method.call(vec![], vm)?;
|
||||
Ok(usize::try_from_object(vm, size_of_return)?)
|
||||
}
|
||||
_ => Err(vm.new_type_error("this type has no size".to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction(name = "LoadLibrary")]
|
||||
fn load_library(name: String, vm: &VirtualMachine) -> PyResult<usize> {
|
||||
// TODO: audit functions first
|
||||
let cache = library::libcache();
|
||||
let mut cache_write = cache.write();
|
||||
let lib_ref = cache_write.get_or_insert_lib(&name, vm).unwrap();
|
||||
Ok(lib_ref.get_pointer())
|
||||
}
|
||||
|
||||
#[pyfunction(name = "FreeLibrary")]
|
||||
fn free_library(handle: usize) -> PyResult<()> {
|
||||
let cache = library::libcache();
|
||||
let mut cache_write = cache.write();
|
||||
cache_write.drop_lib(handle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pyfunction(name = "POINTER")]
|
||||
pub fn pointer(_cls: PyTypeRef) {}
|
||||
|
||||
#[pyfunction]
|
||||
pub fn pointer_fn(_inst: PyObjectRef) {}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[pyfunction(name = "_check_HRESULT")]
|
||||
pub fn check_hresult(_self: PyObjectRef, hr: i32, _vm: &VirtualMachine) -> PyResult<i32> {
|
||||
// TODO: fixme
|
||||
if hr < 0 {
|
||||
// vm.ctx.new_windows_error(hr)
|
||||
todo!();
|
||||
} else {
|
||||
Ok(hr)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn get_errno() -> i32 {
|
||||
errno::errno().0
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn set_errno(value: i32) {
|
||||
errno::set_errno(errno::Errno(value));
|
||||
}
|
||||
}
|
||||
5
vm/src/stdlib/ctypes/array.rs
Normal file
5
vm/src/stdlib/ctypes/array.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[pyclass(name = "Array", module = "_ctypes")]
|
||||
pub struct PyCArray {}
|
||||
|
||||
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
|
||||
impl PyCArray {}
|
||||
224
vm/src/stdlib/ctypes/base.rs
Normal file
224
vm/src/stdlib/ctypes/base.rs
Normal file
@@ -0,0 +1,224 @@
|
||||
use crate::builtins::PyType;
|
||||
use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyTypeRef};
|
||||
use crate::convert::ToPyObject;
|
||||
use crate::function::{Either, OptionalArg};
|
||||
use crate::stdlib::ctypes::_ctypes::new_simple_type;
|
||||
use crate::types::Constructor;
|
||||
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use num_traits::ToPrimitive;
|
||||
use rustpython_common::lock::PyRwLock;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
match _type_ {
|
||||
"c" => {
|
||||
if value
|
||||
.clone()
|
||||
.downcast_exact::<PyBytes>(vm)
|
||||
.is_ok_and(|v| v.len() == 1)
|
||||
|| value
|
||||
.clone()
|
||||
.downcast_exact::<PyBytes>(vm)
|
||||
.is_ok_and(|v| v.len() == 1)
|
||||
|| value
|
||||
.clone()
|
||||
.downcast_exact::<PyInt>(vm)
|
||||
.map_or(Ok(false), |v| {
|
||||
let n = v.as_bigint().to_i64();
|
||||
if let Some(n) = n {
|
||||
Ok((0..=255).contains(&n))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?
|
||||
{
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error(
|
||||
"one character bytes, bytearray or integer expected".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
"u" => {
|
||||
if let Ok(b) = value.str(vm).map(|v| v.to_string().chars().count() == 1) {
|
||||
if b {
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error("one character unicode string expected".to_string()))
|
||||
}
|
||||
} else {
|
||||
Err(vm.new_type_error(format!(
|
||||
"unicode string expected instead of {} instance",
|
||||
value.class().name()
|
||||
)))
|
||||
}
|
||||
}
|
||||
"b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => {
|
||||
if value.clone().downcast_exact::<PyInt>(vm).is_ok() {
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error(format!(
|
||||
"an integer is required (got type {})",
|
||||
value.class().name()
|
||||
)))
|
||||
}
|
||||
}
|
||||
"f" | "d" | "g" => {
|
||||
if value.clone().downcast_exact::<PyFloat>(vm).is_ok() {
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error(format!("must be real number, not {}", value.class().name())))
|
||||
}
|
||||
}
|
||||
"?" => Ok(PyObjectRef::from(
|
||||
vm.ctx.new_bool(value.clone().try_to_bool(vm)?),
|
||||
)),
|
||||
"B" => {
|
||||
if value.clone().downcast_exact::<PyInt>(vm).is_ok() {
|
||||
Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone())?))
|
||||
} else {
|
||||
Err(vm.new_type_error(format!("int expected instead of {}", value.class().name())))
|
||||
}
|
||||
}
|
||||
"z" => {
|
||||
if value.clone().downcast_exact::<PyInt>(vm).is_ok()
|
||||
|| value.clone().downcast_exact::<PyBytes>(vm).is_ok()
|
||||
{
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error(format!(
|
||||
"bytes or integer address expected instead of {} instance",
|
||||
value.class().name()
|
||||
)))
|
||||
}
|
||||
}
|
||||
"Z" => {
|
||||
if value.clone().downcast_exact::<PyStr>(vm).is_ok() {
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error(format!(
|
||||
"unicode string or integer address expected instead of {} instance",
|
||||
value.class().name()
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// "P"
|
||||
if value.clone().downcast_exact::<PyInt>(vm).is_ok()
|
||||
|| value.clone().downcast_exact::<PyNone>(vm).is_ok()
|
||||
{
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error("cannot be converted to pointer".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawBuffer {
|
||||
#[allow(dead_code)]
|
||||
pub inner: Box<[u8]>,
|
||||
#[allow(dead_code)]
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
#[pyclass(name = "_CData", module = "_ctypes")]
|
||||
pub struct PyCData {
|
||||
_objects: AtomicCell<Vec<PyObjectRef>>,
|
||||
_buffer: PyRwLock<RawBuffer>,
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
impl PyCData {}
|
||||
|
||||
#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = "PyType")]
|
||||
pub struct PySimpleMeta {}
|
||||
|
||||
#[pyclass(flags(BASETYPE))]
|
||||
impl PySimpleMeta {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
#[pymethod]
|
||||
fn new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult {
|
||||
Ok(PyObjectRef::from(
|
||||
new_simple_type(Either::B(&cls), vm)?
|
||||
.into_ref_with_type(vm, cls)?
|
||||
.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(
|
||||
name = "_SimpleCData",
|
||||
base = "PyCData",
|
||||
module = "_ctypes",
|
||||
metaclass = "PySimpleMeta"
|
||||
)]
|
||||
#[derive(PyPayload)]
|
||||
pub struct PyCSimple {
|
||||
pub _type_: String,
|
||||
pub value: AtomicCell<PyObjectRef>,
|
||||
}
|
||||
|
||||
impl Debug for PyCSimple {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PyCSimple")
|
||||
.field("_type_", &self._type_)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Constructor for PyCSimple {
|
||||
type Args = (OptionalArg,);
|
||||
|
||||
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
|
||||
let attributes = cls.get_attributes();
|
||||
let _type_ = attributes
|
||||
.iter()
|
||||
.find(|(&k, _)| k.to_object().str(vm).unwrap().to_string() == *"_type_")
|
||||
.unwrap()
|
||||
.1
|
||||
.str(vm)?
|
||||
.to_string();
|
||||
let value = if let Some(ref v) = args.0.into_option() {
|
||||
set_primitive(_type_.as_str(), v, vm)?
|
||||
} else {
|
||||
match _type_.as_str() {
|
||||
"c" | "u" => PyObjectRef::from(vm.ctx.new_bytes(vec![0])),
|
||||
"b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => {
|
||||
PyObjectRef::from(vm.ctx.new_int(0))
|
||||
}
|
||||
"f" | "d" | "g" => PyObjectRef::from(vm.ctx.new_float(0.0)),
|
||||
"?" => PyObjectRef::from(vm.ctx.new_bool(false)),
|
||||
_ => vm.ctx.none(), // "z" | "Z" | "P"
|
||||
}
|
||||
};
|
||||
Ok(PyCSimple {
|
||||
_type_,
|
||||
value: AtomicCell::new(value),
|
||||
}
|
||||
.to_pyobject(vm))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(flags(BASETYPE), with(Constructor))]
|
||||
impl PyCSimple {
|
||||
#[pygetset(name = "value")]
|
||||
pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
let zelf: &Py<Self> = instance
|
||||
.downcast_ref()
|
||||
.ok_or_else(|| vm.new_type_error("cannot get value of instance".to_string()))?;
|
||||
Ok(unsafe { (*zelf.value.as_ptr()).clone() })
|
||||
}
|
||||
|
||||
#[pygetset(name = "value", setter)]
|
||||
fn set_value(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let zelf: PyRef<Self> = instance
|
||||
.downcast()
|
||||
.map_err(|_| vm.new_type_error("cannot set value of instance".to_string()))?;
|
||||
let content = set_primitive(zelf._type_.as_str(), &value, vm)?;
|
||||
zelf.value.store(content);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
24
vm/src/stdlib/ctypes/function.rs
Normal file
24
vm/src/stdlib/ctypes/function.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use crate::stdlib::ctypes::PyCData;
|
||||
use crate::PyObjectRef;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use rustpython_common::lock::PyRwLock;
|
||||
use std::ffi::c_void;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Function {
|
||||
_pointer: *mut c_void,
|
||||
_arguments: Vec<()>,
|
||||
_return_type: Box<()>,
|
||||
}
|
||||
|
||||
#[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")]
|
||||
pub struct PyCFuncPtr {
|
||||
pub _name_: String,
|
||||
pub _argtypes_: AtomicCell<Vec<PyObjectRef>>,
|
||||
pub _restype_: AtomicCell<PyObjectRef>,
|
||||
_handle: PyObjectRef,
|
||||
_f: PyRwLock<Function>,
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
impl PyCFuncPtr {}
|
||||
115
vm/src/stdlib/ctypes/library.rs
Normal file
115
vm/src/stdlib/ctypes/library.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use crate::VirtualMachine;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use libloading::Library;
|
||||
use rustpython_common::lock::PyRwLock;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::c_void;
|
||||
use std::fmt;
|
||||
use std::ptr::null;
|
||||
|
||||
pub struct SharedLibrary {
|
||||
lib: AtomicCell<Option<Library>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for SharedLibrary {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "SharedLibrary")
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedLibrary {
|
||||
pub fn new(name: &str) -> Result<SharedLibrary, libloading::Error> {
|
||||
Ok(SharedLibrary {
|
||||
lib: AtomicCell::new(Some(unsafe { Library::new(name)? })),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_sym(&self, name: &str) -> Result<*mut c_void, String> {
|
||||
if let Some(inner) = unsafe { &*self.lib.as_ptr() } {
|
||||
unsafe {
|
||||
inner
|
||||
.get(name.as_bytes())
|
||||
.map(|f: libloading::Symbol<*mut c_void>| *f)
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
||||
} else {
|
||||
Err("The library has been closed".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pointer(&self) -> usize {
|
||||
if let Some(l) = unsafe { &*self.lib.as_ptr() } {
|
||||
l as *const Library as usize
|
||||
} else {
|
||||
null::<c_void>() as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_closed(&self) -> bool {
|
||||
unsafe { &*self.lib.as_ptr() }.is_none()
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
let old = self.lib.take();
|
||||
self.lib.store(None);
|
||||
drop(old);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SharedLibrary {
|
||||
fn drop(&mut self) {
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExternalLibs {
|
||||
libraries: HashMap<usize, SharedLibrary>,
|
||||
}
|
||||
|
||||
impl ExternalLibs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
libraries: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_lib(&self, key: usize) -> Option<&SharedLibrary> {
|
||||
self.libraries.get(&key)
|
||||
}
|
||||
|
||||
pub fn get_or_insert_lib(
|
||||
&mut self,
|
||||
library_path: &str,
|
||||
_vm: &VirtualMachine,
|
||||
) -> Result<&SharedLibrary, libloading::Error> {
|
||||
let nlib = SharedLibrary::new(library_path)?;
|
||||
let key = nlib.get_pointer();
|
||||
|
||||
match self.libraries.get(&key) {
|
||||
Some(l) => {
|
||||
if l.is_closed() {
|
||||
self.libraries.insert(key, nlib);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.libraries.insert(key, nlib);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(self.libraries.get(&key).unwrap())
|
||||
}
|
||||
|
||||
pub fn drop_lib(&mut self, key: usize) {
|
||||
self.libraries.remove(&key);
|
||||
}
|
||||
}
|
||||
|
||||
rustpython_common::static_cell! {
|
||||
static LIBCACHE: PyRwLock<ExternalLibs>;
|
||||
}
|
||||
|
||||
pub fn libcache() -> &'static PyRwLock<ExternalLibs> {
|
||||
LIBCACHE.get_or_init(|| PyRwLock::new(ExternalLibs::new()))
|
||||
}
|
||||
5
vm/src/stdlib/ctypes/pointer.rs
Normal file
5
vm/src/stdlib/ctypes/pointer.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[pyclass(name = "Pointer", module = "_ctypes")]
|
||||
pub struct PyCPointer {}
|
||||
|
||||
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
|
||||
impl PyCPointer {}
|
||||
5
vm/src/stdlib/ctypes/structure.rs
Normal file
5
vm/src/stdlib/ctypes/structure.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[pyclass(name = "Structure", module = "_ctypes")]
|
||||
pub struct PyCStructure {}
|
||||
|
||||
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
|
||||
impl PyCStructure {}
|
||||
5
vm/src/stdlib/ctypes/union.rs
Normal file
5
vm/src/stdlib/ctypes/union.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[pyclass(name = "Union", module = "_ctypes")]
|
||||
pub struct PyCUnion {}
|
||||
|
||||
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
|
||||
impl PyCUnion {}
|
||||
@@ -482,7 +482,7 @@ mod _io {
|
||||
let size = size.to_usize();
|
||||
let read = instance.get_attr("read", vm)?;
|
||||
let mut res = Vec::new();
|
||||
while size.map_or(true, |s| res.len() < s) {
|
||||
while size.is_none_or(|s| res.len() < s) {
|
||||
let read_res = ArgBytesLike::try_from_object(vm, read.call((1,), vm)?)?;
|
||||
if read_res.with_ref(|b| b.is_empty()) {
|
||||
break;
|
||||
|
||||
@@ -37,6 +37,8 @@ pub mod posix;
|
||||
#[path = "posix_compat.rs"]
|
||||
pub mod posix;
|
||||
|
||||
#[cfg(any(target_family = "unix", target_family = "windows"))]
|
||||
mod ctypes;
|
||||
#[cfg(windows)]
|
||||
pub(crate) mod msvcrt;
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
|
||||
@@ -124,5 +126,9 @@ pub fn get_module_inits() -> StdlibMap {
|
||||
"_winapi" => winapi::make_module,
|
||||
"winreg" => winreg::make_module,
|
||||
}
|
||||
#[cfg(any(target_family = "unix", target_family = "windows"))]
|
||||
{
|
||||
"_ctypes" => ctypes::make_module,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,12 @@ pub(crate) mod module {
|
||||
|| attr & FileSystem::FILE_ATTRIBUTE_DIRECTORY != 0))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
pub(super) fn _supports_virtual_terminal() -> PyResult<bool> {
|
||||
// TODO: implement this
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
pub(super) struct SymlinkArgs {
|
||||
src: OsPath,
|
||||
|
||||
@@ -978,7 +978,7 @@ pub(super) mod _os {
|
||||
return Err(vm.new_value_error("negative argument not allowed".to_owned()));
|
||||
}
|
||||
let mut buf = vec![0u8; size as usize];
|
||||
getrandom::getrandom(&mut buf).map_err(|e| match e.raw_os_error() {
|
||||
getrandom::fill(&mut buf).map_err(|e| match e.raw_os_error() {
|
||||
Some(errno) => io::Error::from_raw_os_error(errno).into_pyexception(vm),
|
||||
None => vm.new_os_error("Getting random failed".to_owned()),
|
||||
})?;
|
||||
|
||||
@@ -43,6 +43,9 @@ mod decl {
|
||||
DateTime, Datelike, Timelike,
|
||||
};
|
||||
use std::time::Duration;
|
||||
#[cfg(target_env = "msvc")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use windows::Win32::System::Time;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) const SEC_TO_MS: i64 = 1000;
|
||||
@@ -152,6 +155,15 @@ mod decl {
|
||||
Ok(get_perf_time(vm)?.as_nanos())
|
||||
}
|
||||
|
||||
#[cfg(target_env = "msvc")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn get_tz_info() -> Time::TIME_ZONE_INFORMATION {
|
||||
let mut info = Time::TIME_ZONE_INFORMATION::default();
|
||||
let info_ptr = &mut info as *mut Time::TIME_ZONE_INFORMATION;
|
||||
let _ = unsafe { Time::GetTimeZoneInformation(info_ptr) };
|
||||
info
|
||||
}
|
||||
|
||||
// #[pyfunction]
|
||||
// fn tzset() {
|
||||
// unsafe { super::_tzset() };
|
||||
@@ -164,6 +176,15 @@ mod decl {
|
||||
unsafe { super::c_timezone }
|
||||
}
|
||||
|
||||
#[cfg(target_env = "msvc")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[pyattr]
|
||||
fn timezone(_vm: &VirtualMachine) -> i32 {
|
||||
let info = get_tz_info();
|
||||
// https://users.rust-lang.org/t/accessing-tzname-and-similar-constants-in-windows/125771/3
|
||||
(info.Bias + info.StandardBias) * 60
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -172,6 +193,15 @@ mod decl {
|
||||
unsafe { super::c_daylight }
|
||||
}
|
||||
|
||||
#[cfg(target_env = "msvc")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[pyattr]
|
||||
fn daylight(_vm: &VirtualMachine) -> i32 {
|
||||
let info = get_tz_info();
|
||||
// https://users.rust-lang.org/t/accessing-tzname-and-similar-constants-in-windows/125771/3
|
||||
(info.StandardBias != info.DaylightBias) as i32
|
||||
}
|
||||
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[pyattr]
|
||||
@@ -184,6 +214,22 @@ mod decl {
|
||||
unsafe { (to_str(super::c_tzname[0]), to_str(super::c_tzname[1])) }.into_pytuple(vm)
|
||||
}
|
||||
|
||||
#[cfg(target_env = "msvc")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[pyattr]
|
||||
fn tzname(vm: &VirtualMachine) -> crate::builtins::PyTupleRef {
|
||||
use crate::builtins::tuple::IntoPyTuple;
|
||||
let info = get_tz_info();
|
||||
let standard = widestring::decode_utf16_lossy(info.StandardName)
|
||||
.filter(|&c| c != '\0')
|
||||
.collect::<String>();
|
||||
let daylight = widestring::decode_utf16_lossy(info.DaylightName)
|
||||
.filter(|&c| c != '\0')
|
||||
.collect::<String>();
|
||||
let tz_name = (&*standard, &*daylight);
|
||||
tz_name.into_pytuple(vm)
|
||||
}
|
||||
|
||||
fn pyobj_to_date_time(
|
||||
value: Either<f64, i64>,
|
||||
vm: &VirtualMachine,
|
||||
|
||||
@@ -75,18 +75,31 @@ pub(crate) mod _typing {
|
||||
#[pyclass(name = "ParamSpec")]
|
||||
#[derive(Debug, PyPayload)]
|
||||
#[allow(dead_code)]
|
||||
struct ParamSpec {}
|
||||
pub(crate) struct ParamSpec {
|
||||
name: PyObjectRef,
|
||||
}
|
||||
|
||||
#[pyclass(flags(BASETYPE))]
|
||||
impl ParamSpec {}
|
||||
|
||||
pub(crate) fn make_paramspec(name: PyObjectRef) -> ParamSpec {
|
||||
ParamSpec { name }
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "TypeVarTuple")]
|
||||
#[derive(Debug, PyPayload)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct TypeVarTuple {}
|
||||
pub(crate) struct TypeVarTuple {
|
||||
name: PyObjectRef,
|
||||
}
|
||||
#[pyclass(flags(BASETYPE))]
|
||||
impl TypeVarTuple {}
|
||||
|
||||
pub(crate) fn make_typevartuple(name: PyObjectRef) -> TypeVarTuple {
|
||||
TypeVarTuple { name }
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "ParamSpecArgs")]
|
||||
#[derive(Debug, PyPayload)]
|
||||
|
||||
@@ -28,10 +28,20 @@ mod _winapi {
|
||||
ERROR_PIPE_CONNECTED, ERROR_SEM_TIMEOUT, GENERIC_READ, GENERIC_WRITE, STILL_ACTIVE,
|
||||
WAIT_ABANDONED, WAIT_ABANDONED_0, WAIT_OBJECT_0, WAIT_TIMEOUT,
|
||||
},
|
||||
Globalization::{
|
||||
LCMAP_FULLWIDTH, LCMAP_HALFWIDTH, LCMAP_HIRAGANA, LCMAP_KATAKANA,
|
||||
LCMAP_LINGUISTIC_CASING, LCMAP_LOWERCASE, LCMAP_SIMPLIFIED_CHINESE, LCMAP_TITLECASE,
|
||||
LCMAP_TRADITIONAL_CHINESE, LCMAP_UPPERCASE,
|
||||
},
|
||||
Storage::FileSystem::{
|
||||
FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ,
|
||||
FILE_GENERIC_WRITE, FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_REMOTE,
|
||||
FILE_TYPE_UNKNOWN, OPEN_EXISTING, PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND, SYNCHRONIZE,
|
||||
COPYFILE2_CALLBACK_CHUNK_FINISHED, COPYFILE2_CALLBACK_CHUNK_STARTED,
|
||||
COPYFILE2_CALLBACK_ERROR, COPYFILE2_CALLBACK_POLL_CONTINUE,
|
||||
COPYFILE2_CALLBACK_STREAM_FINISHED, COPYFILE2_CALLBACK_STREAM_STARTED,
|
||||
COPYFILE2_PROGRESS_CANCEL, COPYFILE2_PROGRESS_CONTINUE, COPYFILE2_PROGRESS_PAUSE,
|
||||
COPYFILE2_PROGRESS_QUIET, COPYFILE2_PROGRESS_STOP, FILE_FLAG_FIRST_PIPE_INSTANCE,
|
||||
FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_TYPE_CHAR,
|
||||
FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_REMOTE, FILE_TYPE_UNKNOWN, OPEN_EXISTING,
|
||||
PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND, SYNCHRONIZE,
|
||||
},
|
||||
System::{
|
||||
Console::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE},
|
||||
@@ -53,6 +63,13 @@ mod _winapi {
|
||||
IDLE_PRIORITY_CLASS, INFINITE, NORMAL_PRIORITY_CLASS, PROCESS_DUP_HANDLE,
|
||||
REALTIME_PRIORITY_CLASS, STARTF_USESHOWWINDOW, STARTF_USESTDHANDLES,
|
||||
},
|
||||
WindowsProgramming::{
|
||||
COPY_FILE_ALLOW_DECRYPTED_DESTINATION, COPY_FILE_COPY_SYMLINK,
|
||||
COPY_FILE_FAIL_IF_EXISTS, COPY_FILE_NO_BUFFERING, COPY_FILE_NO_OFFLOAD,
|
||||
COPY_FILE_OPEN_SOURCE_FOR_WRITE, COPY_FILE_REQUEST_COMPRESSED_TRAFFIC,
|
||||
COPY_FILE_REQUEST_SECURITY_PRIVILEGES, COPY_FILE_RESTARTABLE,
|
||||
COPY_FILE_RESUME_FROM_PAUSE,
|
||||
},
|
||||
},
|
||||
UI::WindowsAndMessaging::SW_HIDE,
|
||||
};
|
||||
@@ -119,6 +136,11 @@ mod _winapi {
|
||||
Ok(HANDLE(target))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn GetACP() -> u32 {
|
||||
unsafe { windows_sys::Win32::Globalization::GetACP() }
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn GetCurrentProcess() -> HANDLE {
|
||||
unsafe { windows::Win32::System::Threading::GetCurrentProcess() }
|
||||
@@ -137,6 +159,16 @@ mod _winapi {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn GetLastError() -> u32 {
|
||||
unsafe { windows_sys::Win32::Foundation::GetLastError() }
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn GetVersion() -> u32 {
|
||||
unsafe { windows_sys::Win32::System::SystemInformation::GetVersion() }
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct CreateProcessArgs {
|
||||
#[pyarg(positional)]
|
||||
@@ -249,6 +281,21 @@ mod _winapi {
|
||||
))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn OpenProcess(
|
||||
desired_access: u32,
|
||||
inherit_handle: bool,
|
||||
process_id: u32,
|
||||
) -> windows_sys::Win32::Foundation::HANDLE {
|
||||
unsafe {
|
||||
windows_sys::Win32::System::Threading::OpenProcess(
|
||||
desired_access,
|
||||
BOOL::from(inherit_handle),
|
||||
process_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn NeedCurrentDirectoryForExePath(exe_name: PyStrRef) -> bool {
|
||||
let exe_name = exe_name.as_str().to_wide_with_nul();
|
||||
@@ -447,4 +494,24 @@ mod _winapi {
|
||||
let (path, _) = path.split_at(length as usize);
|
||||
Ok(String::from_utf16(path).unwrap())
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn OpenMutexW(desired_access: u32, inherit_handle: bool, name: u16) -> PyResult<isize> {
|
||||
let handle = unsafe {
|
||||
windows_sys::Win32::System::Threading::OpenMutexW(
|
||||
desired_access,
|
||||
BOOL::from(inherit_handle),
|
||||
windows_sys::core::PCWSTR::from(name as _),
|
||||
)
|
||||
};
|
||||
// if handle.is_invalid() {
|
||||
// return Err(errno_err(vm));
|
||||
// }
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn ReleaseMutex(handle: isize) -> WindowsSysResult<BOOL> {
|
||||
WindowsSysResult(unsafe { windows_sys::Win32::System::Threading::ReleaseMutex(handle) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,9 +47,13 @@ mod winreg {
|
||||
// value types
|
||||
#[pyattr]
|
||||
pub use windows_sys::Win32::System::Registry::{
|
||||
REG_BINARY, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_DWORD_LITTLE_ENDIAN, REG_EXPAND_SZ,
|
||||
REG_FULL_RESOURCE_DESCRIPTOR, REG_LINK, REG_MULTI_SZ, REG_NONE, REG_QWORD,
|
||||
REG_QWORD_LITTLE_ENDIAN, REG_RESOURCE_LIST, REG_RESOURCE_REQUIREMENTS_LIST, REG_SZ,
|
||||
REG_BINARY, REG_CREATED_NEW_KEY, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_DWORD_LITTLE_ENDIAN,
|
||||
REG_EXPAND_SZ, REG_FULL_RESOURCE_DESCRIPTOR, REG_LINK, REG_MULTI_SZ, REG_NONE,
|
||||
REG_NOTIFY_CHANGE_ATTRIBUTES, REG_NOTIFY_CHANGE_LAST_SET, REG_NOTIFY_CHANGE_NAME,
|
||||
REG_NOTIFY_CHANGE_SECURITY, REG_OPENED_EXISTING_KEY, REG_OPTION_BACKUP_RESTORE,
|
||||
REG_OPTION_CREATE_LINK, REG_OPTION_NON_VOLATILE, REG_OPTION_OPEN_LINK, REG_OPTION_RESERVED,
|
||||
REG_OPTION_VOLATILE, REG_QWORD, REG_QWORD_LITTLE_ENDIAN, REG_RESOURCE_LIST,
|
||||
REG_RESOURCE_REQUIREMENTS_LIST, REG_SZ, REG_WHOLE_HIVE_VOLATILE,
|
||||
};
|
||||
|
||||
#[pyattr]
|
||||
@@ -98,7 +102,7 @@ mod winreg {
|
||||
|
||||
#[pymethod(magic)]
|
||||
fn bool(&self) -> bool {
|
||||
self.key().raw_handle() != 0
|
||||
!self.key().raw_handle().is_null()
|
||||
}
|
||||
#[pymethod(magic)]
|
||||
fn enter(zelf: PyRef<Self>) -> PyRef<Self> {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
use chrono::{prelude::DateTime, Local};
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
// = 3.12.0alpha
|
||||
// = 3.13.0alpha
|
||||
pub const MAJOR: usize = 3;
|
||||
pub const MINOR: usize = 12;
|
||||
pub const MINOR: usize = 13;
|
||||
pub const MICRO: usize = 0;
|
||||
pub const RELEASELEVEL: &str = "alpha";
|
||||
pub const RELEASELEVEL_N: usize = 0xA;
|
||||
|
||||
@@ -298,8 +298,8 @@ impl VirtualMachine {
|
||||
}
|
||||
|
||||
if let Some(slot_c) = class_c.slots.as_number.left_ternary_op(op_slot) {
|
||||
if slot_a.is_some_and(|slot_a| slot_a != slot_c)
|
||||
&& slot_b.is_some_and(|slot_b| slot_b != slot_c)
|
||||
if slot_a.is_some_and(|slot_a| !std::ptr::fn_addr_eq(slot_a, slot_c))
|
||||
&& slot_b.is_some_and(|slot_b| !std::ptr::fn_addr_eq(slot_b, slot_c))
|
||||
{
|
||||
let ret = slot_c(a, b, c, self)?;
|
||||
if !ret.is(&self.ctx.not_implemented) {
|
||||
|
||||
@@ -10,7 +10,14 @@ rust-version.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[[bench]]
|
||||
name = "benches"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
num_enum = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
optional = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true }
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
use test::Bencher;
|
||||
|
||||
use rustpython_sre_engine::{Request, State, StrDrive};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
|
||||
struct Pattern {
|
||||
pattern: &'static str,
|
||||
code: &'static [u32],
|
||||
}
|
||||
|
||||
@@ -25,52 +23,51 @@ impl Pattern {
|
||||
}
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn benchmarks(b: &mut Bencher) {
|
||||
fn basic(c: &mut Criterion) {
|
||||
// # test common prefix
|
||||
// pattern p1 = re.compile('Python|Perl') # , 'Perl'), # Alternation
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p1 = Pattern { code: &[14, 8, 1, 4, 6, 1, 1, 80, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 1] };
|
||||
#[rustfmt::skip] let p1 = Pattern { pattern: "Python|Perl", code: &[14, 8, 1, 4, 6, 1, 1, 80, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 1] };
|
||||
// END GENERATED
|
||||
// pattern p2 = re.compile('(Python|Perl)') #, 'Perl'), # Grouped alternation
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p2 = Pattern { code: &[14, 8, 1, 4, 6, 1, 0, 80, 0, 17, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 17, 1, 1] };
|
||||
#[rustfmt::skip] let p2 = Pattern { pattern: "(Python|Perl)", code: &[14, 8, 1, 4, 6, 1, 0, 80, 0, 17, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 17, 1, 1] };
|
||||
// END GENERATED
|
||||
// pattern p3 = re.compile('Python|Perl|Tcl') #, 'Perl'), # Alternation
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p3 = Pattern { code: &[14, 9, 4, 3, 6, 16, 80, 16, 84, 0, 7, 15, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 22, 11, 16, 80, 16, 101, 16, 114, 16, 108, 15, 11, 9, 16, 84, 16, 99, 16, 108, 15, 2, 0, 1] };
|
||||
#[rustfmt::skip] let p3 = Pattern { pattern: "Python|Perl|Tcl", code: &[14, 9, 4, 3, 6, 16, 80, 16, 84, 0, 7, 15, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 22, 11, 16, 80, 16, 101, 16, 114, 16, 108, 15, 11, 9, 16, 84, 16, 99, 16, 108, 15, 2, 0, 1] };
|
||||
// END GENERATED
|
||||
// pattern p4 = re.compile('(Python|Perl|Tcl)') #, 'Perl'), # Grouped alternation
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p4 = Pattern { code: &[14, 9, 4, 3, 6, 16, 80, 16, 84, 0, 17, 0, 7, 15, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 22, 11, 16, 80, 16, 101, 16, 114, 16, 108, 15, 11, 9, 16, 84, 16, 99, 16, 108, 15, 2, 0, 17, 1, 1] };
|
||||
#[rustfmt::skip] let p4 = Pattern { pattern: "(Python|Perl|Tcl)", code: &[14, 9, 4, 3, 6, 16, 80, 16, 84, 0, 17, 0, 7, 15, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 22, 11, 16, 80, 16, 101, 16, 114, 16, 108, 15, 11, 9, 16, 84, 16, 99, 16, 108, 15, 2, 0, 17, 1, 1] };
|
||||
// END GENERATED
|
||||
// pattern p5 = re.compile('(Python)\\1') #, 'PythonPython'), # Backreference
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p5 = Pattern { code: &[14, 18, 1, 12, 12, 6, 0, 80, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 17, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 11, 0, 1] };
|
||||
#[rustfmt::skip] let p5 = Pattern { pattern: "(Python)\\1", code: &[14, 18, 1, 12, 12, 6, 0, 80, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 17, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 11, 0, 1] };
|
||||
// END GENERATED
|
||||
// pattern p6 = re.compile('([0a-z][a-z0-9]*,)+') #, 'a5,b7,c9,'), # Disable the fastmap optimization
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p6 = Pattern { code: &[14, 4, 0, 2, 4294967295, 23, 31, 1, 4294967295, 17, 0, 13, 7, 16, 48, 22, 97, 122, 0, 24, 13, 0, 4294967295, 13, 8, 22, 97, 122, 22, 48, 57, 0, 1, 16, 44, 17, 1, 18, 1] };
|
||||
#[rustfmt::skip] let p6 = Pattern { pattern: "([0a-z][a-z0-9]*,)+", code: &[14, 4, 0, 2, 4294967295, 23, 31, 1, 4294967295, 17, 0, 13, 7, 16, 48, 22, 97, 122, 0, 24, 13, 0, 4294967295, 13, 8, 22, 97, 122, 22, 48, 57, 0, 1, 16, 44, 17, 1, 18, 1] };
|
||||
// END GENERATED
|
||||
// pattern p7 = re.compile('([a-z][a-z0-9]*,)+') #, 'a5,b7,c9,'), # A few sets
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p7 = Pattern { code: &[14, 4, 0, 2, 4294967295, 23, 29, 1, 4294967295, 17, 0, 13, 5, 22, 97, 122, 0, 24, 13, 0, 4294967295, 13, 8, 22, 97, 122, 22, 48, 57, 0, 1, 16, 44, 17, 1, 18, 1] };
|
||||
#[rustfmt::skip] let p7 = Pattern { pattern: "([a-z][a-z0-9]*,)+", code: &[14, 4, 0, 2, 4294967295, 23, 29, 1, 4294967295, 17, 0, 13, 5, 22, 97, 122, 0, 24, 13, 0, 4294967295, 13, 8, 22, 97, 122, 22, 48, 57, 0, 1, 16, 44, 17, 1, 18, 1] };
|
||||
// END GENERATED
|
||||
// pattern p8 = re.compile('Python') #, 'Python'), # Simple text literal
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p8 = Pattern { code: &[14, 18, 3, 6, 6, 6, 6, 80, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 1] };
|
||||
#[rustfmt::skip] let p8 = Pattern { pattern: "Python", code: &[14, 18, 3, 6, 6, 6, 6, 80, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 1] };
|
||||
// END GENERATED
|
||||
// pattern p9 = re.compile('.*Python') #, 'Python'), # Bad text literal
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p9 = Pattern { code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 1] };
|
||||
#[rustfmt::skip] let p9 = Pattern { pattern: ".*Python", code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 1] };
|
||||
// END GENERATED
|
||||
// pattern p10 = re.compile('.*Python.*') #, 'Python'), # Worse text literal
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p10 = Pattern { code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 24, 5, 0, 4294967295, 2, 1, 1] };
|
||||
#[rustfmt::skip] let p10 = Pattern { pattern: ".*Python.*", code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 24, 5, 0, 4294967295, 2, 1, 1] };
|
||||
// END GENERATED
|
||||
// pattern p11 = re.compile('.*(Python)') #, 'Python'), # Bad text literal with grouping
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p11 = Pattern { code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 17, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 1] };
|
||||
#[rustfmt::skip] let p11 = Pattern { pattern: ".*(Python)", code: &[14, 4, 0, 6, 4294967295, 24, 5, 0, 4294967295, 2, 1, 17, 0, 16, 80, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 1] };
|
||||
// END GENERATED
|
||||
|
||||
let tests = [
|
||||
@@ -87,25 +84,33 @@ fn benchmarks(b: &mut Bencher) {
|
||||
(p11, "Python"),
|
||||
];
|
||||
|
||||
b.iter(move || {
|
||||
for (p, s) in &tests {
|
||||
let (req, mut state) = p.state(s.clone());
|
||||
assert!(state.search(req));
|
||||
let (req, mut state) = p.state(s.clone());
|
||||
assert!(state.pymatch(&req));
|
||||
let (mut req, mut state) = p.state(s.clone());
|
||||
req.match_all = true;
|
||||
assert!(state.pymatch(&req));
|
||||
let s2 = format!("{}{}{}", " ".repeat(10000), s, " ".repeat(10000));
|
||||
let (req, mut state) = p.state_range(s2.as_str(), 0..usize::MAX);
|
||||
assert!(state.search(req));
|
||||
let (req, mut state) = p.state_range(s2.as_str(), 10000..usize::MAX);
|
||||
assert!(state.pymatch(&req));
|
||||
let (req, mut state) = p.state_range(s2.as_str(), 10000..10000 + s.len());
|
||||
assert!(state.pymatch(&req));
|
||||
let (mut req, mut state) = p.state_range(s2.as_str(), 10000..10000 + s.len());
|
||||
req.match_all = true;
|
||||
assert!(state.pymatch(&req));
|
||||
}
|
||||
})
|
||||
let mut group = c.benchmark_group("basic");
|
||||
|
||||
for (p, s) in tests {
|
||||
group.bench_with_input(BenchmarkId::new(p.pattern, s), s, |b, s| {
|
||||
b.iter(|| {
|
||||
let (req, mut state) = p.state(s);
|
||||
assert!(state.search(req));
|
||||
let (req, mut state) = p.state(s);
|
||||
assert!(state.pymatch(&req));
|
||||
let (mut req, mut state) = p.state(s);
|
||||
req.match_all = true;
|
||||
assert!(state.pymatch(&req));
|
||||
let s2 = format!("{}{}{}", " ".repeat(10000), s, " ".repeat(10000));
|
||||
let (req, mut state) = p.state_range(s2.as_str(), 0..usize::MAX);
|
||||
assert!(state.search(req));
|
||||
let (req, mut state) = p.state_range(s2.as_str(), 10000..usize::MAX);
|
||||
assert!(state.pymatch(&req));
|
||||
let (req, mut state) = p.state_range(s2.as_str(), 10000..10000 + s.len());
|
||||
assert!(state.pymatch(&req));
|
||||
let (mut req, mut state) = p.state_range(s2.as_str(), 10000..10000 + s.len());
|
||||
req.match_all = true;
|
||||
assert!(state.pymatch(&req));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, basic);
|
||||
|
||||
criterion_main!(benches);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sre_constants
|
||||
import sre_compile
|
||||
import sre_parse
|
||||
import json
|
||||
from itertools import chain
|
||||
|
||||
@@ -11,13 +8,13 @@ m = re.search(r"const SRE_MAGIC: usize = (\d+);", open("src/constants.rs").read(
|
||||
sre_engine_magic = int(m.group(1))
|
||||
del m
|
||||
|
||||
assert sre_constants.MAGIC == sre_engine_magic
|
||||
assert re._constants.MAGIC == sre_engine_magic
|
||||
|
||||
class CompiledPattern:
|
||||
@classmethod
|
||||
def compile(cls, pattern, flags=0):
|
||||
p = sre_parse.parse(pattern)
|
||||
code = sre_compile._code(p, flags)
|
||||
p = re._parser.parse(pattern)
|
||||
code = re._compiler._code(p, flags)
|
||||
self = cls()
|
||||
self.pattern = pattern
|
||||
self.code = code
|
||||
@@ -28,12 +25,32 @@ for k, v in re.RegexFlag.__members__.items():
|
||||
setattr(CompiledPattern, k, v)
|
||||
|
||||
|
||||
class EscapeRustStr:
|
||||
hardcoded = {
|
||||
ord('\r'): '\\r',
|
||||
ord('\t'): '\\t',
|
||||
ord('\r'): '\\r',
|
||||
ord('\n'): '\\n',
|
||||
ord('\\'): '\\\\',
|
||||
ord('\''): '\\\'',
|
||||
ord('\"'): '\\\"',
|
||||
}
|
||||
@classmethod
|
||||
def __class_getitem__(cls, ch):
|
||||
if (rpl := cls.hardcoded.get(ch)) is not None:
|
||||
return rpl
|
||||
if ch in range(0x20, 0x7f):
|
||||
return ch
|
||||
return f"\\u{{{ch:x}}}"
|
||||
def rust_str(s):
|
||||
return '"' + s.translate(EscapeRustStr) + '"'
|
||||
|
||||
# matches `// pattern {varname} = re.compile(...)`
|
||||
pattern_pattern = re.compile(r"^((\s*)\/\/\s*pattern\s+(\w+)\s+=\s+(.+?))$(?:.+?END GENERATED)?", re.M | re.S)
|
||||
def replace_compiled(m):
|
||||
line, indent, varname, pattern = m.groups()
|
||||
pattern = eval(pattern, {"re": CompiledPattern})
|
||||
pattern = f"Pattern {{ code: &{json.dumps(pattern.code)} }}"
|
||||
pattern = f"Pattern {{ pattern: {rust_str(pattern.pattern)}, code: &{json.dumps(pattern.code)} }}"
|
||||
return f'''{line}
|
||||
{indent}// START GENERATED by generate_tests.py
|
||||
{indent}#[rustfmt::skip] let {varname} = {pattern};
|
||||
|
||||
@@ -181,7 +181,7 @@ unsafe fn next_code_point(ptr: &mut *const u8) -> u32 {
|
||||
let z = **ptr;
|
||||
*ptr = ptr.offset(1);
|
||||
let y_z = utf8_acc_cont_byte((y & CONT_MASK) as u32, z);
|
||||
ch = init << 12 | y_z;
|
||||
ch = (init << 12) | y_z;
|
||||
if x >= 0xF0 {
|
||||
// [x y z w] case
|
||||
// use only the lower 3 bits of `init`
|
||||
@@ -189,7 +189,7 @@ unsafe fn next_code_point(ptr: &mut *const u8) -> u32 {
|
||||
// so the iterator must produce a value here.
|
||||
let w = **ptr;
|
||||
*ptr = ptr.offset(1);
|
||||
ch = (init & 7) << 18 | utf8_acc_cont_byte(y_z, w);
|
||||
ch = ((init & 7) << 18) | utf8_acc_cont_byte(y_z, w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use rustpython_sre_engine::{Request, State, StrDrive};
|
||||
|
||||
struct Pattern {
|
||||
pattern: &'static str,
|
||||
code: &'static [u32],
|
||||
}
|
||||
|
||||
@@ -16,7 +17,7 @@ impl Pattern {
|
||||
fn test_2427() {
|
||||
// pattern lookbehind = re.compile(r'(?<!\.)x\b')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let lookbehind = Pattern { code: &[14, 4, 0, 1, 1, 5, 5, 1, 16, 46, 1, 16, 120, 6, 10, 1] };
|
||||
#[rustfmt::skip] let lookbehind = Pattern { pattern: "(?<!\\.)x\\b", code: &[14, 4, 0, 1, 1, 5, 5, 1, 16, 46, 1, 16, 120, 6, 10, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = lookbehind.state("x");
|
||||
assert!(state.pymatch(&req));
|
||||
@@ -26,7 +27,7 @@ fn test_2427() {
|
||||
fn test_assert() {
|
||||
// pattern positive_lookbehind = re.compile(r'(?<=abc)def')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let positive_lookbehind = Pattern { code: &[14, 4, 0, 3, 3, 4, 9, 3, 16, 97, 16, 98, 16, 99, 1, 16, 100, 16, 101, 16, 102, 1] };
|
||||
#[rustfmt::skip] let positive_lookbehind = Pattern { pattern: "(?<=abc)def", code: &[14, 4, 0, 3, 3, 4, 9, 3, 16, 97, 16, 98, 16, 99, 1, 16, 100, 16, 101, 16, 102, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = positive_lookbehind.state("abcdef");
|
||||
assert!(state.search(req));
|
||||
@@ -36,7 +37,7 @@ fn test_assert() {
|
||||
fn test_string_boundaries() {
|
||||
// pattern big_b = re.compile(r'\B')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let big_b = Pattern { code: &[14, 4, 0, 0, 0, 6, 11, 1] };
|
||||
#[rustfmt::skip] let big_b = Pattern { pattern: "\\B", code: &[14, 4, 0, 0, 0, 6, 11, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = big_b.state("");
|
||||
assert!(!state.search(req));
|
||||
@@ -46,7 +47,7 @@ fn test_string_boundaries() {
|
||||
fn test_zerowidth() {
|
||||
// pattern p = re.compile(r'\b|:+')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 0, 4294967295, 7, 5, 6, 10, 15, 12, 10, 24, 6, 1, 4294967295, 16, 58, 1, 15, 2, 0, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "\\b|:+", code: &[14, 4, 0, 0, 4294967295, 7, 5, 6, 10, 15, 12, 10, 24, 6, 1, 4294967295, 16, 58, 1, 15, 2, 0, 1] };
|
||||
// END GENERATED
|
||||
let (mut req, mut state) = p.state("a:");
|
||||
req.must_advance = true;
|
||||
@@ -59,7 +60,7 @@ fn test_repeat_context_panic() {
|
||||
use optional::Optioned;
|
||||
// pattern p = re.compile(r'(?:a*?(xx)??z)*')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 0, 4294967295, 23, 25, 0, 4294967295, 26, 6, 0, 4294967295, 16, 97, 1, 23, 11, 0, 1, 17, 0, 16, 120, 16, 120, 17, 1, 19, 16, 122, 18, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "(?:a*?(xx)??z)*", code: &[14, 4, 0, 0, 4294967295, 23, 25, 0, 4294967295, 26, 6, 0, 4294967295, 16, 97, 1, 23, 11, 0, 1, 17, 0, 16, 120, 16, 120, 17, 1, 19, 16, 122, 18, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("axxzaz");
|
||||
assert!(state.pymatch(&req));
|
||||
@@ -73,7 +74,7 @@ fn test_repeat_context_panic() {
|
||||
fn test_double_max_until() {
|
||||
// pattern p = re.compile(r'((1)?)*')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 0, 4294967295, 23, 18, 0, 4294967295, 17, 0, 23, 9, 0, 1, 17, 2, 16, 49, 17, 3, 18, 17, 1, 18, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "((1)?)*", code: &[14, 4, 0, 0, 4294967295, 23, 18, 0, 4294967295, 17, 0, 23, 9, 0, 1, 17, 2, 16, 49, 17, 3, 18, 17, 1, 18, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("1111");
|
||||
assert!(state.pymatch(&req));
|
||||
@@ -84,7 +85,7 @@ fn test_double_max_until() {
|
||||
fn test_info_single() {
|
||||
// pattern p = re.compile(r'aa*')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 8, 1, 1, 4294967295, 1, 1, 97, 0, 16, 97, 24, 6, 0, 4294967295, 16, 97, 1, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "aa*", code: &[14, 8, 1, 1, 4294967295, 1, 1, 97, 0, 16, 97, 24, 6, 0, 4294967295, 16, 97, 1, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("baaaa");
|
||||
assert!(state.search(req));
|
||||
@@ -96,7 +97,7 @@ fn test_info_single() {
|
||||
fn test_info_single2() {
|
||||
// pattern p = re.compile(r'Python|Perl')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 8, 1, 4, 6, 1, 1, 80, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "Python|Perl", code: &[14, 8, 1, 4, 6, 1, 1, 80, 0, 16, 80, 7, 13, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 15, 11, 9, 16, 101, 16, 114, 16, 108, 15, 2, 0, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("Perl");
|
||||
assert!(state.search(req));
|
||||
@@ -106,7 +107,7 @@ fn test_info_single2() {
|
||||
fn test_info_literal() {
|
||||
// pattern p = re.compile(r'ababc+')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 14, 1, 5, 4294967295, 4, 4, 97, 98, 97, 98, 0, 0, 1, 2, 16, 97, 16, 98, 16, 97, 16, 98, 24, 6, 1, 4294967295, 16, 99, 1, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "ababc+", code: &[14, 14, 1, 5, 4294967295, 4, 4, 97, 98, 97, 98, 0, 0, 1, 2, 16, 97, 16, 98, 16, 97, 16, 98, 24, 6, 1, 4294967295, 16, 99, 1, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("!ababc");
|
||||
assert!(state.search(req));
|
||||
@@ -116,7 +117,7 @@ fn test_info_literal() {
|
||||
fn test_info_literal2() {
|
||||
// pattern p = re.compile(r'(python)\1')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 18, 1, 12, 12, 6, 0, 112, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 17, 0, 16, 112, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 11, 0, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "(python)\\1", code: &[14, 18, 1, 12, 12, 6, 0, 112, 121, 116, 104, 111, 110, 0, 0, 0, 0, 0, 0, 17, 0, 16, 112, 16, 121, 16, 116, 16, 104, 16, 111, 16, 110, 17, 1, 11, 0, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("pythonpython");
|
||||
assert!(state.search(req));
|
||||
@@ -126,7 +127,7 @@ fn test_info_literal2() {
|
||||
fn test_repeat_in_assertions() {
|
||||
// pattern p = re.compile('^([ab]*?)(?=(b)?)c', re.IGNORECASE)
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 1, 4294967295, 6, 0, 17, 0, 26, 10, 0, 4294967295, 39, 5, 22, 97, 98, 0, 1, 17, 1, 4, 14, 0, 23, 9, 0, 1, 17, 2, 40, 98, 17, 3, 18, 1, 40, 99, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "^([ab]*?)(?=(b)?)c", code: &[14, 4, 0, 1, 4294967295, 6, 0, 17, 0, 26, 10, 0, 4294967295, 39, 5, 22, 97, 98, 0, 1, 17, 1, 4, 14, 0, 23, 9, 0, 1, 17, 2, 40, 98, 17, 3, 18, 1, 40, 99, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("abc");
|
||||
assert!(state.search(req));
|
||||
@@ -136,7 +137,7 @@ fn test_repeat_in_assertions() {
|
||||
fn test_possessive_quantifier() {
|
||||
// pattern p = re.compile('e++a')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 2, 4294967295, 29, 6, 1, 4294967295, 16, 101, 1, 16, 97, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "e++a", code: &[14, 4, 0, 2, 4294967295, 29, 6, 1, 4294967295, 16, 101, 1, 16, 97, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("eeea");
|
||||
assert!(state.pymatch(&req));
|
||||
@@ -146,7 +147,7 @@ fn test_possessive_quantifier() {
|
||||
fn test_possessive_atomic_group() {
|
||||
// pattern p = re.compile('(?>x)++x')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 2, 4294967295, 28, 8, 1, 4294967295, 27, 4, 16, 120, 1, 1, 16, 120, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "(?>x)++x", code: &[14, 4, 0, 2, 4294967295, 28, 8, 1, 4294967295, 27, 4, 16, 120, 1, 1, 16, 120, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("xxx");
|
||||
assert!(!state.pymatch(&req));
|
||||
@@ -156,7 +157,7 @@ fn test_possessive_atomic_group() {
|
||||
fn test_bug_20998() {
|
||||
// pattern p = re.compile('[a-c]+', re.I)
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 1, 4294967295, 24, 10, 1, 4294967295, 39, 5, 22, 97, 99, 0, 1, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "[a-c]+", code: &[14, 4, 0, 1, 4294967295, 24, 10, 1, 4294967295, 39, 5, 22, 97, 99, 0, 1, 1] };
|
||||
// END GENERATED
|
||||
let (mut req, mut state) = p.state("ABC");
|
||||
req.match_all = true;
|
||||
@@ -168,7 +169,7 @@ fn test_bug_20998() {
|
||||
fn test_bigcharset() {
|
||||
// pattern p = re.compile('[a-z]*', re.I)
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { code: &[14, 4, 0, 0, 4294967295, 24, 97, 0, 4294967295, 39, 92, 10, 3, 33685760, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 0, 0, 0, 134217726, 0, 0, 0, 0, 0, 131072, 0, 2147483648, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] };
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "[a-z]*", code: &[14, 4, 0, 0, 4294967295, 24, 97, 0, 4294967295, 39, 92, 10, 3, 33685760, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 33686018, 0, 0, 0, 134217726, 0, 0, 0, 0, 0, 131072, 0, 2147483648, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] };
|
||||
// END GENERATED
|
||||
let (req, mut state) = p.state("x ");
|
||||
assert!(state.pymatch(&req));
|
||||
@@ -178,4 +179,7 @@ fn test_bigcharset() {
|
||||
#[test]
|
||||
fn test_search_nonascii() {
|
||||
// pattern p = re.compile('\xe0+')
|
||||
// START GENERATED by generate_tests.py
|
||||
#[rustfmt::skip] let p = Pattern { pattern: "\u{e0}+", code: &[14, 4, 0, 1, 4294967295, 24, 6, 1, 4294967295, 16, 224, 1, 1] };
|
||||
// END GENERATED
|
||||
}
|
||||
|
||||
5
wasm/lib/.cargo/config.toml
Normal file
5
wasm/lib/.cargo/config.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]
|
||||
@@ -28,6 +28,9 @@ rustpython-parser = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
|
||||
# remove once getrandom 0.2 is no longer otherwise in the dependency tree
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
|
||||
console_error_panic_hook = "0.1"
|
||||
js-sys = "0.3"
|
||||
serde-wasm-bindgen = "0.3.1"
|
||||
@@ -47,4 +50,4 @@ web-sys = { version = "0.3", features = [
|
||||
wasm-opt = false#["-O1"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
workspace = true
|
||||
|
||||
5
wasm/wasm-unknown-test/.cargo/config.toml
Normal file
5
wasm/wasm-unknown-test/.cargo/config.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ["--cfg=getrandom_backend=\"custom\""]
|
||||
@@ -8,6 +8,7 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
getrandom = { version = "0.2.12", features = ["custom"] }
|
||||
getrandom_03 = { package = "getrandom", version = "0.3" }
|
||||
rustpython-vm = { path = "../../vm", default-features = false, features = ["compiler"] }
|
||||
|
||||
[workspace]
|
||||
|
||||
@@ -14,3 +14,11 @@ fn getrandom_always_fail(_buf: &mut [u8]) -> Result<(), getrandom::Error> {
|
||||
}
|
||||
|
||||
getrandom::register_custom_getrandom!(getrandom_always_fail);
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "Rust" fn __getrandom_v03_custom(
|
||||
_dest: *mut u8,
|
||||
_len: usize,
|
||||
) -> Result<(), getrandom_03::Error> {
|
||||
Err(getrandom_03::Error::UNSUPPORTED)
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ GENERATED_FILE = "extra_tests/not_impl.py"
|
||||
implementation = platform.python_implementation()
|
||||
if implementation != "CPython":
|
||||
sys.exit(f"whats_left.py must be run under CPython, got {implementation} instead")
|
||||
if sys.version_info[:2] < (3, 12):
|
||||
sys.exit(f"whats_left.py must be run under CPython 3.12 or newer, got {implementation} {sys.version} instead")
|
||||
if sys.version_info[:2] < (3, 13):
|
||||
sys.exit(f"whats_left.py must be run under CPython 3.13 or newer, got {implementation} {sys.version} instead")
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Process some integers.")
|
||||
|
||||
Reference in New Issue
Block a user