diff --git a/.gitignore b/.gitignore index c0bc411bd4..967a51a834 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target +/*/target **/*.rs.bk **/*.bytecode __pycache__ diff --git a/.travis.yml b/.travis.yml index cbab6ae216..aff28cd6a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -115,6 +115,26 @@ matrix: - TRAVIS_RUST_VERSION=nightly - REGULAR_TEST=false - CODE_COVERAGE=true + - name: test WASM + language: python + python: 3.6 + cache: + pip: true + # Because we're using the Python Travis environment, we can't use + # the built-in cargo cacher + directories: + - /home/travis/.cargo + - target + addons: + firefox: latest + install: + - nvm install node + - pip install pipenv + script: + - wasm/tests/.travis-runner.sh + env: + - REGULAR_TEST=true + - TRAVIS_RUST_VERSION=stable allow_failures: - rust: nightly env: REGULAR_TEST=true diff --git a/Cargo.lock b/Cargo.lock index dfab5148a2..818d0af89e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,15 +174,6 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "constant_time_eq" version = "0.1.3" @@ -271,8 +262,8 @@ name = "failure_derive" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -523,6 +514,16 @@ dependencies = [ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num-rational" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-traits" version = "0.2.6" @@ -574,7 +575,7 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "0.4.20" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -595,10 +596,10 @@ dependencies = [ [[package]] name = "quote" -version = "0.6.3" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -710,6 +711,23 @@ name = "rustc-demangle" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc_version_runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustpython" version = "0.0.1" @@ -723,6 +741,15 @@ dependencies = [ "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustpython_derive" +version = "0.1.0" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustpython_parser" version = "0.0.1" @@ -748,9 +775,12 @@ dependencies = [ "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_derive 0.1.0", "rustpython_parser 0.0.1", "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -761,12 +791,12 @@ dependencies = [ [[package]] name = "rustpython_wasm" -version = "0.1.0" +version = "0.1.0-pre-alpha.1" dependencies = [ "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", @@ -800,6 +830,19 @@ name = "scoped_threadpool" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "1.0.66" @@ -810,8 +853,8 @@ name = "serde_derive" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -913,18 +956,18 @@ name = "syn" version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "0.15.23" +version = "0.15.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -933,8 +976,8 @@ name = "synstructure" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1063,9 +1106,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1084,7 +1127,7 @@ name = "wasm-bindgen-macro" version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-macro-support 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1093,9 +1136,9 @@ name = "wasm-bindgen-macro-support" version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-backend 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1113,9 +1156,9 @@ dependencies = [ "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-backend 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1223,7 +1266,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6c5dd2c094474ec60a6acaf31780af270275e3153bafff2db5995b715295762e" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" "checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603" @@ -1264,6 +1306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "10b8423ea72ec64751198856a853e07b37087cfc9b53a87ecb19bff67b6d1320" "checksum num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "107b9be86cd2481930688277b675b0114578227f034674726605b8a482d8baf8" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" +"checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" "checksum petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" @@ -1271,10 +1314,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2261d544c2bb6aa3b10022b0be371b9c7c64f762ef28c6f5d4f1ef6d97b5930" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" "checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7" -"checksum proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "3d7b7eaaa90b4a90a932a9ea6666c95a389e424eff347f0f793979289429feee" +"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" -"checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035" +"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" "checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" "checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" @@ -1288,9 +1331,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" "checksum regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6de8ecd7fad7731f306f69b6e10ec5a3178c61e464dcc06979427aa4cc891145" "checksum rustyline 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6010155119d53aac4f5b987cb8f6ea913d0d64d9b237da36f8f96a90cb3f5385" "checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "e9a2d9a9ac5120e0f768801ca2b58ad6eec929dc9d1d616c162f208869c2ce95" "checksum serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "0a90213fa7e0f5eac3f7afe2d5ff6b088af515052cc7303bd68c7e3b91a3fb79" "checksum serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "44dd2cfde475037451fa99b7e5df77aa3cfd1536575fa8e7a538ab36dcde49ae" @@ -1306,7 +1353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" -"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc" +"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" "checksum synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7" "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" "checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83" diff --git a/Cargo.toml b/Cargo.toml index a1f366cd43..c109e3fea4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Windel Bouwman", "Shing Lyu "] edition = "2018" [workspace] -members = [".", "vm", "wasm/lib", "parser"] +members = [".", "derive", "vm", "wasm/lib", "parser"] [dependencies] log="0.4.1" @@ -15,6 +15,3 @@ rustpython_parser = {path = "parser"} rustpython_vm = {path = "vm"} rustyline = "2.1.0" xdg = "2.2.0" - -[profile.release] -opt-level = "s" diff --git a/README.md b/README.md index b060a29e92..41bde43e07 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ $ pipenv install $ pipenv run pytest -v ``` -There also are some unit tests, you can run those will cargo: +There also are some unit tests, you can run those with cargo: ```shell $ cargo test --all @@ -201,6 +201,10 @@ The code style used is the default rustfmt codestyle. Please format your code ac Chat with us on [gitter][gitter]. +# Code of conduct + +Our code of conduct [can be found here](code-of-conduct.md). + # Credit The initial work was based on [windelbouwman/rspython](https://github.com/windelbouwman/rspython) and [shinglyu/RustPython](https://github.com/shinglyu/RustPython) diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 0000000000..a783b67225 --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1,77 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at windel.bouwman@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq + diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000000..c15d47ad58 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rustpython_derive" +version = "0.1.0" +authors = ["Joey "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +syn = "0.15.29" +quote = "0.6.11" +proc-macro2 = "0.4.27" diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000000..ad743e72fe --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,48 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{Data, DeriveInput, Fields}; + +#[proc_macro_derive(FromArgs)] +pub fn derive_from_args(input: TokenStream) -> TokenStream { + let ast: DeriveInput = syn::parse(input).unwrap(); + + let gen = impl_from_args(&ast); + gen.to_string().parse().unwrap() +} + +fn impl_from_args(input: &DeriveInput) -> TokenStream2 { + // FIXME: This references types using `crate` instead of `rustpython_vm` + // so that it can be used in the latter. How can we support both? + let fields = match input.data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => fields.named.iter().map(|field| { + let name = &field.ident; + quote! { + #name: crate::pyobject::TryFromObject::try_from_object( + vm, + args.take_keyword(stringify!(#name)).unwrap_or_else(|| vm.ctx.none()) + )?, + } + }), + Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message + } + } + Data::Enum(_) | Data::Union(_) => unimplemented!(), // TODO: better error message + }; + + let name = &input.ident; + quote! { + impl crate::function::FromArgs for #name { + fn from_args( + vm: &crate::vm::VirtualMachine, + args: &mut crate::function::PyFuncArgs + ) -> Result { + Ok(#name { #(#fields)* }) + } + } + } +} diff --git a/parser/src/ast.rs b/parser/src/ast.rs index e607c4e4bf..257ceadf2a 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -122,6 +122,7 @@ pub enum Statement { // docstring: String, body: Vec, decorator_list: Vec, + returns: Option, }, } @@ -217,6 +218,7 @@ pub enum Expression { True, False, None, + Ellipsis, } impl Expression { @@ -259,6 +261,7 @@ impl Expression { Lambda { .. } => "lambda", IfExpression { .. } => "conditional expression", True | False | None => "keyword", + Ellipsis => "ellipsis", } } } @@ -269,14 +272,20 @@ impl Expression { */ #[derive(Debug, PartialEq, Default)] pub struct Parameters { - pub args: Vec, - pub kwonlyargs: Vec, - pub vararg: Option>, // Optionally we handle optionally named '*args' or '*' - pub kwarg: Option>, + pub args: Vec, + pub kwonlyargs: Vec, + pub vararg: Varargs, // Optionally we handle optionally named '*args' or '*' + pub kwarg: Varargs, pub defaults: Vec, pub kw_defaults: Vec>, } +#[derive(Debug, PartialEq, Default)] +pub struct Parameter { + pub arg: String, + pub annotation: Option>, +} + #[derive(Debug, PartialEq)] pub enum ComprehensionKind { GeneratorExpression { element: Expression }, @@ -357,6 +366,17 @@ pub enum Number { Complex { real: f64, imag: f64 }, } +/// Transforms a value prior to formatting it. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ConversionFlag { + /// Converts by calling `str()`. + Str, + /// Converts by calling `ascii()`. + Ascii, + /// Converts by calling `repr()`. + Repr, +} + #[derive(Debug, PartialEq)] pub enum StringGroup { Constant { @@ -364,9 +384,35 @@ pub enum StringGroup { }, FormattedValue { value: Box, + conversion: Option, spec: String, }, Joined { values: Vec, }, } + +#[derive(Debug, PartialEq)] +pub enum Varargs { + None, + Unnamed, + Named(Parameter), +} + +impl Default for Varargs { + fn default() -> Varargs { + Varargs::None + } +} + +impl From>> for Varargs { + fn from(opt: Option>) -> Varargs { + match opt { + Some(inner_opt) => match inner_opt { + Some(param) => Varargs::Named(param), + None => Varargs::Unnamed, + }, + None => Varargs::None, + } + } +} diff --git a/parser/src/fstring.rs b/parser/src/fstring.rs new file mode 100644 index 0000000000..cad885531e --- /dev/null +++ b/parser/src/fstring.rs @@ -0,0 +1,234 @@ +use std::iter; +use std::mem; +use std::str; + +use lalrpop_util::ParseError as LalrpopError; + +use crate::ast::{ConversionFlag, StringGroup}; +use crate::lexer::{LexicalError, Location, Tok}; +use crate::parser::parse_expression; + +use self::FStringError::*; +use self::StringGroup::*; + +// TODO: consolidate these with ParseError +#[derive(Debug, PartialEq)] +pub enum FStringError { + UnclosedLbrace, + UnopenedRbrace, + InvalidExpression, + InvalidConversionFlag, + EmptyExpression, + MismatchedDelimiter, +} + +impl From for LalrpopError { + fn from(_err: FStringError) -> Self { + lalrpop_util::ParseError::User { + error: LexicalError::StringError, + } + } +} + +struct FStringParser<'a> { + chars: iter::Peekable>, +} + +impl<'a> FStringParser<'a> { + fn new(source: &'a str) -> Self { + Self { + chars: source.chars().peekable(), + } + } + + fn parse_formatted_value(&mut self) -> Result { + let mut expression = String::new(); + let mut spec = String::new(); + let mut delims = Vec::new(); + let mut conversion = None; + + while let Some(ch) = self.chars.next() { + match ch { + '!' if delims.is_empty() => { + conversion = Some(match self.chars.next() { + Some('s') => ConversionFlag::Str, + Some('a') => ConversionFlag::Ascii, + Some('r') => ConversionFlag::Repr, + Some(_) => { + return Err(InvalidConversionFlag); + } + None => { + break; + } + }) + } + ':' if delims.is_empty() => { + while let Some(&next) = self.chars.peek() { + if next != '}' { + spec.push(next); + self.chars.next(); + } else { + break; + } + } + } + '(' | '{' | '[' => { + expression.push(ch); + delims.push(ch); + } + ')' => { + if delims.pop() != Some('(') { + return Err(MismatchedDelimiter); + } + expression.push(ch); + } + ']' => { + if delims.pop() != Some('[') { + return Err(MismatchedDelimiter); + } + expression.push(ch); + } + '}' if !delims.is_empty() => { + if delims.pop() != Some('{') { + return Err(MismatchedDelimiter); + } + expression.push(ch); + } + '}' => { + if expression.is_empty() { + return Err(EmptyExpression); + } + return Ok(FormattedValue { + value: Box::new( + parse_expression(expression.trim()).map_err(|_| InvalidExpression)?, + ), + conversion, + spec, + }); + } + '"' | '\'' => { + expression.push(ch); + while let Some(next) = self.chars.next() { + expression.push(next); + if next == ch { + break; + } + } + } + _ => { + expression.push(ch); + } + } + } + + Err(UnclosedLbrace) + } + + fn parse(mut self) -> Result { + let mut content = String::new(); + let mut values = vec![]; + + while let Some(ch) = self.chars.next() { + match ch { + '{' => { + if let Some('{') = self.chars.peek() { + self.chars.next(); + content.push('{'); + } else { + if !content.is_empty() { + values.push(Constant { + value: mem::replace(&mut content, String::new()), + }); + } + + values.push(self.parse_formatted_value()?); + } + } + '}' => { + if let Some('}') = self.chars.peek() { + self.chars.next(); + content.push('}'); + } else { + return Err(UnopenedRbrace); + } + } + _ => { + content.push(ch); + } + } + } + + if !content.is_empty() { + values.push(Constant { value: content }) + } + + Ok(match values.len() { + 0 => Constant { + value: String::new(), + }, + 1 => values.into_iter().next().unwrap(), + _ => Joined { values }, + }) + } +} + +pub fn parse_fstring(source: &str) -> Result { + FStringParser::new(source).parse() +} + +#[cfg(test)] +mod tests { + use crate::ast; + + use super::*; + + fn mk_ident(name: &str) -> ast::Expression { + ast::Expression::Identifier { + name: name.to_owned(), + } + } + + #[test] + fn test_parse_fstring() { + let source = String::from("{a}{ b }{{foo}}"); + let parse_ast = parse_fstring(&source).unwrap(); + + assert_eq!( + parse_ast, + Joined { + values: vec![ + FormattedValue { + value: Box::new(mk_ident("a")), + conversion: None, + spec: String::new(), + }, + FormattedValue { + value: Box::new(mk_ident("b")), + conversion: None, + spec: String::new(), + }, + Constant { + value: "{foo}".to_owned() + } + ] + } + ); + } + + #[test] + fn test_parse_empty_fstring() { + assert_eq!( + parse_fstring(""), + Ok(Constant { + value: String::new(), + }), + ); + } + + #[test] + fn test_parse_invalid_fstring() { + assert_eq!(parse_fstring("{"), Err(UnclosedLbrace)); + assert_eq!(parse_fstring("}"), Err(UnopenedRbrace)); + assert_eq!(parse_fstring("{class}"), Err(InvalidExpression)); + } +} diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 619b2dd1a0..113f7afb1a 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -55,6 +55,7 @@ pub struct Lexer> { pub enum LexicalError { StringError, NestingError, + UnrecognizedToken { tok: char }, } #[derive(Clone, Debug, Default, PartialEq)] @@ -687,7 +688,9 @@ where match self.chr0 { Some('0'..='9') => return Some(self.lex_number()), - Some('_') | Some('a'..='z') | Some('A'..='Z') => return Some(self.lex_identifier()), + Some('_') | Some('a'..='z') | Some('A'..='Z') => { + return Some(self.lex_identifier()); + } Some('#') => { self.lex_comment(); continue; @@ -716,16 +719,13 @@ where Some('+') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::PlusEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Plus, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::PlusEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Plus, tok_end))); } } Some('*') => { @@ -789,61 +789,49 @@ where Some('%') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::PercentEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Percent, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::PercentEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Percent, tok_end))); } } Some('|') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::VbarEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Vbar, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::VbarEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Vbar, tok_end))); } } Some('^') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::CircumflexEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::CircumFlex, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::CircumflexEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::CircumFlex, tok_end))); } } Some('&') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::AmperEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Amper, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::AmperEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Amper, tok_end))); } } Some('-') => { @@ -869,16 +857,13 @@ where Some('@') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::AtEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::At, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::AtEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::At, tok_end))); } } Some('!') => { @@ -1008,8 +993,15 @@ where Some('.') => { let tok_start = self.get_pos(); self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Dot, tok_end))); + if let (Some('.'), Some('.')) = (&self.chr0, &self.chr1) { + self.next_char(); + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Ellipsis, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Dot, tok_end))); + } } Some('\n') => { let tok_start = self.get_pos(); @@ -1033,7 +1025,7 @@ where None => return None, _ => { let c = self.next_char(); - panic!("Not impl {:?}", c) + return Some(Err(LexicalError::UnrecognizedToken { tok: c.unwrap() })); } // Ignore all the rest.. } } diff --git a/parser/src/lib.rs b/parser/src/lib.rs index f7f369968b..b10c3d5514 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -3,6 +3,7 @@ extern crate log; pub mod ast; pub mod error; +mod fstring; pub mod lexer; pub mod parser; #[cfg_attr(rustfmt, rustfmt_skip)] diff --git a/parser/src/parser.rs b/parser/src/parser.rs index fb8f2f0e5a..a119740ec5 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -65,180 +65,12 @@ pub fn parse_expression(source: &str) -> Result { do_lalr_parsing!(source, Expression, StartExpression) } -// TODO: consolidate these with ParseError -#[derive(Debug, PartialEq)] -pub enum FStringError { - UnclosedLbrace, - UnopenedRbrace, - InvalidExpression, -} - -impl From - for lalrpop_util::ParseError -{ - fn from(_err: FStringError) -> Self { - lalrpop_util::ParseError::User { - error: lexer::LexicalError::StringError, - } - } -} - -enum ParseState { - Text { - content: String, - }, - FormattedValue { - expression: String, - spec: Option, - depth: usize, - }, -} - -pub fn parse_fstring(source: &str) -> Result { - use self::ParseState::*; - - let mut values = vec![]; - let mut state = ParseState::Text { - content: String::new(), - }; - - let mut chars = source.chars().peekable(); - while let Some(ch) = chars.next() { - state = match state { - Text { mut content } => match ch { - '{' => { - if let Some('{') = chars.peek() { - chars.next(); - content.push('{'); - Text { content } - } else { - if !content.is_empty() { - values.push(ast::StringGroup::Constant { value: content }); - } - - FormattedValue { - expression: String::new(), - spec: None, - depth: 0, - } - } - } - '}' => { - if let Some('}') = chars.peek() { - chars.next(); - content.push('}'); - Text { content } - } else { - return Err(FStringError::UnopenedRbrace); - } - } - _ => { - content.push(ch); - Text { content } - } - }, - - FormattedValue { - mut expression, - mut spec, - depth, - } => match ch { - ':' if depth == 0 => FormattedValue { - expression, - spec: Some(String::new()), - depth, - }, - '{' => { - if let Some('{') = chars.peek() { - expression.push_str("{{"); - chars.next(); - FormattedValue { - expression, - spec, - depth, - } - } else { - expression.push('{'); - FormattedValue { - expression, - spec, - depth: depth + 1, - } - } - } - '}' => { - if let Some('}') = chars.peek() { - expression.push_str("}}"); - chars.next(); - FormattedValue { - expression, - spec, - depth, - } - } else if depth > 0 { - expression.push('}'); - FormattedValue { - expression, - spec, - depth: depth - 1, - } - } else { - values.push(ast::StringGroup::FormattedValue { - value: Box::new(match parse_expression(expression.trim()) { - Ok(expr) => expr, - Err(_) => return Err(FStringError::InvalidExpression), - }), - spec: spec.unwrap_or_default(), - }); - Text { - content: String::new(), - } - } - } - _ => { - if let Some(spec) = spec.as_mut() { - spec.push(ch) - } else { - expression.push(ch); - } - FormattedValue { - expression, - spec, - depth, - } - } - }, - }; - } - - match state { - Text { content } => { - if !content.is_empty() { - values.push(ast::StringGroup::Constant { value: content }) - } - } - FormattedValue { .. } => { - return Err(FStringError::UnclosedLbrace); - } - } - - Ok(match values.len() { - 0 => ast::StringGroup::Constant { - value: String::new(), - }, - 1 => values.into_iter().next().unwrap(), - _ => ast::StringGroup::Joined { values }, - }) -} - #[cfg(test)] mod tests { use super::ast; use super::parse_expression; - use super::parse_fstring; use super::parse_program; use super::parse_statement; - use super::FStringError; use num_bigint::BigInt; #[test] @@ -412,10 +244,19 @@ mod tests { node: ast::Statement::Expression { expression: ast::Expression::Lambda { args: ast::Parameters { - args: vec![String::from("x"), String::from("y")], + args: vec![ + ast::Parameter { + arg: String::from("x"), + annotation: None, + }, + ast::Parameter { + arg: String::from("y"), + annotation: None, + } + ], kwonlyargs: vec![], - vararg: None, - kwarg: None, + vararg: ast::Varargs::None, + kwarg: ast::Varargs::None, defaults: vec![], kw_defaults: vec![], }, @@ -474,7 +315,9 @@ mod tests { #[test] fn test_parse_class() { - let source = String::from("class Foo(A, B):\n def __init__(self):\n pass\n def method_with_default(self, arg='default'):\n pass\n"); + let source = String::from( + "class Foo(A, B):\n def __init__(self):\n pass\n def method_with_default(self, arg='default'):\n pass\n", + ); assert_eq!( parse_statement(&source), Ok(ast::LocatedStatement { @@ -496,10 +339,13 @@ mod tests { node: ast::Statement::FunctionDef { name: String::from("__init__"), args: ast::Parameters { - args: vec![String::from("self")], + args: vec![ast::Parameter { + arg: String::from("self"), + annotation: None, + }], kwonlyargs: vec![], - vararg: None, - kwarg: None, + vararg: ast::Varargs::None, + kwarg: ast::Varargs::None, defaults: vec![], kw_defaults: vec![], }, @@ -508,6 +354,7 @@ mod tests { node: ast::Statement::Pass, }], decorator_list: vec![], + returns: None, } }, ast::LocatedStatement { @@ -515,10 +362,19 @@ mod tests { node: ast::Statement::FunctionDef { name: String::from("method_with_default"), args: ast::Parameters { - args: vec![String::from("self"), String::from("arg"),], + args: vec![ + ast::Parameter { + arg: String::from("self"), + annotation: None, + }, + ast::Parameter { + arg: String::from("arg"), + annotation: None, + } + ], kwonlyargs: vec![], - vararg: None, - kwarg: None, + vararg: ast::Varargs::None, + kwarg: ast::Varargs::None, defaults: vec![ast::Expression::String { value: ast::StringGroup::Constant { value: "default".to_string() @@ -531,6 +387,7 @@ mod tests { node: ast::Statement::Pass, }], decorator_list: vec![], + returns: None, } } ], @@ -630,55 +487,4 @@ mod tests { } ); } - - fn mk_ident(name: &str) -> ast::Expression { - ast::Expression::Identifier { - name: name.to_owned(), - } - } - - #[test] - fn test_parse_fstring() { - let source = String::from("{a}{ b }{{foo}}"); - let parse_ast = parse_fstring(&source).unwrap(); - - assert_eq!( - parse_ast, - ast::StringGroup::Joined { - values: vec![ - ast::StringGroup::FormattedValue { - value: Box::new(mk_ident("a")), - spec: String::new(), - }, - ast::StringGroup::FormattedValue { - value: Box::new(mk_ident("b")), - spec: String::new(), - }, - ast::StringGroup::Constant { - value: "{foo}".to_owned() - } - ] - } - ); - } - - #[test] - fn test_parse_empty_fstring() { - assert_eq!( - parse_fstring(""), - Ok(ast::StringGroup::Constant { - value: String::new(), - }), - ); - } - - #[test] - fn test_parse_invalid_fstring() { - assert_eq!(parse_fstring("{"), Err(FStringError::UnclosedLbrace)); - assert_eq!(parse_fstring("}"), Err(FStringError::UnopenedRbrace)); - assert_eq!( - parse_fstring("{class}"), - Err(FStringError::InvalidExpression) - ); - } } diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 8ee407f9ae..9e5b309b1e 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -4,10 +4,12 @@ // See also: https://greentreesnakes.readthedocs.io/en/latest/nodes.html#keyword #![allow(unknown_lints,clippy)] -use super::ast; -use super::lexer; -use super::parser; use std::iter::FromIterator; + +use crate::ast; +use crate::fstring::parse_fstring; +use crate::lexer; + use num_bigint::BigInt; grammar; @@ -444,7 +446,7 @@ WithItem: ast::WithItem = { }; FuncDef: ast::LocatedStatement = { - "def" ":" => { + "def" " Test)?> ":" => { ast::LocatedStatement { location: loc, node: ast::Statement::FunctionDef { @@ -452,13 +454,14 @@ FuncDef: ast::LocatedStatement = { args: a, body: s, decorator_list: d, + returns: r.map(|x| x.1), } } }, }; Parameters: ast::Parameters = { - "(" ")" => { + "(" )?> ")" => { match a { Some(a) => a, None => Default::default(), @@ -468,8 +471,10 @@ Parameters: ast::Parameters = { // parameters are (String, None), kwargs are (String, Some(Test)) where Test is // the default -TypedArgsList: ast::Parameters = { - => { +// Note that this is a macro which is used once for function defs, and +// once for lambda defs. +TypedArgsList: ast::Parameters = { + > )?> => { let (names, default_elements) = param1; // Now gather rest of parameters: @@ -481,13 +486,13 @@ TypedArgsList: ast::Parameters = { ast::Parameters { args: names, kwonlyargs: kwonlyargs, - vararg: vararg, - kwarg: kwarg, + vararg: vararg.into(), + kwarg: kwarg.into(), defaults: default_elements, kw_defaults: kw_defaults, } }, - => { + > )> => { let (names, default_elements) = param1; // Now gather rest of parameters: @@ -499,29 +504,29 @@ TypedArgsList: ast::Parameters = { ast::Parameters { args: names, kwonlyargs: kwonlyargs, - vararg: vararg, - kwarg: kwarg, + vararg: vararg.into(), + kwarg: kwarg.into(), defaults: default_elements, kw_defaults: kw_defaults, } }, - => { + > => { let (vararg, kwonlyargs, kw_defaults, kwarg) = params; ast::Parameters { args: vec![], kwonlyargs: kwonlyargs, - vararg: vararg, - kwarg: kwarg, + vararg: vararg.into(), + kwarg: kwarg.into(), defaults: vec![], kw_defaults: kw_defaults, } }, - => { + > => { ast::Parameters { args: vec![], kwonlyargs: vec![], - vararg: None, - kwarg: Some(kw), + vararg: ast::Varargs::None, + kwarg: Some(kw).into(), defaults: vec![], kw_defaults: vec![], } @@ -530,8 +535,8 @@ TypedArgsList: ast::Parameters = { // Use inline here to make sure the "," is not creating an ambiguity. #[inline] -TypedParameters: (Vec, Vec) = { - => { +TypedParameters: (Vec, Vec) = { + > )*> => { // Combine first parameters: let mut args = vec![param1]; args.extend(param2.into_iter().map(|x| x.1)); @@ -540,7 +545,6 @@ TypedParameters: (Vec, Vec) = { let mut default_elements = vec![]; for (name, default) in args.into_iter() { - names.push(name.clone()); if let Some(default) = default { default_elements.push(default); } else { @@ -549,28 +553,35 @@ TypedParameters: (Vec, Vec) = { // have defaults panic!( "non-default argument follows default argument: {}", - name + &name.arg ); } } + names.push(name); } (names, default_elements) } }; -TypedParameterDef: (String, Option) = { - => (i, None), - "=" => (i, Some(e)), +TypedParameterDef: (ast::Parameter, Option) = { + => (i, None), + "=" => (i, Some(e)), +}; + +UntypedParameter: ast::Parameter = { + => ast::Parameter { arg: i, annotation: None }, }; -// TODO: add type annotations here: -TypedParameter: String = { - Identifier, +TypedParameter: ast::Parameter = { + => { + let annotation = a.map(|x| Box::new(x.1)); + ast::Parameter { arg, annotation } + }, }; -ParameterListStarArgs: (Option>, Vec, Vec>, Option>) = { - "*" => { +ParameterListStarArgs: (Option>, Vec, Vec>, Option>) = { + "*" )*> )?> => { // Extract keyword arguments: let mut kwonlyargs = vec![]; let mut kw_defaults = vec![]; @@ -588,8 +599,8 @@ ParameterListStarArgs: (Option>, Vec, Vec = { - "**" => { +KwargParameter: Option = { + "**" => { kwarg } }; @@ -673,7 +684,7 @@ Test: ast::Expression = { }; LambdaDef: ast::Expression = { - "lambda" ":" => + "lambda" ?> ":" => ast::Expression::Lambda { args: p.unwrap_or(Default::default()), body:Box::new(b) @@ -780,10 +791,24 @@ Power: ast::Expression = { AtomExpr: ast::Expression = { => e, "(" ")" => ast::Expression::Call { function: Box::new(f), args: a.0, keywords: a.1 }, - "[" "]" => ast::Expression::Subscript { a: Box::new(e), b: Box::new(s) }, + "[" "]" => ast::Expression::Subscript { a: Box::new(e), b: Box::new(s) }, "." => ast::Expression::Attribute { value: Box::new(e), name: n }, }; +SubscriptList: ast::Expression = { + ","? => { + if s2.is_empty() { + s1 + } else { + let mut dims = vec![s1]; + for x in s2 { + dims.push(x.1) + } + ast::Expression::Tuple { elements: dims } + } + } +}; + Subscript: ast::Expression = { => e, ":" => { @@ -837,6 +862,7 @@ Atom: ast::Expression = { "True" => ast::Expression::True, "False" => ast::Expression::False, "None" => ast::Expression::None, + "..." => ast::Expression::Ellipsis, }; TestListComp: Vec = { @@ -1008,7 +1034,7 @@ StringGroup: ast::StringGroup = { let mut values = vec![]; for (value, is_fstring) in s { values.push(if is_fstring { - parser::parse_fstring(&value)? + parse_fstring(&value)? } else { ast::StringGroup::Constant { value } }) @@ -1046,6 +1072,7 @@ extern { "~" => lexer::Tok::Tilde, ":" => lexer::Tok::Colon, "." => lexer::Tok::Dot, + "..." => lexer::Tok::Ellipsis, "," => lexer::Tok::Comma, "*" => lexer::Tok::Star, "**" => lexer::Tok::DoubleStar, @@ -1084,6 +1111,7 @@ extern { "<=" => lexer::Tok::LessEqual, ">" => lexer::Tok::Greater, ">=" => lexer::Tok::GreaterEqual, + "->" => lexer::Tok::Rarrow, "and" => lexer::Tok::And, "as" => lexer::Tok::As, "assert" => lexer::Tok::Assert, diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000000..32a9786fa1 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +edition = "2018" diff --git a/src/main.rs b/src/main.rs index 662e433858..a0683ec3ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,13 +10,8 @@ extern crate rustyline; use clap::{App, Arg}; use rustpython_parser::error::ParseError; use rustpython_vm::{ - compile, - error::CompileError, - import, - obj::objstr, - print_exception, - pyobject::{AttributeProtocol, PyObjectRef, PyResult}, - util, VirtualMachine, + compile, error::CompileError, frame::Scope, import, obj::objstr, print_exception, + pyobject::PyResult, util, VirtualMachine, }; use rustyline::{error::ReadlineError, Editor}; use std::path::{Path, PathBuf}; @@ -50,26 +45,26 @@ fn main() { .get_matches(); // Construct vm: - let mut vm = VirtualMachine::new(); + let vm = VirtualMachine::new(); // Figure out if a -c option was given: let result = if let Some(command) = matches.value_of("c") { - run_command(&mut vm, command.to_string()) + run_command(&vm, command.to_string()) } else if let Some(module) = matches.value_of("m") { - run_module(&mut vm, module) + run_module(&vm, module) } else { // Figure out if a script was passed: match matches.value_of("script") { - None => run_shell(&mut vm), - Some(filename) => run_script(&mut vm, filename), + None => run_shell(&vm), + Some(filename) => run_script(&vm, filename), } }; // See if any exception leaked out: - handle_exception(&mut vm, result); + handle_exception(&vm, result); } -fn _run_string(vm: &mut VirtualMachine, source: &str, source_path: String) -> PyResult { +fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResult { let code_obj = compile::compile( source, &compile::Mode::Exec, @@ -81,19 +76,18 @@ fn _run_string(vm: &mut VirtualMachine, source: &str, source_path: String) -> Py vm.new_exception(syntax_error, err.to_string()) })?; // trace!("Code object: {:?}", code_obj.borrow()); - let builtins = vm.get_builtin_scope(); - let vars = vm.context().new_scope(Some(builtins)); // Keep track of local variables + let vars = vm.ctx.new_scope(); // Keep track of local variables vm.run_code_obj(code_obj, vars) } -fn handle_exception(vm: &mut VirtualMachine, result: PyResult) { +fn handle_exception(vm: &VirtualMachine, result: PyResult) { if let Err(err) = result { print_exception(vm, &err); std::process::exit(1); } } -fn run_command(vm: &mut VirtualMachine, mut source: String) -> PyResult { +fn run_command(vm: &VirtualMachine, mut source: String) -> PyResult { debug!("Running command {}", source); // This works around https://github.com/RustPython/RustPython/issues/17 @@ -101,13 +95,13 @@ fn run_command(vm: &mut VirtualMachine, mut source: String) -> PyResult { _run_string(vm, &source, "".to_string()) } -fn run_module(vm: &mut VirtualMachine, module: &str) -> PyResult { +fn run_module(vm: &VirtualMachine, module: &str) -> PyResult { debug!("Running module {}", module); let current_path = PathBuf::from("."); import::import_module(vm, current_path, module) } -fn run_script(vm: &mut VirtualMachine, script_file: &str) -> PyResult { +fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult { debug!("Running file {}", script_file); // Parse an ast from it: let file_path = Path::new(script_file); @@ -120,11 +114,7 @@ fn run_script(vm: &mut VirtualMachine, script_file: &str) -> PyResult { } } -fn shell_exec( - vm: &mut VirtualMachine, - source: &str, - scope: PyObjectRef, -) -> Result<(), CompileError> { +fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), CompileError> { match compile::compile( source, &compile::Mode::Single, @@ -163,13 +153,20 @@ fn get_history_path() -> PathBuf { xdg_dirs.place_cache_file("repl_history.txt").unwrap() } -fn run_shell(vm: &mut VirtualMachine) -> PyResult { +fn get_prompt(vm: &VirtualMachine, prompt_name: &str) -> String { + vm.get_attribute(vm.sys_module.clone(), prompt_name) + .ok() + .as_ref() + .map(objstr::get_value) + .unwrap_or_else(String::new) +} + +fn run_shell(vm: &VirtualMachine) -> PyResult { println!( "Welcome to the magnificent Rust Python {} interpreter", crate_version!() ); - let builtins = vm.get_builtin_scope(); - let vars = vm.context().new_scope(Some(builtins)); // Keep track of local variables + let vars = vm.ctx.new_scope(); // Read a single line: let mut input = String::new(); @@ -181,25 +178,35 @@ fn run_shell(vm: &mut VirtualMachine) -> PyResult { println!("No previous history."); } - let ps1 = &objstr::get_value(&vm.sys_module.get_attr("ps1").unwrap()); - let ps2 = &objstr::get_value(&vm.sys_module.get_attr("ps2").unwrap()); - let mut prompt = ps1; + let mut continuing = false; loop { - match repl.readline(prompt) { + let prompt = if continuing { + get_prompt(vm, "ps2") + } else { + get_prompt(vm, "ps1") + }; + match repl.readline(&prompt) { Ok(line) => { debug!("You entered {:?}", line); input.push_str(&line); - input.push_str("\n"); - repl.add_history_entry(line.trim_end().as_ref()); + input.push('\n'); + repl.add_history_entry(line.trim_end()); + + if continuing { + if line.is_empty() { + continuing = false; + } else { + continue; + } + } match shell_exec(vm, &input, vars.clone()) { Err(CompileError::Parse(ParseError::EOF(_))) => { - prompt = ps2; + continuing = true; continue; } _ => { - prompt = ps1; input = String::new(); } } @@ -207,7 +214,8 @@ fn run_shell(vm: &mut VirtualMachine) -> PyResult { Err(ReadlineError::Interrupted) => { // TODO: Raise a real KeyboardInterrupt exception println!("^C"); - break; + continuing = false; + continue; } Err(ReadlineError::Eof) => { break; diff --git a/tests/snippets/attr.py b/tests/snippets/attr.py new file mode 100644 index 0000000000..0e9f1eb9bd --- /dev/null +++ b/tests/snippets/attr.py @@ -0,0 +1,91 @@ +from testutils import assertRaises + + +class A: + pass + + +a = A() +a.b = 10 +assert hasattr(a, 'b') +assert a.b == 10 + +# test override attribute +setattr(a, 'b', 12) +assert a.b == 12 +assert getattr(a, 'b') == 12 + +# test non-existent attribute +with assertRaises(AttributeError): + _ = a.c + +with assertRaises(AttributeError): + getattr(a, 'c') + +assert getattr(a, 'c', 21) == 21 + +# test set attribute +setattr(a, 'c', 20) +assert hasattr(a, 'c') +assert a.c == 20 + +# test delete attribute +delattr(a, 'c') +assert not hasattr(a, 'c') +with assertRaises(AttributeError): + _ = a.c + + +# test setting attribute on builtin +with assertRaises(AttributeError): + object().a = 1 + +with assertRaises(AttributeError): + del object().a + +with assertRaises(AttributeError): + setattr(object(), 'a', 2) + +with assertRaises(AttributeError): + delattr(object(), 'a') + +attrs = {} + + +class CustomLookup: + def __getattr__(self, item): + return "value_{}".format(item) + + def __setattr__(self, key, value): + attrs[key] = value + + +custom = CustomLookup() + +assert custom.attr == "value_attr" + +custom.a = 2 +custom.b = 5 +assert attrs['a'] == 2 +assert attrs['b'] == 5 + + +class GetRaise: + def __init__(self, ex): + self.ex = ex + + def __getattr__(self, item): + raise self.ex + + +assert not hasattr(GetRaise(AttributeError()), 'a') +with assertRaises(AttributeError): + getattr(GetRaise(AttributeError()), 'a') +assert getattr(GetRaise(AttributeError()), 'a', 11) == 11 + +with assertRaises(KeyError): + hasattr(GetRaise(KeyError()), 'a') +with assertRaises(KeyError): + getattr(GetRaise(KeyError()), 'a') +with assertRaises(KeyError): + getattr(GetRaise(KeyError()), 'a', 11) diff --git a/tests/snippets/bools.py b/tests/snippets/bools.py index 2aa817ca46..0c277143bc 100644 --- a/tests/snippets/bools.py +++ b/tests/snippets/bools.py @@ -16,6 +16,9 @@ assert bool(1) == True assert bool({}) == False +assert bool(NotImplemented) == True +assert bool(...) == True + if not 1: raise BaseException diff --git a/tests/snippets/builtin_any.py b/tests/snippets/builtin_any.py new file mode 100644 index 0000000000..a8679d5fe3 --- /dev/null +++ b/tests/snippets/builtin_any.py @@ -0,0 +1,13 @@ +assert any([1]); +assert not any([]); +assert not any([0,0,0,0]); +assert any([0,0,1,0,0]); +def anything(a): + return a + +class Test: + def __iter__(self): + while True: + yield True + +assert any(map(anything, Test())) diff --git a/tests/snippets/builtin_dict.py b/tests/snippets/builtin_dict.py index a57541fa58..3252d7b41f 100644 --- a/tests/snippets/builtin_dict.py +++ b/tests/snippets/builtin_dict.py @@ -8,3 +8,7 @@ d = {} d['a'] = d assert repr(d) == "{'a': {...}}" + +assert {'a': 123}.get('a') == 123 +assert {'a': 123}.get('b') == None +assert {'a': 123}.get('b', 456) == 456 diff --git a/tests/snippets/builtin_dir.py b/tests/snippets/builtin_dir.py new file mode 100644 index 0000000000..a121fb718c --- /dev/null +++ b/tests/snippets/builtin_dir.py @@ -0,0 +1,23 @@ + +class A: + def test(): + pass + +a = A() + +assert "test" in dir(a), "test not in a" +assert "test" in dir(A), "test not in A" + +class B(A): + def __dir__(self): + return ('q', 'h') + +# Gets sorted and turned into a list +assert ['h', 'q'] == dir(B()) + +# This calls type.__dir__ so isn't changed (but inheritance works)! +assert 'test' in dir(A) + +import socket + +assert "AF_INET" in dir(socket) diff --git a/tests/snippets/builtin_locals.py b/tests/snippets/builtin_locals.py new file mode 100644 index 0000000000..6f3fd847c4 --- /dev/null +++ b/tests/snippets/builtin_locals.py @@ -0,0 +1,19 @@ + +a = 5 +b = 6 + +loc = locals() + +assert loc['a'] == 5 +assert loc['b'] == 6 + +def f(): + c = 4 + a = 7 + + loc = locals() + + assert loc['a'] == 4 + assert loc['c'] == 7 + assert not 'b' in loc + diff --git a/tests/snippets/builtin_open.py b/tests/snippets/builtin_open.py index 55eafcd6ce..41bcf2b800 100644 --- a/tests/snippets/builtin_open.py +++ b/tests/snippets/builtin_open.py @@ -4,3 +4,7 @@ assert 'RustPython' in fd.read() assert_raises(FileNotFoundError, lambda: open('DoesNotExist')) + +# Use open as a context manager +with open('README.md') as fp: + fp.read() diff --git a/tests/snippets/class.py b/tests/snippets/class.py index 5bba7be96b..35d043a6d4 100644 --- a/tests/snippets/class.py +++ b/tests/snippets/class.py @@ -16,37 +16,24 @@ def square(self): assert foo.square() == 25 -class Fubar: - def __init__(self): - self.x = 100 - - @property - def foo(self): - value = self.x - self.x += 1 - return value - - -f = Fubar() -assert f.foo == 100 -assert f.foo == 101 - - class Bar: """ W00t """ def __init__(self, x): self.x = x def get_x(self): + assert __class__ is Bar return self.x @classmethod def fubar(cls, x): + assert __class__ is cls assert cls is Bar assert x == 2 @staticmethod def kungfu(x): + assert __class__ is Bar assert x == 3 @@ -66,12 +53,57 @@ class Bar2(Bar): def __init__(self): super().__init__(101) +bar2 = Bar2() +assert bar2.get_x() == 101 + +class A(): + def test(self): + return 100 + +class B(): + def test1(self): + return 200 + +class C(A,B): + def test(self): + return super().test() + + def test1(self): + return super().test1() + +c = C() +assert c.test() == 100 +assert c.test1() == 200 + +class Me(): + + def test(me): + return 100 + +class Me2(Me): + + def test(me): + return super().test() + +class A(): + def f(self): + pass + +class B(A): + def f(self): + super().f() + +class C(B): + def f(self): + super().f() + +C().f() -# TODO: make this work: -# bar2 = Bar2() -# assert bar2.get_x() == 101 +me = Me2() +assert me.test() == 100 -a = super(int, 2) +a = super(bool, True) assert isinstance(a, super) assert type(a) is super +assert a.conjugate() == 1 diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 1e4acc3e3d..170a6492dc 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -17,3 +17,20 @@ def dict_eq(d1, d2): a.clear() assert len(a) == 0 + +a = {'a': 5, 'b': 6} +res = set() +for value in a.values(): + res.add(value) +assert res == set([5,6]) + +count = 0 +for (key, value) in a.items(): + assert a[key] == value + count += 1 +assert count == len(a) + +res = set() +for key in a.keys(): + res.add(key) +assert res == set(['a','b']) diff --git a/tests/snippets/dismod.py b/tests/snippets/dismod.py index cc9c1f63b8..eac1d3f726 100644 --- a/tests/snippets/dismod.py +++ b/tests/snippets/dismod.py @@ -1,10 +1,34 @@ import dis -dis.disassemble(compile("5 + x + 5 or 2", "", "eval")) +dis.dis(compile("5 + x + 5 or 2", "", "eval")) print("\n") -dis.disassemble(compile("def f(x):\n return 1", "", "exec")) +dis.dis(compile("def f(x):\n return 1", "", "exec")) print("\n") -dis.disassemble(compile("if a:\n 1 or 2\nelif x == 'hello':\n 3\nelse:\n 4", "", "exec")) +dis.dis(compile("if a:\n 1 or 2\nelif x == 'hello':\n 3\nelse:\n 4", "", "exec")) print("\n") -dis.disassemble(compile("f(x=1, y=2)", "", "eval")) +dis.dis(compile("f(x=1, y=2)", "", "eval")) print("\n") + +def f(): + with g(): + try: + for a in {1: 4, 2: 5}: + yield [True and False or True, []] + except Exception: + raise not ValueError({1 for i in [1,2,3]}) + +dis.dis(f) + +class A(object): + def f(): + x += 1 + pass + def g(): + for i in range(5): + if i: + continue + else: + break + +print("A.f\n") +dis.dis(A.f) diff --git a/tests/snippets/ellipsis.py b/tests/snippets/ellipsis.py new file mode 100644 index 0000000000..88b1c965d8 --- /dev/null +++ b/tests/snippets/ellipsis.py @@ -0,0 +1,8 @@ + + +a = ... +b = ... +c = type(a)() # Test singleton behavior + +assert a is b +assert b is c diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index ac24a4dbfa..6675615c56 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -87,3 +87,22 @@ assert (1.7).real == 1.7 assert (1.3).is_integer() == False assert (1.0).is_integer() == True + +assert (0.875).as_integer_ratio() == (7, 8) +assert (-0.875).as_integer_ratio() == (-7, 8) +assert (0.0).as_integer_ratio() == (0, 1) +assert (11.5).as_integer_ratio() == (23, 2) +assert (0.0).as_integer_ratio() == (0, 1) +assert (2.5).as_integer_ratio() == (5, 2) +assert (0.5).as_integer_ratio() == (1, 2) +assert (2.1).as_integer_ratio() == (4728779608739021, 2251799813685248) +assert (-2.1).as_integer_ratio() == (-4728779608739021, 2251799813685248) +assert (-2100.0).as_integer_ratio() == (-2100, 1) +assert (2.220446049250313e-16).as_integer_ratio() == (1, 4503599627370496) +assert (1.7976931348623157e+308).as_integer_ratio() == (179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368, 1) +assert (2.2250738585072014e-308).as_integer_ratio() == (1, 44942328371557897693232629769725618340449424473557664318357520289433168951375240783177119330601884005280028469967848339414697442203604155623211857659868531094441973356216371319075554900311523529863270738021251442209537670585615720368478277635206809290837627671146574559986811484619929076208839082406056034304) + +assert_raises(OverflowError, float('inf').as_integer_ratio) +assert_raises(OverflowError, float('-inf').as_integer_ratio) +assert_raises(ValueError, float('nan').as_integer_ratio) + diff --git a/tests/snippets/fstrings.py b/tests/snippets/fstrings.py index 2ee45742f8..c76967acb8 100644 --- a/tests/snippets/fstrings.py +++ b/tests/snippets/fstrings.py @@ -11,7 +11,31 @@ assert f'{f"{{"}' == '{' assert f'{f"}}"}' == '}' assert f'{foo}' f"{foo}" 'foo' == 'barbarfoo' -#assert f'{"!:"}' == '!:' -#assert f"{1 != 2}" == 'True' +assert f'{"!:"}' == '!:' assert fr'x={4*10}\n' == 'x=40\\n' assert f'{16:0>+#10x}' == '00000+0x10' +assert f"{{{(lambda x: f'hello, {x}')('world}')}" == '{hello, world}' + +# Normally `!` cannot appear outside of delimiters in the expression but +# cpython makes an exception for `!=`, so we should too. + +# assert f'{1 != 2}' == 'True' + + +# conversion flags + +class Value: + def __format__(self, spec): + return "foo" + + def __repr__(self): + return "bar" + + def __str__(self): + return "baz" + +v = Value() + +assert f'{v}' == 'foo' +assert f'{v!r}' == 'bar' +assert f'{v!s}' == 'baz' diff --git a/tests/snippets/getframe.py b/tests/snippets/getframe.py index 6b02e027da..d4328286aa 100644 --- a/tests/snippets/getframe.py +++ b/tests/snippets/getframe.py @@ -10,6 +10,7 @@ def test_function(): x = 17 assert sys._getframe().f_locals is not locals_dict assert sys._getframe().f_locals['x'] == 17 + assert sys._getframe(1).f_locals['foo'] == 'bar' test_function() diff --git a/tests/snippets/import.py b/tests/snippets/import.py index 0a36e63429..a481e3f1ae 100644 --- a/tests/snippets/import.py +++ b/tests/snippets/import.py @@ -22,6 +22,28 @@ except ImportError: pass + +test = __import__("import_target") +assert test.X == import_target.X + +import builtins +class OverrideImportContext(): + + def __enter__(self): + self.original_import = builtins.__import__ + + def __exit__(self, exc_type, exc_val, exc_tb): + builtins.__import__ = self.original_import + +with OverrideImportContext(): + def fake_import(name, globals=None, locals=None, fromlist=(), level=0): + return len(name) + + builtins.__import__ = fake_import + import test + assert test == 4 + + # TODO: Once we can determine current directory, use that to construct this # path: #import sys diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index 9aa83eba41..2d36afcb6e 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -1,3 +1,5 @@ +from testutils import assert_raises + # int to int comparisons assert 1 == 1 @@ -15,6 +17,10 @@ assert 1 >= 1.0 assert 1 <= 1.0 +# check for argument handling + +assert int("101", base=2) == 5 + # magic methods should only be implemented for other ints assert (1).__eq__(1) == True @@ -36,6 +42,7 @@ assert (1).real == 1 assert (1).imag == 0 +assert_raises(OverflowError, lambda: 1 << 10 ** 100000) assert (1).__eq__(1.0) == NotImplemented assert (1).__ne__(1.0) == NotImplemented diff --git a/tests/snippets/isinstance.py b/tests/snippets/isinstance.py new file mode 100644 index 0000000000..52645e9b87 --- /dev/null +++ b/tests/snippets/isinstance.py @@ -0,0 +1,54 @@ + +class Regular: + pass + + +assert isinstance(Regular(), Regular) + + +class MCNotInstanceOf(type): + def __instancecheck__(self, instance): + return False + + +class NotInstanceOf(metaclass=MCNotInstanceOf): + pass + + +class InheritedNotInstanceOf(NotInstanceOf): + pass + + +assert not isinstance(Regular(), NotInstanceOf) +assert not isinstance(1, NotInstanceOf) + +# weird cpython behaviour if exact match then isinstance return true +assert isinstance(NotInstanceOf(), NotInstanceOf) +assert not NotInstanceOf.__instancecheck__(NotInstanceOf()) +assert not isinstance(InheritedNotInstanceOf(), NotInstanceOf) + + +class MCAlwaysInstanceOf(type): + def __instancecheck__(self, instance): + return True + + +class AlwaysInstanceOf(metaclass=MCAlwaysInstanceOf): + pass + + +assert isinstance(AlwaysInstanceOf(), AlwaysInstanceOf) +assert isinstance(Regular(), AlwaysInstanceOf) +assert isinstance(1, AlwaysInstanceOf) + + +class MCReturnInt(type): + def __instancecheck__(self, instance): + return 3 + + +class ReturnInt(metaclass=MCReturnInt): + pass + + +assert isinstance("a", ReturnInt) is True diff --git a/tests/snippets/issubclass.py b/tests/snippets/issubclass.py new file mode 100644 index 0000000000..62577f2133 --- /dev/null +++ b/tests/snippets/issubclass.py @@ -0,0 +1,63 @@ + +class A: + pass + + +class B(A): + pass + + +assert issubclass(A, A) +assert issubclass(B, A) +assert not issubclass(A, B) + + +class MCNotSubClass(type): + def __subclasscheck__(self, subclass): + return False + + +class NotSubClass(metaclass=MCNotSubClass): + pass + + +class InheritedNotSubClass(NotSubClass): + pass + + +assert not issubclass(A, NotSubClass) +assert not issubclass(NotSubClass, NotSubClass) +assert not issubclass(InheritedNotSubClass, NotSubClass) +assert not issubclass(NotSubClass, InheritedNotSubClass) + + +class MCAlwaysSubClass(type): + def __subclasscheck__(self, subclass): + return True + + +class AlwaysSubClass(metaclass=MCAlwaysSubClass): + pass + + +class InheritedAlwaysSubClass(AlwaysSubClass): + pass + + +assert issubclass(A, AlwaysSubClass) +assert issubclass(AlwaysSubClass, AlwaysSubClass) +assert issubclass(InheritedAlwaysSubClass, AlwaysSubClass) +assert issubclass(AlwaysSubClass, InheritedAlwaysSubClass) + + +class MCAVirtualSubClass(type): + def __subclasscheck__(self, subclass): + return subclass is A + + +class AVirtualSubClass(metaclass=MCAVirtualSubClass): + pass + + +assert issubclass(A, AVirtualSubClass) +assert not isinstance(B, AVirtualSubClass) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 881279f075..6b9839321d 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -29,8 +29,22 @@ assert x > y, "list __gt__ failed" -assert [1,2,'a'].pop() == 'a', "list pop failed" +x = [0, 1, 2] +assert x.pop() == 2 +assert x == [0, 1] + +def test_pop(lst, idx, value, new_lst): + assert lst.pop(idx) == value + assert lst == new_lst +test_pop([0, 1, 2], -1, 2, [0, 1]) +test_pop([0, 1, 2], 0, 0, [1, 2]) +test_pop([0, 1, 2], 1, 1, [0, 2]) +test_pop([0, 1, 2], 2, 2, [0, 1]) assert_raises(IndexError, lambda: [].pop()) +assert_raises(IndexError, lambda: [].pop(0)) +assert_raises(IndexError, lambda: [].pop(-1)) +assert_raises(IndexError, lambda: [0].pop(1)) +assert_raises(IndexError, lambda: [0].pop(-2)) recursive = [] recursive.append(recursive) @@ -117,3 +131,42 @@ def __eq__(self, x): assert a == b assert [foo] == [foo] + +for size in [1, 2, 3, 4, 5, 8, 10, 100, 1000]: + lst = list(range(size)) + orig = lst[:] + lst.sort() + assert lst == orig + assert sorted(lst) == orig + assert_raises(ZeroDivisionError, lambda: sorted(lst, key=lambda x: 1/x)) + lst.reverse() + assert sorted(lst) == orig + assert sorted(lst, reverse=True) == lst + assert sorted(lst, key=lambda x: -x) == lst + assert sorted(lst, key=lambda x: -x, reverse=True) == orig + +assert sorted([(1, 2, 3), (0, 3, 6)]) == [(0, 3, 6), (1, 2, 3)] +assert sorted([(1, 2, 3), (0, 3, 6)], key=lambda x: x[0]) == [(0, 3, 6), (1, 2, 3)] +assert sorted([(1, 2, 3), (0, 3, 6)], key=lambda x: x[1]) == [(1, 2, 3), (0, 3, 6)] +assert sorted([(1, 2), (), (5,)], key=len) == [(), (5,), (1, 2)] + +lst = [3, 1, 5, 2, 4] +class C: + def __init__(self, x): self.x = x + def __lt__(self, other): return self.x < other.x +lst.sort(key=C) +assert lst == [1, 2, 3, 4, 5] + +lst = [3, 1, 5, 2, 4] +class C: + def __init__(self, x): self.x = x + def __gt__(self, other): return self.x > other.x +lst.sort(key=C) +assert lst == [1, 2, 3, 4, 5] + +lst = [5, 1, 2, 3, 4] +def f(x): + lst.append(1) + return x +assert_raises(ValueError, lambda: lst.sort(key=f)) # "list modified during sort" +assert lst == [1, 2, 3, 4, 5] diff --git a/tests/snippets/mro.py b/tests/snippets/mro.py index 6f3476f91d..18acbb6916 100644 --- a/tests/snippets/mro.py +++ b/tests/snippets/mro.py @@ -18,3 +18,5 @@ class C(A, B): pass assert (C, A, B, X, Y, object) == C.__mro__ + +assert type.__mro__ == (type, object) diff --git a/tests/snippets/property.py b/tests/snippets/property.py new file mode 100644 index 0000000000..c61f131899 --- /dev/null +++ b/tests/snippets/property.py @@ -0,0 +1,77 @@ +from testutils import assertRaises + + +class Fubar: + def __init__(self): + self.x = 100 + + @property + def foo(self): + value = self.x + self.x += 1 + return value + + +f = Fubar() +assert f.foo == 100 +assert f.foo == 101 + +assert type(Fubar.foo) is property + + +class Bar: + def __init__(self): + self.a = 0 + + @property + def foo(self): + return self.a + + @foo.setter + def foo(self, value): + self.a += value + + @foo.deleter + def foo(self): + self.a -= 1 + + +bar = Bar() +assert bar.foo == 0 +bar.foo = 5 +assert bar.a == 5 +del bar.foo +assert bar.a == 4 +del bar.foo +assert bar.a == 3 + + +null_property = property() +assert type(null_property) is property + +p = property(lambda x: x[0]) +assert p.__get__((2,), tuple) == 2 +assert p.__get__((2,)) == 2 + +with assertRaises(AttributeError): + null_property.__get__((), tuple) + +with assertRaises(TypeError): + property.__new__(object) + + +p1 = property("a", "b", "c") + +assert p1.fget == "a" +assert p1.fset == "b" +assert p1.fdel == "c" + +assert p1.getter(1).fget == 1 +assert p1.setter(2).fset == 2 +assert p1.deleter(3).fdel == 3 + +assert p1.getter(None).fget == "a" +assert p1.setter(None).fset == "b" +assert p1.deleter(None).fdel == "c" + +assert p1.__get__(None, object) is p1 diff --git a/tests/snippets/stdlib_socket.py b/tests/snippets/stdlib_socket.py new file mode 100644 index 0000000000..79efaa3639 --- /dev/null +++ b/tests/snippets/stdlib_socket.py @@ -0,0 +1,104 @@ +import socket +from testutils import assertRaises + +MESSAGE_A = b'aaaa' +MESSAGE_B= b'bbbbb' + +# TCP + +listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +listener.bind(("127.0.0.1", 0)) +listener.listen(1) + +connector = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +connector.connect(("127.0.0.1", listener.getsockname()[1])) +(connection, addr) = listener.accept() +assert addr == connector.getsockname() + +connector.send(MESSAGE_A) +connection.send(MESSAGE_B) +recv_a = connection.recv(len(MESSAGE_A)) +recv_b = connector.recv(len(MESSAGE_B)) +assert recv_a == MESSAGE_A +assert recv_b == MESSAGE_B +connection.close() +connector.close() +listener.close() + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +with assertRaises(TypeError): + s.connect(("127.0.0.1", 8888, 8888)) + +with assertRaises(OSError): + # Lets hope nobody is listening on port 1 + s.connect(("127.0.0.1", 1)) + +with assertRaises(TypeError): + s.bind(("127.0.0.1", 8888, 8888)) + +with assertRaises(OSError): + # Lets hope nobody run this test on machine with ip 1.2.3.4 + s.bind(("1.2.3.4", 8888)) + +with assertRaises(TypeError): + s.bind((888, 8888)) + +s.close() +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.bind(("127.0.0.1", 0)) +with assertRaises(OSError): + s.recv(100) + +with assertRaises(OSError): + s.send(MESSAGE_A) + +s.close() + +# UDP +sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock1.bind(("127.0.0.1", 0)) + +sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + +sock2.sendto(MESSAGE_A, sock1.getsockname()) +(recv_a, addr1) = sock1.recvfrom(len(MESSAGE_A)) +assert recv_a == MESSAGE_A + +sock2.sendto(MESSAGE_B, sock1.getsockname()) +(recv_b, addr2) = sock1.recvfrom(len(MESSAGE_B)) +assert recv_b == MESSAGE_B +assert addr1[0] == addr2[0] +assert addr1[1] == addr2[1] + +sock2.close() + +sock3 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock3.bind(("127.0.0.1", 0)) +sock3.sendto(MESSAGE_A, sock1.getsockname()) +(recv_a, addr) = sock1.recvfrom(len(MESSAGE_A)) +assert recv_a == MESSAGE_A +assert addr == sock3.getsockname() + +sock1.connect(("127.0.0.1", sock3.getsockname()[1])) +sock3.connect(("127.0.0.1", sock1.getsockname()[1])) + +sock1.send(MESSAGE_A) +sock3.send(MESSAGE_B) +recv_a = sock3.recv(len(MESSAGE_A)) +recv_b = sock1.recv(len(MESSAGE_B)) +assert recv_a == MESSAGE_A +assert recv_b == MESSAGE_B +sock1.close() +sock3.close() + +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +with assertRaises(OSError): + s.bind(("1.2.3.4", 888)) + +s.close() +### Errors +with assertRaises(OSError): + socket.socket(100, socket.SOCK_STREAM) + +with assertRaises(OSError): + socket.socket(socket.AF_INET, 1000) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 45fbfa0c7c..4483b37f62 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -62,6 +62,45 @@ assert c.title() == 'Hallo' assert c.count('l') == 2 +assert 'aaa'.count('a') == 3 +assert 'aaa'.count('a', 1) == 2 +assert 'aaa'.count('a', 1, 2) == 1 +assert 'aaa'.count('a', 2, 2) == 0 +assert 'aaa'.count('a', 2, 1) == 0 + +assert '___a__'.find('a') == 3 +assert '___a__'.find('a', -10) == 3 +assert '___a__'.find('a', -3) == 3 +assert '___a__'.find('a', -2) == -1 +assert '___a__'.find('a', -1) == -1 +assert '___a__'.find('a', 0) == 3 +assert '___a__'.find('a', 3) == 3 +assert '___a__'.find('a', 4) == -1 +assert '___a__'.find('a', 10) == -1 +assert '___a__'.rfind('a', 3) == 3 +assert '___a__'.index('a', 3) == 3 + +assert '___a__'.find('a', 0, -10) == -1 +assert '___a__'.find('a', 0, -3) == -1 +assert '___a__'.find('a', 0, -2) == 3 +assert '___a__'.find('a', 0, -1) == 3 +assert '___a__'.find('a', 0, 0) == -1 +assert '___a__'.find('a', 0, 3) == -1 +assert '___a__'.find('a', 0, 4) == 3 +assert '___a__'.find('a', 0, 10) == 3 + +assert '___a__'.find('a', 3, 3) == -1 +assert '___a__'.find('a', 3, 4) == 3 +assert '___a__'.find('a', 4, 3) == -1 + +assert 'abcd'.startswith('b', 1) +assert not 'abcd'.startswith('b', -4) +assert 'abcd'.startswith('b', -3) + +assert not 'abcd'.startswith('b', 3, 3) +assert 'abcd'.startswith('', 3, 3) +assert not 'abcd'.startswith('', 4, 3) + assert ' '.isspace() assert 'hello\nhallo\nHallo'.splitlines() == ['hello', 'hallo', 'Hallo'] assert 'abc\t12345\txyz'.expandtabs() == 'abc 12345 xyz' diff --git a/tests/snippets/subclass_str.py b/tests/snippets/subclass_str.py new file mode 100644 index 0000000000..706566ec6d --- /dev/null +++ b/tests/snippets/subclass_str.py @@ -0,0 +1,22 @@ +from testutils import assertRaises + +x = "An interesting piece of text" +assert x is str(x) + +class Stringy(str): + def __new__(cls, value=""): + return str.__new__(cls, value) + + def __init__(self, value): + self.x = "substr" + +y = Stringy(1) +assert type(y) is Stringy, "Type of Stringy should be stringy" +assert type(str(y)) is str, "Str of a str-subtype should be a str." + +assert y + " other" == "1 other" +assert y.x == "substr" + +## Base strings currently get an attribute dict, but shouldn't. +# with assertRaises(AttributeError): +# "hello".x = 5 diff --git a/tests/snippets/test_exec.py b/tests/snippets/test_exec.py new file mode 100644 index 0000000000..61932c24cb --- /dev/null +++ b/tests/snippets/test_exec.py @@ -0,0 +1,46 @@ +exec("def square(x):\n return x * x\n") +assert 16 == square(4) + +d = {} +exec("def square(x):\n return x * x\n", {}, d) +assert 16 == d['square'](4) + +exec("assert 2 == x", {}, {'x': 2}) +exec("assert 2 == x", {'x': 2}, {}) +exec("assert 4 == x", {'x': 2}, {'x': 4}) + +exec("assert max(1, 2) == 2", {}, {}) + +exec("assert max(1, 5, square(5)) == 25", None) + +# Local environment shouldn't replace global environment: +exec("assert max(1, 5, square(5)) == 25", None, {}) + +# Closures aren't available if local scope is replaced: +def g(): + seven = "seven" + def f(): + try: + exec("seven", None, {}) + except NameError: + pass + else: + raise NameError("seven shouldn't be in scope") + f() +g() + +try: + exec("", 1) +except TypeError: + pass +else: + raise TypeError("exec should fail unless globals is a dict or None") + +g = globals() +g['x'] = 2 +exec('x += 2') +assert x == 4 +assert g['x'] == x + +exec("del x") +assert 'x' not in g diff --git a/tests/snippets/test_io.py b/tests/snippets/test_io.py new file mode 100644 index 0000000000..bc49b72870 --- /dev/null +++ b/tests/snippets/test_io.py @@ -0,0 +1,14 @@ + + +import io + +f = io.StringIO() +f.write('bladibla') +assert f.getvalue() == 'bladibla' + +# TODO: +# print('fubar', file=f, end='') +print(f.getvalue()) + +# TODO: +# assert f.getvalue() == 'bladiblafubar' diff --git a/tests/snippets/test_re.py b/tests/snippets/test_re.py index 24a2ee3b4a..2463f380b5 100644 --- a/tests/snippets/test_re.py +++ b/tests/snippets/test_re.py @@ -4,5 +4,10 @@ haystack = "Hello world" needle = 'ello' -print(re.search(needle, haystack)) +mo = re.search(needle, haystack) +print(mo) +# Does not work on python 3.6: +# assert isinstance(mo, re.Match) +assert mo.start() == 1 +assert mo.end() == 5 diff --git a/tests/snippets/testutils.py b/tests/snippets/testutils.py index 8237ceb621..7dda57b17a 100644 --- a/tests/snippets/testutils.py +++ b/tests/snippets/testutils.py @@ -29,7 +29,7 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: - failmsg = '{!s} was not raised'.format(self.expected.__name_) + failmsg = '{!s} was not raised'.format(self.expected.__name__) assert False, failmsg if not issubclass(exc_type, self.expected): return False diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index a121241a87..4e3b7269aa 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -8,6 +8,19 @@ # print(ex.__traceback__) # print(type(ex.__traceback__)) +try: + raise ZeroDivisionError +except ZeroDivisionError as ex: + pass + +class E(Exception): + def __init__(self): + asdf + +try: + raise E +except NameError as ex: + pass l = [] try: diff --git a/tests/snippets/type_hints.py b/tests/snippets/type_hints.py new file mode 100644 index 0000000000..b6c25e59f1 --- /dev/null +++ b/tests/snippets/type_hints.py @@ -0,0 +1,12 @@ + +# See also: https://github.com/RustPython/RustPython/issues/587 + +def curry(foo: int, bla: int =2) -> float: + return foo * 3.1415926 * bla + +assert curry(2) > 10 + +print(curry.__annotations__) +assert curry.__annotations__['foo'] is int +assert curry.__annotations__['return'] is float +assert curry.__annotations__['bla'] is int diff --git a/tests/snippets/types_snippet.py b/tests/snippets/types_snippet.py index 3fc27b71c0..ac715751ef 100644 --- a/tests/snippets/types_snippet.py +++ b/tests/snippets/types_snippet.py @@ -15,5 +15,14 @@ assert type(cls) is metaclass assert type(metaclass) is type +assert issubclass(metaclass, type) +assert isinstance(cls, type) + +assert inst.__class__ is cls +assert cls.__class__ is metaclass +assert metaclass.__class__ is type +assert type.__class__ is type +assert None.__class__ is type(None) + assert isinstance(type, type) assert issubclass(type, type) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index f0ab63aabf..e1b1cc9ffa 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -10,14 +10,17 @@ num-complex = "0.2" num-bigint = "0.2.1" num-traits = "0.2" num-integer = "0.1.39" +num-rational = "0.2.1" rand = "0.5" log = "0.3" +rustpython_derive = {path = "../derive"} rustpython_parser = {path = "../parser"} serde = "1.0.66" serde_derive = "1.0.66" serde_json = "1.0.26" byteorder = "1.2.6" regex = "1" +rustc_version_runtime = "0.1.*" statrs = "0.10.0" caseless = "0.2.1" unicode-segmentation = "1.2.1" diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index a14925add0..68e933db3e 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -2,30 +2,32 @@ //! //! Implements functions listed here: https://docs.python.org/3/library/builtins.html -// use std::ops::Deref; use std::char; use std::io::{self, Write}; +use std::path::PathBuf; + +use num_traits::{Signed, ToPrimitive}; use crate::compile; +use crate::import::import_module; use crate::obj::objbool; use crate::obj::objdict; use crate::obj::objint; use crate::obj::objiter; -use crate::obj::objstr; +use crate::obj::objstr::{self, PyStringRef}; use crate::obj::objtype; +use crate::frame::Scope; +use crate::function::{Args, OptionalArg, PyFuncArgs}; use crate::pyobject::{ - AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, - PyResult, Scope, TypeProtocol, + AttributeProtocol, DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol, }; +use crate::vm::VirtualMachine; #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::io::io_open; -use crate::vm::VirtualMachine; -use num_traits::{Signed, ToPrimitive}; - -fn get_locals(vm: &mut VirtualMachine) -> PyObjectRef { +fn get_locals(vm: &VirtualMachine) -> PyObjectRef { let d = vm.new_dict(); // TODO: implement dict_iter_items? let locals = vm.get_locals(); @@ -36,23 +38,11 @@ fn get_locals(vm: &mut VirtualMachine) -> PyObjectRef { d } -fn dir_locals(vm: &mut VirtualMachine) -> PyObjectRef { +fn dir_locals(vm: &VirtualMachine) -> PyObjectRef { get_locals(vm) } -fn dir_object(vm: &mut VirtualMachine, obj: &PyObjectRef) -> PyObjectRef { - // Gather all members here: - let attributes = objtype::get_attributes(obj); - let mut members: Vec = attributes.into_iter().map(|(n, _o)| n).collect(); - - // Sort members: - members.sort(); - - let members_pystr = members.into_iter().map(|m| vm.ctx.new_str(m)).collect(); - vm.ctx.new_list(members_pystr) -} - -fn builtin_abs(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_abs(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(x, None)]); match vm.get_method(x.clone(), "__abs__") { Ok(attrib) => vm.invoke(attrib, PyFuncArgs::new(vec![], vec![])), @@ -60,7 +50,7 @@ fn builtin_abs(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn builtin_all(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_all(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(iterable, None)]); let items = vm.extract_elements(iterable)?; for item in items { @@ -72,21 +62,23 @@ fn builtin_all(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_bool(true)) } -fn builtin_any(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_any(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(iterable, None)]); - let items = vm.extract_elements(iterable)?; - for item in items { + let iterator = objiter::get_iter(vm, iterable)?; + + while let Some(item) = objiter::get_next_object(vm, &iterator)? { let result = objbool::boolval(vm, item)?; if result { return Ok(vm.new_bool(true)); } } + Ok(vm.new_bool(false)) } // builtin_ascii -fn builtin_bin(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_bin(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(number, Some(vm.ctx.int_type()))]); let n = objint::get_value(number); @@ -101,13 +93,13 @@ fn builtin_bin(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // builtin_breakpoint -fn builtin_callable(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_callable(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); let is_callable = obj.typ().has_attr("__call__"); Ok(vm.new_bool(is_callable)) } -fn builtin_chr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_chr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); let code_point = objint::get_value(i).to_u32().unwrap(); @@ -120,7 +112,7 @@ fn builtin_chr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(txt)) } -fn builtin_compile(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_compile(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -157,7 +149,7 @@ fn builtin_compile(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }) } -fn builtin_delattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_delattr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -166,36 +158,37 @@ fn builtin_delattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vm.del_attr(obj, attr.clone()) } -fn builtin_dir(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_dir(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if args.args.is_empty() { Ok(dir_locals(vm)) } else { let obj = args.args.into_iter().next().unwrap(); - Ok(dir_object(vm, &obj)) + let seq = vm.call_method(&obj, "__dir__", vec![])?; + let sorted = builtin_sorted(vm, PyFuncArgs::new(vec![seq], vec![]))?; + Ok(sorted) } } -fn builtin_divmod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_divmod(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(x, None), (y, None)]); match vm.get_method(x.clone(), "__divmod__") { - Ok(attrib) => vm.invoke(attrib, PyFuncArgs::new(vec![y.clone()], vec![])), + Ok(attrib) => vm.invoke(attrib, vec![y.clone()]), Err(..) => Err(vm.new_type_error("unsupported operand type(s) for divmod".to_string())), } } /// Implements `eval`. /// See also: https://docs.python.org/3/library/functions.html#eval -fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_eval(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, required = [(source, None)], - optional = [ - (_globals, Some(vm.ctx.dict_type())), - (locals, Some(vm.ctx.dict_type())) - ] + optional = [(globals, None), (locals, Some(vm.ctx.dict_type()))] ); + let scope = make_scope(vm, globals, locals)?; + // Determine code object: let code_obj = if objtype::isinstance(source, &vm.ctx.code_type()) { source.clone() @@ -214,25 +207,22 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("code argument must be str or code object".to_string())); }; - let scope = make_scope(vm, locals); - // Run the source: vm.run_code_obj(code_obj.clone(), scope) } /// Implements `exec` /// https://docs.python.org/3/library/functions.html#exec -fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, required = [(source, None)], - optional = [ - (_globals, Some(vm.ctx.dict_type())), - (locals, Some(vm.ctx.dict_type())) - ] + optional = [(globals, None), (locals, Some(vm.ctx.dict_type()))] ); + let scope = make_scope(vm, globals, locals)?; + // Determine code object: let code_obj = if objtype::isinstance(source, &vm.ctx.str_type()) { let mode = compile::Mode::Exec; @@ -251,35 +241,49 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("source argument must be str or code object".to_string())); }; - let scope = make_scope(vm, locals); - // Run the code: vm.run_code_obj(code_obj, scope) } -fn make_scope(vm: &mut VirtualMachine, locals: Option<&PyObjectRef>) -> PyObjectRef { - // handle optional global and locals - let locals = if let Some(locals) = locals { - locals.clone() - } else { - vm.new_dict() +fn make_scope( + vm: &VirtualMachine, + globals: Option<&PyObjectRef>, + locals: Option<&PyObjectRef>, +) -> PyResult { + let dict_type = vm.ctx.dict_type(); + let globals = match globals { + Some(arg) => { + if arg.is(&vm.get_none()) { + None + } else if vm.isinstance(arg, &dict_type)? { + Some(arg) + } else { + let arg_typ = arg.typ(); + let actual_type = vm.to_pystr(&arg_typ)?; + let expected_type_name = vm.to_pystr(&dict_type)?; + return Err(vm.new_type_error(format!( + "globals must be a {}, not {}", + expected_type_name, actual_type + ))); + } + } + None => None, }; - // TODO: handle optional globals - // Construct new scope: - let scope_inner = Scope { - locals, - parent: None, + let current_scope = vm.current_scope(); + let globals = match globals { + Some(dict) => dict.clone(), + None => current_scope.globals.clone(), + }; + let locals = match locals { + Some(dict) => Some(dict.clone()), + None => current_scope.get_only_locals(), }; - PyObject { - payload: PyObjectPayload::Scope { scope: scope_inner }, - typ: None, - } - .into_ref() + Ok(Scope::new(locals, globals)) } -fn builtin_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -292,31 +296,41 @@ fn builtin_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vm.call_method(obj, "__format__", vec![format_spec]) } -fn builtin_getattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(obj, None), (attr, Some(vm.ctx.str_type()))] - ); - vm.get_attribute(obj.clone(), attr.clone()) +fn catch_attr_exception(ex: PyObjectRef, default: T, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&ex, &vm.ctx.exceptions.attribute_error) { + Ok(default) + } else { + Err(ex) + } } -// builtin_globals +fn builtin_getattr( + obj: PyObjectRef, + attr: PyStringRef, + default: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let ret = vm.get_attribute(obj.clone(), attr); + if let OptionalArg::Present(default) = default { + ret.or_else(|ex| catch_attr_exception(ex, default, vm)) + } else { + ret + } +} -fn builtin_hasattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(obj, None), (attr, Some(vm.ctx.str_type()))] - ); - let has_attr = match vm.get_attribute(obj.clone(), attr.clone()) { - Ok(..) => true, - Err(..) => false, - }; - Ok(vm.context().new_bool(has_attr)) +fn builtin_globals(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { + Ok(vm.current_scope().globals.clone()) } -fn builtin_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_hasattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> PyResult { + if let Err(ex) = vm.get_attribute(obj.clone(), attr) { + catch_attr_exception(ex, false, vm) + } else { + Ok(true) + } +} + +fn builtin_hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); vm.call_method(obj, "__hash__", vec![]) @@ -324,7 +338,7 @@ fn builtin_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // builtin_help -fn builtin_hex(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_hex(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(number, Some(vm.ctx.int_type()))]); let n = objint::get_value(number); @@ -337,7 +351,7 @@ fn builtin_hex(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(s)) } -fn builtin_id(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_id(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); Ok(vm.context().new_int(obj.get_id())) @@ -345,30 +359,34 @@ fn builtin_id(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // builtin_input -fn builtin_isinstance(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, None), (typ, None)]); +fn builtin_isinstance(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(obj, None), (typ, Some(vm.get_type()))] + ); - let isinstance = objtype::isinstance(obj, typ); - Ok(vm.context().new_bool(isinstance)) + let isinstance = vm.isinstance(obj, typ)?; + Ok(vm.new_bool(isinstance)) } -fn builtin_issubclass(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - if args.args.len() != 2 { - panic!("issubclass expects exactly two parameters"); - } - - let cls1 = &args.args[0]; - let cls2 = &args.args[1]; +fn builtin_issubclass(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(subclass, Some(vm.get_type())), (cls, Some(vm.get_type()))] + ); - Ok(vm.context().new_bool(objtype::issubclass(cls1, cls2))) + let issubclass = vm.issubclass(subclass, cls)?; + Ok(vm.context().new_bool(issubclass)) } -fn builtin_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(iter_target, None)]); objiter::get_iter(vm, iter_target) } -fn builtin_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); let len_method_name = "__len__"; match vm.get_method(obj.clone(), len_method_name) { @@ -381,12 +399,12 @@ fn builtin_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn builtin_locals(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_locals(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args); Ok(vm.get_locals()) } -fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_max(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let candidates = if args.args.len() > 1 { args.args.clone() } else if args.args.len() == 1 { @@ -413,16 +431,14 @@ fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: this key function looks pretty duplicate. Maybe we can create // a local function? let mut x_key = if let Some(f) = &key_func { - let args = PyFuncArgs::new(vec![x.clone()], vec![]); - vm.invoke(f.clone(), args)? + vm.invoke(f.clone(), vec![x.clone()])? } else { x.clone() }; for y in candidates_iter { let y_key = if let Some(f) = &key_func { - let args = PyFuncArgs::new(vec![y.clone()], vec![]); - vm.invoke(f.clone(), args)? + vm.invoke(f.clone(), vec![y.clone()])? } else { y.clone() }; @@ -437,7 +453,7 @@ fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(x) } -fn builtin_min(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_min(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let candidates = if args.args.len() > 1 { args.args.clone() } else if args.args.len() == 1 { @@ -463,16 +479,14 @@ fn builtin_min(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: this key function looks pretty duplicate. Maybe we can create // a local function? let mut x_key = if let Some(f) = &key_func { - let args = PyFuncArgs::new(vec![x.clone()], vec![]); - vm.invoke(f.clone(), args)? + vm.invoke(f.clone(), vec![x.clone()])? } else { x.clone() }; for y in candidates_iter { let y_key = if let Some(f) = &key_func { - let args = PyFuncArgs::new(vec![y.clone()], vec![]); - vm.invoke(f.clone(), args)? + vm.invoke(f.clone(), vec![y.clone()])? } else { y.clone() }; @@ -487,7 +501,7 @@ fn builtin_min(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(x) } -fn builtin_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -510,7 +524,7 @@ fn builtin_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn builtin_oct(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_oct(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(number, Some(vm.ctx.int_type()))]); let n = objint::get_value(number); @@ -523,7 +537,7 @@ fn builtin_oct(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(s)) } -fn builtin_ord(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_ord(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(string, Some(vm.ctx.str_type()))]); let string = objstr::get_value(string); let string_len = string.chars().count(); @@ -541,7 +555,7 @@ fn builtin_ord(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn builtin_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -550,7 +564,7 @@ fn builtin_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ); let pow_method_name = "__pow__"; let result = match vm.get_method(x.clone(), pow_method_name) { - Ok(attrib) => vm.invoke(attrib, PyFuncArgs::new(vec![y.clone()], vec![])), + Ok(attrib) => vm.invoke(attrib, vec![y.clone()]), Err(..) => Err(vm.new_type_error("unsupported operand type(s) for pow".to_string())), }; //Check if the 3rd argument is defined and perform modulus on the result @@ -560,7 +574,7 @@ fn builtin_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Some(mod_value) => { let mod_method_name = "__mod__"; match vm.get_method(result.expect("result not defined").clone(), mod_method_name) { - Ok(value) => vm.invoke(value, PyFuncArgs::new(vec![mod_value.clone()], vec![])), + Ok(value) => vm.invoke(value, vec![mod_value.clone()]), Err(..) => { Err(vm.new_type_error("unsupported operand type(s) for mod".to_string())) } @@ -570,79 +584,47 @@ fn builtin_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -pub fn builtin_print(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("print called with {:?}", args); - - // Handle 'sep' kwarg: - let sep_arg = args - .get_optional_kwarg("sep") - .filter(|obj| !obj.is(&vm.get_none())); - if let Some(ref obj) = sep_arg { - if !objtype::isinstance(obj, &vm.ctx.str_type()) { - return Err(vm.new_type_error(format!( - "sep must be None or a string, not {}", - objtype::get_type_name(&obj.typ()) - ))); - } - } - let sep_str = sep_arg.as_ref().map(|obj| objstr::borrow_value(obj)); - - // Handle 'end' kwarg: - let end_arg = args - .get_optional_kwarg("end") - .filter(|obj| !obj.is(&vm.get_none())); - if let Some(ref obj) = end_arg { - if !objtype::isinstance(obj, &vm.ctx.str_type()) { - return Err(vm.new_type_error(format!( - "end must be None or a string, not {}", - objtype::get_type_name(&obj.typ()) - ))); - } - } - let end_str = end_arg.as_ref().map(|obj| objstr::borrow_value(obj)); - - // Handle 'flush' kwarg: - let flush = if let Some(flush) = &args.get_optional_kwarg("flush") { - objbool::boolval(vm, flush.clone()).unwrap() - } else { - false - }; +#[derive(Debug, FromArgs)] +pub struct PrintOptions { + sep: Option, + end: Option, + flush: bool, +} +pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { let stdout = io::stdout(); let mut stdout_lock = stdout.lock(); let mut first = true; - for a in &args.args { + for object in objects { if first { first = false; - } else if let Some(ref sep_str) = sep_str { - write!(stdout_lock, "{}", sep_str).unwrap(); + } else if let Some(ref sep) = options.sep { + write!(stdout_lock, "{}", sep.value).unwrap(); } else { write!(stdout_lock, " ").unwrap(); } - let v = vm.to_str(&a)?; - let s = objstr::borrow_value(&v); + let s = &vm.to_str(&object)?.value; write!(stdout_lock, "{}", s).unwrap(); } - if let Some(end_str) = end_str { - write!(stdout_lock, "{}", end_str).unwrap(); + if let Some(end) = options.end { + write!(stdout_lock, "{}", end.value).unwrap(); } else { writeln!(stdout_lock).unwrap(); } - if flush { + if options.flush { stdout_lock.flush().unwrap(); } - Ok(vm.get_none()) + Ok(()) } -fn builtin_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, None)]); - vm.to_repr(obj) +fn builtin_repr(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + vm.to_repr(&obj) } -fn builtin_reversed(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_reversed(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); match vm.get_method(obj.clone(), "__reversed__") { @@ -656,7 +638,7 @@ fn builtin_reversed(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } // builtin_reversed -fn builtin_round(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_round(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -670,25 +652,33 @@ fn builtin_round(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } else { // without a parameter, the result type is coerced to int let rounded = &vm.call_method(number, "__round__", vec![])?; - Ok(vm.ctx.new_int(objint::get_value(rounded))) + Ok(vm.ctx.new_int(objint::get_value(rounded).clone())) } } -fn builtin_setattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(obj, None), (attr, Some(vm.ctx.str_type())), (value, None)] - ); - let name = objstr::get_value(attr); - vm.ctx.set_attr(obj, &name, value.clone()); - Ok(vm.get_none()) +fn builtin_setattr( + obj: PyObjectRef, + attr: PyStringRef, + value: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult<()> { + vm.set_attr(&obj, attr.into_object(), value)?; + Ok(()) } // builtin_slice -// builtin_sorted -fn builtin_sum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn builtin_sorted(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(iterable, None)]); + let items = vm.extract_elements(iterable)?; + let lst = vm.ctx.new_list(items); + + args.shift(); + vm.call_method(&lst, "sort", args)?; + Ok(lst) +} + +fn builtin_sum(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(iterable, None)]); let items = vm.extract_elements(iterable)?; @@ -700,8 +690,27 @@ fn builtin_sum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(sum) } +// Should be renamed to builtin___import__? +fn builtin_import(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(name, Some(vm.ctx.str_type()))], + optional = [ + (_globals, Some(vm.ctx.dict_type())), + (_locals, Some(vm.ctx.dict_type())) + ] + ); + let current_path = { + let mut source_pathbuf = PathBuf::from(&vm.current_frame().code.source_path); + source_pathbuf.pop(); + source_pathbuf + }; + + import_module(vm, current_path, &objstr::get_value(name)) +} + // builtin_vars -// builtin___import__ pub fn make_module(ctx: &PyContext) -> PyObjectRef { let py_mod = py_module!(ctx, "__builtins__", { @@ -732,6 +741,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "filter" => ctx.filter_type(), "format" => ctx.new_rustfunc(builtin_format), "getattr" => ctx.new_rustfunc(builtin_getattr), + "globals" => ctx.new_rustfunc(builtin_globals), "hasattr" => ctx.new_rustfunc(builtin_hasattr), "hash" => ctx.new_rustfunc(builtin_hash), "hex" => ctx.new_rustfunc(builtin_hex), @@ -760,6 +770,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "round" => ctx.new_rustfunc(builtin_round), "set" => ctx.set_type(), "setattr" => ctx.new_rustfunc(builtin_setattr), + "sorted" => ctx.new_rustfunc(builtin_sorted), "slice" => ctx.slice_type(), "staticmethod" => ctx.staticmethod_type(), "str" => ctx.str_type(), @@ -768,6 +779,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "tuple" => ctx.tuple_type(), "type" => ctx.type_type(), "zip" => ctx.zip_type(), + "__import__" => ctx.new_rustfunc(builtin_import), // Constants "NotImplemented" => ctx.not_implemented.clone(), @@ -790,6 +802,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "StopIteration" => ctx.exceptions.stop_iteration.clone(), "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(), "KeyError" => ctx.exceptions.key_error.clone(), + "OSError" => ctx.exceptions.os_error.clone(), }); #[cfg(not(target_arch = "wasm32"))] @@ -798,7 +811,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_mod } -pub fn builtin_build_class_(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { +pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { let function = args.shift(); let name_arg = args.shift(); let bases = args.args.clone(); @@ -815,23 +828,13 @@ pub fn builtin_build_class_(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> Py let bases = vm.context().new_tuple(bases); // Prepare uses full __getattribute__ resolution chain. - let prepare_name = vm.new_str("__prepare__".to_string()); - let prepare = vm.get_attribute(metaclass.clone(), prepare_name)?; - let namespace = vm.invoke( - prepare, - PyFuncArgs { - args: vec![name_arg.clone(), bases.clone()], - kwargs: vec![], - }, - )?; - - vm.invoke( - function, - PyFuncArgs { - args: vec![namespace.clone()], - kwargs: vec![], - }, - )?; - - vm.call_method(&metaclass, "__call__", vec![name_arg, bases, namespace]) + let prepare = vm.get_attribute(metaclass.clone(), "__prepare__")?; + let namespace = vm.invoke(prepare, vec![name_arg.clone(), bases.clone()])?; + + let cells = vm.new_dict(); + + vm.invoke_with_locals(function, cells.clone(), namespace.clone())?; + let class = vm.call_method(&metaclass, "__call__", vec![name_arg, bases, namespace])?; + cells.set_item(&vm.ctx, "__class__", class.clone()); + Ok(class) } diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 1e0b85f133..2fe485b63b 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -18,10 +18,10 @@ pub struct CodeObject { pub instructions: Vec, pub label_map: HashMap, pub locations: Vec, - pub arg_names: Vec, // Names of positional arguments - pub varargs: Option>, // *args or * + pub arg_names: Vec, // Names of positional arguments + pub varargs: Varargs, // *args or * pub kwonlyarg_names: Vec, - pub varkeywords: Option>, // **kwargs or ** + pub varkeywords: Varargs, // **kwargs or ** pub source_path: String, pub first_line_number: usize, pub obj_name: String, // Name of the object that created this code object @@ -31,6 +31,7 @@ pub struct CodeObject { bitflags! { pub struct FunctionOpArg: u8 { const HAS_DEFAULTS = 0x01; + const HAS_ANNOTATIONS = 0x04; } } @@ -159,7 +160,6 @@ pub enum Instruction { }, PrintExpr, LoadBuildClass, - StoreLocals, UnpackSequence { size: usize, }, @@ -169,6 +169,7 @@ pub enum Instruction { }, Unpack, FormatValue { + conversion: Option, spec: String, }, } @@ -190,9 +191,10 @@ pub enum Constant { Boolean { value: bool }, String { value: String }, Bytes { value: Vec }, - Code { code: CodeObject }, + Code { code: Box }, Tuple { elements: Vec }, None, + Ellipsis, } #[derive(Debug, Clone, PartialEq)] @@ -235,6 +237,13 @@ pub enum UnaryOperator { Plus, } +#[derive(Debug, Clone, PartialEq)] +pub enum Varargs { + None, + Unnamed, + Named(String), +} + /* Maintain a stack of blocks on the VM. pub enum BlockType { @@ -246,9 +255,9 @@ pub enum BlockType { impl CodeObject { pub fn new( arg_names: Vec, - varargs: Option>, + varargs: Varargs, kwonlyarg_names: Vec, - varkeywords: Option>, + varkeywords: Varargs, source_path: String, first_line_number: usize, obj_name: String, @@ -268,7 +277,7 @@ impl CodeObject { } } - pub fn get_constants<'a>(&'a self) -> impl Iterator { + pub fn get_constants(&self) -> impl Iterator { self.instructions.iter().filter_map(|x| { if let Instruction::LoadConst { value } = x { Some(value) @@ -357,11 +366,10 @@ impl Instruction { MapAdd { i } => w!(MapAdd, i), PrintExpr => w!(PrintExpr), LoadBuildClass => w!(LoadBuildClass), - StoreLocals => w!(StoreLocals), UnpackSequence { size } => w!(UnpackSequence, size), UnpackEx { before, after } => w!(UnpackEx, before, after), Unpack => w!(Unpack), - FormatValue { spec } => w!(FormatValue, spec), + FormatValue { spec, .. } => w!(FormatValue, spec), // TODO: write conversion } } } @@ -386,6 +394,7 @@ impl fmt::Display for Constant { .join(", ") ), Constant::None => write!(f, "None"), + Constant::Ellipsis => write!(f, "Ellipsis"), } } } @@ -399,3 +408,23 @@ impl fmt::Debug for CodeObject { ) } } + +impl From for Varargs { + fn from(varargs: ast::Varargs) -> Varargs { + match varargs { + ast::Varargs::None => Varargs::None, + ast::Varargs::Unnamed => Varargs::Unnamed, + ast::Varargs::Named(param) => Varargs::Named(param.arg), + } + } +} + +impl<'a> From<&'a ast::Varargs> for Varargs { + fn from(varargs: &'a ast::Varargs) -> Varargs { + match varargs { + ast::Varargs::None => Varargs::None, + ast::Varargs::Unnamed => Varargs::Unnamed, + ast::Varargs::Named(ref param) => Varargs::Named(param.arg.clone()), + } + } +} diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 866b0dad74..1742392cdd 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -5,9 +5,10 @@ //! https://github.com/python/cpython/blob/master/Python/compile.c //! https://github.com/micropython/micropython/blob/master/py/compile.c -use crate::bytecode::{self, CallType, CodeObject, Instruction}; +use crate::bytecode::{self, CallType, CodeObject, Instruction, Varargs}; use crate::error::CompileError; -use crate::pyobject::{PyObject, PyObjectPayload, PyObjectRef}; +use crate::obj::objcode; +use crate::pyobject::{PyObject, PyObjectRef}; use num_complex::Complex64; use rustpython_parser::{ast, parser}; @@ -17,6 +18,7 @@ struct Compiler { source_path: Option, current_source_location: ast::Location, in_loop: bool, + in_function_def: bool, } /// Compile a given sourcecode into a bytecode object. @@ -47,10 +49,7 @@ pub fn compile( let code = compiler.pop_code_object(); trace!("Compilation completed: {:?}", code); - Ok(PyObject::new( - PyObjectPayload::Code { code: code }, - code_type, - )) + Ok(PyObject::new(objcode::PyCode::new(code), code_type)) } pub enum Mode { @@ -75,6 +74,7 @@ impl Compiler { source_path: None, current_source_location: ast::Location::default(), in_loop: false, + in_function_def: false, } } @@ -82,9 +82,9 @@ impl Compiler { let line_number = self.get_source_line_number(); self.code_object_stack.push(CodeObject::new( Vec::new(), - None, + Varargs::None, Vec::new(), - None, + Varargs::None, self.source_path.clone().unwrap(), line_number, obj_name, @@ -232,9 +232,10 @@ impl Compiler { self.compile_test(test, None, Some(else_label), EvalContext::Statement)?; + let was_in_loop = self.in_loop; self.in_loop = true; self.compile_statements(body)?; - self.in_loop = false; + self.in_loop = was_in_loop; self.emit(Instruction::Jump { target: start_label, }); @@ -271,45 +272,7 @@ impl Compiler { iter, body, orelse, - } => { - // Start loop - let start_label = self.new_label(); - let else_label = self.new_label(); - let end_label = self.new_label(); - self.emit(Instruction::SetupLoop { - start: start_label, - end: end_label, - }); - - // The thing iterated: - for i in iter { - self.compile_expression(i)?; - } - - // Retrieve Iterator - self.emit(Instruction::GetIter); - - self.set_label(start_label); - self.emit(Instruction::ForIter { target: else_label }); - - // Start of loop iteration, set targets: - self.compile_store(target)?; - - // Body of loop: - self.in_loop = true; - self.compile_statements(body)?; - self.in_loop = false; - - self.emit(Instruction::Jump { - target: start_label, - }); - self.set_label(else_label); - self.emit(Instruction::PopBlock); - if let Some(orelse) = orelse { - self.compile_statements(orelse)?; - } - self.set_label(end_label); - } + } => self.compile_for(target, iter, body, orelse)?, ast::Statement::Raise { exception, cause } => match exception { Some(value) => { self.compile_expression(value)?; @@ -332,221 +295,21 @@ impl Compiler { handlers, orelse, finalbody, - } => { - let mut handler_label = self.new_label(); - let finally_label = self.new_label(); - let else_label = self.new_label(); - // try: - self.emit(Instruction::SetupExcept { - handler: handler_label, - }); - self.compile_statements(body)?; - self.emit(Instruction::PopBlock); - self.emit(Instruction::Jump { target: else_label }); - - // except handlers: - self.set_label(handler_label); - // Exception is on top of stack now - handler_label = self.new_label(); - for handler in handlers { - // If we gave a typ, - // check if this handler can handle the exception: - if let Some(exc_type) = &handler.typ { - // Duplicate exception for test: - self.emit(Instruction::Duplicate); - - // Check exception type: - self.emit(Instruction::LoadName { - name: String::from("isinstance"), - }); - self.emit(Instruction::Rotate { amount: 2 }); - self.compile_expression(exc_type)?; - self.emit(Instruction::CallFunction { - typ: CallType::Positional(2), - }); - - // We cannot handle this exception type: - self.emit(Instruction::JumpIfFalse { - target: handler_label, - }); - - // We have a match, store in name (except x as y) - if let Some(alias) = &handler.name { - self.emit(Instruction::StoreName { - name: alias.clone(), - }); - } else { - // Drop exception from top of stack: - self.emit(Instruction::Pop); - } - } else { - // Catch all! - // Drop exception from top of stack: - self.emit(Instruction::Pop); - } - - // Handler code: - self.compile_statements(&handler.body)?; - self.emit(Instruction::Jump { - target: finally_label, - }); - - // Emit a new label for the next handler - self.set_label(handler_label); - handler_label = self.new_label(); - } - self.emit(Instruction::Jump { - target: handler_label, - }); - self.set_label(handler_label); - // If code flows here, we have an unhandled exception, - // emit finally code and raise again! - // Duplicate finally code here: - // TODO: this bytecode is now duplicate, could this be - // improved? - if let Some(statements) = finalbody { - self.compile_statements(statements)?; - } - self.emit(Instruction::Raise { argc: 1 }); - - // We successfully ran the try block: - // else: - self.set_label(else_label); - if let Some(statements) = orelse { - self.compile_statements(statements)?; - } - - // finally: - self.set_label(finally_label); - if let Some(statements) = finalbody { - self.compile_statements(statements)?; - } - - // unimplemented!(); - } + } => self.compile_try_statement(body, handlers, orelse, finalbody)?, ast::Statement::FunctionDef { name, args, body, decorator_list, - } => { - // Create bytecode for this function: - let flags = self.enter_function(name, args)?; - self.compile_statements(body)?; - - // Emit None at end: - self.emit(Instruction::LoadConst { - value: bytecode::Constant::None, - }); - self.emit(Instruction::ReturnValue); - let code = self.pop_code_object(); - - self.prepare_decorators(decorator_list)?; - self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code }, - }); - self.emit(Instruction::LoadConst { - value: bytecode::Constant::String { - value: name.clone(), - }, - }); - - // Turn code object into function object: - self.emit(Instruction::MakeFunction { flags }); - self.apply_decorators(decorator_list); - - self.emit(Instruction::StoreName { - name: name.to_string(), - }); - } + returns, + } => self.compile_function_def(name, args, body, decorator_list, returns)?, ast::Statement::ClassDef { name, body, bases, keywords, decorator_list, - } => { - self.prepare_decorators(decorator_list)?; - self.emit(Instruction::LoadBuildClass); - let line_number = self.get_source_line_number(); - self.code_object_stack.push(CodeObject::new( - vec![String::from("__locals__")], - None, - vec![], - None, - self.source_path.clone().unwrap(), - line_number, - name.clone(), - )); - self.emit(Instruction::LoadName { - name: String::from("__locals__"), - }); - self.emit(Instruction::StoreLocals); - self.compile_statements(body)?; - self.emit(Instruction::LoadConst { - value: bytecode::Constant::None, - }); - self.emit(Instruction::ReturnValue); - - let code = self.pop_code_object(); - self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code }, - }); - self.emit(Instruction::LoadConst { - value: bytecode::Constant::String { - value: name.clone(), - }, - }); - - // Turn code object into function object: - self.emit(Instruction::MakeFunction { - flags: bytecode::FunctionOpArg::empty(), - }); - - self.emit(Instruction::LoadConst { - value: bytecode::Constant::String { - value: name.clone(), - }, - }); - - for base in bases { - self.compile_expression(base)?; - } - - if !keywords.is_empty() { - let mut kwarg_names = vec![]; - for keyword in keywords { - if let Some(name) = &keyword.name { - kwarg_names.push(bytecode::Constant::String { - value: name.to_string(), - }); - } else { - // This means **kwargs! - panic!("name must be set"); - } - self.compile_expression(&keyword.value)?; - } - - self.emit(Instruction::LoadConst { - value: bytecode::Constant::Tuple { - elements: kwarg_names, - }, - }); - self.emit(Instruction::CallFunction { - typ: CallType::Keyword(2 + keywords.len() + bases.len()), - }); - } else { - self.emit(Instruction::CallFunction { - typ: CallType::Positional(2 + bases.len()), - }); - } - - self.apply_decorators(decorator_list); - - self.emit(Instruction::StoreName { - name: name.to_string(), - }); - } + } => self.compile_class_def(name, body, bases, keywords, decorator_list)?, ast::Statement::Assert { test, msg } => { // TODO: if some flag, ignore all assert statements! @@ -584,6 +347,9 @@ impl Compiler { self.emit(Instruction::Continue); } ast::Statement::Return { value } => { + if !self.in_function_def { + return Err(CompileError::InvalidReturn); + } match value { Some(e) => { let size = e.len(); @@ -663,7 +429,6 @@ impl Compiler { name: &str, args: &ast::Parameters, ) -> Result { - self.in_loop = false; let have_kwargs = !args.defaults.is_empty(); if have_kwargs { // Construct a tuple: @@ -679,10 +444,10 @@ impl Compiler { let line_number = self.get_source_line_number(); self.code_object_stack.push(CodeObject::new( - args.args.clone(), - args.vararg.clone(), - args.kwonlyargs.clone(), - args.kwarg.clone(), + args.args.iter().map(|a| a.arg.clone()).collect(), + Varargs::from(&args.vararg), + args.kwonlyargs.iter().map(|a| a.arg.clone()).collect(), + Varargs::from(&args.kwarg), self.source_path.clone().unwrap(), line_number, name.to_string(), @@ -715,6 +480,330 @@ impl Compiler { } } + fn compile_try_statement( + &mut self, + body: &[ast::LocatedStatement], + handlers: &[ast::ExceptHandler], + orelse: &Option>, + finalbody: &Option>, + ) -> Result<(), CompileError> { + let mut handler_label = self.new_label(); + let finally_label = self.new_label(); + let else_label = self.new_label(); + // try: + self.emit(Instruction::SetupExcept { + handler: handler_label, + }); + self.compile_statements(body)?; + self.emit(Instruction::PopBlock); + self.emit(Instruction::Jump { target: else_label }); + + // except handlers: + self.set_label(handler_label); + // Exception is on top of stack now + handler_label = self.new_label(); + for handler in handlers { + // If we gave a typ, + // check if this handler can handle the exception: + if let Some(exc_type) = &handler.typ { + // Duplicate exception for test: + self.emit(Instruction::Duplicate); + + // Check exception type: + self.emit(Instruction::LoadName { + name: String::from("isinstance"), + }); + self.emit(Instruction::Rotate { amount: 2 }); + self.compile_expression(exc_type)?; + self.emit(Instruction::CallFunction { + typ: CallType::Positional(2), + }); + + // We cannot handle this exception type: + self.emit(Instruction::JumpIfFalse { + target: handler_label, + }); + + // We have a match, store in name (except x as y) + if let Some(alias) = &handler.name { + self.emit(Instruction::StoreName { + name: alias.clone(), + }); + } else { + // Drop exception from top of stack: + self.emit(Instruction::Pop); + } + } else { + // Catch all! + // Drop exception from top of stack: + self.emit(Instruction::Pop); + } + + // Handler code: + self.compile_statements(&handler.body)?; + self.emit(Instruction::Jump { + target: finally_label, + }); + + // Emit a new label for the next handler + self.set_label(handler_label); + handler_label = self.new_label(); + } + self.emit(Instruction::Jump { + target: handler_label, + }); + self.set_label(handler_label); + // If code flows here, we have an unhandled exception, + // emit finally code and raise again! + // Duplicate finally code here: + // TODO: this bytecode is now duplicate, could this be + // improved? + if let Some(statements) = finalbody { + self.compile_statements(statements)?; + } + self.emit(Instruction::Raise { argc: 1 }); + + // We successfully ran the try block: + // else: + self.set_label(else_label); + if let Some(statements) = orelse { + self.compile_statements(statements)?; + } + + // finally: + self.set_label(finally_label); + if let Some(statements) = finalbody { + self.compile_statements(statements)?; + } + + // unimplemented!(); + Ok(()) + } + + fn compile_function_def( + &mut self, + name: &str, + args: &ast::Parameters, + body: &[ast::LocatedStatement], + decorator_list: &[ast::Expression], + returns: &Option, // TODO: use type hint somehow.. + ) -> Result<(), CompileError> { + // Create bytecode for this function: + // remember to restore self.in_loop to the original after the function is compiled + let was_in_loop = self.in_loop; + let was_in_function_def = self.in_function_def; + self.in_loop = false; + self.in_function_def = true; + let mut flags = self.enter_function(name, args)?; + self.compile_statements(body)?; + + // Emit None at end: + self.emit(Instruction::LoadConst { + value: bytecode::Constant::None, + }); + self.emit(Instruction::ReturnValue); + let code = self.pop_code_object(); + + self.prepare_decorators(decorator_list)?; + + // Prepare type annotations: + let mut num_annotations = 0; + + // Return annotation: + if let Some(annotation) = returns { + // key: + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: "return".to_string(), + }, + }); + // value: + self.compile_expression(annotation)?; + num_annotations += 1; + } + + for arg in args.args.iter() { + if let Some(annotation) = &arg.annotation { + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: arg.arg.to_string(), + }, + }); + self.compile_expression(&annotation)?; + num_annotations += 1; + } + } + + if num_annotations > 0 { + flags |= bytecode::FunctionOpArg::HAS_ANNOTATIONS; + self.emit(Instruction::BuildMap { + size: num_annotations, + unpack: false, + }); + } + + self.emit(Instruction::LoadConst { + value: bytecode::Constant::Code { + code: Box::new(code), + }, + }); + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: name.to_string(), + }, + }); + + // Turn code object into function object: + self.emit(Instruction::MakeFunction { flags }); + self.apply_decorators(decorator_list); + + self.emit(Instruction::StoreName { + name: name.to_string(), + }); + self.in_loop = was_in_loop; + self.in_function_def = was_in_function_def; + Ok(()) + } + + fn compile_class_def( + &mut self, + name: &str, + body: &[ast::LocatedStatement], + bases: &[ast::Expression], + keywords: &[ast::Keyword], + decorator_list: &[ast::Expression], + ) -> Result<(), CompileError> { + let was_in_loop = self.in_loop; + self.in_loop = false; + self.prepare_decorators(decorator_list)?; + self.emit(Instruction::LoadBuildClass); + let line_number = self.get_source_line_number(); + self.code_object_stack.push(CodeObject::new( + vec![], + Varargs::None, + vec![], + Varargs::None, + self.source_path.clone().unwrap(), + line_number, + name.to_string(), + )); + self.compile_statements(body)?; + self.emit(Instruction::LoadConst { + value: bytecode::Constant::None, + }); + self.emit(Instruction::ReturnValue); + + let code = self.pop_code_object(); + self.emit(Instruction::LoadConst { + value: bytecode::Constant::Code { + code: Box::new(code), + }, + }); + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: name.to_string(), + }, + }); + + // Turn code object into function object: + self.emit(Instruction::MakeFunction { + flags: bytecode::FunctionOpArg::empty(), + }); + + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: name.to_string(), + }, + }); + + for base in bases { + self.compile_expression(base)?; + } + + if !keywords.is_empty() { + let mut kwarg_names = vec![]; + for keyword in keywords { + if let Some(name) = &keyword.name { + kwarg_names.push(bytecode::Constant::String { + value: name.to_string(), + }); + } else { + // This means **kwargs! + panic!("name must be set"); + } + self.compile_expression(&keyword.value)?; + } + + self.emit(Instruction::LoadConst { + value: bytecode::Constant::Tuple { + elements: kwarg_names, + }, + }); + self.emit(Instruction::CallFunction { + typ: CallType::Keyword(2 + keywords.len() + bases.len()), + }); + } else { + self.emit(Instruction::CallFunction { + typ: CallType::Positional(2 + bases.len()), + }); + } + + self.apply_decorators(decorator_list); + + self.emit(Instruction::StoreName { + name: name.to_string(), + }); + self.in_loop = was_in_loop; + Ok(()) + } + + fn compile_for( + &mut self, + target: &ast::Expression, + iter: &[ast::Expression], + body: &[ast::LocatedStatement], + orelse: &Option>, + ) -> Result<(), CompileError> { + // Start loop + let start_label = self.new_label(); + let else_label = self.new_label(); + let end_label = self.new_label(); + self.emit(Instruction::SetupLoop { + start: start_label, + end: end_label, + }); + + // The thing iterated: + for i in iter { + self.compile_expression(i)?; + } + + // Retrieve Iterator + self.emit(Instruction::GetIter); + + self.set_label(start_label); + self.emit(Instruction::ForIter { target: else_label }); + + // Start of loop iteration, set targets: + self.compile_store(target)?; + + let was_in_loop = self.in_loop; + self.in_loop = true; + self.compile_statements(body)?; + self.in_loop = was_in_loop; + + self.emit(Instruction::Jump { + target: start_label, + }); + self.set_label(else_label); + self.emit(Instruction::PopBlock); + if let Some(orelse) = orelse { + self.compile_statements(orelse)?; + } + self.set_label(end_label); + Ok(()) + } + fn compile_store(&mut self, target: &ast::Expression) -> Result<(), CompileError> { match target { ast::Expression::Identifier { name } => { @@ -971,6 +1060,9 @@ impl Compiler { self.emit(Instruction::BuildSlice { size }); } ast::Expression::Yield { value } => { + if !self.in_function_def { + return Err(CompileError::InvalidYield); + } self.mark_generator(); match value { Some(expression) => self.compile_expression(expression)?, @@ -1004,6 +1096,11 @@ impl Compiler { value: bytecode::Constant::None, }); } + ast::Expression::Ellipsis => { + self.emit(Instruction::LoadConst { + value: bytecode::Constant::Ellipsis, + }); + } ast::Expression::String { value } => { self.compile_string(value)?; } @@ -1021,12 +1118,15 @@ impl Compiler { } ast::Expression::Lambda { args, body } => { let name = "".to_string(); + // no need to worry about the self.loop_depth because there are no loops in lambda expressions let flags = self.enter_function(&name, args)?; self.compile_expression(body)?; self.emit(Instruction::ReturnValue); let code = self.pop_code_object(); self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code }, + value: bytecode::Constant::Code { + code: Box::new(code), + }, }); self.emit(Instruction::LoadConst { value: bytecode::Constant::String { value: name }, @@ -1193,9 +1293,9 @@ impl Compiler { // Create magnificent function : self.code_object_stack.push(CodeObject::new( vec![".0".to_string()], - None, + Varargs::None, vec![], - None, + Varargs::None, self.source_path.clone().unwrap(), line_number, name.clone(), @@ -1311,7 +1411,9 @@ impl Compiler { // List comprehension code: self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code }, + value: bytecode::Constant::Code { + code: Box::new(code), + }, }); // List comprehension function name: @@ -1352,9 +1454,16 @@ impl Compiler { }, }); } - ast::StringGroup::FormattedValue { value, spec } => { + ast::StringGroup::FormattedValue { + value, + conversion, + spec, + } => { self.compile_expression(value)?; - self.emit(Instruction::FormatValue { spec: spec.clone() }); + self.emit(Instruction::FormatValue { + conversion: *conversion, + spec: spec.clone(), + }); } } Ok(()) @@ -1362,10 +1471,11 @@ impl Compiler { // Low level helper functions: fn emit(&mut self, instruction: Instruction) { - self.current_code_object().instructions.push(instruction); - // TODO: insert source filename let location = self.current_source_location.clone(); - self.current_code_object().locations.push(location); + let cur_code_obj = self.current_code_object(); + cur_code_obj.instructions.push(instruction); + cur_code_obj.locations.push(location); + // TODO: insert source filename } fn current_code_object(&mut self) -> &mut CodeObject { @@ -1406,6 +1516,7 @@ mod tests { use crate::bytecode::Constant::*; use crate::bytecode::Instruction::*; use rustpython_parser::parser; + fn compile_exec(source: &str) -> CodeObject { let mut compiler = Compiler::new(); compiler.source_path = Some("source_path".to_string()); diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index a942081040..436923ea95 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -33,10 +33,10 @@ impl Dict { /// Store a key pub fn insert( &mut self, - vm: &mut VirtualMachine, + vm: &VirtualMachine, key: &PyObjectRef, value: PyObjectRef, - ) -> Result<(), PyObjectRef> { + ) -> PyResult<()> { match self.lookup(vm, key)? { LookupResult::Existing(index) => { // Update existing key @@ -66,11 +66,7 @@ impl Dict { } } - pub fn contains( - &self, - vm: &mut VirtualMachine, - key: &PyObjectRef, - ) -> Result { + pub fn contains(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { if let LookupResult::Existing(_index) = self.lookup(vm, key)? { Ok(true) } else { @@ -79,7 +75,7 @@ impl Dict { } /// Retrieve a key - pub fn get(&self, vm: &mut VirtualMachine, key: &PyObjectRef) -> PyResult { + pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { if let LookupResult::Existing(index) = self.lookup(vm, key)? { if let Some(entry) = &self.entries[index] { Ok(entry.value.clone()) @@ -93,11 +89,7 @@ impl Dict { } /// Delete a key - pub fn delete( - &mut self, - vm: &mut VirtualMachine, - key: &PyObjectRef, - ) -> Result<(), PyObjectRef> { + pub fn delete(&mut self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult<()> { if let LookupResult::Existing(index) = self.lookup(vm, key)? { self.entries[index] = None; self.size -= 1; @@ -126,11 +118,7 @@ impl Dict { } /// Lookup the index for the given key. - fn lookup( - &self, - vm: &mut VirtualMachine, - key: &PyObjectRef, - ) -> Result { + fn lookup(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { let hash_value = calc_hash(vm, key)?; let perturb = hash_value; let mut hash_index: usize = hash_value; @@ -181,14 +169,14 @@ enum LookupResult { Existing(usize), // Existing record, index into entries } -fn calc_hash(vm: &mut VirtualMachine, key: &PyObjectRef) -> Result { +fn calc_hash(vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { let hash = vm.call_method(key, "__hash__", vec![])?; Ok(objint::get_value(&hash).to_usize().unwrap()) } /// Invoke __eq__ on two keys fn do_eq( - vm: &mut VirtualMachine, + vm: &VirtualMachine, key1: &PyObjectRef, key2: &PyObjectRef, ) -> Result { diff --git a/vm/src/error.rs b/vm/src/error.rs index 70c8ff71ec..06eb387e2e 100644 --- a/vm/src/error.rs +++ b/vm/src/error.rs @@ -19,6 +19,8 @@ pub enum CompileError { InvalidBreak, /// Continue statement outside of loop. InvalidContinue, + InvalidReturn, + InvalidYield, } impl fmt::Display for CompileError { @@ -29,8 +31,10 @@ impl fmt::Display for CompileError { CompileError::ExpectExpr => write!(f, "Expecting expression, got statement"), CompileError::Parse(err) => write!(f, "{}", err), CompileError::StarArgs => write!(f, "Two starred expressions in assignment"), - CompileError::InvalidBreak => write!(f, "break outside loop"), - CompileError::InvalidContinue => write!(f, "continue outside loop"), + CompileError::InvalidBreak => write!(f, "'break' outside loop"), + CompileError::InvalidContinue => write!(f, "'continue' outside loop"), + CompileError::InvalidReturn => write!(f, "'return' outside function"), + CompileError::InvalidYield => write!(f, "'yield' outside function"), } } } diff --git a/vm/src/eval.rs b/vm/src/eval.rs index 30c6034f58..202286eb13 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -3,15 +3,11 @@ extern crate rustpython_parser; use std::error::Error; use crate::compile; -use crate::pyobject::{PyObjectRef, PyResult}; +use crate::frame::Scope; +use crate::pyobject::PyResult; use crate::vm::VirtualMachine; -pub fn eval( - vm: &mut VirtualMachine, - source: &str, - scope: PyObjectRef, - source_path: &str, -) -> PyResult { +pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str) -> PyResult { match compile::compile( source, &compile::Mode::Eval, @@ -38,7 +34,7 @@ mod tests { fn test_print_42() { let source = String::from("print('Hello world')\n"); let mut vm = VirtualMachine::new(); - let vars = vm.context().new_scope(None); + let vars = vm.ctx.new_scope(); let _result = eval(&mut vm, &source, vars, ""); // TODO: check result? diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index cf3018426d..d37f174717 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1,12 +1,10 @@ +use crate::function::PyFuncArgs; use crate::obj::objsequence; -use crate::obj::objstr; use crate::obj::objtype; -use crate::pyobject::{ - create_type, AttributeProtocol, PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol, -}; +use crate::pyobject::{create_type, PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; -fn exception_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let zelf = args.args[0].clone(); let msg = if args.args.len() > 1 { args.args[1].clone() @@ -20,8 +18,8 @@ fn exception_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } // Print exception including traceback: -pub fn print_exception(vm: &mut VirtualMachine, exc: &PyObjectRef) { - if let Some(tb) = exc.get_attr("__traceback__") { +pub fn print_exception(vm: &VirtualMachine, exc: &PyObjectRef) { + if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") { println!("Traceback (most recent call last):"); if objtype::isinstance(&tb, &vm.ctx.list_type()) { let mut elements = objsequence::get_elements(&tb).to_vec(); @@ -30,19 +28,19 @@ pub fn print_exception(vm: &mut VirtualMachine, exc: &PyObjectRef) { if objtype::isinstance(&element, &vm.ctx.tuple_type()) { let element = objsequence::get_elements(&element); let filename = if let Ok(x) = vm.to_str(&element[0]) { - objstr::get_value(&x) + x.value.clone() } else { "".to_string() }; let lineno = if let Ok(x) = vm.to_str(&element[1]) { - objstr::get_value(&x) + x.value.clone() } else { "".to_string() }; let obj_name = if let Ok(x) = vm.to_str(&element[2]) { - objstr::get_value(&x) + x.value.clone() } else { "".to_string() }; @@ -58,19 +56,19 @@ pub fn print_exception(vm: &mut VirtualMachine, exc: &PyObjectRef) { } match vm.to_str(exc) { - Ok(txt) => println!("{}", objstr::get_value(&txt)), + Ok(txt) => println!("{}", txt.value), Err(err) => println!("Error during error {:?}", err), } } -fn exception_str(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))] ); let type_name = objtype::get_type_name(&exc.typ()); - let msg = if let Some(m) = exc.get_attr("msg") { + let msg = if let Ok(m) = vm.get_attribute(exc.clone(), "msg") { match vm.to_pystr(&m) { Ok(msg) => msg, _ => "".to_string(), @@ -108,56 +106,29 @@ pub struct ExceptionZoo { } impl ExceptionZoo { - pub fn new( - type_type: &PyObjectRef, - object_type: &PyObjectRef, - dict_type: &PyObjectRef, - ) -> Self { + pub fn new(type_type: &PyObjectRef, object_type: &PyObjectRef) -> Self { // Sorted By Hierarchy then alphabetized. - let base_exception_type = - create_type("BaseException", &type_type, &object_type, &dict_type); - - let exception_type = create_type("Exception", &type_type, &base_exception_type, &dict_type); - - let arithmetic_error = - create_type("ArithmeticError", &type_type, &exception_type, &dict_type); - let assertion_error = - create_type("AssertionError", &type_type, &exception_type, &dict_type); - let attribute_error = - create_type("AttributeError", &type_type, &exception_type, &dict_type); - let import_error = create_type("ImportError", &type_type, &exception_type, &dict_type); - let index_error = create_type("IndexError", &type_type, &exception_type, &dict_type); - let key_error = create_type("KeyError", &type_type, &exception_type, &dict_type); - let name_error = create_type("NameError", &type_type, &exception_type, &dict_type); - let os_error = create_type("OSError", &type_type, &exception_type, &dict_type); - let runtime_error = create_type("RuntimeError", &type_type, &exception_type, &dict_type); - let stop_iteration = create_type("StopIteration", &type_type, &exception_type, &dict_type); - let syntax_error = create_type("SyntaxError", &type_type, &exception_type, &dict_type); - let type_error = create_type("TypeError", &type_type, &exception_type, &dict_type); - let value_error = create_type("ValueError", &type_type, &exception_type, &dict_type); - - let overflow_error = - create_type("OverflowError", &type_type, &arithmetic_error, &dict_type); - let zero_division_error = create_type( - "ZeroDivisionError", - &type_type, - &arithmetic_error, - &dict_type, - ); - - let module_not_found_error = - create_type("ModuleNotFoundError", &type_type, &import_error, &dict_type); - - let not_implemented_error = create_type( - "NotImplementedError", - &type_type, - &runtime_error, - &dict_type, - ); - - let file_not_found_error = - create_type("FileNotFoundError", &type_type, &os_error, &dict_type); - let permission_error = create_type("PermissionError", &type_type, &os_error, &dict_type); + let base_exception_type = create_type("BaseException", &type_type, &object_type); + let exception_type = create_type("Exception", &type_type, &base_exception_type); + let arithmetic_error = create_type("ArithmeticError", &type_type, &exception_type); + let assertion_error = create_type("AssertionError", &type_type, &exception_type); + let attribute_error = create_type("AttributeError", &type_type, &exception_type); + let import_error = create_type("ImportError", &type_type, &exception_type); + let index_error = create_type("IndexError", &type_type, &exception_type); + let key_error = create_type("KeyError", &type_type, &exception_type); + let name_error = create_type("NameError", &type_type, &exception_type); + let os_error = create_type("OSError", &type_type, &exception_type); + let runtime_error = create_type("RuntimeError", &type_type, &exception_type); + let stop_iteration = create_type("StopIteration", &type_type, &exception_type); + let syntax_error = create_type("SyntaxError", &type_type, &exception_type); + let type_error = create_type("TypeError", &type_type, &exception_type); + let value_error = create_type("ValueError", &type_type, &exception_type); + let overflow_error = create_type("OverflowError", &type_type, &arithmetic_error); + let zero_division_error = create_type("ZeroDivisionError", &type_type, &arithmetic_error); + let module_not_found_error = create_type("ModuleNotFoundError", &type_type, &import_error); + let not_implemented_error = create_type("NotImplementedError", &type_type, &runtime_error); + let file_not_found_error = create_type("FileNotFoundError", &type_type, &os_error); + let permission_error = create_type("PermissionError", &type_type, &os_error); ExceptionZoo { arithmetic_error, diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 5de7f68099..0015557fec 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1,26 +1,166 @@ -extern crate rustpython_parser; - -use self::rustpython_parser::ast; +use std::cell::RefCell; use std::fmt; -use std::mem; -use std::path::PathBuf; +use std::rc::Rc; + +use num_bigint::BigInt; + +use rustpython_parser::ast; use crate::builtins; use crate::bytecode; -use crate::import::{import, import_module}; +use crate::function::PyFuncArgs; use crate::obj::objbool; +use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode; use crate::obj::objdict; +use crate::obj::objdict::PyDict; +use crate::obj::objint::PyInt; use crate::obj::objiter; use crate::obj::objlist; +use crate::obj::objslice::PySlice; use crate::obj::objstr; use crate::obj::objtype; use crate::pyobject::{ - DictProtocol, IdProtocol, ParentProtocol, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, - PyResult, TypeProtocol, + DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TryFromObject, + TypeProtocol, }; use crate::vm::VirtualMachine; -use num_bigint::BigInt; + +/* + * So a scope is a linked list of scopes. + * When a name is looked up, it is check in its scope. + */ +#[derive(Debug)] +struct RcListNode { + elem: T, + next: Option>>, +} + +#[derive(Debug, Clone)] +struct RcList { + head: Option>>, +} + +struct Iter<'a, T: 'a> { + next: Option<&'a RcListNode>, +} + +impl RcList { + pub fn new() -> Self { + RcList { head: None } + } + + pub fn insert(self, elem: T) -> Self { + RcList { + head: Some(Rc::new(RcListNode { + elem, + next: self.head, + })), + } + } + + pub fn iter(&self) -> Iter { + Iter { + next: self.head.as_ref().map(|node| &**node), + } + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + self.next.map(|node| { + self.next = node.next.as_ref().map(|node| &**node); + &node.elem + }) + } +} + +#[derive(Clone)] +pub struct Scope { + locals: RcList, + pub globals: PyObjectRef, +} + +impl fmt::Debug for Scope { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: have a more informative Debug impl that DOESN'T recurse and cause a stack overflow + f.write_str("Scope") + } +} + +impl Scope { + pub fn new(locals: Option, globals: PyObjectRef) -> Scope { + let locals = match locals { + Some(dict) => RcList::new().insert(dict), + None => RcList::new(), + }; + Scope { locals, globals } + } + + pub fn get_locals(&self) -> PyObjectRef { + match self.locals.iter().next() { + Some(dict) => dict.clone(), + None => self.globals.clone(), + } + } + + pub fn get_only_locals(&self) -> Option { + self.locals.iter().next().cloned() + } + + pub fn child_scope_with_locals(&self, locals: PyObjectRef) -> Scope { + Scope { + locals: self.locals.clone().insert(locals), + globals: self.globals.clone(), + } + } + + pub fn child_scope(&self, ctx: &PyContext) -> Scope { + self.child_scope_with_locals(ctx.new_dict()) + } +} + +pub trait NameProtocol { + fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option; + fn store_name(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); + fn delete_name(&self, vm: &VirtualMachine, name: &str); + fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option; +} + +impl NameProtocol for Scope { + fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option { + for dict in self.locals.iter() { + if let Some(value) = dict.get_item(name) { + return Some(value); + } + } + + if let Some(value) = self.globals.get_item(name) { + return Some(value); + } + + vm.builtins.get_item(name) + } + + fn load_cell(&self, _vm: &VirtualMachine, name: &str) -> Option { + for dict in self.locals.iter().skip(1) { + if let Some(value) = dict.get_item(name) { + return Some(value); + } + } + None + } + + fn store_name(&self, vm: &VirtualMachine, key: &str, value: PyObjectRef) { + self.get_locals().set_item(&vm.ctx, key, value) + } + + fn delete_name(&self, _vm: &VirtualMachine, key: &str) { + self.get_locals().del_item(key) + } +} #[derive(Clone, Debug)] struct Block { @@ -45,14 +185,19 @@ enum BlockType { }, } -#[derive(Clone)] pub struct Frame { pub code: bytecode::CodeObject, // We need 1 stack per frame - stack: Vec, // The main data frame of the stack machine - blocks: Vec, // Block frames, for controlling loops and exceptions - pub locals: PyObjectRef, // Variables - pub lasti: usize, // index of last instruction ran + stack: RefCell>, // The main data frame of the stack machine + blocks: RefCell>, // Block frames, for controlling loops and exceptions + pub scope: Scope, // Variables + pub lasti: RefCell, // index of last instruction ran +} + +impl PyValue for Frame { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.frame_type() + } } // Running a frame can result in one of the below: @@ -65,7 +210,7 @@ pub enum ExecutionResult { pub type FrameResult = Result, PyObjectRef>; impl Frame { - pub fn new(code: PyObjectRef, globals: PyObjectRef) -> Frame { + pub fn new(code: PyObjectRef, scope: Scope) -> Frame { //populate the globals and locals //TODO: This is wrong, check https://github.com/nedbat/byterun/blob/31e6c4a8212c35b5157919abff43a7daa0f377c6/byterun/pyvm2.py#L95 /* @@ -74,37 +219,28 @@ impl Frame { None => HashMap::new(), }; */ - let locals = globals; + // let locals = globals; // locals.extend(callargs); Frame { code: objcode::get_value(&code), - stack: vec![], - blocks: vec![], + stack: RefCell::new(vec![]), + blocks: RefCell::new(vec![]), // save the callargs as locals // globals: locals.clone(), - locals, - lasti: 0, - } - } - - pub fn run_frame_full(&mut self, vm: &mut VirtualMachine) -> PyResult { - match self.run_frame(vm)? { - ExecutionResult::Return(value) => Ok(value), - _ => panic!("Got unexpected result from function"), + scope, + lasti: RefCell::new(0), } } - pub fn run_frame(&mut self, vm: &mut VirtualMachine) -> Result { + pub fn run(&self, vm: &VirtualMachine) -> Result { let filename = &self.code.source_path.to_string(); - let prev_frame = mem::replace(&mut vm.current_frame, Some(vm.ctx.new_frame(self.clone()))); - // This is the name of the object being run: let run_obj_name = &self.code.obj_name.to_string(); // Execute until return or exception: - let value = loop { + loop { let lineno = self.get_lineno(); let result = self.execute_instruction(vm); match result { @@ -119,22 +255,16 @@ impl Frame { &exception, &vm.ctx.exceptions.base_exception_type )); - let traceback_name = vm.new_str("__traceback__".to_string()); - let traceback = vm.get_attribute(exception.clone(), traceback_name).unwrap(); + let traceback = vm + .get_attribute(exception.clone(), "__traceback__") + .unwrap(); trace!("Adding to traceback: {:?} {:?}", traceback, lineno); let pos = vm.ctx.new_tuple(vec![ vm.ctx.new_str(filename.clone()), vm.ctx.new_int(lineno.get_row()), vm.ctx.new_str(run_obj_name.clone()), ]); - objlist::list_append( - vm, - PyFuncArgs { - args: vec![traceback, pos], - kwargs: vec![], - }, - ) - .unwrap(); + objlist::PyListRef::try_from_object(vm, traceback)?.append(pos, vm); // exception.__trace match self.unwind_exception(vm, exception) { None => {} @@ -146,22 +276,17 @@ impl Frame { } } } - }; - - vm.current_frame = prev_frame; - value + } } - pub fn fetch_instruction(&mut self) -> bytecode::Instruction { - // TODO: an immutable reference is enough, we should not - // clone the instruction. - let ins2 = self.code.instructions[self.lasti].clone(); - self.lasti += 1; + pub fn fetch_instruction(&self) -> &bytecode::Instruction { + let ins2 = &self.code.instructions[*self.lasti.borrow()]; + *self.lasti.borrow_mut() += 1; ins2 } // Execute a single instruction: - fn execute_instruction(&mut self, vm: &mut VirtualMachine) -> FrameResult { + fn execute_instruction(&self, vm: &VirtualMachine) -> FrameResult { let instruction = self.fetch_instruction(); { trace!("======="); @@ -281,10 +406,14 @@ impl Frame { let mut out: Vec> = elements .into_iter() - .map(|x| match x.borrow().payload { - PyObjectPayload::Integer { ref value } => Some(value.clone()), - PyObjectPayload::None => None, - _ => panic!("Expect Int or None as BUILD_SLICE arguments, got {:?}", x), + .map(|x| { + if x.is(&vm.ctx.none()) { + None + } else if let Some(i) = x.payload::() { + Some(i.value.clone()) + } else { + panic!("Expect Int or None as BUILD_SLICE arguments") + } }) .collect(); @@ -292,23 +421,14 @@ impl Frame { let stop = out[1].take(); let step = if out.len() == 3 { out[2].take() } else { None }; - let obj = PyObject::new( - PyObjectPayload::Slice { start, stop, step }, - vm.ctx.slice_type(), - ); - self.push_value(obj); + let obj = PySlice { start, stop, step }.into_ref(vm); + self.push_value(obj.into_object()); Ok(None) } bytecode::Instruction::ListAppend { i } => { let list_obj = self.nth_value(*i); let item = self.pop_value(); - objlist::list_append( - vm, - PyFuncArgs { - args: vec![list_obj.clone(), item], - kwargs: vec![], - }, - )?; + objlist::PyListRef::try_from_object(vm, list_obj)?.append(item, vm); Ok(None) } bytecode::Instruction::SetAdd { i } => { @@ -354,7 +474,7 @@ impl Frame { match next_obj { Some(value) => { // Set back program counter: - self.lasti -= 1; + *self.lasti.borrow_mut() -= 1; Ok(Some(ExecutionResult::Yield(value))) } None => { @@ -442,15 +562,26 @@ impl Frame { bytecode::Instruction::MakeFunction { flags } => { let _qualified_name = self.pop_value(); let code_obj = self.pop_value(); + + let annotations = if flags.contains(bytecode::FunctionOpArg::HAS_ANNOTATIONS) { + self.pop_value() + } else { + vm.new_dict() + }; + let defaults = if flags.contains(bytecode::FunctionOpArg::HAS_DEFAULTS) { self.pop_value() } else { vm.get_none() }; + // pop argc arguments // argument: name, args, globals - let scope = self.locals.clone(); + let scope = self.scope.clone(); let obj = vm.ctx.new_function(code_obj, scope, defaults); + + vm.ctx.set_attr(&obj, "__annotations__", annotations); + self.push_value(obj); Ok(None) } @@ -528,10 +659,16 @@ impl Frame { if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { info!("Exception raised: {:?}", exception); Err(exception) + } else if objtype::isinstance(&exception, &vm.ctx.type_type()) + && objtype::issubclass(&exception, &vm.ctx.exceptions.base_exception_type) + { + let exception = vm.new_empty_exception(exception)?; + info!("Exception raised: {:?}", exception); + Err(exception) } else { let msg = format!( "Can only raise BaseException derived types, not {}", - exception.borrow() + exception ); let type_error_type = vm.ctx.exceptions.type_error.clone(); let type_error = vm.new_exception(type_error_type, msg); @@ -564,39 +701,19 @@ impl Frame { } bytecode::Instruction::PrintExpr => { let expr = self.pop_value(); - match expr.borrow().payload { - PyObjectPayload::None => (), - _ => { - let repr = vm.to_repr(&expr)?; - builtins::builtin_print( - vm, - PyFuncArgs { - args: vec![repr], - kwargs: vec![], - }, - )?; + if !expr.is(&vm.get_none()) { + let repr = vm.to_repr(&expr)?; + // TODO: implement sys.displayhook + if let Some(print) = vm.ctx.get_attr(&vm.builtins, "print") { + vm.invoke(print, vec![repr.into_object()])?; } } Ok(None) } bytecode::Instruction::LoadBuildClass => { - let rustfunc = PyObject::new( - PyObjectPayload::RustFunction { - function: Box::new(builtins::builtin_build_class_), - }, - vm.ctx.type_type(), - ); - self.push_value(rustfunc); - Ok(None) - } - bytecode::Instruction::StoreLocals => { - let locals = self.pop_value(); - match self.locals.borrow_mut().payload { - PyObjectPayload::Scope { ref mut scope } => { - scope.locals = locals; - } - _ => panic!("We really expect our scope to be a scope!"), - } + let rustfunc = + PyBuiltinFunction::new(Box::new(builtins::builtin_build_class_)).into_ref(vm); + self.push_value(rustfunc.into_object()); Ok(None) } bytecode::Instruction::UnpackSequence { size } => { @@ -654,8 +771,15 @@ impl Frame { } Ok(None) } - bytecode::Instruction::FormatValue { spec } => { - let value = self.pop_value(); + bytecode::Instruction::FormatValue { conversion, spec } => { + use ast::ConversionFlag::*; + let value = match conversion { + Some(Str) => vm.to_str(&self.pop_value())?.into_object(), + Some(Repr) => vm.to_repr(&self.pop_value())?.into_object(), + Some(Ascii) => self.pop_value(), // TODO + None => self.pop_value(), + }; + let spec = vm.new_str(spec.clone()); let formatted = vm.call_method(&value, "__format__", vec![spec])?; self.push_value(formatted); @@ -665,8 +789,8 @@ impl Frame { } fn get_elements( - &mut self, - vm: &mut VirtualMachine, + &self, + vm: &VirtualMachine, size: usize, unpack: bool, ) -> Result, PyObjectRef> { @@ -685,44 +809,36 @@ impl Frame { } } - fn import( - &mut self, - vm: &mut VirtualMachine, - module: &str, - symbol: &Option, - ) -> FrameResult { - let current_path = { - let mut source_pathbuf = PathBuf::from(&self.code.source_path); - source_pathbuf.pop(); - source_pathbuf + fn import(&self, vm: &VirtualMachine, module: &str, symbol: &Option) -> FrameResult { + let module = vm.import(module)?; + + // If we're importing a symbol, look it up and use it, otherwise construct a module and return + // that + let obj = match symbol { + Some(symbol) => vm.get_attribute(module, symbol.as_str()).map_err(|_| { + let import_error = vm.context().exceptions.import_error.clone(); + vm.new_exception(import_error, format!("cannot import name '{}'", symbol)) + }), + None => Ok(module), }; - let obj = import(vm, current_path, module, symbol)?; - // Push module on stack: - self.push_value(obj); + self.push_value(obj?); Ok(None) } - fn import_star(&mut self, vm: &mut VirtualMachine, module: &str) -> FrameResult { - let current_path = { - let mut source_pathbuf = PathBuf::from(&self.code.source_path); - source_pathbuf.pop(); - source_pathbuf - }; + fn import_star(&self, vm: &VirtualMachine, module: &str) -> FrameResult { + let module = vm.import(module)?; // Grab all the names from the module and put them in the context - let obj = import_module(vm, current_path, module)?; - - for (k, v) in obj.get_key_value_pairs().iter() { - vm.ctx - .set_attr(&self.locals, &objstr::get_value(k), v.clone()); + for (k, v) in module.get_key_value_pairs().iter() { + self.scope.store_name(&vm, &objstr::get_value(k), v.clone()); } Ok(None) } // Unwind all blocks: - fn unwind_blocks(&mut self, vm: &mut VirtualMachine) -> Option { + fn unwind_blocks(&self, vm: &VirtualMachine) -> Option { while let Some(block) = self.pop_block() { match block.typ { BlockType::Loop { .. } => {} @@ -746,9 +862,9 @@ impl Frame { None } - fn unwind_loop(&mut self, vm: &mut VirtualMachine) -> Block { + fn unwind_loop(&self, vm: &VirtualMachine) -> Block { loop { - let block = self.current_block().cloned().expect("not in a loop"); + let block = self.current_block().expect("not in a loop"); match block.typ { BlockType::Loop { .. } => break block, BlockType::TryExcept { .. } => { @@ -768,11 +884,7 @@ impl Frame { } } - fn unwind_exception( - &mut self, - vm: &mut VirtualMachine, - exc: PyObjectRef, - ) -> Option { + fn unwind_exception(&self, vm: &VirtualMachine, exc: PyObjectRef) -> Option { // unwind block stack on exception and find any handlers: while let Some(block) = self.pop_block() { match block.typ { @@ -817,7 +929,7 @@ impl Frame { fn with_exit( &self, - vm: &mut VirtualMachine, + vm: &VirtualMachine, context_manager: &PyObjectRef, exc: Option, ) -> PyResult { @@ -840,48 +952,37 @@ impl Frame { vm.call_method(context_manager, "__exit__", args) } - fn store_name(&mut self, vm: &mut VirtualMachine, name: &str) -> FrameResult { + fn store_name(&self, vm: &VirtualMachine, name: &str) -> FrameResult { let obj = self.pop_value(); - vm.ctx.set_attr(&self.locals, name, obj); + self.scope.store_name(&vm, name, obj); Ok(None) } - fn delete_name(&mut self, vm: &mut VirtualMachine, name: &str) -> FrameResult { - let locals = match self.locals.borrow().payload { - PyObjectPayload::Scope { ref scope } => scope.locals.clone(), - _ => panic!("We really expect our scope to be a scope!"), - }; - - // Assume here that locals is a dict - let name = vm.ctx.new_str(name.to_string()); - vm.call_method(&locals, "__delitem__", vec![name])?; + fn delete_name(&self, vm: &VirtualMachine, name: &str) -> FrameResult { + self.scope.delete_name(vm, name); Ok(None) } - fn load_name(&mut self, vm: &mut VirtualMachine, name: &str) -> FrameResult { - // Lookup name in scope and put it onto the stack! - let mut scope = self.locals.clone(); - loop { - if scope.contains_key(name) { - let obj = scope.get_item(name).unwrap(); - self.push_value(obj); - break Ok(None); - } else if scope.has_parent() { - scope = scope.get_parent(); - } else { + fn load_name(&self, vm: &VirtualMachine, name: &str) -> FrameResult { + match self.scope.load_name(&vm, name) { + Some(value) => { + self.push_value(value); + Ok(None) + } + None => { let name_error_type = vm.ctx.exceptions.name_error.clone(); let msg = format!("name '{}' is not defined", name); let name_error = vm.new_exception(name_error_type, msg); - break Err(name_error); + Err(name_error) } } } - fn subscript(&mut self, vm: &mut VirtualMachine, a: PyObjectRef, b: PyObjectRef) -> PyResult { + fn subscript(&self, vm: &VirtualMachine, a: PyObjectRef, b: PyObjectRef) -> PyResult { vm.call_method(&a, "__getitem__", vec![b]) } - fn execute_store_subscript(&mut self, vm: &mut VirtualMachine) -> FrameResult { + fn execute_store_subscript(&self, vm: &VirtualMachine) -> FrameResult { let idx = self.pop_value(); let obj = self.pop_value(); let value = self.pop_value(); @@ -889,22 +990,22 @@ impl Frame { Ok(None) } - fn execute_delete_subscript(&mut self, vm: &mut VirtualMachine) -> FrameResult { + fn execute_delete_subscript(&self, vm: &VirtualMachine) -> FrameResult { let idx = self.pop_value(); let obj = self.pop_value(); vm.call_method(&obj, "__delitem__", vec![idx])?; Ok(None) } - fn jump(&mut self, label: bytecode::Label) { + fn jump(&self, label: bytecode::Label) { let target_pc = self.code.label_map[&label]; trace!("program counter from {:?} to {:?}", self.lasti, target_pc); - self.lasti = target_pc; + *self.lasti.borrow_mut() = target_pc; } fn execute_binop( - &mut self, - vm: &mut VirtualMachine, + &self, + vm: &VirtualMachine, op: &bytecode::BinaryOperator, inplace: bool, ) -> FrameResult { @@ -946,11 +1047,7 @@ impl Frame { Ok(None) } - fn execute_unop( - &mut self, - vm: &mut VirtualMachine, - op: &bytecode::UnaryOperator, - ) -> FrameResult { + fn execute_unop(&self, vm: &VirtualMachine, op: &bytecode::UnaryOperator) -> FrameResult { let a = self.pop_value(); let value = match *op { bytecode::UnaryOperator::Minus => vm.call_method(&a, "__neg__", vec![])?, @@ -971,8 +1068,8 @@ impl Frame { // https://docs.python.org/3/reference/expressions.html#membership-test-operations fn _membership( - &mut self, - vm: &mut VirtualMachine, + &self, + vm: &VirtualMachine, needle: PyObjectRef, haystack: &PyObjectRef, ) -> PyResult { @@ -981,12 +1078,7 @@ impl Frame { // not implemented. } - fn _in( - &mut self, - vm: &mut VirtualMachine, - needle: PyObjectRef, - haystack: PyObjectRef, - ) -> PyResult { + fn _in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult { match self._membership(vm, needle, &haystack) { Ok(found) => Ok(found), Err(_) => Err(vm.new_type_error(format!( @@ -996,12 +1088,7 @@ impl Frame { } } - fn _not_in( - &mut self, - vm: &mut VirtualMachine, - needle: PyObjectRef, - haystack: PyObjectRef, - ) -> PyResult { + fn _not_in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult { match self._membership(vm, needle, &haystack) { Ok(found) => Ok(vm.ctx.new_bool(!objbool::get_value(&found))), Err(_) => Err(vm.new_type_error(format!( @@ -1023,8 +1110,8 @@ impl Frame { } fn execute_compare( - &mut self, - vm: &mut VirtualMachine, + &self, + vm: &VirtualMachine, op: &bytecode::ComparisonOperator, ) -> FrameResult { let b = self.pop_value(); @@ -1046,22 +1133,21 @@ impl Frame { Ok(None) } - fn load_attr(&mut self, vm: &mut VirtualMachine, attr_name: &str) -> FrameResult { + fn load_attr(&self, vm: &VirtualMachine, attr_name: &str) -> FrameResult { let parent = self.pop_value(); - let attr_name = vm.new_str(attr_name.to_string()); let obj = vm.get_attribute(parent, attr_name)?; self.push_value(obj); Ok(None) } - fn store_attr(&mut self, vm: &mut VirtualMachine, attr_name: &str) -> FrameResult { + fn store_attr(&self, vm: &VirtualMachine, attr_name: &str) -> FrameResult { let parent = self.pop_value(); let value = self.pop_value(); - vm.ctx.set_attr(&parent, attr_name, value); + vm.set_attr(&parent, vm.new_str(attr_name.to_string()), value)?; Ok(None) } - fn delete_attr(&mut self, vm: &mut VirtualMachine, attr_name: &str) -> FrameResult { + fn delete_attr(&self, vm: &VirtualMachine, attr_name: &str) -> FrameResult { let parent = self.pop_value(); let name = vm.ctx.new_str(attr_name.to_string()); vm.del_attr(&parent, name)?; @@ -1069,49 +1155,50 @@ impl Frame { } pub fn get_lineno(&self) -> ast::Location { - self.code.locations[self.lasti].clone() + self.code.locations[*self.lasti.borrow()].clone() } - fn push_block(&mut self, typ: BlockType) { - self.blocks.push(Block { + fn push_block(&self, typ: BlockType) { + self.blocks.borrow_mut().push(Block { typ, - level: self.stack.len(), + level: self.stack.borrow().len(), }); } - fn pop_block(&mut self) -> Option { - let block = self.blocks.pop()?; - self.stack.truncate(block.level); + fn pop_block(&self) -> Option { + let block = self.blocks.borrow_mut().pop()?; + self.stack.borrow_mut().truncate(block.level); Some(block) } - fn current_block(&self) -> Option<&Block> { - self.blocks.last() + fn current_block(&self) -> Option { + self.blocks.borrow().last().cloned() } - pub fn push_value(&mut self, obj: PyObjectRef) { - self.stack.push(obj); + pub fn push_value(&self, obj: PyObjectRef) { + self.stack.borrow_mut().push(obj); } - fn pop_value(&mut self) -> PyObjectRef { - self.stack.pop().unwrap() + fn pop_value(&self) -> PyObjectRef { + self.stack.borrow_mut().pop().unwrap() } - fn pop_multiple(&mut self, count: usize) -> Vec { + fn pop_multiple(&self, count: usize) -> Vec { let mut objs: Vec = Vec::new(); for _x in 0..count { - objs.push(self.stack.pop().unwrap()); + objs.push(self.pop_value()); } objs.reverse(); objs } fn last_value(&self) -> PyObjectRef { - self.stack.last().unwrap().clone() + self.stack.borrow().last().unwrap().clone() } fn nth_value(&self, depth: usize) -> PyObjectRef { - self.stack[self.stack.len() - depth - 1].clone() + let stack = self.stack.borrow_mut(); + stack[stack.len() - depth - 1].clone() } } @@ -1119,31 +1206,28 @@ impl fmt::Debug for Frame { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let stack_str = self .stack + .borrow() .iter() - .map(|elem| format!("\n > {:?}", elem.borrow())) - .collect::>() - .join(""); + .map(|elem| { + if elem.payload.as_any().is::() { + "\n > {frame}".to_string() + } else { + format!("\n > {:?}", elem) + } + }) + .collect::(); let block_str = self .blocks + .borrow() .iter() .map(|elem| format!("\n > {:?}", elem)) - .collect::>() - .join(""); - let local_str = match self.locals.borrow().payload { - PyObjectPayload::Scope { ref scope } => match scope.locals.borrow().payload { - PyObjectPayload::Dict { ref elements } => { - objdict::get_key_value_pairs_from_content(elements) - .iter() - .map(|elem| format!("\n {:?} = {:?}", elem.0.borrow(), elem.1.borrow())) - .collect::>() - .join("") - } - ref unexpected => panic!( - "locals unexpectedly not wrapping a dict! instead: {:?}", - unexpected - ), - }, - ref unexpected => panic!("locals unexpectedly not a scope! instead: {:?}", unexpected), + .collect::(); + let local_str = match self.scope.get_locals().payload::() { + Some(dict) => objdict::get_key_value_pairs_from_content(&dict.entries.borrow()) + .iter() + .map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1)) + .collect::(), + None => panic!("locals unexpectedly not wrapping a dict!",), }; write!( f, diff --git a/vm/src/function.rs b/vm/src/function.rs new file mode 100644 index 0000000000..c5ec06cb1c --- /dev/null +++ b/vm/src/function.rs @@ -0,0 +1,433 @@ +use std::collections::HashMap; +use std::ops::RangeInclusive; + +use crate::obj::objtype; +use crate::pyobject::{IntoPyObject, PyObjectRef, PyResult, TryFromObject, TypeProtocol}; +use crate::vm::VirtualMachine; + +use self::OptionalArg::*; + +/// The `PyFuncArgs` struct is one of the most used structs then creating +/// a rust function that can be called from python. It holds both positional +/// arguments, as well as keyword arguments passed to the function. +#[derive(Debug, Default, Clone)] +pub struct PyFuncArgs { + pub args: Vec, + pub kwargs: Vec<(String, PyObjectRef)>, +} + +/// Conversion from vector of python objects to function arguments. +impl From> for PyFuncArgs { + fn from(args: Vec) -> Self { + PyFuncArgs { + args, + kwargs: vec![], + } + } +} + +impl From for PyFuncArgs { + fn from(arg: PyObjectRef) -> Self { + PyFuncArgs { + args: vec![arg], + kwargs: vec![], + } + } +} + +impl PyFuncArgs { + pub fn new(mut args: Vec, kwarg_names: Vec) -> PyFuncArgs { + let mut kwargs = vec![]; + for name in kwarg_names.iter().rev() { + kwargs.push((name.clone(), args.pop().unwrap())); + } + PyFuncArgs { args, kwargs } + } + + pub fn insert(&self, item: PyObjectRef) -> PyFuncArgs { + let mut args = PyFuncArgs { + args: self.args.clone(), + kwargs: self.kwargs.clone(), + }; + args.args.insert(0, item); + args + } + + pub fn shift(&mut self) -> PyObjectRef { + self.args.remove(0) + } + + pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef { + for (arg_name, arg_value) in self.kwargs.iter() { + if arg_name == key { + return arg_value.clone(); + } + } + default.clone() + } + + pub fn get_optional_kwarg(&self, key: &str) -> Option { + for (arg_name, arg_value) in self.kwargs.iter() { + if arg_name == key { + return Some(arg_value.clone()); + } + } + None + } + + pub fn get_optional_kwarg_with_type( + &self, + key: &str, + ty: PyObjectRef, + vm: &VirtualMachine, + ) -> Result, PyObjectRef> { + match self.get_optional_kwarg(key) { + Some(kwarg) => { + if objtype::isinstance(&kwarg, &ty) { + Ok(Some(kwarg)) + } else { + let expected_ty_name = vm.to_pystr(&ty)?; + let actual_ty_name = vm.to_pystr(&kwarg.typ())?; + Err(vm.new_type_error(format!( + "argument of type {} is required for named parameter `{}` (got: {})", + expected_ty_name, key, actual_ty_name + ))) + } + } + None => Ok(None), + } + } + + pub fn next_positional(&mut self) -> Option { + if self.args.is_empty() { + None + } else { + Some(self.args.remove(0)) + } + } + + pub fn take_keyword(&mut self, name: &str) -> Option { + // TODO: change kwarg representation so this scan isn't necessary + if let Some(index) = self + .kwargs + .iter() + .position(|(arg_name, _)| arg_name == name) + { + Some(self.kwargs.remove(index).1) + } else { + None + } + } + + pub fn remaining_keyword<'a>(&'a mut self) -> impl Iterator + 'a { + self.kwargs.drain(..) + } + + /// Binds these arguments to their respective values. + /// + /// If there is an insufficient number of arguments, there are leftover + /// arguments after performing the binding, or if an argument is not of + /// the expected type, a TypeError is raised. + /// + /// If the given `FromArgs` includes any conversions, exceptions raised + /// during the conversion will halt the binding and return the error. + fn bind(mut self, vm: &VirtualMachine) -> PyResult { + let given_args = self.args.len(); + let bound = match T::from_args(vm, &mut self) { + Ok(args) => args, + Err(ArgumentError::TooFewArgs) => { + return Err(vm.new_type_error(format!( + "Expected at least {} arguments ({} given)", + T::arity().start(), + given_args, + ))); + } + Err(ArgumentError::TooManyArgs) => { + return Err(vm.new_type_error(format!( + "Expected at most {} arguments ({} given)", + T::arity().end(), + given_args, + ))); + } + Err(ArgumentError::InvalidKeywordArgument(name)) => { + return Err(vm.new_type_error(format!("{} is an invalid keyword argument", name))); + } + Err(ArgumentError::Exception(ex)) => { + return Err(ex); + } + }; + + if !self.args.is_empty() { + Err(vm.new_type_error(format!( + "Expected at most {} arguments ({} given)", + T::arity().end(), + given_args, + ))) + } else if !self.kwargs.is_empty() { + Err(vm.new_type_error(format!("Unexpected keyword argument {}", self.kwargs[0].0))) + } else { + Ok(bound) + } + } +} + +/// An error encountered while binding arguments to the parameters of a Python +/// function call. +pub enum ArgumentError { + /// The call provided fewer positional arguments than the function requires. + TooFewArgs, + /// The call provided more positional arguments than the function accepts. + TooManyArgs, + /// The function doesn't accept a keyword argument with the given name. + InvalidKeywordArgument(String), + /// An exception was raised while binding arguments to the function + /// parameters. + Exception(PyObjectRef), +} + +impl From for ArgumentError { + fn from(ex: PyObjectRef) -> Self { + ArgumentError::Exception(ex) + } +} + +/// Implemented by any type that can be accepted as a parameter to a built-in +/// function. +/// +pub trait FromArgs: Sized { + /// The range of positional arguments permitted by the function signature. + /// + /// Returns an empty range if not applicable. + fn arity() -> RangeInclusive { + 0..=0 + } + + /// Extracts this item from the next argument(s). + fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result; +} + +/// A map of keyword arguments to their values. +/// +/// A built-in function with a `KwArgs` parameter is analagous to a Python +/// function with `**kwargs`. All remaining keyword arguments are extracted +/// (and hence the function will permit an arbitrary number of them). +/// +/// `KwArgs` optionally accepts a generic type parameter to allow type checks +/// or conversions of each argument. +/// +/// Note: +/// +/// KwArgs is only for functions that accept arbitrary keyword arguments. For +/// functions that accept only *specific* named arguments, a rust struct with +/// an appropriate FromArgs implementation must be created. +pub struct KwArgs(HashMap); + +impl FromArgs for KwArgs +where + T: TryFromObject, +{ + fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { + let mut kwargs = HashMap::new(); + for (name, value) in args.remaining_keyword() { + kwargs.insert(name, T::try_from_object(vm, value)?); + } + Ok(KwArgs(kwargs)) + } +} + +/// A list of positional argument values. +/// +/// A built-in function with a `Args` parameter is analagous to a Python +/// function with `*args`. All remaining positional arguments are extracted +/// (and hence the function will permit an arbitrary number of them). +/// +/// `Args` optionally accepts a generic type parameter to allow type checks +/// or conversions of each argument. +pub struct Args(Vec); + +impl FromArgs for Args +where + T: TryFromObject, +{ + fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { + let mut varargs = Vec::new(); + while let Some(value) = args.next_positional() { + varargs.push(T::try_from_object(vm, value)?); + } + Ok(Args(varargs)) + } +} + +impl IntoIterator for Args { + type Item = T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromArgs for T +where + T: TryFromObject, +{ + fn arity() -> RangeInclusive { + 1..=1 + } + + fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { + if let Some(value) = args.next_positional() { + Ok(T::try_from_object(vm, value)?) + } else { + Err(ArgumentError::TooFewArgs) + } + } +} + +/// An argument that may or may not be provided by the caller. +/// +/// This style of argument is not possible in pure Python. +pub enum OptionalArg { + Present(T), + Missing, +} + +impl OptionalArg { + pub fn into_option(self) -> Option { + match self { + Present(value) => Some(value), + Missing => None, + } + } +} + +impl FromArgs for OptionalArg +where + T: TryFromObject, +{ + fn arity() -> RangeInclusive { + 0..=1 + } + + fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { + if let Some(value) = args.next_positional() { + Ok(Present(T::try_from_object(vm, value)?)) + } else { + Ok(Missing) + } + } +} + +// For functions that accept no arguments. Implemented explicitly instead of via +// macro below to avoid unused warnings. +impl FromArgs for () { + fn from_args(_vm: &VirtualMachine, _args: &mut PyFuncArgs) -> Result { + Ok(()) + } +} + +// A tuple of types that each implement `FromArgs` represents a sequence of +// arguments that can be bound and passed to a built-in function. +// +// Technically, a tuple can contain tuples, which can contain tuples, and so on, +// so this actually represents a tree of values to be bound from arguments, but +// in practice this is only used for the top-level parameters. +macro_rules! tuple_from_py_func_args { + ($($T:ident),+) => { + impl<$($T),+> FromArgs for ($($T,)+) + where + $($T: FromArgs),+ + { + fn arity() -> RangeInclusive { + let mut min = 0; + let mut max = 0; + $( + let (start, end) = $T::arity().into_inner(); + min += start; + max += end; + )+ + min..=max + } + + fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { + Ok(($($T::from_args(vm, args)?,)+)) + } + } + }; +} + +// Implement `FromArgs` for up to 5-tuples, allowing built-in functions to bind +// up to 5 top-level parameters (note that `Args`, `KwArgs`, nested tuples, etc. +// count as 1, so this should actually be more than enough). +tuple_from_py_func_args!(A); +tuple_from_py_func_args!(A, B); +tuple_from_py_func_args!(A, B, C); +tuple_from_py_func_args!(A, B, C, D); +tuple_from_py_func_args!(A, B, C, D, E); + +/// A built-in Python function. +pub type PyNativeFunc = Box PyResult + 'static>; + +/// Implemented by types that are or can generate built-in functions. +/// +/// For example, any function that: +/// +/// - Accepts a sequence of types that implement `FromArgs`, followed by a +/// `&VirtualMachine` +/// - Returns some type that implements `IntoPyObject` +/// +/// will generate a `PyNativeFunc` that performs the appropriate type and arity +/// checking, any requested conversions, and then if successful call the function +/// with the bound values. +/// +/// A bare `PyNativeFunc` also implements this trait, allowing the above to be +/// done manually, for rare situations that don't fit into this model. +pub trait IntoPyNativeFunc { + fn into_func(self) -> PyNativeFunc; +} + +impl IntoPyNativeFunc for F +where + F: Fn(&VirtualMachine, PyFuncArgs) -> PyResult + 'static, +{ + fn into_func(self) -> PyNativeFunc { + Box::new(self) + } +} + +impl IntoPyNativeFunc for PyNativeFunc { + fn into_func(self) -> PyNativeFunc { + self + } +} + +// This is the "magic" that allows rust functions of varying signatures to +// generate native python functions. +// +// Note that this could be done without a macro - it is simply to avoid repetition. +macro_rules! into_py_native_func_tuple { + ($(($n:tt, $T:ident)),*) => { + impl IntoPyNativeFunc<($($T,)*), R> for F + where + F: Fn($($T,)* &VirtualMachine) -> R + 'static, + $($T: FromArgs,)* + ($($T,)*): FromArgs, + R: IntoPyObject, + { + fn into_func(self) -> PyNativeFunc { + Box::new(move |vm, args| { + let ($($n,)*) = args.bind::<($($T,)*)>(vm)?; + + (self)($($n,)* vm).into_pyobject(vm) + }) + } + } + }; +} + +into_py_native_func_tuple!(); +into_py_native_func_tuple!((a, A)); +into_py_native_func_tuple!((a, A), (b, B)); +into_py_native_func_tuple!((a, A), (b, B), (c, C)); +into_py_native_func_tuple!((a, A), (b, B), (c, C), (d, D)); +into_py_native_func_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); diff --git a/vm/src/import.rs b/vm/src/import.rs index b0b53156c8..ed5a6d094b 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -6,18 +6,15 @@ use std::error::Error; use std::path::PathBuf; use crate::compile; +use crate::frame::Scope; use crate::obj::{objsequence, objstr}; -use crate::pyobject::{AttributeProtocol, DictProtocol, PyResult}; +use crate::pyobject::{DictProtocol, PyResult}; use crate::util; use crate::vm::VirtualMachine; -fn import_uncached_module( - vm: &mut VirtualMachine, - current_path: PathBuf, - module: &str, -) -> PyResult { +fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &str) -> PyResult { // Check for Rust-native modules - if let Some(module) = vm.stdlib_inits.get(module) { + if let Some(module) = vm.stdlib_inits.borrow().get(module) { return Ok(module(&vm.ctx).clone()); } @@ -41,53 +38,25 @@ fn import_uncached_module( })?; // trace!("Code object: {:?}", code_obj); - let builtins = vm.get_builtin_scope(); - let scope = vm.ctx.new_scope(Some(builtins)); - vm.ctx - .set_attr(&scope, "__name__", vm.new_str(module.to_string())); - vm.run_code_obj(code_obj, scope.clone())?; - Ok(vm.ctx.new_module(module, scope)) + let attrs = vm.ctx.new_dict(); + attrs.set_item(&vm.ctx, "__name__", vm.new_str(module.to_string())); + vm.run_code_obj(code_obj, Scope::new(None, attrs.clone()))?; + Ok(vm.ctx.new_module(module, attrs)) } -pub fn import_module( - vm: &mut VirtualMachine, - current_path: PathBuf, - module_name: &str, -) -> PyResult { +pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &str) -> PyResult { // First, see if we already loaded the module: - let sys_modules = vm.sys_module.get_attr("modules").unwrap(); + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules")?; if let Some(module) = sys_modules.get_item(module_name) { return Ok(module); } let module = import_uncached_module(vm, current_path, module_name)?; - vm.ctx.set_item(&sys_modules, module_name, module.clone()); + sys_modules.set_item(&vm.ctx, module_name, module.clone()); Ok(module) } -pub fn import( - vm: &mut VirtualMachine, - current_path: PathBuf, - module_name: &str, - symbol: &Option, -) -> PyResult { - let module = import_module(vm, current_path, module_name)?; - // If we're importing a symbol, look it up and use it, otherwise construct a module and return - // that - if let Some(symbol) = symbol { - module.get_attr(symbol).map_or_else( - || { - let import_error = vm.context().exceptions.import_error.clone(); - Err(vm.new_exception(import_error, format!("cannot import name '{}'", symbol))) - }, - Ok, - ) - } else { - Ok(module) - } -} - fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result { - let sys_path = vm.sys_module.get_attr("path").unwrap(); + let sys_path = vm.get_attribute(vm.sys_module.clone(), "path").unwrap(); let mut paths: Vec = objsequence::get_elements(&sys_path) .iter() .map(|item| PathBuf::from(objstr::get_value(item))) @@ -105,7 +74,7 @@ fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result } } - match file_paths.iter().filter(|p| p.exists()).next() { + match file_paths.iter().find(|p| p.exists()) { Some(path) => Ok(path.to_path_buf()), None => Err(format!("No module named '{}'", name)), } diff --git a/vm/src/lib.rs b/vm/src/lib.rs index f785e294d7..3892fe2a45 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -5,6 +5,13 @@ //! - Import mechanics //! - Base objects +// for methods like vm.to_str(), not the typical use of 'to' as a method prefix +#![allow( + clippy::wrong_self_convention, + clippy::let_and_return, + clippy::implicit_hasher +)] + #[macro_use] extern crate bitflags; #[macro_use] @@ -22,6 +29,8 @@ extern crate serde_json; extern crate statrs; extern crate rustpython_parser; +#[macro_use] +extern crate rustpython_derive; //extern crate eval; use eval::eval::*; // use py_code_object::{Function, NativeType, PyCodeObject}; @@ -37,7 +46,8 @@ pub mod error; pub mod eval; mod exceptions; pub mod format; -mod frame; +pub mod frame; +pub mod function; pub mod import; pub mod obj; pub mod pyobject; diff --git a/vm/src/macros.rs b/vm/src/macros.rs index de94fc4415..4d4e6ebe2e 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -18,6 +18,7 @@ macro_rules! type_check { // None indicates that we have no type requirement (i.e. we accept any type) if let Some(expected_type) = $arg_type { let arg = &$args.args[$arg_count]; + if !$crate::obj::objtype::isinstance(arg, &expected_type) { let arg_typ = arg.typ(); let expected_type_name = $vm.to_pystr(&expected_type)?; @@ -111,14 +112,38 @@ macro_rules! no_kwargs { }; } +#[macro_export] macro_rules! py_module { ( $ctx:expr, $module_name:expr, { $($name:expr => $value:expr),* $(,)* }) => { { - let py_mod = $ctx.new_module($module_name, $ctx.new_scope(None)); + let py_mod = $ctx.new_module($module_name, $ctx.new_dict()); + $( + $ctx.set_attr(&py_mod, $name, $value); + )* + py_mod + } + } +} + +#[macro_export] +macro_rules! py_class { + ( $ctx:expr, $class_name:expr, $class_base:expr, { $($name:expr => $value:expr),* $(,)* }) => { + { + let py_class = $ctx.new_class($class_name, $class_base); + $( + $ctx.set_attr(&py_class, $name, $value); + )* + py_class + } + } +} + +#[macro_export] +macro_rules! extend_class { + ( $ctx:expr, $class:expr, { $($name:expr => $value:expr),* $(,)* }) => { + let class = $class; $( - $ctx.set_attr(&py_mod, $name, $value); + $ctx.set_attr(&class, $name, $value); )* - py_mod - } } } diff --git a/vm/src/obj/mod.rs b/vm/src/obj/mod.rs index f5dff7563c..0419d827f0 100644 --- a/vm/src/obj/mod.rs +++ b/vm/src/obj/mod.rs @@ -1,11 +1,14 @@ //! This package contains the python basic/builtin types pub mod objbool; +pub mod objbuiltinfunc; pub mod objbytearray; pub mod objbytes; +pub mod objclassmethod; pub mod objcode; pub mod objcomplex; pub mod objdict; +pub mod objellipsis; pub mod objenumerate; pub mod objfilter; pub mod objfloat; @@ -17,6 +20,7 @@ pub mod objiter; pub mod objlist; pub mod objmap; pub mod objmemory; +pub mod objmodule; pub mod objnone; pub mod objobject; pub mod objproperty; @@ -24,8 +28,10 @@ pub mod objrange; pub mod objsequence; pub mod objset; pub mod objslice; +pub mod objstaticmethod; pub mod objstr; pub mod objsuper; pub mod objtuple; pub mod objtype; +pub mod objweakref; pub mod objzip; diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index c5c9cb43b1..4feb1300bd 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -1,33 +1,60 @@ -use super::objtype; +use num_traits::Zero; + +use crate::function::PyFuncArgs; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + IntoPyObject, PyContext, PyObjectRef, PyResult, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; -use num_traits::Zero; -pub fn boolval(vm: &mut VirtualMachine, obj: PyObjectRef) -> Result { - let result = match obj.borrow().payload { - PyObjectPayload::Integer { ref value } => !value.is_zero(), - PyObjectPayload::Float { value } => value != 0.0, - PyObjectPayload::Sequence { ref elements } => !elements.is_empty(), - PyObjectPayload::Dict { ref elements } => !elements.is_empty(), - PyObjectPayload::String { ref value } => !value.is_empty(), - PyObjectPayload::None { .. } => false, - _ => { - if let Ok(f) = vm.get_method(obj.clone(), "__bool__") { - let bool_res = vm.invoke(f, PyFuncArgs::default())?; - let v = match bool_res.borrow().payload { - PyObjectPayload::Integer { ref value } => !value.is_zero(), - _ => return Err(vm.new_type_error(String::from("TypeError"))), - }; +use super::objdict::PyDict; +use super::objfloat::PyFloat; +use super::objint::PyInt; +use super::objlist::PyList; +use super::objstr::PyString; +use super::objtuple::PyTuple; +use super::objtype; + +impl IntoPyObject for bool { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bool(self)) + } +} + +impl TryFromObject for bool { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + boolval(vm, obj) + } +} - v - } else { - true - } +pub fn boolval(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + if let Some(s) = obj.payload::() { + return Ok(!s.value.is_empty()); + } + if let Some(value) = obj.payload::() { + return Ok(*value != PyFloat::from(0.0)); + } + if let Some(dict) = obj.payload::() { + return Ok(!dict.entries.borrow().is_empty()); + } + if let Some(i) = obj.payload::() { + return Ok(!i.value.is_zero()); + } + if let Some(list) = obj.payload::() { + return Ok(!list.elements.borrow().is_empty()); + } + if let Some(tuple) = obj.payload::() { + return Ok(!tuple.elements.borrow().is_empty()); + } + + Ok(if let Ok(f) = vm.get_method(obj.clone(), "__bool__") { + let bool_res = vm.invoke(f, PyFuncArgs::default())?; + match bool_res.payload::() { + Some(i) => !i.value.is_zero(), + None => return Err(vm.new_type_error(String::from("TypeError"))), } - }; - Ok(result) + } else { + true + }) } pub fn init(context: &PyContext) { @@ -43,7 +70,7 @@ The class bool is a subclass of the class int, and cannot be subclassed."; context.set_attr(&bool_type, "__doc__", context.new_str(bool_doc.to_string())); } -pub fn not(vm: &mut VirtualMachine, obj: &PyObjectRef) -> PyResult { +pub fn not(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { if objtype::isinstance(obj, &vm.ctx.bool_type()) { let value = get_value(obj); Ok(vm.ctx.new_bool(!value)) @@ -54,14 +81,10 @@ pub fn not(vm: &mut VirtualMachine, obj: &PyObjectRef) -> PyResult { // Retrieve inner int value: pub fn get_value(obj: &PyObjectRef) -> bool { - if let PyObjectPayload::Integer { value } = &obj.borrow().payload { - !value.is_zero() - } else { - panic!("Inner error getting inner boolean"); - } + !obj.payload::().unwrap().value.is_zero() } -fn bool_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> Result { +fn bool_repr(vm: &VirtualMachine, args: PyFuncArgs) -> Result { arg_check!(vm, args, required = [(obj, Some(vm.ctx.bool_type()))]); let v = get_value(obj); let s = if v { @@ -72,7 +95,7 @@ fn bool_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> Result PyResult { +fn bool_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, diff --git a/vm/src/obj/objbuiltinfunc.rs b/vm/src/obj/objbuiltinfunc.rs new file mode 100644 index 0000000000..259c94b627 --- /dev/null +++ b/vm/src/obj/objbuiltinfunc.rs @@ -0,0 +1,28 @@ +use std::fmt; + +use crate::function::PyNativeFunc; +use crate::pyobject::{PyObjectRef, PyValue}; +use crate::vm::VirtualMachine; + +pub struct PyBuiltinFunction { + // TODO: shouldn't be public + pub value: PyNativeFunc, +} + +impl PyValue for PyBuiltinFunction { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.builtin_function_or_method_type() + } +} + +impl fmt::Debug for PyBuiltinFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "builtin function") + } +} + +impl PyBuiltinFunction { + pub fn new(value: PyNativeFunc) -> Self { + Self { value } + } +} diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index d98b80e740..c7038fe9ac 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -1,14 +1,46 @@ //! Implementation of the python bytearray object. -use crate::pyobject::{PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyResult, TypeProtocol}; +use std::cell::RefCell; +use std::fmt::Write; +use std::ops::{Deref, DerefMut}; -use super::objint; +use num_traits::ToPrimitive; -use super::objbytes::get_mut_value; -use super::objbytes::get_value; -use super::objtype; +use crate::function::{OptionalArg, PyFuncArgs}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; -use num_traits::ToPrimitive; + +use super::objint; +use super::objtype::{self, PyClassRef}; + +#[derive(Debug)] +pub struct PyByteArray { + // TODO: shouldn't be public + pub value: RefCell>, +} +type PyByteArrayRef = PyRef; + +impl PyByteArray { + pub fn new(data: Vec) -> Self { + PyByteArray { + value: RefCell::new(data), + } + } +} + +impl PyValue for PyByteArray { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.bytearray_type() + } +} + +pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { + obj.payload::().unwrap().value.borrow() +} + +pub fn get_mut_value<'a>(obj: &'a PyObjectRef) -> impl DerefMut> + 'a { + obj.payload::().unwrap().value.borrow_mut() +} // Binary data support @@ -112,20 +144,14 @@ pub fn init(context: &PyContext) { ); } -fn bytearray_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None)], - optional = [(val_option, None)] - ); - if !objtype::issubclass(cls, &vm.ctx.bytearray_type()) { - return Err(vm.new_type_error(format!("{:?} is not a subtype of bytearray", cls))); - } - +fn bytearray_new( + cls: PyClassRef, + val_option: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { // Create bytes data: - let value = if let Some(ival) = val_option { - let elements = vm.extract_elements(ival)?; + let value = if let OptionalArg::Present(ival) = val_option { + let elements = vm.extract_elements(&ival)?; let mut data_bytes = vec![]; for elem in elements.iter() { let v = objint::to_int(vm, elem, 10)?; @@ -140,17 +166,17 @@ fn bytearray_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } else { vec![] }; - Ok(PyObject::new(PyObjectPayload::Bytes { value }, cls.clone())) + PyByteArray::new(value).into_ref_with_type(vm, cls.clone()) } -fn bytesarray_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytesarray_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(a, Some(vm.ctx.bytearray_type()))]); let byte_vec = get_value(a).to_vec(); Ok(vm.ctx.new_int(byte_vec.len())) } -fn bytearray_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -165,31 +191,31 @@ fn bytearray_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(result)) } -fn bytearray_isalnum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_isalnum(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); let bytes = get_value(zelf); Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphanumeric()))) } -fn bytearray_isalpha(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_isalpha(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); let bytes = get_value(zelf); Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphabetic()))) } -fn bytearray_isascii(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_isascii(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); let bytes = get_value(zelf); Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_ascii()))) } -fn bytearray_isdigit(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_isdigit(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); let bytes = get_value(zelf); Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_digit(10)))) } -fn bytearray_islower(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_islower(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); let bytes = get_value(zelf); Ok(vm.new_bool( @@ -201,13 +227,13 @@ fn bytearray_islower(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { )) } -fn bytearray_isspace(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_isspace(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); let bytes = get_value(zelf); Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_whitespace()))) } -fn bytearray_isupper(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_isupper(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); let bytes = get_value(zelf); Ok(vm.new_bool( @@ -219,7 +245,7 @@ fn bytearray_isupper(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { )) } -fn bytearray_istitle(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_istitle(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); let bytes = get_value(zelf); @@ -258,7 +284,7 @@ fn is_cased(c: char) -> bool { } /* -fn bytearray_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_getitem(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -274,26 +300,29 @@ fn set_value(obj: &PyObjectRef, value: Vec) { } */ -fn bytearray_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +/// Return a lowercase hex representation of a bytearray +fn bytearray_to_hex(bytearray: &[u8]) -> String { + bytearray.iter().fold(String::new(), |mut s, b| { + let _ = write!(s, "\\x{:02x}", b); + s + }) +} + +fn bytearray_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytearray_type()))]); let value = get_value(obj); - let data = String::from_utf8(value.to_vec()).unwrap(); + let data = + String::from_utf8(value.to_vec()).unwrap_or_else(|_| bytearray_to_hex(&value.to_vec())); Ok(vm.new_str(format!("bytearray(b'{}')", data))) } -fn bytearray_clear(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_clear(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - let mut mut_obj = zelf.borrow_mut(); - match mut_obj.payload { - PyObjectPayload::Bytes { ref mut value } => { - value.clear(); - Ok(vm.get_none()) - } - _ => panic!("Bytearray has incorrect payload."), - } + get_mut_value(zelf).clear(); + Ok(vm.get_none()) } -fn bytearray_pop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_pop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytearray_type()))]); let mut value = get_mut_value(obj); @@ -304,20 +333,24 @@ fn bytearray_pop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn bytearray_lower(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_lower(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytearray_type()))]); let value = get_value(obj).to_vec().to_ascii_lowercase(); - Ok(PyObject::new( - PyObjectPayload::Bytes { value }, - vm.ctx.bytearray_type(), - )) + Ok(vm.ctx.new_bytearray(value)) } -fn bytearray_upper(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytearray_upper(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytearray_type()))]); let value = get_value(obj).to_vec().to_ascii_uppercase(); - Ok(PyObject::new( - PyObjectPayload::Bytes { value }, - vm.ctx.bytearray_type(), - )) + Ok(vm.ctx.new_bytearray(value)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bytearray_to_hex_formatting() { + assert_eq!(&bytearray_to_hex(&[11u8, 222u8]), "\\x0b\\xde"); + } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index f63a9c80de..dc1f930a46 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,15 +1,43 @@ -use super::objint; -use super::objtype; +use std::cell::Cell; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; + +use num_traits::ToPrimitive; + +use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; -use num_traits::ToPrimitive; -use std::cell::Ref; -use std::cell::RefMut; -use std::hash::{Hash, Hasher}; -use std::ops::Deref; -use std::ops::DerefMut; + +use super::objint; +use super::objtype::{self, PyClassRef}; + +#[derive(Debug)] +pub struct PyBytes { + value: Vec, +} +type PyBytesRef = PyRef; + +impl PyBytes { + pub fn new(data: Vec) -> Self { + PyBytes { value: data } + } +} + +impl Deref for PyBytes { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.value + } +} + +impl PyValue for PyBytes { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.bytes_type() + } +} // Binary data support @@ -45,20 +73,14 @@ pub fn init(context: &PyContext) { ); } -fn bytes_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None)], - optional = [(val_option, None)] - ); - if !objtype::issubclass(cls, &vm.ctx.bytes_type()) { - return Err(vm.new_type_error(format!("{:?} is not a subtype of bytes", cls))); - } - +fn bytes_new( + cls: PyClassRef, + val_option: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { // Create bytes data: - let value = if let Some(ival) = val_option { - let elements = vm.extract_elements(ival)?; + let value = if let OptionalArg::Present(ival) = val_option { + let elements = vm.extract_elements(&ival)?; let mut data_bytes = vec![]; for elem in elements.iter() { let v = objint::to_int(vm, elem, 10)?; @@ -70,10 +92,10 @@ fn bytes_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vec![] }; - Ok(PyObject::new(PyObjectPayload::Bytes { value }, cls.clone())) + PyBytes::new(value).into_ref_with_type(vm, cls) } -fn bytes_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytes_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -88,7 +110,7 @@ fn bytes_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(result)) } -fn bytes_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytes_ge(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -98,16 +120,12 @@ fn bytes_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { get_value(a).to_vec() >= get_value(b).to_vec() } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '>'", - a.borrow(), - b.borrow() - ))); + return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>'", a, b))); }; Ok(vm.ctx.new_bool(result)) } -fn bytes_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytes_gt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -117,16 +135,12 @@ fn bytes_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { get_value(a).to_vec() > get_value(b).to_vec() } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '>='", - a.borrow(), - b.borrow() - ))); + return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>='", a, b))); }; Ok(vm.ctx.new_bool(result)) } -fn bytes_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytes_le(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -136,16 +150,12 @@ fn bytes_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { get_value(a).to_vec() <= get_value(b).to_vec() } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '<'", - a.borrow(), - b.borrow() - ))); + return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<'", a, b))); }; Ok(vm.ctx.new_bool(result)) } -fn bytes_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytes_lt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -155,23 +165,19 @@ fn bytes_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { get_value(a).to_vec() < get_value(b).to_vec() } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '<='", - a.borrow(), - b.borrow() - ))); + return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<='", a, b))); }; Ok(vm.ctx.new_bool(result)) } -fn bytes_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytes_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(a, Some(vm.ctx.bytes_type()))]); let byte_vec = get_value(a).to_vec(); Ok(vm.ctx.new_int(byte_vec.len())) } -fn bytes_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytes_hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytes_type()))]); let data = get_value(zelf); let mut hasher = std::collections::hash_map::DefaultHasher::new(); @@ -181,42 +187,19 @@ fn bytes_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { - Ref::map(obj.borrow(), |py_obj| { - if let PyObjectPayload::Bytes { ref value } = py_obj.payload { - value - } else { - panic!("Inner error getting bytearray {:?}", obj); - } - }) + &obj.payload::().unwrap().value } -pub fn get_mut_value<'a>(obj: &'a PyObjectRef) -> impl DerefMut> + 'a { - RefMut::map(obj.borrow_mut(), |py_obj| { - if let PyObjectPayload::Bytes { ref mut value } = py_obj.payload { - value - } else { - panic!("Inner error getting bytearray {:?}", obj); - } - }) -} - -fn bytes_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytes_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytes_type()))]); let value = get_value(obj); let data = String::from_utf8(value.to_vec()).unwrap(); Ok(vm.new_str(format!("b'{}'", data))) } -fn bytes_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytes_type()))]); - - let iter_obj = PyObject::new( - PyObjectPayload::Iterator { - position: 0, - iterated_obj: obj.clone(), - }, - vm.ctx.iter_type(), - ); - - Ok(iter_obj) +fn bytes_iter(obj: PyBytesRef, _vm: &VirtualMachine) -> PyIteratorValue { + PyIteratorValue { + position: Cell::new(0), + iterated_obj: obj.into_object(), + } } diff --git a/vm/src/obj/objclassmethod.rs b/vm/src/obj/objclassmethod.rs new file mode 100644 index 0000000000..725f508e98 --- /dev/null +++ b/vm/src/obj/objclassmethod.rs @@ -0,0 +1,42 @@ +use super::objtype::PyClassRef; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::vm::VirtualMachine; + +#[derive(Clone, Debug)] +pub struct PyClassMethod { + pub callable: PyObjectRef, +} +pub type PyClassMethodRef = PyRef; + +impl PyValue for PyClassMethod { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.classmethod_type() + } +} + +impl PyClassMethodRef { + fn new( + cls: PyClassRef, + callable: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + PyClassMethod { + callable: callable.clone(), + } + .into_ref_with_type(vm, cls) + } + + fn get(self, _inst: PyObjectRef, owner: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bound_method(self.callable.clone(), owner.clone())) + } +} + +pub fn init(context: &PyContext) { + let classmethod_type = &context.classmethod_type; + extend_class!(context, classmethod_type, { + "__get__" => context.new_rustfunc(PyClassMethodRef::get), + "__new__" => context.new_rustfunc(PyClassMethodRef::new) + }); +} diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 05d1b6238d..21df7f2cc1 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -2,12 +2,35 @@ */ +use std::fmt; + use crate::bytecode; -use crate::pyobject::{ - IdProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, -}; +use crate::function::PyFuncArgs; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; +pub struct PyCode { + code: bytecode::CodeObject, +} + +impl PyCode { + pub fn new(code: bytecode::CodeObject) -> PyCode { + PyCode { code } + } +} + +impl fmt::Debug for PyCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "code: {:?}", self.code) + } +} + +impl PyValue for PyCode { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.code_type() + } +} + pub fn init(context: &PyContext) { let code_type = &context.code_type; context.set_attr(code_type, "__new__", context.new_rustfunc(code_new)); @@ -16,7 +39,7 @@ pub fn init(context: &PyContext) { for (name, f) in &[ ( "co_argcount", - code_co_argcount as fn(&mut VirtualMachine, PyFuncArgs) -> PyResult, + code_co_argcount as fn(&VirtualMachine, PyFuncArgs) -> PyResult, ), ("co_consts", code_co_consts), ("co_filename", code_co_filename), @@ -24,24 +47,24 @@ pub fn init(context: &PyContext) { ("co_kwonlyargcount", code_co_kwonlyargcount), ("co_name", code_co_name), ] { - context.set_attr(code_type, name, context.new_member_descriptor(f)) + context.set_attr(code_type, name, context.new_property(f)) } } pub fn get_value(obj: &PyObjectRef) -> bytecode::CodeObject { - if let PyObjectPayload::Code { code } = &obj.borrow().payload { - code.clone() + if let Some(code) = obj.payload::() { + code.code.clone() } else { panic!("Inner error getting code {:?}", obj) } } -fn code_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn code_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(_cls, None)]); Err(vm.new_type_error("Cannot directly create code object".to_string())) } -fn code_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn code_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(o, Some(vm.ctx.code_type()))]); let code = get_value(o); @@ -55,43 +78,33 @@ fn code_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(repr)) } -fn member_code_obj( - vm: &mut VirtualMachine, - args: PyFuncArgs, -) -> Result { - arg_check!( - vm, - args, - required = [ - (zelf, Some(vm.ctx.code_type())), - (_cls, Some(vm.ctx.type_type())) - ] - ); +fn member_code_obj(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.code_type()))]); Ok(get_value(zelf)) } -fn code_co_argcount(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn code_co_argcount(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let code_obj = member_code_obj(vm, args)?; Ok(vm.ctx.new_int(code_obj.arg_names.len())) } -fn code_co_filename(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn code_co_filename(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let code_obj = member_code_obj(vm, args)?; let source_path = code_obj.source_path; Ok(vm.new_str(source_path)) } -fn code_co_firstlineno(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn code_co_firstlineno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let code_obj = member_code_obj(vm, args)?; Ok(vm.ctx.new_int(code_obj.first_line_number)) } -fn code_co_kwonlyargcount(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn code_co_kwonlyargcount(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let code_obj = member_code_obj(vm, args)?; Ok(vm.ctx.new_int(code_obj.kwonlyarg_names.len())) } -fn code_co_consts(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn code_co_consts(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let code_obj = member_code_obj(vm, args)?; let consts = code_obj .get_constants() @@ -100,7 +113,7 @@ fn code_co_consts(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_tuple(consts)) } -fn code_co_name(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn code_co_name(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let code_obj = member_code_obj(vm, args)?; Ok(vm.new_str(code_obj.obj_name)) } diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 9182f7bc2e..7443306512 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -1,13 +1,32 @@ -use super::objfloat; -use super::objint; -use super::objtype; -use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, -}; -use crate::vm::VirtualMachine; use num_complex::Complex64; use num_traits::ToPrimitive; +use crate::function::{OptionalArg, PyFuncArgs}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::vm::VirtualMachine; + +use super::objfloat; +use super::objint; +use super::objtype::{self, PyClassRef}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PyComplex { + value: Complex64, +} +type PyComplexRef = PyRef; + +impl PyValue for PyComplex { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.complex_type() + } +} + +impl From for PyComplex { + fn from(value: Complex64) -> Self { + PyComplex { value } + } +} + pub fn init(context: &PyContext) { let complex_type = &context.complex_type; @@ -45,63 +64,49 @@ pub fn init(context: &PyContext) { } pub fn get_value(obj: &PyObjectRef) -> Complex64 { - if let PyObjectPayload::Complex { value } = &obj.borrow().payload { - *value - } else { - panic!("Inner error getting complex"); - } + obj.payload::().unwrap().value } -fn complex_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None)], - optional = [(real, None), (imag, None)] - ); - - if !objtype::issubclass(cls, &vm.ctx.complex_type()) { - return Err(vm.new_type_error(format!("{:?} is not a subtype of complex", cls))); - } - +fn complex_new( + cls: PyClassRef, + real: OptionalArg, + imag: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { let real = match real { - None => 0.0, - Some(value) => objfloat::make_float(vm, value)?, + OptionalArg::Missing => 0.0, + OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, }; let imag = match imag { - None => 0.0, - Some(value) => objfloat::make_float(vm, value)?, + OptionalArg::Missing => 0.0, + OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, }; let value = Complex64::new(real, imag); - - Ok(PyObject::new( - PyObjectPayload::Complex { value }, - cls.clone(), - )) + PyComplex { value }.into_ref_with_type(vm, cls) } -fn complex_real(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn complex_real(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); - let Complex64 { re, im: _ } = get_value(zelf); + let Complex64 { re, .. } = get_value(zelf); Ok(vm.ctx.new_float(re)) } -fn complex_imag(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn complex_imag(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); - let Complex64 { re: _, im } = get_value(zelf); + let Complex64 { im, .. } = get_value(zelf); Ok(vm.ctx.new_float(im)) } -fn complex_abs(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn complex_abs(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); let Complex64 { re, im } = get_value(zelf); Ok(vm.ctx.new_float(re.hypot(im))) } -fn complex_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn complex_add(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -117,11 +122,11 @@ fn complex_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { v1.im, ))) } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", i.borrow(), i2.borrow()))) + Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2))) } } -fn complex_radd(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn complex_radd(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -136,18 +141,18 @@ fn complex_radd(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { v1.im, ))) } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", i.borrow(), i2.borrow()))) + Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2))) } } -fn complex_conjugate(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn complex_conjugate(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(i, Some(vm.ctx.complex_type()))]); let v1 = get_value(i); Ok(vm.ctx.new_complex(v1.conj())) } -fn complex_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn complex_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -172,12 +177,12 @@ fn complex_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(result)) } -fn complex_neg(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn complex_neg(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); Ok(vm.ctx.new_complex(-get_value(zelf))) } -fn complex_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn complex_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.complex_type()))]); let v = get_value(obj); let repr = if v.re == 0. { diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 42e188b467..ad134f5aeb 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -1,53 +1,51 @@ -use super::objiter; -use super::objstr; -use super::objtype; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::fmt; +use std::ops::{Deref, DerefMut}; + +use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - PyAttributes, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, - TypeProtocol, + PyAttributes, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::{ReprGuard, VirtualMachine}; -use std::cell::{Ref, RefCell, RefMut}; -use std::collections::HashMap; -use std::ops::{Deref, DerefMut}; -// This typedef abstracts the actual dict type used. -// pub type DictContentType = HashMap>; -// pub type DictContentType = HashMap; +use super::objiter; +use super::objstr::{self, PyStringRef}; +use super::objtype; + pub type DictContentType = HashMap; -// pub type DictContentType = HashMap>; - -pub fn new(dict_type: PyObjectRef) -> PyObjectRef { - PyObject::new( - PyObjectPayload::Dict { - elements: HashMap::new(), - }, - dict_type.clone(), - ) + +#[derive(Default)] +pub struct PyDict { + // TODO: should be private + pub entries: RefCell, +} +pub type PyDictRef = PyRef; + +impl fmt::Debug for PyDict { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: implement more detailed, non-recursive Debug formatter + f.write_str("dict") + } +} + +impl PyValue for PyDict { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.dict_type() + } } pub fn get_elements<'a>(obj: &'a PyObjectRef) -> impl Deref + 'a { - Ref::map(obj.borrow(), |py_obj| { - if let PyObjectPayload::Dict { ref elements } = py_obj.payload { - elements - } else { - panic!("Cannot extract dict elements"); - } - }) + obj.payload::().unwrap().entries.borrow() } -fn get_mut_elements<'a>(obj: &'a PyObjectRef) -> impl DerefMut + 'a { - RefMut::map(obj.borrow_mut(), |py_obj| { - if let PyObjectPayload::Dict { ref mut elements } = py_obj.payload { - elements - } else { - panic!("Cannot extract dict elements"); - } - }) +pub fn get_mut_elements<'a>(obj: &'a PyObjectRef) -> impl DerefMut + 'a { + obj.payload::().unwrap().entries.borrow_mut() } pub fn set_item( dict: &PyObjectRef, - _vm: &mut VirtualMachine, + _vm: &VirtualMachine, needle: &PyObjectRef, value: &PyObjectRef, ) { @@ -125,7 +123,7 @@ pub fn py_dict_to_attributes(dict: &PyObjectRef) -> PyAttributes { attrs } -pub fn attributes_to_py_dict(vm: &mut VirtualMachine, attributes: PyAttributes) -> PyResult { +pub fn attributes_to_py_dict(vm: &VirtualMachine, attributes: PyAttributes) -> PyResult { let dict = vm.ctx.new_dict(); for (key, value) in attributes { let key = vm.ctx.new_str(key); @@ -135,8 +133,7 @@ pub fn attributes_to_py_dict(vm: &mut VirtualMachine, attributes: PyAttributes) } // Python dict methods: - -fn dict_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn dict_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -152,7 +149,7 @@ fn dict_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } else { let iter = objiter::get_iter(vm, dict_obj)?; loop { - fn err(vm: &mut VirtualMachine) -> PyObjectRef { + fn err(vm: &VirtualMachine) -> PyObjectRef { vm.new_type_error("Iterator must have exactly two elements".to_string()) } let element = match objiter::get_next_object(vm, &iter)? { @@ -176,172 +173,144 @@ fn dict_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(dict) } -fn dict_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(dict_obj, Some(vm.ctx.dict_type()))]); - let elements = get_elements(dict_obj); - Ok(vm.ctx.new_int(elements.len())) -} +impl PyDictRef { + fn len(self, _vm: &VirtualMachine) -> usize { + self.entries.borrow().len() + } -fn dict_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(dict_obj, Some(vm.ctx.dict_type()))]); - - let s = if let Some(_guard) = ReprGuard::enter(dict_obj) { - let elements = get_key_value_pairs(dict_obj); - let mut str_parts = vec![]; - for (key, value) in elements { - let key_repr = vm.to_repr(&key)?; - let value_repr = vm.to_repr(&value)?; - let key_str = objstr::get_value(&key_repr); - let value_str = objstr::get_value(&value_repr); - str_parts.push(format!("{}: {}", key_str, value_str)); - } + fn repr(self, vm: &VirtualMachine) -> PyResult { + let s = if let Some(_guard) = ReprGuard::enter(self.as_object()) { + let elements = get_key_value_pairs(self.as_object()); + let mut str_parts = vec![]; + for (key, value) in elements { + let key_repr = vm.to_repr(&key)?; + let value_repr = vm.to_repr(&value)?; + str_parts.push(format!("{}: {}", key_repr.value, value_repr.value)); + } - format!("{{{}}}", str_parts.join(", ")) - } else { - "{...}".to_string() - }; - Ok(vm.new_str(s)) -} + format!("{{{}}}", str_parts.join(", ")) + } else { + "{...}".to_string() + }; + Ok(vm.new_str(s)) + } -pub fn dict_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (dict, Some(vm.ctx.dict_type())), - (needle, Some(vm.ctx.str_type())) - ] - ); + fn contains(self, key: PyStringRef, _vm: &VirtualMachine) -> bool { + self.entries.borrow().contains_key(&key.value) + } - let needle = objstr::get_value(&needle); - for element in get_elements(dict).iter() { - if &needle == element.0 { - return Ok(vm.new_bool(true)); + fn delitem(self, key: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + let key = &key.value; + // Delete the item: + let mut elements = self.entries.borrow_mut(); + match elements.remove(key) { + Some(_) => Ok(()), + None => Err(vm.new_value_error(format!("Key not found: {}", key))), } } - Ok(vm.new_bool(false)) -} - -fn dict_delitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (dict, Some(vm.ctx.dict_type())), - (needle, Some(vm.ctx.str_type())) - ] - ); - - // What we are looking for: - let needle = objstr::get_value(&needle); - - // Delete the item: - let mut elements = get_mut_elements(dict); - match elements.remove(&needle) { - Some(_) => Ok(vm.get_none()), - None => Err(vm.new_value_error(format!("Key not found: {}", needle))), + fn clear(self, _vm: &VirtualMachine) { + self.entries.borrow_mut().clear() } -} - -fn dict_clear(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(dict, Some(vm.ctx.dict_type()))]); - get_mut_elements(dict).clear(); - Ok(vm.get_none()) -} - -/// When iterating over a dictionary, we iterate over the keys of it. -fn dict_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(dict, Some(vm.ctx.dict_type()))]); - - let keys = get_elements(dict) - .keys() - .map(|k| vm.ctx.new_str(k.to_string())) - .collect(); - let key_list = vm.ctx.new_list(keys); - let iter_obj = PyObject::new( - PyObjectPayload::Iterator { - position: 0, + /// When iterating over a dictionary, we iterate over the keys of it. + fn iter(self, vm: &VirtualMachine) -> PyIteratorValue { + let keys = self + .entries + .borrow() + .values() + .map(|(k, _v)| k.clone()) + .collect(); + let key_list = vm.ctx.new_list(keys); + + PyIteratorValue { + position: Cell::new(0), iterated_obj: key_list, - }, - vm.ctx.iter_type(), - ); - - Ok(iter_obj) -} - -fn dict_setitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (dict, Some(vm.ctx.dict_type())), - (needle, Some(vm.ctx.str_type())), - (value, None) - ] - ); + } + } - set_item(dict, vm, needle, value); + fn values(self, vm: &VirtualMachine) -> PyIteratorValue { + let values = self + .entries + .borrow() + .values() + .map(|(_k, v)| v.clone()) + .collect(); + let values_list = vm.ctx.new_list(values); + + PyIteratorValue { + position: Cell::new(0), + iterated_obj: values_list, + } + } - Ok(vm.get_none()) -} + fn items(self, vm: &VirtualMachine) -> PyIteratorValue { + let items = self + .entries + .borrow() + .values() + .map(|(k, v)| vm.ctx.new_tuple(vec![k.clone(), v.clone()])) + .collect(); + let items_list = vm.ctx.new_list(items); + + PyIteratorValue { + position: Cell::new(0), + iterated_obj: items_list, + } + } -fn dict_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (dict, Some(vm.ctx.dict_type())), - (needle, Some(vm.ctx.str_type())) - ] - ); + fn setitem(self, needle: PyObjectRef, value: PyObjectRef, _vm: &VirtualMachine) { + let mut elements = self.entries.borrow_mut(); + set_item_in_content(&mut elements, &needle, &value) + } - // What we are looking for: - let needle = objstr::get_value(&needle); + fn getitem(self, key: PyStringRef, vm: &VirtualMachine) -> PyResult { + let key = &key.value; - let elements = get_elements(dict); - if elements.contains_key(&needle) { - Ok(elements[&needle].1.clone()) - } else { - Err(vm.new_value_error(format!("Key not found: {}", needle))) + // What we are looking for: + let elements = self.entries.borrow(); + if elements.contains_key(key) { + Ok(elements[key].1.clone()) + } else { + Err(vm.new_value_error(format!("Key not found: {}", key))) + } } -} -pub fn create_type(type_type: PyObjectRef, object_type: PyObjectRef, dict_type: PyObjectRef) { - (*dict_type.borrow_mut()).payload = PyObjectPayload::Class { - name: String::from("dict"), - dict: RefCell::new(HashMap::new()), - mro: vec![object_type], - }; - (*dict_type.borrow_mut()).typ = Some(type_type.clone()); + fn get( + self, + key: PyStringRef, + default: OptionalArg, + vm: &VirtualMachine, + ) -> PyObjectRef { + // What we are looking for: + let key = &key.value; + + let elements = self.entries.borrow(); + if elements.contains_key(key) { + elements[key].1.clone() + } else { + match default { + OptionalArg::Present(value) => value, + OptionalArg::Missing => vm.ctx.none(), + } + } + } } pub fn init(context: &PyContext) { - let dict_type = &context.dict_type; - context.set_attr(&dict_type, "__len__", context.new_rustfunc(dict_len)); - context.set_attr( - &dict_type, - "__contains__", - context.new_rustfunc(dict_contains), - ); - context.set_attr( - &dict_type, - "__delitem__", - context.new_rustfunc(dict_delitem), - ); - context.set_attr( - &dict_type, - "__getitem__", - context.new_rustfunc(dict_getitem), - ); - context.set_attr(&dict_type, "__iter__", context.new_rustfunc(dict_iter)); - context.set_attr(&dict_type, "__new__", context.new_rustfunc(dict_new)); - context.set_attr(&dict_type, "__repr__", context.new_rustfunc(dict_repr)); - context.set_attr( - &dict_type, - "__setitem__", - context.new_rustfunc(dict_setitem), - ); - context.set_attr(&dict_type, "clear", context.new_rustfunc(dict_clear)); + extend_class!(context, &context.dict_type, { + "__len__" => context.new_rustfunc(PyDictRef::len), + "__contains__" => context.new_rustfunc(PyDictRef::contains), + "__delitem__" => context.new_rustfunc(PyDictRef::delitem), + "__getitem__" => context.new_rustfunc(PyDictRef::getitem), + "__iter__" => context.new_rustfunc(PyDictRef::iter), + "__new__" => context.new_rustfunc(dict_new), + "__repr__" => context.new_rustfunc(PyDictRef::repr), + "__setitem__" => context.new_rustfunc(PyDictRef::setitem), + "clear" => context.new_rustfunc(PyDictRef::clear), + "values" => context.new_rustfunc(PyDictRef::values), + "items" => context.new_rustfunc(PyDictRef::items), + "keys" => context.new_rustfunc(PyDictRef::iter), + "get" => context.new_rustfunc(PyDictRef::get), + }); } diff --git a/vm/src/obj/objellipsis.rs b/vm/src/obj/objellipsis.rs new file mode 100644 index 0000000000..03c9d69e8a --- /dev/null +++ b/vm/src/obj/objellipsis.rs @@ -0,0 +1,23 @@ +use crate::function::PyFuncArgs; +use crate::pyobject::{PyContext, PyResult, TypeProtocol}; +use crate::vm::VirtualMachine; + +pub fn init(context: &PyContext) { + let ellipsis_type = &context.ellipsis_type; + context.set_attr(ellipsis_type, "__new__", context.new_rustfunc(ellipsis_new)); + context.set_attr( + ellipsis_type, + "__repr__", + context.new_rustfunc(ellipsis_repr), + ); +} + +fn ellipsis_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(_cls, None)]); + Ok(vm.ctx.ellipsis()) +} + +fn ellipsis_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(_cls, None)]); + Ok(vm.new_str("Ellipsis".to_string())) +} diff --git a/vm/src/obj/objenumerate.rs b/vm/src/obj/objenumerate.rs index 9e5f369f1e..5a66b7b5e2 100644 --- a/vm/src/obj/objenumerate.rs +++ b/vm/src/obj/objenumerate.rs @@ -1,48 +1,67 @@ -use super::objint; -use super::objiter; -use crate::pyobject::{PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyResult, TypeProtocol}; -use crate::vm::VirtualMachine; +use std::cell::RefCell; +use std::ops::AddAssign; + use num_bigint::BigInt; use num_traits::Zero; -use std::ops::AddAssign; -fn enumerate_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, Some(vm.ctx.type_type())), (iterable, None)], - optional = [(start, Some(vm.ctx.int_type()))] - ); - let counter = if let Some(x) = start { - objint::get_value(x) - } else { - BigInt::zero() +use crate::function::{OptionalArg, PyFuncArgs}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::vm::VirtualMachine; + +use super::objint::PyIntRef; +use super::objiter; +use super::objtype::PyClassRef; + +#[derive(Debug)] +pub struct PyEnumerate { + counter: RefCell, + iterator: PyObjectRef, +} +type PyEnumerateRef = PyRef; + +impl PyValue for PyEnumerate { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.enumerate_type() + } +} + +fn enumerate_new( + cls: PyClassRef, + iterable: PyObjectRef, + start: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let counter = match start { + OptionalArg::Present(start) => start.value.clone(), + OptionalArg::Missing => BigInt::zero(), }; - let iterator = objiter::get_iter(vm, iterable)?; - Ok(PyObject::new( - PyObjectPayload::EnumerateIterator { counter, iterator }, - cls.clone(), - )) + + let iterator = objiter::get_iter(vm, &iterable)?; + PyEnumerate { + counter: RefCell::new(counter.clone()), + iterator, + } + .into_ref_with_type(vm, cls) } -fn enumerate_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn enumerate_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, required = [(enumerate, Some(vm.ctx.enumerate_type()))] ); - if let PyObjectPayload::EnumerateIterator { - ref mut counter, - ref mut iterator, - } = enumerate.borrow_mut().payload + if let Some(PyEnumerate { + ref counter, + ref iterator, + }) = enumerate.payload() { let next_obj = objiter::call_next(vm, iterator)?; let result = vm .ctx - .new_tuple(vec![vm.ctx.new_int(counter.clone()), next_obj]); + .new_tuple(vec![vm.ctx.new_int(counter.borrow().clone()), next_obj]); - AddAssign::add_assign(counter, 1); + AddAssign::add_assign(&mut counter.borrow_mut() as &mut BigInt, 1); Ok(result) } else { diff --git a/vm/src/obj/objfilter.rs b/vm/src/obj/objfilter.rs index 1fe163374a..d83f70cc5c 100644 --- a/vm/src/obj/objfilter.rs +++ b/vm/src/obj/objfilter.rs @@ -1,11 +1,25 @@ -use super::objbool; -use super::objiter; +use crate::function::PyFuncArgs; use crate::pyobject::{ - IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyResult, TypeProtocol, + IdProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; // Required for arg_check! to use isinstance -fn filter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +use super::objbool; +use super::objiter; + +#[derive(Debug)] +pub struct PyFilter { + predicate: PyObjectRef, + iterator: PyObjectRef, +} + +impl PyValue for PyFilter { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.filter_type() + } +} + +fn filter_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -13,7 +27,7 @@ fn filter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ); let iterator = objiter::get_iter(vm, iterable)?; Ok(PyObject::new( - PyObjectPayload::FilterIterator { + PyFilter { predicate: function.clone(), iterator, }, @@ -21,13 +35,13 @@ fn filter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { )) } -fn filter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn filter_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(filter, Some(vm.ctx.filter_type()))]); - if let PyObjectPayload::FilterIterator { - ref mut predicate, - ref mut iterator, - } = filter.borrow_mut().payload + if let Some(PyFilter { + ref predicate, + ref iterator, + }) = filter.payload() { loop { let next_obj = objiter::call_next(vm, iterator)?; @@ -36,13 +50,7 @@ fn filter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } else { // the predicate itself can raise StopIteration which does stop the filter // iteration - vm.invoke( - predicate.clone(), - PyFuncArgs { - args: vec![next_obj.clone()], - kwargs: vec![], - }, - )? + vm.invoke(predicate.clone(), vec![next_obj.clone()])? }; if objbool::boolval(vm, predicate_value)? { return Ok(next_obj); @@ -63,11 +71,9 @@ pub fn init(context: &PyContext) { Return an iterator yielding those items of iterable for which function(item)\n\ is true. If function is None, return the items that are true."; - context.set_attr(&filter_type, "__new__", context.new_rustfunc(filter_new)); - context.set_attr( - &filter_type, - "__doc__", - context.new_str(filter_doc.to_string()), - ); - context.set_attr(&filter_type, "__next__", context.new_rustfunc(filter_next)); + extend_class!(context, filter_type, { + "__new__" => context.new_rustfunc(filter_new), + "__doc__" => context.new_str(filter_doc.to_string()), + "__next__" => context.new_rustfunc(filter_next) + }); } diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index c7b46299d3..849b31d408 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -3,485 +3,382 @@ use super::objint; use super::objstr; use super::objtype; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + IntoPyObject, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; use num_bigint::ToBigInt; +use num_rational::Ratio; use num_traits::ToPrimitive; -fn float_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(float, Some(vm.ctx.float_type()))]); - let v = get_value(float); - Ok(vm.new_str(v.to_string())) +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PyFloat { + value: f64, } -// __init__() -fn float_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.float_type())), (arg, None)] - ); - let val = if objtype::isinstance(arg, &vm.ctx.float_type()) { - get_value(arg) - } else if objtype::isinstance(arg, &vm.ctx.int_type()) { - match objint::get_value(arg).to_f64() { - Some(f) => f, - None => { - return Err(vm.new_overflow_error("int too large to convert to float".to_string())); - } - } - } else if objtype::isinstance(arg, &vm.ctx.str_type()) { - match lexical::try_parse(objstr::get_value(arg)) { - Ok(f) => f, - Err(_) => { - let arg_repr = vm.to_pystr(arg)?; - return Err( - vm.new_value_error(format!("could not convert string to float: {}", arg_repr)) - ); - } - } - } else if objtype::isinstance(arg, &vm.ctx.bytes_type()) { - match lexical::try_parse(objbytes::get_value(arg).as_slice()) { - Ok(f) => f, - Err(_) => { - let arg_repr = vm.to_pystr(arg)?; - return Err( - vm.new_value_error(format!("could not convert string to float: {}", arg_repr)) - ); - } - } - } else { - let type_name = objtype::get_type_name(&arg.typ()); - return Err(vm.new_type_error(format!("can't convert {} to float", type_name))); - }; - set_value(zelf, val); - Ok(vm.get_none()) +impl PyValue for PyFloat { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.float_type() + } } -// Retrieve inner float value: -pub fn get_value(obj: &PyObjectRef) -> f64 { - if let PyObjectPayload::Float { value } = &obj.borrow().payload { - *value - } else { - panic!("Inner error getting float"); +impl IntoPyObject for f64 { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_float(self)) } } -pub fn make_float(vm: &mut VirtualMachine, obj: &PyObjectRef) -> Result { - if objtype::isinstance(obj, &vm.ctx.float_type()) { - Ok(get_value(obj)) - } else if let Ok(method) = vm.get_method(obj.clone(), "__float__") { - let res = vm.invoke( - method, - PyFuncArgs { - args: vec![], - kwargs: vec![], - }, - )?; - Ok(get_value(&res)) - } else { - Err(vm.new_type_error(format!("Cannot cast {} to float", obj.borrow()))) +impl From for PyFloat { + fn from(value: f64) -> Self { + PyFloat { value } } } -fn set_value(obj: &PyObjectRef, value: f64) { - obj.borrow_mut().payload = PyObjectPayload::Float { value }; -} +pub type PyFloatRef = PyRef; + +impl PyFloatRef { + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + let value = self.value; + let result = if objtype::isinstance(&other, &vm.ctx.float_type()) { + let other = get_value(&other); + value == other + } else if objtype::isinstance(&other, &vm.ctx.int_type()) { + let other_int = objint::get_value(&other); + + if let (Some(self_int), Some(other_float)) = (value.to_bigint(), other_int.to_f64()) { + value == other_float && self_int == *other_int + } else { + false + } + } else { + return vm.ctx.not_implemented(); + }; + vm.ctx.new_bool(result) + } -fn float_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.float_type())), (other, None)] - ); - let zelf = get_value(zelf); - let result = if objtype::isinstance(other, &vm.ctx.float_type()) { - let other = get_value(other); - zelf == other - } else if objtype::isinstance(other, &vm.ctx.int_type()) { - let other_int = objint::get_value(other); - - if let (Some(zelf_int), Some(other_float)) = (zelf.to_bigint(), other_int.to_f64()) { - zelf == other_float && zelf_int == other_int + fn lt(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + let v1 = self.value; + if objtype::isinstance(&i2, &vm.ctx.float_type()) { + vm.ctx.new_bool(v1 < get_value(&i2)) + } else if objtype::isinstance(&i2, &vm.ctx.int_type()) { + vm.ctx + .new_bool(v1 < objint::get_value(&i2).to_f64().unwrap()) } else { - false + vm.ctx.not_implemented() } - } else { - return Ok(vm.ctx.not_implemented()); - }; - Ok(vm.ctx.new_bool(result)) -} + } -fn float_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.float_type())), (i2, None)] - ); - - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.float_type()) { - Ok(vm.ctx.new_bool(v1 < get_value(i2))) - } else if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_bool(v1 < objint::get_value(i2).to_f64().unwrap())) - } else { - Ok(vm.ctx.not_implemented()) + fn le(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + let v1 = self.value; + if objtype::isinstance(&i2, &vm.ctx.float_type()) { + vm.ctx.new_bool(v1 <= get_value(&i2)) + } else if objtype::isinstance(&i2, &vm.ctx.int_type()) { + vm.ctx + .new_bool(v1 <= objint::get_value(&i2).to_f64().unwrap()) + } else { + vm.ctx.not_implemented() + } } -} -fn float_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.float_type())), (i2, None)] - ); - - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.float_type()) { - Ok(vm.ctx.new_bool(v1 <= get_value(i2))) - } else if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_bool(v1 <= objint::get_value(i2).to_f64().unwrap())) - } else { - Ok(vm.ctx.not_implemented()) + fn gt(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + let v1 = self.value; + if objtype::isinstance(&i2, &vm.ctx.float_type()) { + vm.ctx.new_bool(v1 > get_value(&i2)) + } else if objtype::isinstance(&i2, &vm.ctx.int_type()) { + vm.ctx + .new_bool(v1 > objint::get_value(&i2).to_f64().unwrap()) + } else { + vm.ctx.not_implemented() + } } -} -fn float_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.float_type())), (i2, None)] - ); - - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.float_type()) { - Ok(vm.ctx.new_bool(v1 > get_value(i2))) - } else if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_bool(v1 > objint::get_value(i2).to_f64().unwrap())) - } else { - Ok(vm.ctx.not_implemented()) + fn ge(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + let v1 = self.value; + if objtype::isinstance(&i2, &vm.ctx.float_type()) { + vm.ctx.new_bool(v1 >= get_value(&i2)) + } else if objtype::isinstance(&i2, &vm.ctx.int_type()) { + vm.ctx + .new_bool(v1 >= objint::get_value(&i2).to_f64().unwrap()) + } else { + vm.ctx.not_implemented() + } } -} -fn float_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.float_type())), (i2, None)] - ); - - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.float_type()) { - Ok(vm.ctx.new_bool(v1 >= get_value(i2))) - } else if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_bool(v1 >= objint::get_value(i2).to_f64().unwrap())) - } else { - Ok(vm.ctx.not_implemented()) + fn abs(self, _vm: &VirtualMachine) -> f64 { + self.value.abs() } -} -fn float_abs(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.float_type()))]); - Ok(vm.ctx.new_float(get_value(i).abs())) -} + fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + let v1 = self.value; + if objtype::isinstance(&other, &vm.ctx.float_type()) { + vm.ctx.new_float(v1 + get_value(&other)) + } else if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx + .new_float(v1 + objint::get_value(&other).to_f64().unwrap()) + } else { + vm.ctx.not_implemented() + } + } -fn float_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.float_type())), (other, None)] - ); - - let v1 = get_value(zelf); - if objtype::isinstance(other, &vm.ctx.float_type()) { - Ok(vm.ctx.new_float(v1 + get_value(other))) - } else if objtype::isinstance(other, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_float(v1 + objint::get_value(other).to_f64().unwrap())) - } else { - Ok(vm.ctx.not_implemented()) + fn divmod(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.float_type()) + || objtype::isinstance(&other, &vm.ctx.int_type()) + { + let r1 = PyFloatRef::floordiv(self.clone(), other.clone(), vm)?; + let r2 = PyFloatRef::mod_(self, other, vm)?; + Ok(vm.ctx.new_tuple(vec![r1, r2])) + } else { + Ok(vm.ctx.not_implemented()) + } } -} -fn float_radd(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - float_add(vm, args) -} + fn floordiv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let v1 = self.value; + let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { + get_value(&other) + } else if objtype::isinstance(&other, &vm.ctx.int_type) { + objint::get_value(&other).to_f64().ok_or_else(|| { + vm.new_overflow_error("int too large to convert to float".to_string()) + })? + } else { + return Ok(vm.ctx.not_implemented()); + }; -fn float_divmod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.float_type())), (i2, None)] - ); - let args = PyFuncArgs::new(vec![i.clone(), i2.clone()], vec![]); - if objtype::isinstance(i2, &vm.ctx.float_type()) || objtype::isinstance(i2, &vm.ctx.int_type()) - { - let r1 = float_floordiv(vm, args.clone())?; - let r2 = float_mod(vm, args.clone())?; - Ok(vm.ctx.new_tuple(vec![r1, r2])) - } else { - Ok(vm.ctx.not_implemented()) + if v2 != 0.0 { + Ok(vm.ctx.new_float((v1 / v2).floor())) + } else { + Err(vm.new_zero_division_error("float floordiv by zero".to_string())) + } } -} -fn float_floordiv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.float_type())), (i2, None)] - ); - - let v1 = get_value(i); - let v2 = if objtype::isinstance(i2, &vm.ctx.float_type) { - get_value(i2) - } else if objtype::isinstance(i2, &vm.ctx.int_type) { - objint::get_value(i2) - .to_f64() - .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))? - } else { - return Ok(vm.ctx.not_implemented()); - }; + fn new_float(cls: PyObjectRef, arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let value = if objtype::isinstance(&arg, &vm.ctx.float_type()) { + get_value(&arg) + } else if objtype::isinstance(&arg, &vm.ctx.int_type()) { + match objint::get_value(&arg).to_f64() { + Some(f) => f, + None => { + return Err( + vm.new_overflow_error("int too large to convert to float".to_string()) + ); + } + } + } else if objtype::isinstance(&arg, &vm.ctx.str_type()) { + match lexical::try_parse(objstr::get_value(&arg)) { + Ok(f) => f, + Err(_) => { + let arg_repr = vm.to_pystr(&arg)?; + return Err(vm.new_value_error(format!( + "could not convert string to float: {}", + arg_repr + ))); + } + } + } else if objtype::isinstance(&arg, &vm.ctx.bytes_type()) { + match lexical::try_parse(objbytes::get_value(&arg).as_slice()) { + Ok(f) => f, + Err(_) => { + let arg_repr = vm.to_pystr(&arg)?; + return Err(vm.new_value_error(format!( + "could not convert string to float: {}", + arg_repr + ))); + } + } + } else { + let type_name = objtype::get_type_name(&arg.typ()); + return Err(vm.new_type_error(format!("can't convert {} to float", type_name))); + }; + Ok(PyObject::new(PyFloat { value }, cls.clone())) + } - if v2 != 0.0 { - Ok(vm.ctx.new_float((v1 / v2).floor())) - } else { - Err(vm.new_zero_division_error("float floordiv by zero".to_string())) + fn mod_(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let v1 = self.value; + let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { + get_value(&other) + } else if objtype::isinstance(&other, &vm.ctx.int_type) { + objint::get_value(&other).to_f64().ok_or_else(|| { + vm.new_overflow_error("int too large to convert to float".to_string()) + })? + } else { + return Ok(vm.ctx.not_implemented()); + }; + + if v2 != 0.0 { + Ok(vm.ctx.new_float(v1 % v2)) + } else { + Err(vm.new_zero_division_error("float mod by zero".to_string())) + } } -} -fn float_sub(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.float_type())), (other, None)] - ); - let v1 = get_value(zelf); - if objtype::isinstance(other, &vm.ctx.float_type()) { - Ok(vm.ctx.new_float(v1 - get_value(other))) - } else if objtype::isinstance(other, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_float(v1 - objint::get_value(other).to_f64().unwrap())) - } else { - Ok(vm.ctx.not_implemented()) + fn neg(self, _vm: &VirtualMachine) -> f64 { + -self.value } -} -fn float_rsub(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.float_type())), (other, None)] - ); - let v1 = get_value(zelf); - if objtype::isinstance(other, &vm.ctx.float_type()) { - Ok(vm.ctx.new_float(get_value(other) - v1)) - } else if objtype::isinstance(other, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_float(objint::get_value(other).to_f64().unwrap() - v1)) - } else { - Ok(vm.ctx.not_implemented()) + fn pow(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + let v1 = self.value; + if objtype::isinstance(&other, &vm.ctx.float_type()) { + vm.ctx.new_float(v1.powf(get_value(&other))) + } else if objtype::isinstance(&other, &vm.ctx.int_type()) { + let result = v1.powf(objint::get_value(&other).to_f64().unwrap()); + vm.ctx.new_float(result) + } else { + vm.ctx.not_implemented() + } } -} -fn float_mod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.float_type())), (i2, None)] - ); - - let v1 = get_value(i); - let v2 = if objtype::isinstance(i2, &vm.ctx.float_type) { - get_value(i2) - } else if objtype::isinstance(i2, &vm.ctx.int_type) { - objint::get_value(i2) - .to_f64() - .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))? - } else { - return Ok(vm.ctx.not_implemented()); - }; + fn sub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let v1 = self.value; + if objtype::isinstance(&other, &vm.ctx.float_type()) { + Ok(vm.ctx.new_float(v1 - get_value(&other))) + } else if objtype::isinstance(&other, &vm.ctx.int_type()) { + Ok(vm + .ctx + .new_float(v1 - objint::get_value(&other).to_f64().unwrap())) + } else { + Ok(vm.ctx.not_implemented()) + } + } - if v2 != 0.0 { - Ok(vm.ctx.new_float(v1 % v2)) - } else { - Err(vm.new_zero_division_error("float mod by zero".to_string())) + fn rsub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let v1 = self.value; + if objtype::isinstance(&other, &vm.ctx.float_type()) { + Ok(vm.ctx.new_float(get_value(&other) - v1)) + } else if objtype::isinstance(&other, &vm.ctx.int_type()) { + Ok(vm + .ctx + .new_float(objint::get_value(&other).to_f64().unwrap() - v1)) + } else { + Ok(vm.ctx.not_implemented()) + } } -} -fn float_neg(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.float_type()))]); + fn repr(self, _vm: &VirtualMachine) -> String { + self.value.to_string() + } - let v1 = get_value(i); - Ok(vm.ctx.new_float(-v1)) -} + fn truediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let v1 = self.value; + let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { + get_value(&other) + } else if objtype::isinstance(&other, &vm.ctx.int_type) { + objint::get_value(&other).to_f64().ok_or_else(|| { + vm.new_overflow_error("int too large to convert to float".to_string()) + })? + } else { + return Ok(vm.ctx.not_implemented()); + }; -fn float_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.float_type())), (i2, None)] - ); - - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.float_type()) { - let result = v1.powf(get_value(i2)); - Ok(vm.ctx.new_float(result)) - } else if objtype::isinstance(i2, &vm.ctx.int_type()) { - let result = v1.powf(objint::get_value(i2).to_f64().unwrap()); - Ok(vm.ctx.new_float(result)) - } else { - Ok(vm.ctx.not_implemented()) + if v2 != 0.0 { + Ok(vm.ctx.new_float(v1 / v2)) + } else { + Err(vm.new_zero_division_error("float division by zero".to_string())) + } } -} -fn float_truediv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.float_type())), (other, None)] - ); - - let v1 = get_value(zelf); - let v2 = if objtype::isinstance(other, &vm.ctx.float_type) { - get_value(other) - } else if objtype::isinstance(other, &vm.ctx.int_type) { - objint::get_value(other) - .to_f64() - .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))? - } else { - return Ok(vm.ctx.not_implemented()); - }; + fn rtruediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let v1 = self.value; + let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { + get_value(&other) + } else if objtype::isinstance(&other, &vm.ctx.int_type) { + objint::get_value(&other).to_f64().ok_or_else(|| { + vm.new_overflow_error("int too large to convert to float".to_string()) + })? + } else { + return Ok(vm.ctx.not_implemented()); + }; - if v2 != 0.0 { - Ok(vm.ctx.new_float(v1 / v2)) - } else { - Err(vm.new_zero_division_error("float division by zero".to_string())) + if v1 != 0.0 { + Ok(vm.ctx.new_float(v2 / v1)) + } else { + Err(vm.new_zero_division_error("float division by zero".to_string())) + } } -} -fn float_rtruediv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.float_type())), (other, None)] - ); - - let v1 = get_value(zelf); - let v2 = if objtype::isinstance(other, &vm.ctx.float_type) { - get_value(other) - } else if objtype::isinstance(other, &vm.ctx.int_type) { - objint::get_value(other) - .to_f64() - .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))? - } else { - return Ok(vm.ctx.not_implemented()); - }; + fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let v1 = self.value; + if objtype::isinstance(&other, &vm.ctx.float_type) { + Ok(vm.ctx.new_float(v1 * get_value(&other))) + } else if objtype::isinstance(&other, &vm.ctx.int_type) { + Ok(vm + .ctx + .new_float(v1 * objint::get_value(&other).to_f64().unwrap())) + } else { + Ok(vm.ctx.not_implemented()) + } + } - if v1 != 0.0 { - Ok(vm.ctx.new_float(v2 / v1)) - } else { - Err(vm.new_zero_division_error("float division by zero".to_string())) + fn is_integer(self, _vm: &VirtualMachine) -> bool { + let v = self.value; + (v - v.round()).abs() < std::f64::EPSILON } -} -fn float_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.float_type())), (other, None)] - ); - let v1 = get_value(zelf); - if objtype::isinstance(other, &vm.ctx.float_type) { - Ok(vm.ctx.new_float(v1 * get_value(other))) - } else if objtype::isinstance(other, &vm.ctx.int_type) { - Ok(vm - .ctx - .new_float(v1 * objint::get_value(other).to_f64().unwrap())) - } else { - Ok(vm.ctx.not_implemented()) + fn real(self, _vm: &VirtualMachine) -> Self { + self } -} -fn float_rmul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - float_mul(vm, args) + fn as_integer_ratio(self, vm: &VirtualMachine) -> PyResult { + let value = self.value; + if value.is_infinite() { + return Err( + vm.new_overflow_error("cannot convert Infinity to integer ratio".to_string()) + ); + } + if value.is_nan() { + return Err(vm.new_value_error("cannot convert NaN to integer ratio".to_string())); + } + + let ratio = Ratio::from_float(value).unwrap(); + let numer = vm.ctx.new_int(ratio.numer().clone()); + let denom = vm.ctx.new_int(ratio.denom().clone()); + Ok(vm.ctx.new_tuple(vec![numer, denom])) + } } -fn float_real(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.float_type()))]); - let v = get_value(i); - Ok(vm.ctx.new_float(v)) +// Retrieve inner float value: +pub fn get_value(obj: &PyObjectRef) -> f64 { + obj.payload::().unwrap().value } -fn float_is_integer(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.float_type()))]); - let v = get_value(i); - let result = v == v.round(); - Ok(vm.ctx.new_bool(result)) +pub fn make_float(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { + if objtype::isinstance(obj, &vm.ctx.float_type()) { + Ok(get_value(obj)) + } else if let Ok(method) = vm.get_method(obj.clone(), "__float__") { + let res = vm.invoke(method, vec![])?; + Ok(get_value(&res)) + } else { + Err(vm.new_type_error(format!("Cannot cast {} to float", obj))) + } } +#[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { let float_type = &context.float_type; let float_doc = "Convert a string or number to a floating point number, if possible."; - context.set_attr(&float_type, "__eq__", context.new_rustfunc(float_eq)); - context.set_attr(&float_type, "__lt__", context.new_rustfunc(float_lt)); - context.set_attr(&float_type, "__le__", context.new_rustfunc(float_le)); - context.set_attr(&float_type, "__gt__", context.new_rustfunc(float_gt)); - context.set_attr(&float_type, "__ge__", context.new_rustfunc(float_ge)); - context.set_attr(&float_type, "__abs__", context.new_rustfunc(float_abs)); - context.set_attr(&float_type, "__add__", context.new_rustfunc(float_add)); - context.set_attr(&float_type, "__radd__", context.new_rustfunc(float_radd)); - context.set_attr( - &float_type, - "__divmod__", - context.new_rustfunc(float_divmod), - ); - context.set_attr( - &float_type, - "__floordiv__", - context.new_rustfunc(float_floordiv), - ); - context.set_attr(&float_type, "__init__", context.new_rustfunc(float_init)); - context.set_attr(&float_type, "__mod__", context.new_rustfunc(float_mod)); - context.set_attr(&float_type, "__neg__", context.new_rustfunc(float_neg)); - context.set_attr(&float_type, "__pow__", context.new_rustfunc(float_pow)); - context.set_attr(&float_type, "__sub__", context.new_rustfunc(float_sub)); - context.set_attr(&float_type, "__rsub__", context.new_rustfunc(float_rsub)); - context.set_attr(&float_type, "__repr__", context.new_rustfunc(float_repr)); - context.set_attr( - &float_type, - "__doc__", - context.new_str(float_doc.to_string()), - ); - context.set_attr( - &float_type, - "__truediv__", - context.new_rustfunc(float_truediv), - ); - context.set_attr( - &float_type, - "__rtruediv__", - context.new_rustfunc(float_rtruediv), - ); - context.set_attr(&float_type, "__mul__", context.new_rustfunc(float_mul)); - context.set_attr(&float_type, "__rmul__", context.new_rustfunc(float_rmul)); - context.set_attr(&float_type, "real", context.new_property(float_real)); - context.set_attr( - &float_type, - "is_integer", - context.new_rustfunc(float_is_integer), - ); + context.set_attr(&float_type, "__eq__", context.new_rustfunc(PyFloatRef::eq)); + context.set_attr(&float_type, "__lt__", context.new_rustfunc(PyFloatRef::lt)); + context.set_attr(&float_type, "__le__", context.new_rustfunc(PyFloatRef::le)); + context.set_attr(&float_type, "__gt__", context.new_rustfunc(PyFloatRef::gt)); + context.set_attr(&float_type, "__ge__", context.new_rustfunc(PyFloatRef::ge)); + context.set_attr(&float_type, "__abs__", context.new_rustfunc(PyFloatRef::abs)); + context.set_attr(&float_type, "__add__", context.new_rustfunc(PyFloatRef::add)); + context.set_attr(&float_type, "__radd__", context.new_rustfunc(PyFloatRef::add)); + context.set_attr(&float_type, "__divmod__", context.new_rustfunc(PyFloatRef::divmod)); + context.set_attr(&float_type, "__floordiv__", context.new_rustfunc(PyFloatRef::floordiv)); + context.set_attr(&float_type, "__new__", context.new_rustfunc(PyFloatRef::new_float)); + context.set_attr(&float_type, "__mod__", context.new_rustfunc(PyFloatRef::mod_)); + context.set_attr(&float_type, "__neg__", context.new_rustfunc(PyFloatRef::neg)); + context.set_attr(&float_type, "__pow__", context.new_rustfunc(PyFloatRef::pow)); + context.set_attr(&float_type, "__sub__", context.new_rustfunc(PyFloatRef::sub)); + context.set_attr(&float_type, "__rsub__", context.new_rustfunc(PyFloatRef::rsub)); + context.set_attr(&float_type, "__repr__", context.new_rustfunc(PyFloatRef::repr)); + context.set_attr(&float_type, "__doc__", context.new_str(float_doc.to_string())); + context.set_attr(&float_type, "__truediv__", context.new_rustfunc(PyFloatRef::truediv)); + context.set_attr(&float_type, "__rtruediv__", context.new_rustfunc(PyFloatRef::rtruediv)); + context.set_attr(&float_type, "__mul__", context.new_rustfunc(PyFloatRef::mul)); + context.set_attr(&float_type, "__rmul__", context.new_rustfunc(PyFloatRef::mul)); + context.set_attr(&float_type, "real", context.new_property(PyFloatRef::real)); + context.set_attr(&float_type, "is_integer", context.new_rustfunc(PyFloatRef::is_integer)); + context.set_attr(&float_type, "as_integer_ratio", context.new_rustfunc(PyFloatRef::as_integer_ratio)); } diff --git a/vm/src/obj/objframe.rs b/vm/src/obj/objframe.rs index 3f9dd07d78..2d179fa24d 100644 --- a/vm/src/obj/objframe.rs +++ b/vm/src/obj/objframe.rs @@ -3,9 +3,8 @@ */ use crate::frame::Frame; -use crate::pyobject::{ - PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, -}; +use crate::function::PyFuncArgs; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; pub fn init(context: &PyContext) { @@ -16,39 +15,28 @@ pub fn init(context: &PyContext) { context.set_attr(&frame_type, "f_code", context.new_property(frame_fcode)); } -fn frame_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn frame_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(_cls, None)]); Err(vm.new_type_error("Cannot directly create frame object".to_string())) } -fn frame_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn frame_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(_frame, Some(vm.ctx.frame_type()))]); let repr = "".to_string(); Ok(vm.new_str(repr)) } -fn frame_flocals(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn frame_flocals(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(frame, Some(vm.ctx.frame_type()))]); let frame = get_value(frame); - let py_scope = frame.locals.clone(); - let py_scope = py_scope.borrow(); - - if let PyObjectPayload::Scope { scope } = &py_scope.payload { - Ok(scope.locals.clone()) - } else { - panic!("The scope isn't a scope!"); - } + Ok(frame.scope.get_locals().clone()) } -fn frame_fcode(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn frame_fcode(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(frame, Some(vm.ctx.frame_type()))]); - Ok(vm.ctx.new_code_object(get_value(frame).code)) + Ok(vm.ctx.new_code_object(get_value(frame).code.clone())) } -pub fn get_value(obj: &PyObjectRef) -> Frame { - if let PyObjectPayload::Frame { frame } = &obj.borrow().payload { - frame.clone() - } else { - panic!("Inner error getting int {:?}", obj); - } +pub fn get_value(obj: &PyObjectRef) -> &Frame { + &obj.payload::().unwrap() } diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index d59c7bb284..c4c480d67b 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -1,154 +1,81 @@ -use crate::pyobject::{ - AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyResult, TypeProtocol, -}; +use crate::frame::Scope; +use crate::function::PyFuncArgs; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; -pub fn init(context: &PyContext) { - let function_type = &context.function_type; - context.set_attr(&function_type, "__get__", context.new_rustfunc(bind_method)); - - context.set_attr( - &function_type, - "__code__", - context.new_member_descriptor(function_code), - ); - - let builtin_function_or_method_type = &context.builtin_function_or_method_type; - context.set_attr( - &builtin_function_or_method_type, - "__get__", - context.new_rustfunc(bind_method), - ); - - let member_descriptor_type = &context.member_descriptor_type; - context.set_attr( - &member_descriptor_type, - "__get__", - context.new_rustfunc(member_get), - ); - - let classmethod_type = &context.classmethod_type; - context.set_attr( - &classmethod_type, - "__get__", - context.new_rustfunc(classmethod_get), - ); - context.set_attr( - &classmethod_type, - "__new__", - context.new_rustfunc(classmethod_new), - ); - - let staticmethod_type = &context.staticmethod_type; - context.set_attr( - staticmethod_type, - "__get__", - context.new_rustfunc(staticmethod_get), - ); - context.set_attr( - staticmethod_type, - "__new__", - context.new_rustfunc(staticmethod_new), - ); +#[derive(Debug)] +pub struct PyFunction { + // TODO: these shouldn't be public + pub code: PyObjectRef, + pub scope: Scope, + pub defaults: PyObjectRef, } -fn bind_method(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(function, None), (obj, None), (cls, None)] - ); - - if obj.is(&vm.get_none()) && !cls.is(&obj.typ()) { - Ok(function.clone()) - } else { - Ok(vm.ctx.new_bound_method(function.clone(), obj.clone())) +impl PyFunction { + pub fn new(code: PyObjectRef, scope: Scope, defaults: PyObjectRef) -> Self { + PyFunction { + code, + scope, + defaults, + } } } -fn function_code(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - match args.args[0].borrow().payload { - PyObjectPayload::Function { ref code, .. } => Ok(code.clone()), - _ => Err(vm.new_type_error("no code".to_string())), +impl PyValue for PyFunction { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.function_type() } } -fn member_get(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { - match args.shift().get_attr("function") { - Some(function) => vm.invoke(function, args), - None => { - let attribute_error = vm.context().exceptions.attribute_error.clone(); - Err(vm.new_exception(attribute_error, String::from("Attribute Error"))) - } +#[derive(Debug)] +pub struct PyMethod { + // TODO: these shouldn't be public + pub object: PyObjectRef, + pub function: PyObjectRef, +} + +impl PyMethod { + pub fn new(object: PyObjectRef, function: PyObjectRef) -> Self { + PyMethod { object, function } } } -// Classmethod type methods: -fn classmethod_get(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("classmethod.__get__ {:?}", args.args); - arg_check!( - vm, - args, - required = [ - (cls, Some(vm.ctx.classmethod_type())), - (_inst, None), - (owner, None) - ] - ); - match cls.get_attr("function") { - Some(function) => { - let py_obj = owner.clone(); - let py_method = vm.ctx.new_bound_method(function, py_obj); - Ok(py_method) - } - None => { - let attribute_error = vm.context().exceptions.attribute_error.clone(); - Err(vm.new_exception( - attribute_error, - String::from("Attribute Error: classmethod must have 'function' attribute"), - )) - } +impl PyValue for PyMethod { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.bound_method_type() } } -fn classmethod_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("classmethod.__new__ {:?}", args.args); - arg_check!(vm, args, required = [(cls, None), (callable, None)]); +pub fn init(context: &PyContext) { + let function_type = &context.function_type; + extend_class!(context, function_type, { + "__get__" => context.new_rustfunc(bind_method), + "__code__" => context.new_property(function_code) + }); - let py_obj = vm.ctx.new_instance(cls.clone(), None); - vm.ctx.set_attr(&py_obj, "function", callable.clone()); - Ok(py_obj) + let builtin_function_or_method_type = &context.builtin_function_or_method_type; + extend_class!(context, builtin_function_or_method_type, { + "__get__" => context.new_rustfunc(bind_method) + }); } -// `staticmethod` methods. -fn staticmethod_get(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("staticmethod.__get__ {:?}", args.args); +fn bind_method(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, - required = [ - (cls, Some(vm.ctx.staticmethod_type())), - (_inst, None), - (_owner, None) - ] + required = [(function, None), (obj, None), (cls, None)] ); - match cls.get_attr("function") { - Some(function) => Ok(function), - None => { - let attribute_error = vm.context().exceptions.attribute_error.clone(); - Err(vm.new_exception( - attribute_error, - String::from("Attribute Error: staticmethod must have 'function' attribute"), - )) - } + + if obj.is(&vm.get_none()) && !cls.is(&obj.typ()) { + Ok(function.clone()) + } else { + Ok(vm.ctx.new_bound_method(function.clone(), obj.clone())) } } -fn staticmethod_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("staticmethod.__new__ {:?}", args.args); - arg_check!(vm, args, required = [(cls, None), (callable, None)]); - - let py_obj = vm.ctx.new_instance(cls.clone(), None); - vm.ctx.set_attr(&py_obj, "function", callable.clone()); - Ok(py_obj) +fn function_code(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + match args.args[0].payload() { + Some(PyFunction { ref code, .. }) => Ok(code.clone()), + None => Err(vm.new_type_error("no code".to_string())), + } } diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index 0f204e5baa..208bcb43f9 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -3,11 +3,22 @@ */ use crate::frame::{ExecutionResult, Frame}; -use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, -}; +use crate::function::PyFuncArgs; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; +#[derive(Debug)] +pub struct PyGenerator { + frame: PyObjectRef, +} +type PyGeneratorRef = PyRef; + +impl PyValue for PyGenerator { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.generator_type() + } +} + pub fn init(context: &PyContext) { let generator_type = &context.generator_type; context.set_attr( @@ -27,26 +38,22 @@ pub fn init(context: &PyContext) { ); } -pub fn new_generator(vm: &mut VirtualMachine, frame: Frame) -> PyResult { - let g = PyObject::new( - PyObjectPayload::Generator { frame }, - vm.ctx.generator_type.clone(), - ); - Ok(g) +pub fn new_generator(frame: PyObjectRef, vm: &VirtualMachine) -> PyGeneratorRef { + PyGenerator { frame }.into_ref(vm) } -fn generator_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn generator_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(o, Some(vm.ctx.generator_type()))]); Ok(o.clone()) } -fn generator_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn generator_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(o, Some(vm.ctx.generator_type()))]); let value = vm.get_none(); send(vm, o, &value) } -fn generator_send(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn generator_send(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -55,10 +62,15 @@ fn generator_send(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { send(vm, o, value) } -fn send(vm: &mut VirtualMachine, gen: &PyObjectRef, value: &PyObjectRef) -> PyResult { - if let PyObjectPayload::Generator { ref mut frame } = gen.borrow_mut().payload { - frame.push_value(value.clone()); - match frame.run_frame(vm)? { +fn send(vm: &VirtualMachine, gen: &PyObjectRef, value: &PyObjectRef) -> PyResult { + if let Some(PyGenerator { ref frame }) = gen.payload() { + if let Some(frame) = frame.payload::() { + frame.push_value(value.clone()); + } else { + panic!("Generator frame isn't a frame."); + } + + match vm.run_frame(frame.clone())? { ExecutionResult::Yield(value) => Ok(value), ExecutionResult::Return(_value) => { // Stop iteration! diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index de53f50821..553042ed5f 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -1,434 +1,432 @@ -use super::objfloat; -use super::objstr; -use super::objtype; +use std::hash::{Hash, Hasher}; + +use num_bigint::{BigInt, ToBigInt}; +use num_integer::Integer; +use num_traits::{Pow, Signed, ToPrimitive, Zero}; + use crate::format::FormatSpec; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - FromPyObjectRef, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, + IntoPyObject, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; -use num_bigint::{BigInt, ToBigInt}; -use num_integer::Integer; -use num_traits::{Pow, Signed, ToPrimitive, Zero}; -use std::hash::{Hash, Hasher}; -// This proxy allows for easy switching between types. -type IntType = BigInt; +use super::objfloat; +use super::objstr; +use super::objtype; -fn int_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(int, Some(vm.ctx.int_type()))]); - let v = get_value(int); - Ok(vm.new_str(v.to_string())) +#[derive(Debug)] +pub struct PyInt { + // TODO: shouldn't be public + pub value: BigInt, } -fn int_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None)], - optional = [(val_option, None)] - ); - if !objtype::issubclass(cls, &vm.ctx.int_type()) { - return Err(vm.new_type_error(format!("{:?} is not a subtype of int", cls))); - } +pub type PyIntRef = PyRef; - // TODO: extract kwargs: - let base = 10; - let val = match val_option { - Some(val) => to_int(vm, val, base)?, - None => Zero::zero(), - }; - Ok(PyObject::new( - PyObjectPayload::Integer { value: val }, - cls.clone(), - )) -} - -// Casting function: -pub fn to_int( - vm: &mut VirtualMachine, - obj: &PyObjectRef, - base: u32, -) -> Result { - let val = if objtype::isinstance(obj, &vm.ctx.int_type()) { - get_value(obj) - } else if objtype::isinstance(obj, &vm.ctx.float_type()) { - objfloat::get_value(obj).to_bigint().unwrap() - } else if objtype::isinstance(obj, &vm.ctx.str_type()) { - let s = objstr::get_value(obj); - match i32::from_str_radix(&s, base) { - Ok(v) => v.to_bigint().unwrap(), - Err(err) => { - trace!("Error occurred during int conversion {:?}", err); - return Err(vm.new_value_error(format!( - "invalid literal for int() with base {}: '{}'", - base, s - ))); - } - } - } else { - let type_name = objtype::get_type_name(&obj.typ()); - return Err(vm.new_type_error(format!( - "int() argument must be a string or a number, not '{}'", - type_name - ))); - }; - Ok(val) -} - -// Retrieve inner int value: -pub fn get_value(obj: &PyObjectRef) -> IntType { - if let PyObjectPayload::Integer { value } = &obj.borrow().payload { - value.clone() - } else { - panic!("Inner error getting int {:?}", obj); +impl PyInt { + pub fn new>(i: T) -> Self { + PyInt { value: i.into() } } } -impl FromPyObjectRef for BigInt { - fn from_pyobj(obj: &PyObjectRef) -> BigInt { - get_value(obj) +impl IntoPyObject for BigInt { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_int(self)) } } -fn int_bool(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.int_type()))]); - let result = !BigInt::from_pyobj(zelf).is_zero(); - Ok(vm.ctx.new_bool(result)) +impl PyValue for PyInt { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.int_type() + } } -fn int_invert(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.int_type()))]); - - let result = !BigInt::from_pyobj(zelf); +macro_rules! impl_into_pyobject_int { + ($($t:ty)*) => {$( + impl IntoPyObject for $t { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_int(self)) + } + } + )*}; +} + +impl_into_pyobject_int!(isize i8 i16 i32 i64 usize u8 u16 u32 u64) ; + +macro_rules! impl_try_from_object_int { + ($(($t:ty, $to_prim:ident),)*) => {$( + impl TryFromObject for $t { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + match PyRef::::try_from_object(vm, obj)?.value.$to_prim() { + Some(value) => Ok(value), + None => Err( + vm.new_overflow_error(concat!( + "Int value cannot fit into Rust ", + stringify!($t) + ).to_string()) + ), + } + } + } + )*}; +} + +impl_try_from_object_int!( + (isize, to_isize), + (i8, to_i8), + (i16, to_i16), + (i32, to_i32), + (i64, to_i64), + (usize, to_usize), + (u8, to_u8), + (u16, to_u16), + (u32, to_u32), + (u64, to_u64), +); + +impl PyIntRef { + fn pass_value(self, _vm: &VirtualMachine) -> Self { + self + } - Ok(vm.ctx.new_int(result)) -} + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_bool(self.value == *get_value(&other)) + } else { + vm.ctx.not_implemented() + } + } -fn int_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); + fn ne(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_bool(self.value != *get_value(&other)) + } else { + vm.ctx.not_implemented() + } + } - let zelf = BigInt::from_pyobj(zelf); - let result = if objtype::isinstance(other, &vm.ctx.int_type()) { - let other = BigInt::from_pyobj(other); - zelf == other - } else { - return Ok(vm.ctx.not_implemented()); - }; - Ok(vm.ctx.new_bool(result)) -} + fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_bool(self.value < *get_value(&other)) + } else { + vm.ctx.not_implemented() + } + } -fn int_ne(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); + fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_bool(self.value <= *get_value(&other)) + } else { + vm.ctx.not_implemented() + } + } - let zelf = BigInt::from_pyobj(zelf); - let result = if objtype::isinstance(other, &vm.ctx.int_type()) { - let other = BigInt::from_pyobj(other); - zelf != other - } else { - return Ok(vm.ctx.not_implemented()); - }; - Ok(vm.ctx.new_bool(result)) -} + fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_bool(self.value > *get_value(&other)) + } else { + vm.ctx.not_implemented() + } + } -fn int_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); + fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_bool(self.value >= *get_value(&other)) + } else { + vm.ctx.not_implemented() + } + } - if !objtype::isinstance(other, &vm.ctx.int_type()) { - return Ok(vm.ctx.not_implemented()); + fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_int((&self.value) + get_value(&other)) + } else { + vm.ctx.not_implemented() + } } - let zelf = BigInt::from_pyobj(zelf); - let other = BigInt::from_pyobj(other); - let result = zelf < other; - Ok(vm.ctx.new_bool(result)) -} + fn sub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_int((&self.value) - get_value(&other)) + } else { + vm.ctx.not_implemented() + } + } -fn int_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); + fn rsub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_int(get_value(&other) - (&self.value)) + } else { + vm.ctx.not_implemented() + } + } - if !objtype::isinstance(other, &vm.ctx.int_type()) { - return Ok(vm.ctx.not_implemented()); + fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_int((&self.value) * get_value(&other)) + } else { + vm.ctx.not_implemented() + } } - let zelf = BigInt::from_pyobj(zelf); - let other = BigInt::from_pyobj(other); - let result = zelf <= other; - Ok(vm.ctx.new_bool(result)) -} + fn truediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + div_ints(vm, &self.value, &get_value(&other)) + } else { + Ok(vm.ctx.not_implemented()) + } + } -fn int_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); + fn rtruediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + div_ints(vm, &get_value(&other), &self.value) + } else { + Ok(vm.ctx.not_implemented()) + } + } - if !objtype::isinstance(other, &vm.ctx.int_type()) { - return Ok(vm.ctx.not_implemented()); + fn floordiv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + let v2 = get_value(&other); + if *v2 != BigInt::zero() { + Ok(vm.ctx.new_int((&self.value) / v2)) + } else { + Err(vm.new_zero_division_error("integer floordiv by zero".to_string())) + } + } else { + Ok(vm.ctx.not_implemented()) + } } - let zelf = BigInt::from_pyobj(zelf); - let other = BigInt::from_pyobj(other); - let result = zelf > other; - Ok(vm.ctx.new_bool(result)) -} + fn lshift(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if !objtype::isinstance(&other, &vm.ctx.int_type()) { + return Err(vm.new_type_error(format!( + "unsupported operand type(s) for << '{}' and '{}'", + objtype::get_type_name(&self.as_object().typ()), + objtype::get_type_name(&other.typ()) + ))); + } -fn int_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); + if let Some(n_bits) = get_value(&other).to_usize() { + return Ok(vm.ctx.new_int((&self.value) << n_bits)); + } - if !objtype::isinstance(other, &vm.ctx.int_type()) { - return Ok(vm.ctx.not_implemented()); + // i2 failed `to_usize()` conversion + match get_value(&other) { + v if *v < BigInt::zero() => Err(vm.new_value_error("negative shift count".to_string())), + v if *v > BigInt::from(usize::max_value()) => { + Err(vm.new_overflow_error("the number is too large to convert to int".to_string())) + } + _ => panic!("Failed converting {} to rust usize", get_value(&other)), + } } - let zelf = BigInt::from_pyobj(zelf); - let other = BigInt::from_pyobj(other); - let result = zelf >= other; - Ok(vm.ctx.new_bool(result)) -} + fn rshift(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if !objtype::isinstance(&other, &vm.ctx.int_type()) { + return Err(vm.new_type_error(format!( + "unsupported operand type(s) for >> '{}' and '{}'", + objtype::get_type_name(&self.as_object().typ()), + objtype::get_type_name(&other.typ()) + ))); + } -fn int_lshift(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); + if let Some(n_bits) = get_value(&other).to_usize() { + return Ok(vm.ctx.new_int((&self.value) >> n_bits)); + } - if !objtype::isinstance(i2, &vm.ctx.int_type()) { - return Err(vm.new_type_error(format!( - "unsupported operand type(s) for << '{}' and '{}'", - objtype::get_type_name(&i.typ()), - objtype::get_type_name(&i2.typ()) - ))); + // i2 failed `to_usize()` conversion + match get_value(&other) { + v if *v < BigInt::zero() => Err(vm.new_value_error("negative shift count".to_string())), + v if *v > BigInt::from(usize::max_value()) => { + Err(vm.new_overflow_error("the number is too large to convert to int".to_string())) + } + _ => panic!("Failed converting {} to rust usize", get_value(&other)), + } } - if let Some(n_bits) = get_value(i2).to_usize() { - return Ok(vm.ctx.new_int(get_value(i) << n_bits)); + fn xor(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_int((&self.value) ^ get_value(&other)) + } else { + vm.ctx.not_implemented() + } } - // i2 failed `to_usize()` conversion - match get_value(i2) { - ref v if *v < BigInt::zero() => Err(vm.new_value_error("negative shift count".to_string())), - ref v if *v > BigInt::from(usize::max_value()) => { - // TODO: raise OverflowError - panic!("Failed converting {} to rust usize", get_value(i2)); + fn rxor(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_int(get_value(&other) ^ (&self.value)) + } else { + vm.ctx.not_implemented() } - _ => panic!("Failed converting {} to rust usize", get_value(i2)), } -} -fn int_rshift(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); + fn or(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_int((&self.value) | get_value(&other)) + } else { + vm.ctx.not_implemented() + } + } - if !objtype::isinstance(i2, &vm.ctx.int_type()) { - return Err(vm.new_type_error(format!( - "unsupported operand type(s) for >> '{}' and '{}'", - objtype::get_type_name(&i.typ()), - objtype::get_type_name(&i2.typ()) - ))); + fn and(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + let v2 = get_value(&other); + vm.ctx.new_int((&self.value) & v2) + } else { + vm.ctx.not_implemented() + } } - if let Some(n_bits) = get_value(i2).to_usize() { - return Ok(vm.ctx.new_int(get_value(i) >> n_bits)); + fn pow(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + let v2 = get_value(&other).to_u32().unwrap(); + vm.ctx.new_int(self.value.pow(v2)) + } else if objtype::isinstance(&other, &vm.ctx.float_type()) { + let v2 = objfloat::get_value(&other); + vm.ctx.new_float((self.value.to_f64().unwrap()).powf(v2)) + } else { + vm.ctx.not_implemented() + } } - // i2 failed `to_usize()` conversion - match get_value(i2) { - ref v if *v < BigInt::zero() => Err(vm.new_value_error("negative shift count".to_string())), - ref v if *v > BigInt::from(usize::max_value()) => { - // TODO: raise OverflowError - panic!("Failed converting {} to rust usize", get_value(i2)); + fn mod_(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + let v2 = get_value(&other); + if *v2 != BigInt::zero() { + Ok(vm.ctx.new_int((&self.value) % v2)) + } else { + Err(vm.new_zero_division_error("integer modulo by zero".to_string())) + } + } else { + Ok(vm.ctx.not_implemented()) } - _ => panic!("Failed converting {} to rust usize", get_value(i2)), } -} -fn int_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.int_type()))]); - let value = BigInt::from_pyobj(zelf); - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - value.hash(&mut hasher); - let hash = hasher.finish(); - Ok(vm.ctx.new_int(hash)) -} + fn divmod(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + let v2 = get_value(&other); + if *v2 != BigInt::zero() { + let (r1, r2) = self.value.div_rem(v2); + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_int(r1), vm.ctx.new_int(r2)])) + } else { + Err(vm.new_zero_division_error("integer divmod by zero".to_string())) + } + } else { + Ok(vm.ctx.not_implemented()) + } + } -fn int_abs(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); - Ok(vm.ctx.new_int(get_value(i).abs())) -} + fn neg(self, _vm: &VirtualMachine) -> BigInt { + -(&self.value) + } -fn int_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); - if objtype::isinstance(other, &vm.ctx.int_type()) { - Ok(vm.ctx.new_int(get_value(zelf) + get_value(other))) - } else { - Ok(vm.ctx.not_implemented()) + fn hash(self, _vm: &VirtualMachine) -> u64 { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + self.value.hash(&mut hasher); + hasher.finish() } -} -fn int_radd(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - int_add(vm, args) -} + fn abs(self, _vm: &VirtualMachine) -> BigInt { + self.value.abs() + } -fn int_float(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); - let i = get_value(i); - Ok(vm.ctx.new_float(i.to_f64().unwrap())) -} + fn round(self, _precision: OptionalArg, _vm: &VirtualMachine) -> Self { + self + } -fn int_floordiv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); - if objtype::isinstance(i2, &vm.ctx.int_type()) { - let (v1, v2) = (get_value(i), get_value(i2)); + fn float(self, _vm: &VirtualMachine) -> f64 { + self.value.to_f64().unwrap() + } - if v2 != BigInt::zero() { - Ok(vm.ctx.new_int(v1 / v2)) - } else { - Err(vm.new_zero_division_error("integer floordiv by zero".to_string())) - } - } else { - Ok(vm.ctx.not_implemented()) + fn invert(self, _vm: &VirtualMachine) -> BigInt { + !(&self.value) } -} -fn int_round(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type()))], - optional = [(_precision, None)] - ); - Ok(vm.ctx.new_int(get_value(i))) -} + fn repr(self, _vm: &VirtualMachine) -> String { + self.value.to_string() + } -fn int_pass_value(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); - Ok(vm.ctx.new_int(get_value(i))) -} + fn format(self, spec: PyRef, vm: &VirtualMachine) -> PyResult { + let format_spec = FormatSpec::parse(&spec.value); + match format_spec.format_int(&self.value) { + Ok(string) => Ok(string), + Err(err) => Err(vm.new_value_error(err.to_string())), + } + } -fn int_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (i, Some(vm.ctx.int_type())), - (format_spec, Some(vm.ctx.str_type())) - ] - ); - let string_value = objstr::get_value(format_spec); - let format_spec = FormatSpec::parse(&string_value); - let int_value = get_value(i); - match format_spec.format_int(&int_value) { - Ok(string) => Ok(vm.ctx.new_str(string)), - Err(err) => Err(vm.new_value_error(err.to_string())), + fn bool(self, _vm: &VirtualMachine) -> bool { + !self.value.is_zero() } -} -fn int_sub(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); - if objtype::isinstance(other, &vm.ctx.int_type()) { - Ok(vm.ctx.new_int(get_value(zelf) - get_value(other))) - } else { - Ok(vm.ctx.not_implemented()) + fn bit_length(self, _vm: &VirtualMachine) -> usize { + self.value.bits() } -} -fn int_rsub(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); - if objtype::isinstance(other, &vm.ctx.int_type()) { - Ok(vm.ctx.new_int(get_value(other) - get_value(zelf))) - } else { - Ok(vm.ctx.not_implemented()) + fn imag(self, _vm: &VirtualMachine) -> usize { + 0 } } -fn int_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn int_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] + required = [(cls, None)], + optional = [(val_option, None)] ); - if objtype::isinstance(other, &vm.ctx.int_type()) { - Ok(vm.ctx.new_int(get_value(zelf) * get_value(other))) - } else { - Ok(vm.ctx.not_implemented()) + if !objtype::issubclass(cls, &vm.ctx.int_type()) { + return Err(vm.new_type_error(format!("{:?} is not a subtype of int", cls))); } -} -fn int_rmul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - int_mul(vm, args) + let base = match args.get_optional_kwarg("base") { + Some(argument) => get_value(&argument).to_u32().unwrap(), + None => 10, + }; + let val = match val_option { + Some(val) => to_int(vm, val, base)?, + None => Zero::zero(), + }; + Ok(PyObject::new(PyInt::new(val), cls.clone())) } -fn int_truediv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); - - if objtype::isinstance(other, &vm.ctx.int_type()) { - div_ints(vm, &get_value(zelf), &get_value(other)) +// Casting function: +pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult { + let val = if objtype::isinstance(obj, &vm.ctx.int_type()) { + get_value(obj).clone() + } else if objtype::isinstance(obj, &vm.ctx.float_type()) { + objfloat::get_value(obj).to_bigint().unwrap() + } else if objtype::isinstance(obj, &vm.ctx.str_type()) { + let s = objstr::get_value(obj); + match i32::from_str_radix(&s, base) { + Ok(v) => v.to_bigint().unwrap(), + Err(err) => { + trace!("Error occurred during int conversion {:?}", err); + return Err(vm.new_value_error(format!( + "invalid literal for int() with base {}: '{}'", + base, s + ))); + } + } } else { - Ok(vm.ctx.not_implemented()) - } + let type_name = objtype::get_type_name(&obj.typ()); + return Err(vm.new_type_error(format!( + "int() argument must be a string or a number, not '{}'", + type_name + ))); + }; + Ok(val) } -fn int_rtruediv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.int_type())), (other, None)] - ); - - if objtype::isinstance(other, &vm.ctx.int_type()) { - div_ints(vm, &get_value(other), &get_value(zelf)) - } else { - Ok(vm.ctx.not_implemented()) - } +// Retrieve inner int value: +pub fn get_value(obj: &PyObjectRef) -> &BigInt { + &obj.payload::().unwrap().value } #[inline] -fn div_ints(vm: &mut VirtualMachine, i1: &BigInt, i2: &BigInt) -> PyResult { +fn div_ints(vm: &VirtualMachine, i1: &BigInt, i2: &BigInt) -> PyResult { if i2.is_zero() { return Err(vm.new_zero_division_error("integer division by zero".to_string())); } @@ -459,167 +457,7 @@ fn div_ints(vm: &mut VirtualMachine, i1: &BigInt, i2: &BigInt) -> PyResult { } } -fn int_mod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.int_type()) { - let v2 = get_value(i2); - - if v2 != BigInt::zero() { - Ok(vm.ctx.new_int(v1 % get_value(i2))) - } else { - Err(vm.new_zero_division_error("integer modulo by zero".to_string())) - } - } else { - Ok(vm.ctx.not_implemented()) - } -} - -fn int_neg(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); - let i = BigInt::from_pyobj(i); - Ok(vm.ctx.new_int(-i)) -} - -fn int_pos(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); - Ok(i.clone()) -} - -fn int_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.int_type()) { - let v2 = get_value(i2).to_u32().unwrap(); - Ok(vm.ctx.new_int(v1.pow(v2))) - } else if objtype::isinstance(i2, &vm.ctx.float_type()) { - let v2 = objfloat::get_value(i2); - Ok(vm.ctx.new_float((v1.to_f64().unwrap()).powf(v2))) - } else { - Ok(vm.ctx.not_implemented()) - } -} - -fn int_divmod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); - - if objtype::isinstance(i2, &vm.ctx.int_type()) { - let v1 = get_value(i); - let v2 = get_value(i2); - - if v2 != BigInt::zero() { - let (r1, r2) = v1.div_rem(&v2); - - Ok(vm - .ctx - .new_tuple(vec![vm.ctx.new_int(r1), vm.ctx.new_int(r2)])) - } else { - Err(vm.new_zero_division_error("integer divmod by zero".to_string())) - } - } else { - Ok(vm.ctx.not_implemented()) - } -} - -fn int_xor(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.int_type()) { - let v2 = get_value(i2); - Ok(vm.ctx.new_int(v1 ^ v2)) - } else { - Ok(vm.ctx.not_implemented()) - } -} - -fn int_rxor(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); - - if objtype::isinstance(i2, &vm.ctx.int_type()) { - let right_val = get_value(i); - let left_val = get_value(i2); - - Ok(vm.ctx.new_int(left_val ^ right_val)) - } else { - Ok(vm.ctx.not_implemented()) - } -} - -fn int_or(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.int_type()) { - let v2 = get_value(i2); - Ok(vm.ctx.new_int(v1 | v2)) - } else { - Ok(vm.ctx.not_implemented()) - } -} - -fn int_and(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.int_type())), (i2, None)] - ); - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.int_type()) { - let v2 = get_value(i2); - Ok(vm.ctx.new_int(v1 & v2)) - } else { - Ok(vm.ctx.not_implemented()) - } -} - -fn int_bit_length(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); - let v = get_value(i); - let bits = v.bits(); - Ok(vm.ctx.new_int(bits)) -} - -fn int_conjugate(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); - let v = get_value(i); - Ok(vm.ctx.new_int(v)) -} - -fn int_real(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.int_type()))]); - let value = BigInt::from_pyobj(zelf); - Ok(vm.ctx.new_int(value)) -} - -fn int_imag(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(_zelf, Some(vm.ctx.int_type()))]); - let value = BigInt::from(0); - Ok(vm.ctx.new_int(value)) -} - +#[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { let int_doc = "int(x=0) -> integer int(x, base=10) -> integer @@ -637,61 +475,49 @@ Base 0 means to interpret the base from the string as an integer literal. 4"; let int_type = &context.int_type; - context.set_attr(&int_type, "__eq__", context.new_rustfunc(int_eq)); - context.set_attr(&int_type, "__ne__", context.new_rustfunc(int_ne)); - context.set_attr(&int_type, "__lt__", context.new_rustfunc(int_lt)); - context.set_attr(&int_type, "__le__", context.new_rustfunc(int_le)); - context.set_attr(&int_type, "__gt__", context.new_rustfunc(int_gt)); - context.set_attr(&int_type, "__ge__", context.new_rustfunc(int_ge)); - context.set_attr(&int_type, "__abs__", context.new_rustfunc(int_abs)); - context.set_attr(&int_type, "__add__", context.new_rustfunc(int_add)); - context.set_attr(&int_type, "__radd__", context.new_rustfunc(int_radd)); - context.set_attr(&int_type, "__and__", context.new_rustfunc(int_and)); - context.set_attr(&int_type, "__divmod__", context.new_rustfunc(int_divmod)); - context.set_attr(&int_type, "__float__", context.new_rustfunc(int_float)); - context.set_attr(&int_type, "__round__", context.new_rustfunc(int_round)); - context.set_attr(&int_type, "__ceil__", context.new_rustfunc(int_pass_value)); - context.set_attr(&int_type, "__floor__", context.new_rustfunc(int_pass_value)); - context.set_attr(&int_type, "__index__", context.new_rustfunc(int_pass_value)); - context.set_attr(&int_type, "__trunc__", context.new_rustfunc(int_pass_value)); - context.set_attr(&int_type, "__int__", context.new_rustfunc(int_pass_value)); - context.set_attr( - &int_type, - "__floordiv__", - context.new_rustfunc(int_floordiv), - ); - context.set_attr(&int_type, "__hash__", context.new_rustfunc(int_hash)); - context.set_attr(&int_type, "__lshift__", context.new_rustfunc(int_lshift)); - context.set_attr(&int_type, "__rshift__", context.new_rustfunc(int_rshift)); - context.set_attr(&int_type, "__new__", context.new_rustfunc(int_new)); - context.set_attr(&int_type, "__mod__", context.new_rustfunc(int_mod)); - context.set_attr(&int_type, "__mul__", context.new_rustfunc(int_mul)); - context.set_attr(&int_type, "__rmul__", context.new_rustfunc(int_rmul)); - context.set_attr(&int_type, "__neg__", context.new_rustfunc(int_neg)); - context.set_attr(&int_type, "__or__", context.new_rustfunc(int_or)); - context.set_attr(&int_type, "__pos__", context.new_rustfunc(int_pos)); - context.set_attr(&int_type, "__pow__", context.new_rustfunc(int_pow)); - context.set_attr(&int_type, "__repr__", context.new_rustfunc(int_repr)); - context.set_attr(&int_type, "__sub__", context.new_rustfunc(int_sub)); - context.set_attr(&int_type, "__rsub__", context.new_rustfunc(int_rsub)); - context.set_attr(&int_type, "__format__", context.new_rustfunc(int_format)); - context.set_attr(&int_type, "__truediv__", context.new_rustfunc(int_truediv)); - context.set_attr( - &int_type, - "__rtruediv__", - context.new_rustfunc(int_rtruediv), - ); - context.set_attr(&int_type, "__xor__", context.new_rustfunc(int_xor)); - context.set_attr(&int_type, "__rxor__", context.new_rustfunc(int_rxor)); - context.set_attr(&int_type, "__bool__", context.new_rustfunc(int_bool)); - context.set_attr(&int_type, "__invert__", context.new_rustfunc(int_invert)); - context.set_attr( - &int_type, - "bit_length", - context.new_rustfunc(int_bit_length), - ); context.set_attr(&int_type, "__doc__", context.new_str(int_doc.to_string())); - context.set_attr(&int_type, "conjugate", context.new_rustfunc(int_conjugate)); - context.set_attr(&int_type, "real", context.new_property(int_real)); - context.set_attr(&int_type, "imag", context.new_property(int_imag)); + context.set_attr(&int_type, "__eq__", context.new_rustfunc(PyIntRef::eq)); + context.set_attr(&int_type, "__ne__", context.new_rustfunc(PyIntRef::ne)); + context.set_attr(&int_type, "__lt__", context.new_rustfunc(PyIntRef::lt)); + context.set_attr(&int_type, "__le__", context.new_rustfunc(PyIntRef::le)); + context.set_attr(&int_type, "__gt__", context.new_rustfunc(PyIntRef::gt)); + context.set_attr(&int_type, "__ge__", context.new_rustfunc(PyIntRef::ge)); + context.set_attr(&int_type, "__abs__", context.new_rustfunc(PyIntRef::abs)); + context.set_attr(&int_type, "__add__", context.new_rustfunc(PyIntRef::add)); + context.set_attr(&int_type, "__radd__", context.new_rustfunc(PyIntRef::add)); + context.set_attr(&int_type, "__and__", context.new_rustfunc(PyIntRef::and)); + context.set_attr(&int_type, "__divmod__", context.new_rustfunc(PyIntRef::divmod)); + context.set_attr(&int_type, "__float__", context.new_rustfunc(PyIntRef::float)); + context.set_attr(&int_type, "__round__", context.new_rustfunc(PyIntRef::round)); + context.set_attr(&int_type, "__ceil__", context.new_rustfunc(PyIntRef::pass_value)); + context.set_attr(&int_type, "__floor__", context.new_rustfunc(PyIntRef::pass_value)); + context.set_attr(&int_type, "__index__", context.new_rustfunc(PyIntRef::pass_value)); + context.set_attr(&int_type, "__trunc__", context.new_rustfunc(PyIntRef::pass_value)); + context.set_attr(&int_type, "__int__", context.new_rustfunc(PyIntRef::pass_value)); + context.set_attr(&int_type, "__floordiv__", context.new_rustfunc(PyIntRef::floordiv)); + context.set_attr(&int_type, "__hash__", context.new_rustfunc(PyIntRef::hash)); + context.set_attr(&int_type, "__lshift__", context.new_rustfunc(PyIntRef::lshift)); + context.set_attr(&int_type, "__rshift__", context.new_rustfunc(PyIntRef::rshift)); + context.set_attr(&int_type, "__new__", context.new_rustfunc(int_new)); + context.set_attr(&int_type, "__mod__", context.new_rustfunc(PyIntRef::mod_)); + context.set_attr(&int_type, "__mul__", context.new_rustfunc(PyIntRef::mul)); + context.set_attr(&int_type, "__rmul__", context.new_rustfunc(PyIntRef::mul)); + context.set_attr(&int_type, "__or__", context.new_rustfunc(PyIntRef::or)); + context.set_attr(&int_type, "__neg__", context.new_rustfunc(PyIntRef::neg)); + context.set_attr(&int_type, "__pos__", context.new_rustfunc(PyIntRef::pass_value)); + context.set_attr(&int_type, "__pow__", context.new_rustfunc(PyIntRef::pow)); + context.set_attr(&int_type, "__repr__", context.new_rustfunc(PyIntRef::repr)); + context.set_attr(&int_type, "__sub__", context.new_rustfunc(PyIntRef::sub)); + context.set_attr(&int_type, "__rsub__", context.new_rustfunc(PyIntRef::rsub)); + context.set_attr(&int_type, "__format__", context.new_rustfunc(PyIntRef::format)); + context.set_attr(&int_type, "__truediv__", context.new_rustfunc(PyIntRef::truediv)); + context.set_attr(&int_type, "__rtruediv__", context.new_rustfunc(PyIntRef::rtruediv)); + context.set_attr(&int_type, "__xor__", context.new_rustfunc(PyIntRef::xor)); + context.set_attr(&int_type, "__rxor__", context.new_rustfunc(PyIntRef::rxor)); + context.set_attr(&int_type, "__bool__", context.new_rustfunc(PyIntRef::bool)); + context.set_attr(&int_type, "__invert__", context.new_rustfunc(PyIntRef::invert)); + context.set_attr(&int_type, "bit_length", context.new_rustfunc(PyIntRef::bit_length)); + context.set_attr(&int_type, "conjugate", context.new_rustfunc(PyIntRef::pass_value)); + context.set_attr(&int_type, "real", context.new_property(PyIntRef::pass_value)); + context.set_attr(&int_type, "imag", context.new_property(PyIntRef::imag)); } diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index a3e30a1522..1b7709d748 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -2,27 +2,30 @@ * Various types to support iteration. */ -use super::objbool; -use crate::pyobject::{ - PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, -}; +use crate::function::PyFuncArgs; +use crate::pyobject::{PyContext, PyIteratorValue, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; -// use super::objstr; -use super::objtype; // Required for arg_check! to use isinstance + +use super::objbool; +use super::objbytearray::PyByteArray; +use super::objbytes::PyBytes; +use super::objrange::PyRange; +use super::objsequence; +use super::objtype; /* * This helper function is called at multiple places. First, it is called * in the vm when a for loop is entered. Next, it is used when the builtin * function 'iter' is called. */ -pub fn get_iter(vm: &mut VirtualMachine, iter_target: &PyObjectRef) -> PyResult { +pub fn get_iter(vm: &VirtualMachine, iter_target: &PyObjectRef) -> PyResult { vm.call_method(iter_target, "__iter__", vec![]) // let type_str = objstr::get_value(&vm.to_str(iter_target.typ()).unwrap()); // let type_error = vm.new_type_error(format!("Cannot iterate over {}", type_str)); // return Err(type_error); } -pub fn call_next(vm: &mut VirtualMachine, iter_obj: &PyObjectRef) -> PyResult { +pub fn call_next(vm: &VirtualMachine, iter_obj: &PyObjectRef) -> PyResult { vm.call_method(iter_obj, "__next__", vec![]) } @@ -30,9 +33,9 @@ pub fn call_next(vm: &mut VirtualMachine, iter_obj: &PyObjectRef) -> PyResult { * Helper function to retrieve the next object (or none) from an iterator. */ pub fn get_next_object( - vm: &mut VirtualMachine, + vm: &VirtualMachine, iter_obj: &PyObjectRef, -) -> Result, PyObjectRef> { +) -> PyResult> { let next_obj: PyResult = call_next(vm, iter_obj); match next_obj { @@ -49,10 +52,7 @@ pub fn get_next_object( } /* Retrieve all elements from an iterator */ -pub fn get_all( - vm: &mut VirtualMachine, - iter_obj: &PyObjectRef, -) -> Result, PyObjectRef> { +pub fn get_all(vm: &VirtualMachine, iter_obj: &PyObjectRef) -> PyResult> { let mut elements = vec![]; loop { let element = get_next_object(vm, iter_obj)?; @@ -64,12 +64,12 @@ pub fn get_all( Ok(elements) } -pub fn new_stop_iteration(vm: &mut VirtualMachine) -> PyObjectRef { +pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { let stop_iteration_type = vm.ctx.exceptions.stop_iteration.clone(); vm.new_exception(stop_iteration_type, "End of iterator".to_string()) } -fn contains(vm: &mut VirtualMachine, args: PyFuncArgs, iter_type: PyObjectRef) -> PyResult { +fn contains(vm: &VirtualMachine, args: PyFuncArgs, iter_type: PyObjectRef) -> PyResult { arg_check!( vm, args, @@ -93,9 +93,7 @@ fn contains(vm: &mut VirtualMachine, args: PyFuncArgs, iter_type: PyObjectRef) - pub fn iter_type_init(context: &PyContext, iter_type: &PyObjectRef) { let contains_func = { let cloned_iter_type = iter_type.clone(); - move |vm: &mut VirtualMachine, args: PyFuncArgs| { - contains(vm, args, cloned_iter_type.clone()) - } + move |vm: &VirtualMachine, args: PyFuncArgs| contains(vm, args, cloned_iter_type.clone()) }; context.set_attr( &iter_type, @@ -104,7 +102,7 @@ pub fn iter_type_init(context: &PyContext, iter_type: &PyObjectRef) { ); let iter_func = { let cloned_iter_type = iter_type.clone(); - move |vm: &mut VirtualMachine, args: PyFuncArgs| { + move |vm: &VirtualMachine, args: PyFuncArgs| { arg_check!( vm, args, @@ -118,53 +116,51 @@ pub fn iter_type_init(context: &PyContext, iter_type: &PyObjectRef) { } // Sequence iterator: -fn iter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn iter_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(iter_target, None)]); get_iter(vm, iter_target) } -fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn iter_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(iter, Some(vm.ctx.iter_type()))]); - if let PyObjectPayload::Iterator { - ref mut position, - iterated_obj: ref mut iterated_obj_ref, - } = iter.borrow_mut().payload + if let Some(PyIteratorValue { + ref position, + iterated_obj: ref iterated_obj_ref, + }) = iter.payload() { - let iterated_obj = iterated_obj_ref.borrow_mut(); - match iterated_obj.payload { - PyObjectPayload::Sequence { ref elements } => { - if *position < elements.len() { - let obj_ref = elements[*position].clone(); - *position += 1; - Ok(obj_ref) - } else { - Err(new_stop_iteration(vm)) - } + if let Some(range) = iterated_obj_ref.payload::() { + if let Some(int) = range.get(position.get()) { + position.set(position.get() + 1); + Ok(vm.ctx.new_int(int)) + } else { + Err(new_stop_iteration(vm)) } - - PyObjectPayload::Range { ref range } => { - if let Some(int) = range.get(*position) { - *position += 1; - Ok(vm.ctx.new_int(int)) - } else { - Err(new_stop_iteration(vm)) - } + } else if let Some(bytes) = iterated_obj_ref.payload::() { + if position.get() < bytes.len() { + let obj_ref = vm.ctx.new_int(bytes[position.get()]); + position.set(position.get() + 1); + Ok(obj_ref) + } else { + Err(new_stop_iteration(vm)) } - - PyObjectPayload::Bytes { ref value } => { - if *position < value.len() { - let obj_ref = vm.ctx.new_int(value[*position]); - *position += 1; - Ok(obj_ref) - } else { - Err(new_stop_iteration(vm)) - } + } else if let Some(bytes) = iterated_obj_ref.payload::() { + if position.get() < bytes.value.borrow().len() { + let obj_ref = vm.ctx.new_int(bytes.value.borrow()[position.get()]); + position.set(position.get() + 1); + Ok(obj_ref) + } else { + Err(new_stop_iteration(vm)) } - - _ => { - panic!("NOT IMPL"); + } else { + let elements = objsequence::get_elements(iterated_obj_ref); + if position.get() < elements.len() { + let obj_ref = elements[position.get()].clone(); + position.set(position.get() + 1); + Ok(obj_ref) + } else { + Err(new_stop_iteration(vm)) } } } else { diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 53f0d74981..1b3a9b4fa9 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -1,474 +1,418 @@ +use std::cell::{Cell, RefCell}; +use std::fmt; + +use num_traits::ToPrimitive; + +use crate::function::{OptionalArg, PyFuncArgs}; +use crate::pyobject::{ + IdProtocol, PyContext, PyIteratorValue, PyObject, PyObjectRef, PyRef, PyResult, PyValue, + TypeProtocol, +}; +use crate::vm::{ReprGuard, VirtualMachine}; + use super::objbool; use super::objint; use super::objsequence::{ - get_elements, get_item, get_mut_elements, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, + get_elements, get_elements_cell, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, PySliceableSequence, }; -use super::objstr; use super::objtype; -use crate::pyobject::{ - IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, - TypeProtocol, -}; -use crate::vm::{ReprGuard, VirtualMachine}; -use num_traits::ToPrimitive; -// set_item: -fn set_item( - vm: &mut VirtualMachine, - l: &mut Vec, - idx: PyObjectRef, - obj: PyObjectRef, -) -> PyResult { - if objtype::isinstance(&idx, &vm.ctx.int_type()) { - let value = objint::get_value(&idx).to_i32().unwrap(); - if let Some(pos_index) = l.get_pos(value) { - l[pos_index] = obj; - Ok(vm.get_none()) - } else { - Err(vm.new_index_error("list index out of range".to_string())) - } - } else { - panic!( - "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", - l, idx - ) - } +#[derive(Default)] +pub struct PyList { + // TODO: shouldn't be public + pub elements: RefCell>, } -fn list_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None)], - optional = [(iterable, None)] - ); - - if !objtype::issubclass(cls, &vm.ctx.list_type()) { - return Err(vm.new_type_error(format!("{:?} is not a subtype of list", cls))); +impl fmt::Debug for PyList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: implement more detailed, non-recursive Debug formatter + f.write_str("list") } - - let elements = if let Some(iterable) = iterable { - vm.extract_elements(iterable)? - } else { - vec![] - }; - - Ok(PyObject::new( - PyObjectPayload::Sequence { elements }, - cls.clone(), - )) } -fn list_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.list_type())), (other, None)] - ); - - if zelf.is(&other) { - return Ok(vm.ctx.new_bool(true)); +impl From> for PyList { + fn from(elements: Vec) -> Self { + PyList { + elements: RefCell::new(elements), + } } - - let result = if objtype::isinstance(other, &vm.ctx.list_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_equal(vm, &zelf, &other)? - } else { - false - }; - Ok(vm.ctx.new_bool(result)) } -fn list_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.list_type())), (other, None)] - ); - - let result = if objtype::isinstance(other, &vm.ctx.list_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_lt(vm, &zelf, &other)? - } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '<'", - zelf.borrow(), - other.borrow() - ))); - }; - - Ok(vm.ctx.new_bool(result)) +impl PyValue for PyList { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.list_type() + } } -fn list_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.list_type())), (other, None)] - ); - - let result = if objtype::isinstance(other, &vm.ctx.list_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_gt(vm, &zelf, &other)? - } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '>'", - zelf.borrow(), - other.borrow() - ))); - }; +pub type PyListRef = PyRef; - Ok(vm.ctx.new_bool(result)) -} +impl PyListRef { + pub fn append(self, x: PyObjectRef, _vm: &VirtualMachine) { + self.elements.borrow_mut().push(x); + } -fn list_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.list_type())), (other, None)] - ); - - let result = if objtype::isinstance(other, &vm.ctx.list_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_ge(vm, &zelf, &other)? - } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '>='", - zelf.borrow(), - other.borrow() - ))); - }; + fn extend(self, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let mut new_elements = vm.extract_elements(&x)?; + self.elements.borrow_mut().append(&mut new_elements); + Ok(()) + } - Ok(vm.ctx.new_bool(result)) -} + fn insert(self, position: isize, element: PyObjectRef, _vm: &VirtualMachine) { + let mut vec = self.elements.borrow_mut(); + let vec_len = vec.len().to_isize().unwrap(); + // This unbounded position can be < 0 or > vec.len() + let unbounded_position = if position < 0 { + vec_len + position + } else { + position + }; + // Bound it by [0, vec.len()] + let position = unbounded_position.max(0).min(vec_len).to_usize().unwrap(); + vec.insert(position, element.clone()); + } -fn list_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.list_type())), (other, None)] - ); - - let result = if objtype::isinstance(other, &vm.ctx.list_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_le(vm, &zelf, &other)? - } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '<='", - zelf.borrow(), - other.borrow() - ))); - }; + fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.list_type()) { + let e1 = self.elements.borrow(); + let e2 = get_elements(&other); + let elements = e1.iter().chain(e2.iter()).cloned().collect(); + Ok(vm.ctx.new_list(elements)) + } else { + Err(vm.new_type_error(format!("Cannot add {} and {}", self.as_object(), other))) + } + } - Ok(vm.ctx.new_bool(result)) -} + fn iadd(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.list_type()) { + self.elements + .borrow_mut() + .extend_from_slice(&get_elements(&other)); + Ok(self.into_object()) + } else { + Ok(vm.ctx.not_implemented()) + } + } -fn list_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(o, Some(vm.ctx.list_type())), (o2, None)] - ); - - if objtype::isinstance(o2, &vm.ctx.list_type()) { - let e1 = get_elements(o); - let e2 = get_elements(o2); - let elements = e1.iter().chain(e2.iter()).cloned().collect(); - Ok(vm.ctx.new_list(elements)) - } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", o.borrow(), o2.borrow()))) + fn clear(self, _vm: &VirtualMachine) { + self.elements.borrow_mut().clear(); } -} -fn list_iadd(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.list_type())), (other, None)] - ); + fn copy(self, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.new_list(self.elements.borrow().clone()) + } - if objtype::isinstance(other, &vm.ctx.list_type()) { - get_mut_elements(zelf).extend_from_slice(&get_elements(other)); - Ok(zelf.clone()) - } else { - Ok(vm.ctx.not_implemented()) + fn len(self, _vm: &VirtualMachine) -> usize { + self.elements.borrow().len() } -} -fn list_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(o, Some(vm.ctx.list_type()))]); + fn reverse(self, _vm: &VirtualMachine) { + self.elements.borrow_mut().reverse(); + } - let s = if let Some(_guard) = ReprGuard::enter(o) { - let elements = get_elements(o); - let mut str_parts = vec![]; - for elem in elements.iter() { - let s = vm.to_repr(elem)?; - str_parts.push(objstr::get_value(&s)); + fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + get_item( + vm, + self.as_object(), + &self.elements.borrow(), + needle.clone(), + ) + } + + fn iter(self, _vm: &VirtualMachine) -> PyIteratorValue { + PyIteratorValue { + position: Cell::new(0), + iterated_obj: self.into_object(), } - format!("[{}]", str_parts.join(", ")) - } else { - "[...]".to_string() - }; + } - Ok(vm.new_str(s)) -} + fn setitem(self, key: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let mut elements = self.elements.borrow_mut(); -pub fn list_append(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("list.append called with: {:?}", args); - arg_check!( - vm, - args, - required = [(list, Some(vm.ctx.list_type())), (x, None)] - ); - let mut elements = get_mut_elements(list); - elements.push(x.clone()); - Ok(vm.get_none()) -} + if objtype::isinstance(&key, &vm.ctx.int_type()) { + let idx = objint::get_value(&key).to_i32().unwrap(); + if let Some(pos_index) = elements.get_pos(idx) { + elements[pos_index] = value; + Ok(vm.get_none()) + } else { + Err(vm.new_index_error("list index out of range".to_string())) + } + } else { + panic!( + "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", + elements, key + ) + } + } -fn list_clear(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("list.clear called with: {:?}", args); - arg_check!(vm, args, required = [(list, Some(vm.ctx.list_type()))]); - let mut elements = get_mut_elements(list); - elements.clear(); - Ok(vm.get_none()) -} + fn repr(self, vm: &VirtualMachine) -> PyResult { + let s = if let Some(_guard) = ReprGuard::enter(self.as_object()) { + let mut str_parts = vec![]; + for elem in self.elements.borrow().iter() { + let s = vm.to_repr(elem)?; + str_parts.push(s.value.clone()); + } + format!("[{}]", str_parts.join(", ")) + } else { + "[...]".to_string() + }; + Ok(s) + } -fn list_copy(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.list_type()))]); - let elements = get_elements(zelf); - Ok(vm.ctx.new_list(elements.clone())) -} + fn mul(self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { + let new_elements = seq_mul(&self.elements.borrow(), counter); + vm.ctx.new_list(new_elements) + } -fn list_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.list_type())), (value, None)] - ); - let elements = get_elements(zelf); - let mut count: usize = 0; - for element in elements.iter() { - if value.is(&element) { - count += 1; - } else { - let is_eq = vm._eq(element.clone(), value.clone())?; - if objbool::boolval(vm, is_eq)? { + fn count(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let mut count: usize = 0; + for element in self.elements.borrow().iter() { + if needle.is(element) { count += 1; + } else { + let py_equal = vm._eq(element.clone(), needle.clone())?; + if objbool::boolval(vm, py_equal)? { + count += 1; + } } } + Ok(count) } - Ok(vm.context().new_int(count)) -} -pub fn list_extend(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(list, Some(vm.ctx.list_type())), (x, None)] - ); - let mut new_elements = vm.extract_elements(x)?; - let mut elements = get_mut_elements(list); - elements.append(&mut new_elements); - Ok(vm.get_none()) -} + fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + for element in self.elements.borrow().iter() { + if needle.is(element) { + return Ok(true); + } + let py_equal = vm._eq(element.clone(), needle.clone())?; + if objbool::boolval(vm, py_equal)? { + return Ok(true); + } + } + + Ok(false) + } + + fn index(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + for (index, element) in self.elements.borrow().iter().enumerate() { + if needle.is(element) { + return Ok(index); + } + let py_equal = vm._eq(needle.clone(), element.clone())?; + if objbool::boolval(vm, py_equal)? { + return Ok(index); + } + } + let needle_str = &vm.to_str(&needle)?.value; + Err(vm.new_value_error(format!("'{}' is not in list", needle_str))) + } -fn list_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("list.index called with: {:?}", args); - arg_check!( - vm, - args, - required = [(list, Some(vm.ctx.list_type())), (needle, None)] - ); - for (index, element) in get_elements(list).iter().enumerate() { - if needle.is(&element) { - return Ok(vm.context().new_int(index)); + fn pop(self, i: OptionalArg, vm: &VirtualMachine) -> PyResult { + let mut i = i.into_option().unwrap_or(-1); + let mut elements = self.elements.borrow_mut(); + if i < 0 { + i += elements.len() as isize; } - let py_equal = vm._eq(needle.clone(), element.clone())?; - if objbool::get_value(&py_equal) { - return Ok(vm.context().new_int(index)); + if elements.is_empty() { + Err(vm.new_index_error("pop from empty list".to_string())) + } else if i < 0 || i as usize >= elements.len() { + Err(vm.new_index_error("pop index out of range".to_string())) + } else { + Ok(elements.remove(i as usize)) } } - let needle_str = objstr::get_value(&vm.to_str(needle).unwrap()); - Err(vm.new_value_error(format!("'{}' is not in list", needle_str))) -} -fn list_insert(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("list.insert called with: {:?}", args); - arg_check!( - vm, - args, - required = [ - (list, Some(vm.ctx.list_type())), - (insert_position, Some(vm.ctx.int_type())), - (element, None) - ] - ); - let int_position = match objint::get_value(insert_position).to_isize() { - Some(i) => i, - None => { - return Err( - vm.new_overflow_error("Python int too large to convert to Rust isize".to_string()) - ); + fn remove(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let mut ri: Option = None; + for (index, element) in self.elements.borrow().iter().enumerate() { + if needle.is(element) { + ri = Some(index); + break; + } + let py_equal = vm._eq(needle.clone(), element.clone())?; + if objbool::get_value(&py_equal) { + ri = Some(index); + break; + } } - }; - let mut vec = get_mut_elements(list); - let vec_len = vec.len().to_isize().unwrap(); - // This unbounded position can be < 0 or > vec.len() - let unbounded_position = if int_position < 0 { - vec_len + int_position - } else { - int_position - }; - // Bound it by [0, vec.len()] - let position = unbounded_position.max(0).min(vec_len).to_usize().unwrap(); - vec.insert(position, element.clone()); - Ok(vm.get_none()) -} -fn list_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("list.len called with: {:?}", args); - arg_check!(vm, args, required = [(list, Some(vm.ctx.list_type()))]); - let elements = get_elements(list); - Ok(vm.context().new_int(elements.len())) -} + if let Some(index) = ri { + self.elements.borrow_mut().remove(index); + Ok(()) + } else { + let needle_str = &vm.to_str(&needle)?.value; + Err(vm.new_value_error(format!("'{}' is not in list", needle_str))) + } + } -fn list_reverse(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("list.reverse called with: {:?}", args); - arg_check!(vm, args, required = [(list, Some(vm.ctx.list_type()))]); - let mut elements = get_mut_elements(list); - elements.reverse(); - Ok(vm.get_none()) -} + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if self.as_object().is(&other) { + return Ok(vm.new_bool(true)); + } -fn list_sort(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(list, Some(vm.ctx.list_type()))]); - let mut _elements = get_mut_elements(list); - unimplemented!("TODO: figure out how to invoke `sort_by` on a Vec"); - // elements.sort_by(); - // Ok(vm.get_none()) -} + if objtype::isinstance(&other, &vm.ctx.list_type()) { + let zelf = self.elements.borrow(); + let other = get_elements(&other); + let res = seq_equal(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) + } + } -fn list_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("list.contains called with: {:?}", args); - arg_check!( - vm, - args, - required = [(list, Some(vm.ctx.list_type())), (needle, None)] - ); - for element in get_elements(list).iter() { - if needle.is(&element) { - return Ok(vm.new_bool(true)); + fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.list_type()) { + let zelf = self.elements.borrow(); + let other = get_elements(&other); + let res = seq_lt(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) } - match vm._eq(needle.clone(), element.clone()) { - Ok(value) => { - if objbool::get_value(&value) { - return Ok(vm.new_bool(true)); - } - } - Err(_) => return Err(vm.new_type_error("".to_string())), + } + + fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.list_type()) { + let zelf = self.elements.borrow(); + let other = get_elements(&other); + let res = seq_gt(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) } } - Ok(vm.new_bool(false)) -} + fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.list_type()) { + let zelf = self.elements.borrow(); + let other = get_elements(&other); + let res = seq_ge(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) + } + } -fn list_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("list.getitem called with: {:?}", args); - arg_check!( - vm, - args, - required = [(list, Some(vm.ctx.list_type())), (needle, None)] - ); - get_item(vm, list, &get_elements(list), needle.clone()) + fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.list_type()) { + let zelf = self.elements.borrow(); + let other = get_elements(&other); + let res = seq_le(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) + } + } } -fn list_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(list, Some(vm.ctx.list_type()))]); +fn list_new( + cls: PyRef, + iterable: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + if !objtype::issubclass(cls.as_object(), &vm.ctx.list_type()) { + return Err(vm.new_type_error(format!("{} is not a subtype of list", cls))); + } - let iter_obj = PyObject::new( - PyObjectPayload::Iterator { - position: 0, - iterated_obj: list.clone(), - }, - vm.ctx.iter_type(), - ); + let elements = if let OptionalArg::Present(iterable) = iterable { + vm.extract_elements(&iterable)? + } else { + vec![] + }; - // We are all good here: - Ok(iter_obj) + Ok(PyObject::new(PyList::from(elements), cls.into_object())) } -fn list_setitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(list, Some(vm.ctx.list_type())), (key, None), (value, None)] - ); - let mut elements = get_mut_elements(list); - set_item(vm, &mut elements, key.clone(), value.clone()) +fn quicksort( + vm: &VirtualMachine, + keys: &mut [PyObjectRef], + values: &mut [PyObjectRef], +) -> PyResult<()> { + let len = values.len(); + if len >= 2 { + let pivot = partition(vm, keys, values)?; + quicksort(vm, &mut keys[0..pivot], &mut values[0..pivot])?; + quicksort(vm, &mut keys[pivot + 1..len], &mut values[pivot + 1..len])?; + } + Ok(()) } -fn list_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (list, Some(vm.ctx.list_type())), - (product, Some(vm.ctx.int_type())) - ] - ); - - let new_elements = seq_mul(&get_elements(list), product); +fn partition( + vm: &VirtualMachine, + keys: &mut [PyObjectRef], + values: &mut [PyObjectRef], +) -> PyResult { + let len = values.len(); + let pivot = len / 2; + + values.swap(pivot, len - 1); + keys.swap(pivot, len - 1); + + let mut store_idx = 0; + for i in 0..len - 1 { + let result = vm._lt(keys[i].clone(), keys[len - 1].clone())?; + let boolval = objbool::boolval(vm, result)?; + if boolval { + values.swap(i, store_idx); + keys.swap(i, store_idx); + store_idx += 1; + } + } - Ok(vm.ctx.new_list(new_elements)) + values.swap(store_idx, len - 1); + keys.swap(store_idx, len - 1); + Ok(store_idx) } -fn list_pop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.list_type()))]); +fn do_sort( + vm: &VirtualMachine, + values: &mut Vec, + key_func: Option, + reverse: bool, +) -> PyResult<()> { + // build a list of keys. If no keyfunc is provided, it's a copy of the list. + let mut keys: Vec = vec![]; + for x in values.iter() { + keys.push(match &key_func { + None => x.clone(), + Some(ref func) => vm.invoke((*func).clone(), vec![x.clone()])?, + }); + } - let mut elements = get_mut_elements(zelf); - if let Some(result) = elements.pop() { - Ok(result) - } else { - Err(vm.new_index_error("pop from empty list".to_string())) + quicksort(vm, &mut keys, values)?; + + if reverse { + values.reverse(); } + + Ok(()) } -fn list_remove(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(list, Some(vm.ctx.list_type())), (needle, None)] - ); - - let mut ri: Option = None; - for (index, element) in get_elements(list).iter().enumerate() { - if needle.is(&element) { - ri = Some(index); - break; - } - let py_equal = vm._eq(needle.clone(), element.clone())?; - if objbool::get_value(&py_equal) { - ri = Some(index); - break; - } - } +fn list_sort(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(list, Some(vm.ctx.list_type()))]); + let key_func = args.get_optional_kwarg("key"); + let reverse = args.get_optional_kwarg("reverse"); + let reverse = match reverse { + None => false, + Some(val) => objbool::boolval(vm, val)?, + }; - if let Some(index) = ri { - let mut elements = get_mut_elements(list); - elements.remove(index); - Ok(vm.get_none()) - } else { - let needle_str = objstr::get_value(&vm.to_str(needle)?); - Err(vm.new_value_error(format!("'{}' is not in list", needle_str))) + let elements_cell = get_elements_cell(list); + // replace list contents with [] for duration of sort. + // this prevents keyfunc from messing with the list and makes it easy to + // check if it tries to append elements to it. + let mut elements = elements_cell.replace(vec![]); + do_sort(vm, &mut elements, key_func, reverse)?; + let temp_elements = elements_cell.replace(elements); + + if !temp_elements.is_empty() { + return Err(vm.new_value_error("list modified during sort".to_string())); } + + Ok(vm.get_none()) } +#[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { let list_type = &context.list_type; @@ -476,43 +420,31 @@ pub fn init(context: &PyContext) { If no argument is given, the constructor creates a new empty list.\n\ The argument must be an iterable if specified."; - context.set_attr(&list_type, "__add__", context.new_rustfunc(list_add)); - context.set_attr(&list_type, "__iadd__", context.new_rustfunc(list_iadd)); - context.set_attr( - &list_type, - "__contains__", - context.new_rustfunc(list_contains), - ); - context.set_attr(&list_type, "__eq__", context.new_rustfunc(list_eq)); - context.set_attr(&list_type, "__lt__", context.new_rustfunc(list_lt)); - context.set_attr(&list_type, "__gt__", context.new_rustfunc(list_gt)); - context.set_attr(&list_type, "__le__", context.new_rustfunc(list_le)); - context.set_attr(&list_type, "__ge__", context.new_rustfunc(list_ge)); - context.set_attr( - &list_type, - "__getitem__", - context.new_rustfunc(list_getitem), - ); - context.set_attr(&list_type, "__iter__", context.new_rustfunc(list_iter)); - context.set_attr( - &list_type, - "__setitem__", - context.new_rustfunc(list_setitem), - ); - context.set_attr(&list_type, "__mul__", context.new_rustfunc(list_mul)); - context.set_attr(&list_type, "__len__", context.new_rustfunc(list_len)); + context.set_attr(&list_type, "__add__", context.new_rustfunc(PyListRef::add)); + context.set_attr(&list_type, "__iadd__", context.new_rustfunc(PyListRef::iadd)); + context.set_attr(&list_type, "__contains__", context.new_rustfunc(PyListRef::contains)); + context.set_attr(&list_type, "__eq__", context.new_rustfunc(PyListRef::eq)); + context.set_attr(&list_type, "__lt__", context.new_rustfunc(PyListRef::lt)); + context.set_attr(&list_type, "__gt__", context.new_rustfunc(PyListRef::gt)); + context.set_attr(&list_type, "__le__", context.new_rustfunc(PyListRef::le)); + context.set_attr(&list_type, "__ge__", context.new_rustfunc(PyListRef::ge)); + context.set_attr(&list_type, "__getitem__", context.new_rustfunc(PyListRef::getitem)); + context.set_attr(&list_type, "__iter__", context.new_rustfunc(PyListRef::iter)); + context.set_attr(&list_type, "__setitem__", context.new_rustfunc(PyListRef::setitem)); + context.set_attr(&list_type, "__mul__", context.new_rustfunc(PyListRef::mul)); + context.set_attr(&list_type, "__len__", context.new_rustfunc(PyListRef::len)); context.set_attr(&list_type, "__new__", context.new_rustfunc(list_new)); - context.set_attr(&list_type, "__repr__", context.new_rustfunc(list_repr)); + context.set_attr(&list_type, "__repr__", context.new_rustfunc(PyListRef::repr)); context.set_attr(&list_type, "__doc__", context.new_str(list_doc.to_string())); - context.set_attr(&list_type, "append", context.new_rustfunc(list_append)); - context.set_attr(&list_type, "clear", context.new_rustfunc(list_clear)); - context.set_attr(&list_type, "copy", context.new_rustfunc(list_copy)); - context.set_attr(&list_type, "count", context.new_rustfunc(list_count)); - context.set_attr(&list_type, "extend", context.new_rustfunc(list_extend)); - context.set_attr(&list_type, "index", context.new_rustfunc(list_index)); - context.set_attr(&list_type, "insert", context.new_rustfunc(list_insert)); - context.set_attr(&list_type, "reverse", context.new_rustfunc(list_reverse)); + context.set_attr(&list_type, "append", context.new_rustfunc(PyListRef::append)); + context.set_attr(&list_type, "clear", context.new_rustfunc(PyListRef::clear)); + context.set_attr(&list_type, "copy", context.new_rustfunc(PyListRef::copy)); + context.set_attr(&list_type, "count", context.new_rustfunc(PyListRef::count)); + context.set_attr(&list_type, "extend", context.new_rustfunc(PyListRef::extend)); + context.set_attr(&list_type, "index", context.new_rustfunc(PyListRef::index)); + context.set_attr(&list_type, "insert", context.new_rustfunc(PyListRef::insert)); + context.set_attr(&list_type, "reverse", context.new_rustfunc(PyListRef::reverse)); context.set_attr(&list_type, "sort", context.new_rustfunc(list_sort)); - context.set_attr(&list_type, "pop", context.new_rustfunc(list_pop)); - context.set_attr(&list_type, "remove", context.new_rustfunc(list_remove)); + context.set_attr(&list_type, "pop", context.new_rustfunc(PyListRef::pop)); + context.set_attr(&list_type, "remove", context.new_rustfunc(PyListRef::remove)); } diff --git a/vm/src/obj/objmap.rs b/vm/src/obj/objmap.rs index 9ba88955b4..74aabe1072 100644 --- a/vm/src/obj/objmap.rs +++ b/vm/src/obj/objmap.rs @@ -1,36 +1,47 @@ +use crate::function::{Args, PyFuncArgs}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::vm::VirtualMachine; + use super::objiter; -use crate::pyobject::{PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyResult, TypeProtocol}; -use crate::vm::VirtualMachine; // Required for arg_check! to use isinstance +use super::objtype::PyClassRef; -fn map_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - no_kwargs!(vm, args); - let cls = &args.args[0]; - if args.args.len() < 3 { - Err(vm.new_type_error("map() must have at least two arguments.".to_owned())) - } else { - let function = &args.args[1]; - let iterables = &args.args[2..]; - let iterators = iterables - .iter() - .map(|iterable| objiter::get_iter(vm, iterable)) - .collect::, _>>()?; - Ok(PyObject::new( - PyObjectPayload::MapIterator { - mapper: function.clone(), - iterators, - }, - cls.clone(), - )) +#[derive(Debug)] +pub struct PyMap { + mapper: PyObjectRef, + iterators: Vec, +} +type PyMapRef = PyRef; + +impl PyValue for PyMap { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.map_type() + } +} + +fn map_new( + cls: PyClassRef, + function: PyObjectRef, + iterables: Args, + vm: &VirtualMachine, +) -> PyResult { + let iterators = iterables + .into_iter() + .map(|iterable| objiter::get_iter(vm, &iterable)) + .collect::, _>>()?; + PyMap { + mapper: function.clone(), + iterators, } + .into_ref_with_type(vm, cls.clone()) } -fn map_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn map_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(map, Some(vm.ctx.map_type()))]); - if let PyObjectPayload::MapIterator { - ref mut mapper, - ref mut iterators, - } = map.borrow_mut().payload + if let Some(PyMap { + ref mapper, + ref iterators, + }) = map.payload() { let next_objs = iterators .iter() @@ -38,13 +49,7 @@ fn map_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { .collect::, _>>()?; // the mapper itself can raise StopIteration which does stop the map iteration - vm.invoke( - mapper.clone(), - PyFuncArgs { - args: next_objs, - kwargs: vec![], - }, - ) + vm.invoke(mapper.clone(), next_objs) } else { panic!("map doesn't have correct payload"); } diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index 8119f9fee9..aa67985514 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -1,11 +1,23 @@ -use crate::pyobject::{PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyResult, TypeProtocol}; +use crate::function::PyFuncArgs; +use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; -pub fn new_memory_view(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +#[derive(Debug)] +pub struct PyMemoryView { + obj: PyObjectRef, +} + +impl PyValue for PyMemoryView { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.memoryview_type() + } +} + +pub fn new_memory_view(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(cls, None), (bytes_object, None)]); vm.ctx.set_attr(&cls, "obj", bytes_object.clone()); Ok(PyObject::new( - PyObjectPayload::MemoryView { + PyMemoryView { obj: bytes_object.clone(), }, cls.clone(), diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs new file mode 100644 index 0000000000..fe6e3fca1d --- /dev/null +++ b/vm/src/obj/objmodule.rs @@ -0,0 +1,39 @@ +use crate::obj::objstr::PyStringRef; +use crate::pyobject::{DictProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::vm::VirtualMachine; + +#[derive(Clone, Debug)] +pub struct PyModule { + pub name: String, + pub dict: PyObjectRef, +} +pub type PyModuleRef = PyRef; + +impl PyValue for PyModule { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.module_type() + } +} + +impl PyModuleRef { + fn dir(self: PyModuleRef, vm: &VirtualMachine) -> PyResult { + let keys = self + .dict + .get_key_value_pairs() + .iter() + .map(|(k, _v)| k.clone()) + .collect(); + Ok(vm.ctx.new_list(keys)) + } + + fn set_attr(self, attr: PyStringRef, value: PyObjectRef, vm: &VirtualMachine) { + self.dict.set_item(&vm.ctx, &attr.value, value) + } +} + +pub fn init(context: &PyContext) { + extend_class!(&context, &context.module_type, { + "__dir__" => context.new_rustfunc(PyModuleRef::dir), + "__setattr__" => context.new_rustfunc(PyModuleRef::set_attr) + }); +} diff --git a/vm/src/obj/objnone.rs b/vm/src/obj/objnone.rs index 2aceea9151..6bee900312 100644 --- a/vm/src/obj/objnone.rs +++ b/vm/src/obj/objnone.rs @@ -1,13 +1,101 @@ -use crate::pyobject::{PyContext, PyFuncArgs, PyResult, TypeProtocol}; +use crate::function::PyFuncArgs; +use crate::obj::objproperty::PyPropertyRef; +use crate::obj::objstr::PyStringRef; +use crate::pyobject::{ + AttributeProtocol, IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, TypeProtocol, +}; use crate::vm::VirtualMachine; -pub fn init(context: &PyContext) { - let none_type = &context.none.typ(); - context.set_attr(&none_type, "__new__", context.new_rustfunc(none_new)); - context.set_attr(&none_type, "__repr__", context.new_rustfunc(none_repr)); +#[derive(Clone, Debug)] +pub struct PyNone; +pub type PyNoneRef = PyRef; + +impl PyValue for PyNone { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.none().typ() + } +} + +// This allows a built-in function to not return a value, mapping to +// Python's behavior of returning `None` in this situation. +impl IntoPyObject for () { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.none()) + } +} + +impl IntoPyObject for Option { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + match self { + Some(x) => x.into_pyobject(vm), + None => Ok(vm.ctx.none()), + } + } +} + +impl PyNoneRef { + fn repr(self, _vm: &VirtualMachine) -> PyResult { + Ok("None".to_string()) + } + + fn bool(self, _vm: &VirtualMachine) -> PyResult { + Ok(false) + } + + fn get_attribute(self, name: PyStringRef, vm: &VirtualMachine) -> PyResult { + trace!("None.__getattribute__({:?}, {:?})", self, name); + let cls = self.typ().into_object(); + + // Properties use a comparision with None to determine if they are either invoked by am + // instance binding or a class binding. But if the object itself is None then this detection + // won't work. Instead we call a special function on property that bypasses this check, as + // we are invoking it as a instance binding. + // + // In CPython they instead call the slot tp_descr_get with NULL to indicates it's an + // instance binding. + // https://github.com/python/cpython/blob/master/Objects/typeobject.c#L3281 + fn call_descriptor( + descriptor: PyObjectRef, + get_func: PyObjectRef, + obj: PyObjectRef, + cls: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + if let Ok(property) = PyPropertyRef::try_from_object(vm, descriptor.clone()) { + property.instance_binding_get(obj, vm) + } else { + vm.invoke(get_func, vec![descriptor, obj, cls]) + } + } + + if let Some(attr) = cls.get_attr(&name.value) { + let attr_class = attr.typ(); + if attr_class.has_attr("__set__") { + if let Some(get_func) = attr_class.get_attr("__get__") { + return call_descriptor(attr, get_func, self.into_object(), cls.clone(), vm); + } + } + } + + if let Some(obj_attr) = self.as_object().get_attr(&name.value) { + Ok(obj_attr) + } else if let Some(attr) = cls.get_attr(&name.value) { + let attr_class = attr.typ(); + if let Some(get_func) = attr_class.get_attr("__get__") { + call_descriptor(attr, get_func, self.into_object(), cls.clone(), vm) + } else { + Ok(attr) + } + } else if let Some(getter) = cls.get_attr("__getattr__") { + vm.invoke(getter, vec![self.into_object(), name.into_object()]) + } else { + Err(vm.new_attribute_error(format!("{} has no attribute '{}'", self.as_object(), name))) + } + } } -fn none_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn none_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -16,7 +104,11 @@ fn none_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -fn none_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(_zelf, Some(vm.ctx.none().typ()))]); - Ok(vm.ctx.new_str("None".to_string())) +pub fn init(context: &PyContext) { + extend_class!(context, &context.none.typ(), { + "__new__" => context.new_rustfunc(none_new), + "__repr__" => context.new_rustfunc(PyNoneRef::repr), + "__bool__" => context.new_rustfunc(PyNoneRef::bool), + "__getattribute__" => context.new_rustfunc(PyNoneRef::get_attribute) + }); } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 62f31b8c21..86c5469022 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -1,30 +1,36 @@ -use super::objstr; +use super::objlist::PyList; +use super::objstr::{self, PyStringRef}; use super::objtype; +use crate::function::PyFuncArgs; +use crate::obj::objproperty::PropertyBuilder; use crate::pyobject::{ - AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, - TypeProtocol, + AttributeProtocol, DictProtocol, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, + PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; -use std::cell::RefCell; -use std::collections::HashMap; -pub fn new_instance(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { - // more or less __new__ operator - let type_ref = args.shift(); - let obj = vm.ctx.new_instance(type_ref.clone(), None); - Ok(obj) +#[derive(Clone, Debug)] +pub struct PyInstance; + +impl PyValue for PyInstance { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.object() + } } -pub fn create_object(type_type: PyObjectRef, object_type: PyObjectRef, _dict_type: PyObjectRef) { - (*object_type.borrow_mut()).payload = PyObjectPayload::Class { - name: String::from("object"), - dict: RefCell::new(HashMap::new()), - mro: vec![], - }; - (*object_type.borrow_mut()).typ = Some(type_type.clone()); +pub type PyInstanceRef = PyRef; + +pub fn new_instance(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { + // more or less __new__ operator + let cls = args.shift(); + Ok(if cls.is(&vm.ctx.object) { + PyObject::new_without_dict(PyInstance, cls) + } else { + PyObject::new(PyInstance, cls) + }) } -fn object_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -33,7 +39,7 @@ fn object_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.not_implemented()) } -fn object_ne(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_ne(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -43,7 +49,7 @@ fn object_ne(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.not_implemented()) } -fn object_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_lt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -53,7 +59,7 @@ fn object_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.not_implemented()) } -fn object_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_le(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -63,7 +69,7 @@ fn object_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.not_implemented()) } -fn object_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_gt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -73,7 +79,7 @@ fn object_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.not_implemented()) } -fn object_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_ge(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -83,57 +89,95 @@ fn object_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.not_implemented()) } -fn object_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(_zelf, Some(vm.ctx.object()))]); // For now default to non hashable Err(vm.new_type_error("unhashable type".to_string())) } -// TODO: is object the right place for delattr? -fn object_delattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (zelf, Some(vm.ctx.object())), - (attr, Some(vm.ctx.str_type())) - ] - ); +fn object_setattr( + obj: PyInstanceRef, + attr_name: PyStringRef, + value: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult<()> { + trace!("object.__setattr__({:?}, {}, {:?})", obj, attr_name, value); + let cls = obj.as_object().typ(); + + if let Some(attr) = cls.get_attr(&attr_name.value) { + let attr_class = attr.typ(); + if let Some(descriptor) = attr_class.get_attr("__set__") { + return vm + .invoke(descriptor, vec![attr, obj.into_object(), value]) + .map(|_| ()); + } + } + + if let Some(ref dict) = obj.as_object().dict { + dict.borrow_mut().insert(attr_name.value.clone(), value); + Ok(()) + } else { + let type_name = objtype::get_type_name(&obj.as_object().typ()); + Err(vm.new_attribute_error(format!( + "'{}' object has no attribute '{}'", + type_name, &attr_name.value + ))) + } +} - match zelf.borrow().payload { - PyObjectPayload::Class { ref dict, .. } | PyObjectPayload::Instance { ref dict, .. } => { - let attr_name = objstr::get_value(attr); - dict.borrow_mut().remove(&attr_name); - Ok(vm.get_none()) +fn object_delattr(obj: PyInstanceRef, attr_name: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + let cls = obj.as_object().typ(); + + if let Some(attr) = cls.get_attr(&attr_name.value) { + let attr_class = attr.typ(); + if let Some(descriptor) = attr_class.get_attr("__delete__") { + return vm + .invoke(descriptor, vec![attr, obj.into_object()]) + .map(|_| ()); } - _ => Err(vm.new_type_error("TypeError: no dictionary.".to_string())), + } + + if let Some(ref dict) = obj.as_object().dict { + dict.borrow_mut().remove(&attr_name.value); + Ok(()) + } else { + let type_name = objtype::get_type_name(&obj.as_object().typ()); + Err(vm.new_attribute_error(format!( + "'{}' object has no attribute '{}'", + type_name, &attr_name.value + ))) } } -fn object_str(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.object()))]); vm.call_method(zelf, "__repr__", vec![]) } -fn object_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.object()))]); let type_name = objtype::get_type_name(&obj.typ()); let address = obj.get_id(); Ok(vm.new_str(format!("<{} object at 0x{:x}>", type_name, address))) } -fn object_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (obj, Some(vm.ctx.object())), - (format_spec, Some(vm.ctx.str_type())) - ] - ); - if objstr::get_value(format_spec).is_empty() { - vm.to_str(obj) +pub fn object_dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyList { + let attributes = get_attributes(&obj); + let attributes: Vec = attributes + .keys() + .map(|k| vm.ctx.new_str(k.to_string())) + .collect(); + PyList::from(attributes) +} + +fn object_format( + obj: PyObjectRef, + format_spec: PyStringRef, + vm: &VirtualMachine, +) -> PyResult { + if format_spec.value.is_empty() { + vm.to_str(&obj) } else { Err(vm.new_type_error("unsupported format string passed to object.__format__".to_string())) } @@ -145,18 +189,24 @@ pub fn init(context: &PyContext) { context.set_attr(&object, "__new__", context.new_rustfunc(new_instance)); context.set_attr(&object, "__init__", context.new_rustfunc(object_init)); + context.set_attr( + &object, + "__class__", + PropertyBuilder::new(context) + .add_getter(object_class) + .add_setter(object_class_setter) + .create(), + ); context.set_attr(&object, "__eq__", context.new_rustfunc(object_eq)); context.set_attr(&object, "__ne__", context.new_rustfunc(object_ne)); context.set_attr(&object, "__lt__", context.new_rustfunc(object_lt)); context.set_attr(&object, "__le__", context.new_rustfunc(object_le)); context.set_attr(&object, "__gt__", context.new_rustfunc(object_gt)); context.set_attr(&object, "__ge__", context.new_rustfunc(object_ge)); + context.set_attr(&object, "__setattr__", context.new_rustfunc(object_setattr)); context.set_attr(&object, "__delattr__", context.new_rustfunc(object_delattr)); - context.set_attr( - &object, - "__dict__", - context.new_member_descriptor(object_dict), - ); + context.set_attr(&object, "__dict__", context.new_property(object_dict)); + context.set_attr(&object, "__dir__", context.new_rustfunc(object_dir)); context.set_attr(&object, "__hash__", context.new_rustfunc(object_hash)); context.set_attr(&object, "__str__", context.new_rustfunc(object_str)); context.set_attr(&object, "__repr__", context.new_rustfunc(object_repr)); @@ -169,24 +219,36 @@ pub fn init(context: &PyContext) { context.set_attr(&object, "__doc__", context.new_str(object_doc.to_string())); } -fn object_init(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { +fn object_init(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { Ok(vm.ctx.none()) } -fn object_dict(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - match args.args[0].borrow().payload { - PyObjectPayload::Class { ref dict, .. } | PyObjectPayload::Instance { ref dict, .. } => { - let new_dict = vm.new_dict(); - for (attr, value) in dict.borrow().iter() { - vm.ctx.set_item(&new_dict, &attr, value.clone()); - } - Ok(new_dict) +fn object_class(obj: PyObjectRef, _vm: &VirtualMachine) -> PyObjectRef { + obj.typ() +} + +fn object_class_setter( + instance: PyObjectRef, + _value: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + let type_repr = vm.to_pystr(&instance.typ())?; + Err(vm.new_type_error(format!("can't change class of type '{}'", type_repr))) +} + +fn object_dict(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + if let Some(ref dict) = args.args[0].dict { + let new_dict = vm.new_dict(); + for (attr, value) in dict.borrow().iter() { + new_dict.set_item(&vm.ctx, &attr, value.clone()); } - _ => Err(vm.new_type_error("TypeError: no dictionary.".to_string())), + Ok(new_dict) + } else { + Err(vm.new_type_error("TypeError: no dictionary.".to_string())) } } -fn object_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn object_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -203,13 +265,7 @@ fn object_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let attr_class = attr.typ(); if attr_class.has_attr("__set__") { if let Some(descriptor) = attr_class.get_attr("__get__") { - return vm.invoke( - descriptor, - PyFuncArgs { - args: vec![attr, obj.clone(), cls], - kwargs: vec![], - }, - ); + return vm.invoke(descriptor, vec![attr, obj.clone(), cls]); } } } @@ -219,18 +275,22 @@ fn object_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } else if let Some(attr) = cls.get_attr(&name) { vm.call_get_descriptor(attr, obj.clone()) } else if let Some(getter) = cls.get_attr("__getattr__") { - vm.invoke( - getter, - PyFuncArgs { - args: vec![cls, name_str.clone()], - kwargs: vec![], - }, - ) + vm.invoke(getter, vec![obj.clone(), name_str.clone()]) } else { - let attribute_error = vm.context().exceptions.attribute_error.clone(); - Err(vm.new_exception( - attribute_error, - format!("{} has no attribute '{}'", obj.borrow(), name), - )) + Err(vm.new_attribute_error(format!("{} has no attribute '{}'", obj, name))) } } + +pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { + // Get class attributes: + let mut attributes = objtype::get_attributes(obj.type_pyref()); + + // Get instance attributes: + if let Some(dict) = &obj.dict { + for (name, value) in dict.borrow().iter() { + attributes.insert(name.to_string(), value.clone()); + } + } + + attributes +} diff --git a/vm/src/obj/objproperty.rs b/vm/src/obj/objproperty.rs index 0a952375d6..d1791f2485 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -2,11 +2,221 @@ */ -use crate::pyobject::{PyContext, PyFuncArgs, PyResult, TypeProtocol}; +use crate::function::IntoPyNativeFunc; +use crate::function::OptionalArg; +use crate::obj::objstr::PyStringRef; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::{IdProtocol, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; +/// Read-only property, doesn't have __set__ or __delete__ +#[derive(Debug)] +pub struct PyReadOnlyProperty { + getter: PyObjectRef, +} + +impl PyValue for PyReadOnlyProperty { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.readonly_property_type() + } +} + +pub type PyReadOnlyPropertyRef = PyRef; + +impl PyReadOnlyPropertyRef { + fn get( + self, + obj: PyObjectRef, + _owner: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + if obj.is(&vm.ctx.none) { + Ok(self.into_object()) + } else { + vm.invoke(self.getter.clone(), obj) + } + } +} + +/// Fully fledged property +#[derive(Debug)] +pub struct PyProperty { + getter: Option, + setter: Option, + deleter: Option, +} + +impl PyValue for PyProperty { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.property_type() + } +} + +pub type PyPropertyRef = PyRef; + +impl PyPropertyRef { + fn new_property( + cls: PyClassRef, + fget: OptionalArg, + fset: OptionalArg, + fdel: OptionalArg, + _doc: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + PyProperty { + getter: fget.into_option(), + setter: fset.into_option(), + deleter: fdel.into_option(), + } + .into_ref_with_type(vm, cls) + } + + // Descriptor methods + + // specialised version that doesn't check for None + pub(crate) fn instance_binding_get(self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(getter) = self.getter.as_ref() { + vm.invoke(getter.clone(), obj) + } else { + Err(vm.new_attribute_error("unreadable attribute".to_string())) + } + } + + fn get( + self, + obj: PyObjectRef, + _owner: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + if let Some(getter) = self.getter.as_ref() { + if obj.is(&vm.ctx.none) { + Ok(self.into_object()) + } else { + vm.invoke(getter.clone(), obj) + } + } else { + Err(vm.new_attribute_error("unreadable attribute".to_string())) + } + } + + fn set(self, obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(setter) = self.setter.as_ref() { + vm.invoke(setter.clone(), vec![obj, value]) + } else { + Err(vm.new_attribute_error("can't set attribute".to_string())) + } + } + + fn delete(self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(deleter) = self.deleter.as_ref() { + vm.invoke(deleter.clone(), obj) + } else { + Err(vm.new_attribute_error("can't delete attribute".to_string())) + } + } + + // Access functions + + fn fget(self, _vm: &VirtualMachine) -> Option { + self.getter.clone() + } + + fn fset(self, _vm: &VirtualMachine) -> Option { + self.setter.clone() + } + + fn fdel(self, _vm: &VirtualMachine) -> Option { + self.deleter.clone() + } + + // Python builder functions + + fn getter(self, getter: Option, vm: &VirtualMachine) -> PyResult { + PyProperty { + getter: getter.or_else(|| self.getter.clone()), + setter: self.setter.clone(), + deleter: self.deleter.clone(), + } + .into_ref_with_type(vm, self.typ()) + } + + fn setter(self, setter: Option, vm: &VirtualMachine) -> PyResult { + PyProperty { + getter: self.getter.clone(), + setter: setter.or_else(|| self.setter.clone()), + deleter: self.deleter.clone(), + } + .into_ref_with_type(vm, self.typ()) + } + + fn deleter(self, deleter: Option, vm: &VirtualMachine) -> PyResult { + PyProperty { + getter: self.getter.clone(), + setter: self.setter.clone(), + deleter: deleter.or_else(|| self.deleter.clone()), + } + .into_ref_with_type(vm, self.typ()) + } +} + +pub struct PropertyBuilder<'a> { + ctx: &'a PyContext, + getter: Option, + setter: Option, +} + +impl<'a> PropertyBuilder<'a> { + pub fn new(ctx: &'a PyContext) -> Self { + Self { + ctx, + getter: None, + setter: None, + } + } + + pub fn add_getter>(self, func: F) -> Self { + let func = self.ctx.new_rustfunc(func); + Self { + ctx: self.ctx, + getter: Some(func), + setter: self.setter, + } + } + + pub fn add_setter>(self, func: F) -> Self { + let func = self.ctx.new_rustfunc(func); + Self { + ctx: self.ctx, + getter: self.getter, + setter: Some(func), + } + } + + pub fn create(self) -> PyObjectRef { + if self.setter.is_some() { + let payload = PyProperty { + getter: self.getter.clone(), + setter: self.setter.clone(), + deleter: None, + }; + + PyObject::new(payload, self.ctx.property_type()) + } else { + let payload = PyReadOnlyProperty { + getter: self.getter.expect( + "One of add_getter/add_setter must be called when constructing a property", + ), + }; + + PyObject::new(payload, self.ctx.readonly_property_type()) + } + } +} + pub fn init(context: &PyContext) { - let property_type = &context.property_type; + extend_class!(context, &context.readonly_property_type, { + "__get__" => context.new_rustfunc(PyReadOnlyPropertyRef::get), + }); let property_doc = "Property attribute.\n\n \ @@ -36,57 +246,20 @@ pub fn init(context: &PyContext) { def x(self):\n \ del self._x"; - context.set_attr( - &property_type, - "__get__", - context.new_rustfunc(property_get), - ); - context.set_attr( - &property_type, - "__new__", - context.new_rustfunc(property_new), - ); - context.set_attr( - &property_type, - "__doc__", - context.new_str(property_doc.to_string()), - ); - // TODO: how to handle __set__ ? -} + extend_class!(context, &context.property_type, { + "__new__" => context.new_rustfunc(PyPropertyRef::new_property), + "__doc__" => context.new_str(property_doc.to_string()), -// `property` methods. -fn property_get(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("property.__get__ {:?}", args.args); - arg_check!( - vm, - args, - required = [ - (cls, Some(vm.ctx.property_type())), - (inst, None), - (_owner, None) - ] - ); - - match vm.ctx.get_attr(&cls, "fget") { - Some(getter) => { - let py_method = vm.ctx.new_bound_method(getter, inst.clone()); - vm.invoke(py_method, PyFuncArgs::default()) - } - None => { - let attribute_error = vm.context().exceptions.attribute_error.clone(); - Err(vm.new_exception( - attribute_error, - String::from("Attribute Error: property must have 'fget' attribute"), - )) - } - } -} + "__get__" => context.new_rustfunc(PyPropertyRef::get), + "__set__" => context.new_rustfunc(PyPropertyRef::set), + "__delete__" => context.new_rustfunc(PyPropertyRef::delete), -fn property_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("property.__new__ {:?}", args.args); - arg_check!(vm, args, required = [(cls, None), (fget, None)]); + "fget" => context.new_property(PyPropertyRef::fget), + "fset" => context.new_property(PyPropertyRef::fset), + "fdel" => context.new_property(PyPropertyRef::fdel), - let py_obj = vm.ctx.new_instance(cls.clone(), None); - vm.ctx.set_attr(&py_obj, "fget", fget.clone()); - Ok(py_obj) + "getter" => context.new_rustfunc(PyPropertyRef::getter), + "setter" => context.new_rustfunc(PyPropertyRef::setter), + "deleter" => context.new_rustfunc(PyPropertyRef::deleter), + }); } diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index c5075ccbd3..0730762a84 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -1,16 +1,22 @@ -use super::objint; -use super::objtype; -use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, -}; -use crate::vm::VirtualMachine; +use std::cell::Cell; +use std::ops::Mul; + use num_bigint::{BigInt, Sign}; use num_integer::Integer; use num_traits::{One, Signed, ToPrimitive, Zero}; -use std::ops::Mul; + +use crate::function::PyFuncArgs; +use crate::pyobject::{ + PyContext, PyIteratorValue, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, +}; +use crate::vm::VirtualMachine; + +use super::objint::{self, PyInt}; +use super::objslice::PySlice; +use super::objtype; #[derive(Debug, Clone)] -pub struct RangeType { +pub struct PyRange { // Unfortunately Rust's built in range type doesn't support things like indexing // or ranges where start > end so we need to roll our own. pub start: BigInt, @@ -18,7 +24,13 @@ pub struct RangeType { pub step: BigInt, } -impl RangeType { +impl PyValue for PyRange { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.range_type() + } +} + +impl PyRange { #[inline] pub fn try_len(&self) -> Option { match self.step.sign() { @@ -114,12 +126,12 @@ impl RangeType { }; match self.step.sign() { - Sign::Plus => RangeType { + Sign::Plus => PyRange { start, end: &self.start - 1, step: -&self.step, }, - Sign::Minus => RangeType { + Sign::Minus => PyRange { start, end: &self.start + 1, step: -&self.step, @@ -137,12 +149,8 @@ impl RangeType { } } -pub fn get_value(obj: &PyObjectRef) -> RangeType { - if let PyObjectPayload::Range { range } = &obj.borrow().payload { - range.clone() - } else { - panic!("Inner error getting range {:?}", obj); - } +pub fn get_value(obj: &PyObjectRef) -> PyRange { + obj.payload::().unwrap().clone() } pub fn init(context: &PyContext) { @@ -188,7 +196,7 @@ pub fn init(context: &PyContext) { context.set_attr(&range_type, "step", context.new_property(range_step)); } -fn range_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -200,19 +208,19 @@ fn range_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ); let start = if second.is_some() { - objint::get_value(first) + objint::get_value(first).clone() } else { BigInt::zero() }; let end = if let Some(pyint) = second { - objint::get_value(pyint) + objint::get_value(pyint).clone() } else { - objint::get_value(first) + objint::get_value(first).clone() }; let step = if let Some(pyint) = step { - objint::get_value(pyint) + objint::get_value(pyint).clone() } else { BigInt::one() }; @@ -220,42 +228,37 @@ fn range_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { if step.is_zero() { Err(vm.new_value_error("range with 0 step size".to_string())) } else { - Ok(PyObject::new( - PyObjectPayload::Range { - range: RangeType { start, end, step }, - }, - cls.clone(), - )) + Ok(PyObject::new(PyRange { start, end, step }, cls.clone())) } } -fn range_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(range, Some(vm.ctx.range_type()))]); Ok(PyObject::new( - PyObjectPayload::Iterator { - position: 0, + PyIteratorValue { + position: Cell::new(0), iterated_obj: range.clone(), }, vm.ctx.iter_type(), )) } -fn range_reversed(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_reversed(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); let range = get_value(zelf).reversed(); Ok(PyObject::new( - PyObjectPayload::Iterator { - position: 0, - iterated_obj: PyObject::new(PyObjectPayload::Range { range }, vm.ctx.range_type()), + PyIteratorValue { + position: Cell::new(0), + iterated_obj: PyObject::new(range, vm.ctx.range_type()), }, vm.ctx.iter_type(), )) } -fn range_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); if let Some(len) = get_value(zelf).try_len() { @@ -265,7 +268,7 @@ fn range_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_getitem(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -274,62 +277,58 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let range = get_value(zelf); - match subscript.borrow().payload { - PyObjectPayload::Integer { ref value } => { - if let Some(int) = range.get(value) { - Ok(vm.ctx.new_int(int)) - } else { - Err(vm.new_index_error("range object index out of range".to_string())) - } + if let Some(i) = subscript.payload::() { + if let Some(int) = range.get(i.value.clone()) { + Ok(vm.ctx.new_int(int)) + } else { + Err(vm.new_index_error("range object index out of range".to_string())) } - PyObjectPayload::Slice { - ref start, - ref stop, - ref step, - } => { - let new_start = if let Some(int) = start { - if let Some(i) = range.get(int) { - i - } else { - range.start.clone() - } + } else if let Some(PySlice { + ref start, + ref stop, + ref step, + }) = subscript.payload() + { + let new_start = if let Some(int) = start { + if let Some(i) = range.get(int) { + i } else { range.start.clone() - }; - - let new_end = if let Some(int) = stop { - if let Some(i) = range.get(int) { - i - } else { - range.end - } + } + } else { + range.start.clone() + }; + + let new_end = if let Some(int) = stop { + if let Some(i) = range.get(int) { + i } else { range.end - }; + } + } else { + range.end + }; - let new_step = if let Some(int) = step { - int * range.step - } else { - range.step - }; - - Ok(PyObject::new( - PyObjectPayload::Range { - range: RangeType { - start: new_start, - end: new_end, - step: new_step, - }, - }, - vm.ctx.range_type(), - )) - } + let new_step = if let Some(int) = step { + int * range.step + } else { + range.step + }; - _ => Err(vm.new_type_error("range indices must be integer or slice".to_string())), + Ok(PyObject::new( + PyRange { + start: new_start, + end: new_end, + step: new_step, + }, + vm.ctx.range_type(), + )) + } else { + Err(vm.new_type_error("range indices must be integer or slice".to_string())) } } -fn range_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); let repr = get_value(zelf).repr(); @@ -337,7 +336,7 @@ fn range_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_str(repr)) } -fn range_bool(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_bool(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); let len = get_value(zelf).len(); @@ -345,7 +344,7 @@ fn range_bool(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(len > 0)) } -fn range_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_contains(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -363,7 +362,7 @@ fn range_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(result)) } -fn range_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_index(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -384,7 +383,7 @@ fn range_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn range_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_count(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -400,17 +399,17 @@ fn range_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn range_start(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_start(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); Ok(vm.ctx.new_int(get_value(zelf).start)) } -fn range_stop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_stop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); Ok(vm.ctx.new_int(get_value(zelf).end)) } -fn range_step(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn range_step(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); Ok(vm.ctx.new_int(get_value(zelf).step)) } diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index b52c8e04d1..ad9c7bf361 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -1,13 +1,19 @@ -use super::objbool; -use super::objint; -use crate::pyobject::{IdProtocol, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol}; -use crate::vm::VirtualMachine; -use num_bigint::BigInt; -use num_traits::{One, Signed, ToPrimitive, Zero}; -use std::cell::{Ref, RefMut}; +use std::cell::RefCell; use std::marker::Sized; use std::ops::{Deref, DerefMut, Range}; +use num_bigint::BigInt; +use num_traits::{One, Signed, ToPrimitive, Zero}; + +use crate::pyobject::{IdProtocol, PyObject, PyObjectRef, PyResult, TypeProtocol}; +use crate::vm::VirtualMachine; + +use super::objbool; +use super::objint::PyInt; +use super::objlist::PyList; +use super::objslice::PySlice; +use super::objtuple::PyTuple; + pub trait PySliceableSequence { fn do_slice(&self, range: Range) -> Self; fn do_slice_reverse(&self, range: Range) -> Self; @@ -55,23 +61,20 @@ pub trait PySliceableSequence { start..stop } - fn get_slice_items( - &self, - vm: &mut VirtualMachine, - slice: &PyObjectRef, - ) -> Result + fn get_slice_items(&self, vm: &VirtualMachine, slice: &PyObjectRef) -> Result where Self: Sized, { // TODO: we could potentially avoid this copy and use slice - match &(slice.borrow()).payload { - PyObjectPayload::Slice { start, stop, step } => { + match slice.payload() { + Some(PySlice { start, stop, step }) => { let step = step.clone().unwrap_or_else(BigInt::one); if step.is_zero() { Err(vm.new_value_error("slice step cannot be zero".to_string())) } else if step.is_positive() { let range = self.get_slice_range(start, stop); if range.start < range.end { + #[allow(clippy::range_plus_one)] match step.to_i32() { Some(1) => Ok(self.do_slice(range)), Some(num) => Ok(self.do_stepped_slice(range, num as usize)), @@ -135,13 +138,13 @@ impl PySliceableSequence for Vec { } pub fn get_item( - vm: &mut VirtualMachine, + vm: &VirtualMachine, sequence: &PyObjectRef, elements: &[PyObjectRef], subscript: PyObjectRef, ) -> PyResult { - match &(subscript.borrow()).payload { - PyObjectPayload::Integer { value } => match value.to_i32() { + if let Some(i) = subscript.payload::() { + return match i.value.to_i32() { Some(value) => { if let Some(pos_index) = elements.to_vec().get_pos(value) { let obj = elements[pos_index].clone(); @@ -153,32 +156,39 @@ pub fn get_item( None => { Err(vm.new_index_error("cannot fit 'int' into an index-sized integer".to_string())) } - }, - - PyObjectPayload::Slice { .. } => Ok(PyObject::new( - match &(sequence.borrow()).payload { - PyObjectPayload::Sequence { .. } => PyObjectPayload::Sequence { - elements: elements.to_vec().get_slice_items(vm, &subscript)?, - }, - ref payload => panic!("sequence get_item called for non-sequence: {:?}", payload), - }, - sequence.typ(), - )), - _ => Err(vm.new_type_error(format!( + }; + } + + if subscript.payload::().is_some() { + if sequence.payload::().is_some() { + Ok(PyObject::new( + PyList::from(elements.to_vec().get_slice_items(vm, &subscript)?), + sequence.typ(), + )) + } else if sequence.payload::().is_some() { + Ok(PyObject::new( + PyTuple::from(elements.to_vec().get_slice_items(vm, &subscript)?), + sequence.typ(), + )) + } else { + panic!("sequence get_item called for non-sequence") + } + } else { + Err(vm.new_type_error(format!( "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", sequence, subscript - ))), + ))) } } pub fn seq_equal( - vm: &mut VirtualMachine, + vm: &VirtualMachine, zelf: &[PyObjectRef], other: &[PyObjectRef], ) -> Result { if zelf.len() == other.len() { for (a, b) in Iterator::zip(zelf.iter(), other.iter()) { - if !a.is(&b) { + if !a.is(b) { let eq = vm._eq(a.clone(), b.clone())?; let value = objbool::boolval(vm, eq)?; if !value { @@ -193,7 +203,7 @@ pub fn seq_equal( } pub fn seq_lt( - vm: &mut VirtualMachine, + vm: &VirtualMachine, zelf: &[PyObjectRef], other: &[PyObjectRef], ) -> Result { @@ -233,7 +243,7 @@ pub fn seq_lt( } pub fn seq_gt( - vm: &mut VirtualMachine, + vm: &VirtualMachine, zelf: &[PyObjectRef], other: &[PyObjectRef], ) -> Result { @@ -272,7 +282,7 @@ pub fn seq_gt( } pub fn seq_ge( - vm: &mut VirtualMachine, + vm: &VirtualMachine, zelf: &[PyObjectRef], other: &[PyObjectRef], ) -> Result { @@ -280,16 +290,14 @@ pub fn seq_ge( } pub fn seq_le( - vm: &mut VirtualMachine, + vm: &VirtualMachine, zelf: &[PyObjectRef], other: &[PyObjectRef], ) -> Result { Ok(seq_lt(vm, zelf, other)? || seq_equal(vm, zelf, other)?) } -pub fn seq_mul(elements: &[PyObjectRef], product: &PyObjectRef) -> Vec { - let counter = objint::get_value(&product).to_isize().unwrap(); - +pub fn seq_mul(elements: &[PyObjectRef], counter: isize) -> Vec { let current_len = elements.len(); let new_len = counter.max(0) as usize * current_len; let mut new_elements = Vec::with_capacity(new_len); @@ -301,24 +309,32 @@ pub fn seq_mul(elements: &[PyObjectRef], product: &PyObjectRef) -> Vec(obj: &'a PyObjectRef) -> &'a RefCell> { + if let Some(list) = obj.payload::() { + return &list.elements; + } + if let Some(tuple) = obj.payload::() { + return &tuple.elements; + } + panic!("Cannot extract elements from non-sequence"); +} + pub fn get_elements<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { - Ref::map(obj.borrow(), |x| { - if let PyObjectPayload::Sequence { ref elements } = x.payload { - elements - } else { - panic!("Cannot extract elements from non-sequence"); - } - }) + if let Some(list) = obj.payload::() { + return list.elements.borrow(); + } + if let Some(tuple) = obj.payload::() { + return tuple.elements.borrow(); + } + panic!("Cannot extract elements from non-sequence"); } pub fn get_mut_elements<'a>(obj: &'a PyObjectRef) -> impl DerefMut> + 'a { - RefMut::map(obj.borrow_mut(), |x| { - if let PyObjectPayload::Sequence { ref mut elements } = x.payload { - elements - } else { - panic!("Cannot extract list elements from non-sequence"); - // TODO: raise proper error? - // Err(vm.new_type_error("list.append is called with no list".to_string())) - } - }) + if let Some(list) = obj.payload::() { + return list.elements.borrow_mut(); + } + if let Some(tuple) = obj.payload::() { + return tuple.elements.borrow_mut(); + } + panic!("Cannot extract elements from non-sequence"); } diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index ebae2b44cd..b11c21aa39 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -2,32 +2,50 @@ * Builtin set type with a sequence of unique items. */ -use super::objbool; -use super::objint; -use super::objiter; -use super::objstr; -use super::objtype; +use std::cell::{Cell, RefCell}; +use std::collections::{hash_map::DefaultHasher, HashMap}; +use std::fmt; +use std::hash::{Hash, Hasher}; + +use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::{ReprGuard, VirtualMachine}; -use std::collections::hash_map::DefaultHasher; -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; -pub fn get_elements(obj: &PyObjectRef) -> HashMap { - if let PyObjectPayload::Set { elements } = &obj.borrow().payload { - elements.clone() - } else { - panic!("Cannot extract set elements from non-set"); +use super::objbool; +use super::objint; +use super::objiter; +use super::objtype::{self, PyClassRef}; + +#[derive(Default)] +pub struct PySet { + elements: RefCell>, +} +pub type PySetRef = PyRef; + +impl fmt::Debug for PySet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: implement more detailed, non-recursive Debug formatter + f.write_str("set") } } +impl PyValue for PySet { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.set_type() + } +} + +pub fn get_elements(obj: &PyObjectRef) -> HashMap { + obj.payload::().unwrap().elements.borrow().clone() +} + fn perform_action_with_hash( - vm: &mut VirtualMachine, + vm: &VirtualMachine, elements: &mut HashMap, item: &PyObjectRef, - f: &Fn(&mut VirtualMachine, &mut HashMap, u64, &PyObjectRef) -> PyResult, + f: &Fn(&VirtualMachine, &mut HashMap, u64, &PyObjectRef) -> PyResult, ) -> PyResult { let hash: PyObjectRef = vm.call_method(item, "__hash__", vec![])?; @@ -39,12 +57,12 @@ fn perform_action_with_hash( } fn insert_into_set( - vm: &mut VirtualMachine, + vm: &VirtualMachine, elements: &mut HashMap, item: &PyObjectRef, ) -> PyResult { fn insert( - vm: &mut VirtualMachine, + vm: &VirtualMachine, elements: &mut HashMap, key: u64, value: &PyObjectRef, @@ -55,65 +73,59 @@ fn insert_into_set( perform_action_with_hash(vm, elements, item, &insert) } -fn set_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_add(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("set.add called with: {:?}", args); arg_check!( vm, args, - required = [(s, Some(vm.ctx.set_type())), (item, None)] + required = [(zelf, Some(vm.ctx.set_type())), (item, None)] ); - let mut mut_obj = s.borrow_mut(); - - match mut_obj.payload { - PyObjectPayload::Set { ref mut elements } => insert_into_set(vm, elements, item), + match zelf.payload::() { + Some(set) => insert_into_set(vm, &mut set.elements.borrow_mut(), item), _ => Err(vm.new_type_error("set.add is called with no item".to_string())), } } -fn set_remove(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_remove(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("set.remove called with: {:?}", args); arg_check!( vm, args, required = [(s, Some(vm.ctx.set_type())), (item, None)] ); - let mut mut_obj = s.borrow_mut(); - - match mut_obj.payload { - PyObjectPayload::Set { ref mut elements } => { + match s.payload::() { + Some(set) => { fn remove( - vm: &mut VirtualMachine, + vm: &VirtualMachine, elements: &mut HashMap, key: u64, value: &PyObjectRef, ) -> PyResult { match elements.remove(&key) { None => { - let item_str = format!("{:?}", value.borrow()); + let item_str = format!("{:?}", value); Err(vm.new_key_error(item_str)) } Some(_) => Ok(vm.get_none()), } } - perform_action_with_hash(vm, elements, item, &remove) + perform_action_with_hash(vm, &mut set.elements.borrow_mut(), item, &remove) } _ => Err(vm.new_type_error("set.remove is called with no item".to_string())), } } -fn set_discard(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_discard(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("set.discard called with: {:?}", args); arg_check!( vm, args, required = [(s, Some(vm.ctx.set_type())), (item, None)] ); - let mut mut_obj = s.borrow_mut(); - - match mut_obj.payload { - PyObjectPayload::Set { ref mut elements } => { + match s.payload::() { + Some(set) => { fn discard( - vm: &mut VirtualMachine, + vm: &VirtualMachine, elements: &mut HashMap, key: u64, _value: &PyObjectRef, @@ -121,43 +133,39 @@ fn set_discard(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { elements.remove(&key); Ok(vm.get_none()) } - perform_action_with_hash(vm, elements, item, &discard) + perform_action_with_hash(vm, &mut set.elements.borrow_mut(), item, &discard) } - _ => Err(vm.new_type_error("set.discard is called with no item".to_string())), + None => Err(vm.new_type_error("set.discard is called with no item".to_string())), } } -fn set_clear(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_clear(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("set.clear called"); arg_check!(vm, args, required = [(s, Some(vm.ctx.set_type()))]); - let mut mut_obj = s.borrow_mut(); - match mut_obj.payload { - PyObjectPayload::Set { ref mut elements } => { - elements.clear(); + match s.payload::() { + Some(set) => { + set.elements.borrow_mut().clear(); Ok(vm.get_none()) } - _ => Err(vm.new_type_error("".to_string())), + None => Err(vm.new_type_error("".to_string())), } } /* Create a new object of sub-type of set */ -fn set_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None)], - optional = [(iterable, None)] - ); - - if !objtype::issubclass(cls, &vm.ctx.set_type()) { - return Err(vm.new_type_error(format!("{} is not a subtype of set", cls.borrow()))); +fn set_new( + cls: PyClassRef, + iterable: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + if !objtype::issubclass(cls.as_object(), &vm.ctx.set_type()) { + return Err(vm.new_type_error(format!("{} is not a subtype of set", cls))); } let elements: HashMap = match iterable { - None => HashMap::new(), - Some(iterable) => { + OptionalArg::Missing => HashMap::new(), + OptionalArg::Present(iterable) => { let mut elements = HashMap::new(); - let iterator = objiter::get_iter(vm, iterable)?; + let iterator = objiter::get_iter(vm, &iterable)?; while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { insert_into_set(vm, &mut elements, &v)?; } @@ -165,30 +173,28 @@ fn set_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } }; - Ok(PyObject::new( - PyObjectPayload::Set { elements }, - cls.clone(), - )) + PySet { + elements: RefCell::new(elements), + } + .into_ref_with_type(vm, cls) } -fn set_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("set.len called with: {:?}", args); arg_check!(vm, args, required = [(s, Some(vm.ctx.set_type()))]); let elements = get_elements(s); Ok(vm.context().new_int(elements.len())) } -fn set_copy(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("set.copy called with: {:?}", args); - arg_check!(vm, args, required = [(s, Some(vm.ctx.set_type()))]); - let elements = get_elements(s); - Ok(PyObject::new( - PyObjectPayload::Set { elements }, - vm.ctx.set_type(), - )) +fn set_copy(obj: PySetRef, _vm: &VirtualMachine) -> PySet { + trace!("set.copy called with: {:?}", obj); + let elements = obj.elements.borrow().clone(); + PySet { + elements: RefCell::new(elements), + } } -fn set_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(o, Some(vm.ctx.set_type()))]); let elements = get_elements(o); @@ -198,7 +204,7 @@ fn set_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mut str_parts = vec![]; for elem in elements.values() { let part = vm.to_repr(elem)?; - str_parts.push(objstr::get_value(&part)); + str_parts.push(part.value.clone()); } format!("{{{}}}", str_parts.join(", ")) @@ -208,7 +214,7 @@ fn set_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(s)) } -pub fn set_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +pub fn set_contains(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -228,7 +234,7 @@ pub fn set_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_bool(false)) } -fn set_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_compare_inner( vm, args, @@ -237,7 +243,7 @@ fn set_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ) } -fn set_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_ge(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_compare_inner( vm, args, @@ -246,7 +252,7 @@ fn set_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ) } -fn set_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_gt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_compare_inner( vm, args, @@ -255,7 +261,7 @@ fn set_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ) } -fn set_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_le(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_compare_inner( vm, args, @@ -264,7 +270,7 @@ fn set_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ) } -fn set_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_lt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_compare_inner( vm, args, @@ -274,7 +280,7 @@ fn set_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } fn set_compare_inner( - vm: &mut VirtualMachine, + vm: &VirtualMachine, args: PyFuncArgs, size_func: &Fn(usize, usize) -> bool, swap: bool, @@ -321,63 +327,47 @@ fn set_compare_inner( Ok(vm.new_bool(true)) } -fn set_union(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (zelf, Some(vm.ctx.set_type())), - (other, Some(vm.ctx.set_type())) - ] - ); - - let mut elements = get_elements(zelf).clone(); - elements.extend(get_elements(other).clone()); +fn set_union(zelf: PySetRef, other: PySetRef, _vm: &VirtualMachine) -> PySet { + let mut elements = zelf.elements.borrow().clone(); + elements.extend(other.elements.borrow().clone()); - Ok(PyObject::new( - PyObjectPayload::Set { elements }, - vm.ctx.set_type(), - )) + PySet { + elements: RefCell::new(elements), + } } -fn set_intersection(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - set_combine_inner(vm, args, SetCombineOperation::Intersection) +fn set_intersection(zelf: PySetRef, other: PySetRef, vm: &VirtualMachine) -> PyResult { + set_combine_inner(zelf, other, vm, SetCombineOperation::Intersection) } -fn set_difference(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - set_combine_inner(vm, args, SetCombineOperation::Difference) +fn set_difference(zelf: PySetRef, other: PySetRef, vm: &VirtualMachine) -> PyResult { + set_combine_inner(zelf, other, vm, SetCombineOperation::Difference) } -fn set_symmetric_difference(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (zelf, Some(vm.ctx.set_type())), - (other, Some(vm.ctx.set_type())) - ] - ); - +fn set_symmetric_difference( + zelf: PySetRef, + other: PySetRef, + vm: &VirtualMachine, +) -> PyResult { let mut elements = HashMap::new(); - for element in get_elements(zelf).iter() { - let value = vm.call_method(other, "__contains__", vec![element.1.clone()])?; + for element in zelf.elements.borrow().iter() { + let value = vm.call_method(other.as_object(), "__contains__", vec![element.1.clone()])?; if !objbool::get_value(&value) { elements.insert(element.0.clone(), element.1.clone()); } } - for element in get_elements(other).iter() { - let value = vm.call_method(zelf, "__contains__", vec![element.1.clone()])?; + for element in other.elements.borrow().iter() { + let value = vm.call_method(zelf.as_object(), "__contains__", vec![element.1.clone()])?; if !objbool::get_value(&value) { elements.insert(element.0.clone(), element.1.clone()); } } - Ok(PyObject::new( - PyObjectPayload::Set { elements }, - vm.ctx.set_type(), - )) + Ok(PySet { + elements: RefCell::new(elements), + }) } enum SetCombineOperation { @@ -386,23 +376,15 @@ enum SetCombineOperation { } fn set_combine_inner( - vm: &mut VirtualMachine, - args: PyFuncArgs, + zelf: PySetRef, + other: PySetRef, + vm: &VirtualMachine, op: SetCombineOperation, -) -> PyResult { - arg_check!( - vm, - args, - required = [ - (zelf, Some(vm.ctx.set_type())), - (other, Some(vm.ctx.set_type())) - ] - ); - +) -> PyResult { let mut elements = HashMap::new(); - for element in get_elements(zelf).iter() { - let value = vm.call_method(other, "__contains__", vec![element.1.clone()])?; + for element in zelf.elements.borrow().iter() { + let value = vm.call_method(other.as_object(), "__contains__", vec![element.1.clone()])?; let should_add = match op { SetCombineOperation::Intersection => objbool::get_value(&value), SetCombineOperation::Difference => !objbool::get_value(&value), @@ -412,45 +394,43 @@ fn set_combine_inner( } } - Ok(PyObject::new( - PyObjectPayload::Set { elements }, - vm.ctx.set_type(), - )) + Ok(PySet { + elements: RefCell::new(elements), + }) } -fn set_pop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_pop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.set_type()))]); - let mut mut_obj = s.borrow_mut(); - - match mut_obj.payload { - PyObjectPayload::Set { ref mut elements } => match elements.clone().keys().next() { - Some(key) => Ok(elements.remove(key).unwrap()), - None => Err(vm.new_key_error("pop from an empty set".to_string())), - }, + match s.payload::() { + Some(set) => { + let mut elements = set.elements.borrow_mut(); + match elements.clone().keys().next() { + Some(key) => Ok(elements.remove(key).unwrap()), + None => Err(vm.new_key_error("pop from an empty set".to_string())), + } + } _ => Err(vm.new_type_error("".to_string())), } } -fn set_update(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_update(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_ior(vm, args)?; Ok(vm.get_none()) } -fn set_ior(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_ior(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, required = [(zelf, Some(vm.ctx.set_type())), (iterable, None)] ); - let mut mut_obj = zelf.borrow_mut(); - - match mut_obj.payload { - PyObjectPayload::Set { ref mut elements } => { + match zelf.payload::() { + Some(set) => { let iterator = objiter::get_iter(vm, iterable)?; while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { - insert_into_set(vm, elements, &v)?; + insert_into_set(vm, &mut set.elements.borrow_mut(), &v)?; } } _ => return Err(vm.new_type_error("set.update is called with no other".to_string())), @@ -458,26 +438,26 @@ fn set_ior(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(zelf.clone()) } -fn set_intersection_update(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_intersection_update(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_combine_update_inner(vm, args, SetCombineOperation::Intersection)?; Ok(vm.get_none()) } -fn set_iand(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_iand(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_combine_update_inner(vm, args, SetCombineOperation::Intersection) } -fn set_difference_update(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_difference_update(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_combine_update_inner(vm, args, SetCombineOperation::Difference)?; Ok(vm.get_none()) } -fn set_isub(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_isub(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_combine_update_inner(vm, args, SetCombineOperation::Difference) } fn set_combine_update_inner( - vm: &mut VirtualMachine, + vm: &VirtualMachine, args: PyFuncArgs, op: SetCombineOperation, ) -> PyResult { @@ -487,10 +467,9 @@ fn set_combine_update_inner( required = [(zelf, Some(vm.ctx.set_type())), (iterable, None)] ); - let mut mut_obj = zelf.borrow_mut(); - - match mut_obj.payload { - PyObjectPayload::Set { ref mut elements } => { + match zelf.payload::() { + Some(set) => { + let mut elements = set.elements.borrow_mut(); for element in elements.clone().iter() { let value = vm.call_method(iterable, "__contains__", vec![element.1.clone()])?; let should_remove = match op { @@ -507,31 +486,29 @@ fn set_combine_update_inner( Ok(zelf.clone()) } -fn set_symmetric_difference_update(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_symmetric_difference_update(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { set_ixor(vm, args)?; Ok(vm.get_none()) } -fn set_ixor(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn set_ixor(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, required = [(zelf, Some(vm.ctx.set_type())), (iterable, None)] ); - let mut mut_obj = zelf.borrow_mut(); - - match mut_obj.payload { - PyObjectPayload::Set { ref mut elements } => { - let elements_original = elements.clone(); + match zelf.payload::() { + Some(set) => { + let elements_original = set.elements.borrow().clone(); let iterator = objiter::get_iter(vm, iterable)?; while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { - insert_into_set(vm, elements, &v)?; + insert_into_set(vm, &mut set.elements.borrow_mut(), &v)?; } for element in elements_original.iter() { let value = vm.call_method(iterable, "__contains__", vec![element.1.clone()])?; if objbool::get_value(&value) { - elements.remove(&element.0.clone()); + set.elements.borrow_mut().remove(&element.0.clone()); } } } @@ -541,23 +518,16 @@ fn set_ixor(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(zelf.clone()) } -fn set_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.set_type()))]); - - let items = get_elements(zelf).values().map(|x| x.clone()).collect(); +fn set_iter(zelf: PySetRef, vm: &VirtualMachine) -> PyIteratorValue { + let items = zelf.elements.borrow().values().cloned().collect(); let set_list = vm.ctx.new_list(items); - let iter_obj = PyObject::new( - PyObjectPayload::Iterator { - position: 0, - iterated_obj: set_list, - }, - vm.ctx.iter_type(), - ); - - Ok(iter_obj) + PyIteratorValue { + position: Cell::new(0), + iterated_obj: set_list, + } } -fn frozenset_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn frozenset_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(o, Some(vm.ctx.frozenset_type()))]); let elements = get_elements(o); @@ -567,7 +537,7 @@ fn frozenset_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mut str_parts = vec![]; for elem in elements.values() { let part = vm.to_repr(elem)?; - str_parts.push(objstr::get_value(&part)); + str_parts.push(part.value.clone()); } format!("frozenset({{{}}})", str_parts.join(", ")) diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 19abc214fa..263490565d 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -1,11 +1,26 @@ -use super::objint; -use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, -}; -use crate::vm::VirtualMachine; use num_bigint::BigInt; -fn slice_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +use crate::function::PyFuncArgs; +use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::vm::VirtualMachine; + +use super::objint; + +#[derive(Debug)] +pub struct PySlice { + // TODO: should be private + pub start: Option, + pub stop: Option, + pub step: Option, +} + +impl PyValue for PySlice { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.slice_type() + } +} + +fn slice_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { no_kwargs!(vm, args); let (cls, start, stop, step): ( &PyObjectRef, @@ -40,16 +55,16 @@ fn slice_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } }?; Ok(PyObject::new( - PyObjectPayload::Slice { - start: start.map(|x| objint::get_value(x)), - stop: stop.map(|x| objint::get_value(x)), - step: step.map(|x| objint::get_value(x)), + PySlice { + start: start.map(|x| objint::get_value(x).clone()), + stop: stop.map(|x| objint::get_value(x).clone()), + step: step.map(|x| objint::get_value(x).clone()), }, cls.clone(), )) } -fn get_property_value(vm: &mut VirtualMachine, value: &Option) -> PyResult { +fn get_property_value(vm: &VirtualMachine, value: &Option) -> PyResult { if let Some(value) = value { Ok(vm.ctx.new_int(value.clone())) } else { @@ -57,27 +72,27 @@ fn get_property_value(vm: &mut VirtualMachine, value: &Option) -> PyResu } } -fn slice_start(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn slice_start(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); - if let PyObjectPayload::Slice { start, .. } = &slice.borrow().payload { + if let Some(PySlice { start, .. }) = &slice.payload() { get_property_value(vm, start) } else { panic!("Slice has incorrect payload."); } } -fn slice_stop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn slice_stop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); - if let PyObjectPayload::Slice { stop, .. } = &slice.borrow().payload { + if let Some(PySlice { stop, .. }) = &slice.payload() { get_property_value(vm, stop) } else { panic!("Slice has incorrect payload."); } } -fn slice_step(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn slice_step(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); - if let PyObjectPayload::Slice { step, .. } = &slice.borrow().payload { + if let Some(PySlice { step, .. }) = &slice.payload() { get_property_value(vm, step) } else { panic!("Slice has incorrect payload."); diff --git a/vm/src/obj/objstaticmethod.rs b/vm/src/obj/objstaticmethod.rs new file mode 100644 index 0000000000..f8430979da --- /dev/null +++ b/vm/src/obj/objstaticmethod.rs @@ -0,0 +1,40 @@ +use super::objtype::PyClassRef; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::vm::VirtualMachine; + +#[derive(Clone, Debug)] +pub struct PyStaticMethod { + pub callable: PyObjectRef, +} +pub type PyStaticMethodRef = PyRef; + +impl PyValue for PyStaticMethod { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.staticmethod_type() + } +} + +impl PyStaticMethodRef { + fn new( + cls: PyClassRef, + callable: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + PyStaticMethod { + callable: callable.clone(), + } + .into_ref_with_type(vm, cls) + } + + fn get(self, _inst: PyObjectRef, _owner: PyObjectRef, _vm: &VirtualMachine) -> PyResult { + Ok(self.callable.clone()) + } +} + +pub fn init(context: &PyContext) { + let staticmethod_type = &context.staticmethod_type; + extend_class!(context, staticmethod_type, { + "__get__" => context.new_rustfunc(PyStaticMethodRef::get), + "__new__" => context.new_rustfunc(PyStaticMethodRef::new), + }); +} diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 9c2051e6f7..826396925c 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1,257 +1,704 @@ -use super::objint; -use super::objsequence::PySliceableSequence; -use super::objtype; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::ops::Range; +use std::str::FromStr; +use std::string::ToString; + +use num_traits::ToPrimitive; +use unicode_segmentation::UnicodeSegmentation; + use crate::format::{FormatParseError, FormatPart, FormatString}; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + IdProtocol, IntoPyObject, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, TryIntoRef, TypeProtocol, }; use crate::vm::VirtualMachine; -use num_traits::ToPrimitive; -use std::cell::Ref; -use std::hash::{Hash, Hasher}; -use std::ops::Range; -use std::str::FromStr; -// rust's builtin to_lowercase isn't sufficient for casefold -extern crate caseless; -extern crate unicode_segmentation; -use self::unicode_segmentation::UnicodeSegmentation; +use super::objint; +use super::objsequence::PySliceableSequence; +use super::objslice::PySlice; +use super::objtype::{self, PyClassRef}; -pub fn init(context: &PyContext) { - let str_type = &context.str_type; - context.set_attr(&str_type, "__add__", context.new_rustfunc(str_add)); - context.set_attr(&str_type, "__eq__", context.new_rustfunc(str_eq)); - context.set_attr( - &str_type, - "__contains__", - context.new_rustfunc(str_contains), - ); - context.set_attr(&str_type, "__getitem__", context.new_rustfunc(str_getitem)); - context.set_attr(&str_type, "__gt__", context.new_rustfunc(str_gt)); - context.set_attr(&str_type, "__ge__", context.new_rustfunc(str_ge)); - context.set_attr(&str_type, "__lt__", context.new_rustfunc(str_lt)); - context.set_attr(&str_type, "__le__", context.new_rustfunc(str_le)); - context.set_attr(&str_type, "__hash__", context.new_rustfunc(str_hash)); - context.set_attr(&str_type, "__len__", context.new_rustfunc(str_len)); - context.set_attr(&str_type, "__mul__", context.new_rustfunc(str_mul)); - context.set_attr(&str_type, "__new__", context.new_rustfunc(str_new)); - context.set_attr(&str_type, "__str__", context.new_rustfunc(str_str)); - context.set_attr(&str_type, "__repr__", context.new_rustfunc(str_repr)); - context.set_attr(&str_type, "format", context.new_rustfunc(str_format)); - context.set_attr(&str_type, "lower", context.new_rustfunc(str_lower)); - context.set_attr(&str_type, "casefold", context.new_rustfunc(str_casefold)); - context.set_attr(&str_type, "upper", context.new_rustfunc(str_upper)); - context.set_attr( - &str_type, - "capitalize", - context.new_rustfunc(str_capitalize), - ); - context.set_attr(&str_type, "split", context.new_rustfunc(str_split)); - context.set_attr(&str_type, "rsplit", context.new_rustfunc(str_rsplit)); - context.set_attr(&str_type, "strip", context.new_rustfunc(str_strip)); - context.set_attr(&str_type, "lstrip", context.new_rustfunc(str_lstrip)); - context.set_attr(&str_type, "rstrip", context.new_rustfunc(str_rstrip)); - context.set_attr(&str_type, "endswith", context.new_rustfunc(str_endswith)); - context.set_attr( - &str_type, - "startswith", - context.new_rustfunc(str_startswith), - ); - context.set_attr(&str_type, "isalnum", context.new_rustfunc(str_isalnum)); - context.set_attr(&str_type, "isnumeric", context.new_rustfunc(str_isnumeric)); - context.set_attr(&str_type, "isdigit", context.new_rustfunc(str_isdigit)); - context.set_attr(&str_type, "isdecimal", context.new_rustfunc(str_isdecimal)); - context.set_attr(&str_type, "title", context.new_rustfunc(str_title)); - context.set_attr(&str_type, "swapcase", context.new_rustfunc(str_swapcase)); - context.set_attr(&str_type, "isalpha", context.new_rustfunc(str_isalpha)); - context.set_attr(&str_type, "replace", context.new_rustfunc(str_replace)); - context.set_attr(&str_type, "center", context.new_rustfunc(str_center)); - context.set_attr(&str_type, "isspace", context.new_rustfunc(str_isspace)); - context.set_attr(&str_type, "isupper", context.new_rustfunc(str_isupper)); - context.set_attr(&str_type, "islower", context.new_rustfunc(str_islower)); - context.set_attr(&str_type, "isascii", context.new_rustfunc(str_isascii)); - context.set_attr( - &str_type, - "splitlines", - context.new_rustfunc(str_splitlines), - ); - context.set_attr(&str_type, "join", context.new_rustfunc(str_join)); - context.set_attr(&str_type, "find", context.new_rustfunc(str_find)); - context.set_attr(&str_type, "rfind", context.new_rustfunc(str_rfind)); - context.set_attr(&str_type, "index", context.new_rustfunc(str_index)); - context.set_attr(&str_type, "rindex", context.new_rustfunc(str_rindex)); - context.set_attr(&str_type, "partition", context.new_rustfunc(str_partition)); - context.set_attr( - &str_type, - "rpartition", - context.new_rustfunc(str_rpartition), - ); - context.set_attr(&str_type, "istitle", context.new_rustfunc(str_istitle)); - context.set_attr(&str_type, "count", context.new_rustfunc(str_count)); - context.set_attr(&str_type, "zfill", context.new_rustfunc(str_zfill)); - context.set_attr(&str_type, "ljust", context.new_rustfunc(str_ljust)); - context.set_attr(&str_type, "rjust", context.new_rustfunc(str_rjust)); - context.set_attr( - &str_type, - "expandtabs", - context.new_rustfunc(str_expandtabs), - ); - context.set_attr( - &str_type, - "isidentifier", - context.new_rustfunc(str_isidentifier), - ); +#[derive(Clone, Debug)] +pub struct PyString { + // TODO: shouldn't be public + pub value: String, } +pub type PyStringRef = PyRef; -pub fn get_value(obj: &PyObjectRef) -> String { - if let PyObjectPayload::String { value } = &obj.borrow().payload { - value.to_string() - } else { - panic!("Inner error getting str"); +impl fmt::Display for PyString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.value, f) } } -pub fn borrow_value(obj: &PyObjectRef) -> Ref { - Ref::map(obj.borrow(), |py_obj| { - if let PyObjectPayload::String { value } = &py_obj.payload { - value.as_ref() - } else { - panic!("Inner error getting str"); +impl TryIntoRef for String { + fn try_into_ref(self, vm: &VirtualMachine) -> PyResult> { + Ok(PyString { value: self }.into_ref(vm)) + } +} + +impl TryIntoRef for &str { + fn try_into_ref(self, vm: &VirtualMachine) -> PyResult> { + Ok(PyString { + value: self.to_string(), } - }) + .into_ref(vm)) + } } -fn str_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.str_type())), (b, None)] - ); +impl PyStringRef { + fn add(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&rhs, &vm.ctx.str_type()) { + Ok(format!("{}{}", self.value, get_value(&rhs))) + } else { + Err(vm.new_type_error(format!("Cannot add {} and {}", self, rhs))) + } + } - let result = if objtype::isinstance(b, &vm.ctx.str_type()) { - get_value(a) == get_value(b) - } else { - false - }; - Ok(vm.ctx.new_bool(result)) -} + fn eq(self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { + if objtype::isinstance(&rhs, &vm.ctx.str_type()) { + self.value == get_value(&rhs) + } else { + false + } + } -fn str_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.str_type())), (i2, None)] - ); + fn contains(self, needle: PyStringRef, _vm: &VirtualMachine) -> bool { + self.value.contains(&needle.value) + } - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.str_type()) { - Ok(vm.ctx.new_bool(v1 > get_value(i2))) - } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", i.borrow(), i2.borrow()))) + fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + subscript(vm, &self.value, needle) } -} -fn str_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.str_type())), (i2, None)] - ); + fn gt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&rhs, &vm.ctx.str_type()) { + Ok(self.value > get_value(&rhs)) + } else { + Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) + } + } - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.str_type()) { - Ok(vm.ctx.new_bool(v1 >= get_value(i2))) - } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", i.borrow(), i2.borrow()))) + fn ge(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&rhs, &vm.ctx.str_type()) { + Ok(self.value >= get_value(&rhs)) + } else { + Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) + } } -} -fn str_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.str_type())), (i2, None)] - ); + fn lt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&rhs, &vm.ctx.str_type()) { + Ok(self.value < get_value(&rhs)) + } else { + Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) + } + } - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.str_type()) { - Ok(vm.ctx.new_bool(v1 < get_value(i2))) - } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", i.borrow(), i2.borrow()))) + fn le(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&rhs, &vm.ctx.str_type()) { + Ok(self.value <= get_value(&rhs)) + } else { + Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) + } } -} -fn str_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.str_type())), (i2, None)] - ); + fn hash(self, _vm: &VirtualMachine) -> usize { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + self.value.hash(&mut hasher); + hasher.finish() as usize + } - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.str_type()) { - Ok(vm.ctx.new_bool(v1 <= get_value(i2))) - } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", i.borrow(), i2.borrow()))) + fn len(self, _vm: &VirtualMachine) -> usize { + self.value.chars().count() } -} -fn str_str(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - Ok(s.clone()) -} + fn mul(self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&val, &vm.ctx.int_type()) { + let value = &self.value; + let multiplier = objint::get_value(&val).to_i32().unwrap(); + let mut result = String::new(); + for _x in 0..multiplier { + result.push_str(value.as_str()); + } + Ok(result) + } else { + Err(vm.new_type_error(format!("Cannot multiply {} and {}", self, val))) + } + } -fn count_char(s: &str, c: char) -> usize { - s.chars().filter(|x| *x == c).count() -} + fn str(self, _vm: &VirtualMachine) -> PyStringRef { + self + } -fn str_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(s); - let quote_char = if count_char(&value, '\'') > count_char(&value, '"') { - '"' - } else { - '\'' - }; - let mut formatted = String::new(); - formatted.push(quote_char); - for c in value.chars() { - if c == quote_char || c == '\\' { - formatted.push('\\'); - formatted.push(c); - } else if c == '\n' { - formatted.push('\\'); - formatted.push('n'); - } else if c == '\t' { - formatted.push('\\'); - formatted.push('t'); - } else if c == '\r' { - formatted.push('\\'); - formatted.push('r'); + fn repr(self, _vm: &VirtualMachine) -> String { + let value = &self.value; + let quote_char = if count_char(value, '\'') > count_char(value, '"') { + '"' + } else { + '\'' + }; + let mut formatted = String::new(); + formatted.push(quote_char); + for c in value.chars() { + if c == quote_char || c == '\\' { + formatted.push('\\'); + formatted.push(c); + } else if c == '\n' { + formatted.push('\\'); + formatted.push('n'); + } else if c == '\t' { + formatted.push('\\'); + formatted.push('t'); + } else if c == '\r' { + formatted.push('\\'); + formatted.push('r'); + } else { + formatted.push(c); + } + } + formatted.push(quote_char); + formatted + } + + fn lower(self, _vm: &VirtualMachine) -> String { + self.value.to_lowercase() + } + + // casefold is much more aggressive than lower + fn casefold(self, _vm: &VirtualMachine) -> String { + caseless::default_case_fold_str(&self.value) + } + + fn upper(self, _vm: &VirtualMachine) -> String { + self.value.to_uppercase() + } + + fn capitalize(self, _vm: &VirtualMachine) -> String { + let (first_part, lower_str) = self.value.split_at(1); + format!("{}{}", first_part.to_uppercase(), lower_str) + } + + fn split( + self, + pattern: OptionalArg, + num: OptionalArg, + vm: &VirtualMachine, + ) -> PyObjectRef { + let value = &self.value; + let pattern = match pattern { + OptionalArg::Present(ref s) => &s.value, + OptionalArg::Missing => " ", + }; + let num_splits = num + .into_option() + .unwrap_or_else(|| value.split(pattern).count()); + let elements = value + .splitn(num_splits + 1, pattern) + .map(|o| vm.ctx.new_str(o.to_string())) + .collect(); + vm.ctx.new_list(elements) + } + + fn rsplit( + self, + pattern: OptionalArg, + num: OptionalArg, + vm: &VirtualMachine, + ) -> PyObjectRef { + let value = &self.value; + let pattern = match pattern { + OptionalArg::Present(ref s) => &s.value, + OptionalArg::Missing => " ", + }; + let num_splits = num + .into_option() + .unwrap_or_else(|| value.split(pattern).count()); + let elements = value + .rsplitn(num_splits + 1, pattern) + .map(|o| vm.ctx.new_str(o.to_string())) + .collect(); + vm.ctx.new_list(elements) + } + + fn strip(self, _vm: &VirtualMachine) -> String { + self.value.trim().to_string() + } + + fn lstrip(self, _vm: &VirtualMachine) -> String { + self.value.trim_start().to_string() + } + + fn rstrip(self, _vm: &VirtualMachine) -> String { + self.value.trim_end().to_string() + } + + fn endswith( + self, + suffix: PyStringRef, + start: OptionalArg, + end: OptionalArg, + _vm: &VirtualMachine, + ) -> bool { + if let Some((start, end)) = adjust_indices(start, end, self.value.len()) { + self.value[start..end].ends_with(&suffix.value) + } else { + false + } + } + + fn startswith( + self, + prefix: PyStringRef, + start: OptionalArg, + end: OptionalArg, + _vm: &VirtualMachine, + ) -> bool { + if let Some((start, end)) = adjust_indices(start, end, self.value.len()) { + self.value[start..end].starts_with(&prefix.value) } else { - formatted.push(c); + false + } + } + + fn isalnum(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) + } + + fn isnumeric(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(char::is_numeric) + } + + fn isdigit(self, _vm: &VirtualMachine) -> bool { + // python's isdigit also checks if exponents are digits, these are the unicodes for exponents + let valid_unicodes: [u16; 10] = [ + 0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079, + ]; + + if self.value.is_empty() { + false + } else { + self.value + .chars() + .filter(|c| !c.is_digit(10)) + .all(|c| valid_unicodes.contains(&(c as u16))) + } + } + + fn isdecimal(self, _vm: &VirtualMachine) -> bool { + if self.value.is_empty() { + false + } else { + self.value.chars().all(|c| c.is_ascii_digit()) + } + } + + fn title(self, _vm: &VirtualMachine) -> String { + make_title(&self.value) + } + + fn swapcase(self, _vm: &VirtualMachine) -> String { + let mut swapped_str = String::with_capacity(self.value.len()); + for c in self.value.chars() { + // to_uppercase returns an iterator, to_ascii_uppercase returns the char + if c.is_lowercase() { + swapped_str.push(c.to_ascii_uppercase()); + } else if c.is_uppercase() { + swapped_str.push(c.to_ascii_lowercase()); + } else { + swapped_str.push(c); + } + } + swapped_str + } + + fn isalpha(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) + } + + fn replace( + self, + old: Self, + new: Self, + num: OptionalArg, + _vm: &VirtualMachine, + ) -> String { + match num.into_option() { + Some(num) => self.value.replacen(&old.value, &new.value, num), + None => self.value.replace(&old.value, &new.value), + } + } + + // cpython's isspace ignores whitespace, including \t and \n, etc, unless the whole string is empty + // which is why isspace is using is_ascii_whitespace. Same for isupper & islower + fn isspace(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii_whitespace()) + } + + fn isupper(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() + && self + .value + .chars() + .filter(|x| !x.is_ascii_whitespace()) + .all(char::is_uppercase) + } + + fn islower(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() + && self + .value + .chars() + .filter(|x| !x.is_ascii_whitespace()) + .all(char::is_lowercase) + } + + fn isascii(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii()) + } + + // doesn't implement keep new line delimiter just yet + fn splitlines(self, vm: &VirtualMachine) -> PyObjectRef { + let elements = self + .value + .split('\n') + .map(|e| vm.ctx.new_str(e.to_string())) + .collect(); + vm.ctx.new_list(elements) + } + + fn join(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + let mut joined = String::new(); + + for (idx, elem) in iterable.iter(vm)?.enumerate() { + let elem = elem?; + if idx != 0 { + joined.push_str(&self.value); + } + joined.push_str(&elem.value) + } + + Ok(joined) + } + + fn find( + self, + sub: Self, + start: OptionalArg, + end: OptionalArg, + _vm: &VirtualMachine, + ) -> isize { + let value = &self.value; + if let Some((start, end)) = adjust_indices(start, end, value.len()) { + match value[start..end].find(&sub.value) { + Some(num) => (start + num) as isize, + None => -1 as isize, + } + } else { + -1 as isize + } + } + + fn rfind( + self, + sub: Self, + start: OptionalArg, + end: OptionalArg, + _vm: &VirtualMachine, + ) -> isize { + let value = &self.value; + if let Some((start, end)) = adjust_indices(start, end, value.len()) { + match value[start..end].rfind(&sub.value) { + Some(num) => (start + num) as isize, + None => -1 as isize, + } + } else { + -1 as isize + } + } + + fn index( + self, + sub: Self, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let value = &self.value; + if let Some((start, end)) = adjust_indices(start, end, value.len()) { + match value[start..end].find(&sub.value) { + Some(num) => Ok(start + num), + None => Err(vm.new_value_error("substring not found".to_string())), + } + } else { + Err(vm.new_value_error("substring not found".to_string())) + } + } + + fn rindex( + self, + sub: Self, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let value = &self.value; + if let Some((start, end)) = adjust_indices(start, end, value.len()) { + match value[start..end].rfind(&sub.value) { + Some(num) => Ok(start + num), + None => Err(vm.new_value_error("substring not found".to_string())), + } + } else { + Err(vm.new_value_error("substring not found".to_string())) + } + } + + fn partition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { + let value = &self.value; + let sub = &sub.value; + let mut new_tup = Vec::new(); + if value.contains(sub) { + new_tup = value + .splitn(2, sub) + .map(|s| vm.ctx.new_str(s.to_string())) + .collect(); + new_tup.insert(1, vm.ctx.new_str(sub.clone())); + } else { + new_tup.push(vm.ctx.new_str(value.clone())); + new_tup.push(vm.ctx.new_str("".to_string())); + new_tup.push(vm.ctx.new_str("".to_string())); + } + vm.ctx.new_tuple(new_tup) + } + + fn rpartition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { + let value = &self.value; + let sub = &sub.value; + let mut new_tup = Vec::new(); + if value.contains(sub) { + new_tup = value + .rsplitn(2, sub) + .map(|s| vm.ctx.new_str(s.to_string())) + .collect(); + new_tup.swap(0, 1); // so it's in the right order + new_tup.insert(1, vm.ctx.new_str(sub.clone())); + } else { + new_tup.push(vm.ctx.new_str(value.clone())); + new_tup.push(vm.ctx.new_str("".to_string())); + new_tup.push(vm.ctx.new_str("".to_string())); + } + vm.ctx.new_tuple(new_tup) + } + + fn istitle(self, _vm: &VirtualMachine) -> bool { + if self.value.is_empty() { + false + } else { + self.value.split(' ').all(|word| word == make_title(word)) + } + } + + fn count( + self, + sub: Self, + start: OptionalArg, + end: OptionalArg, + _vm: &VirtualMachine, + ) -> usize { + let value = &self.value; + if let Some((start, end)) = adjust_indices(start, end, value.len()) { + self.value[start..end].matches(&sub.value).count() + } else { + 0 + } + } + + fn zfill(self, len: usize, _vm: &VirtualMachine) -> String { + let value = &self.value; + if len <= value.len() { + value.to_string() + } else { + format!("{}{}", "0".repeat(len - value.len()), value) + } + } + + fn get_fill_char<'a>(rep: &'a OptionalArg, vm: &VirtualMachine) -> PyResult<&'a str> { + let rep_str = match rep { + OptionalArg::Present(ref st) => &st.value, + OptionalArg::Missing => " ", + }; + if rep_str.len() == 1 { + Ok(rep_str) + } else { + Err(vm.new_type_error( + "The fill character must be exactly one character long".to_string(), + )) + } + } + + fn ljust(self, len: usize, rep: OptionalArg, vm: &VirtualMachine) -> PyResult { + let value = &self.value; + let rep_char = PyStringRef::get_fill_char(&rep, vm)?; + Ok(format!("{}{}", value, rep_char.repeat(len))) + } + + fn rjust(self, len: usize, rep: OptionalArg, vm: &VirtualMachine) -> PyResult { + let value = &self.value; + let rep_char = PyStringRef::get_fill_char(&rep, vm)?; + Ok(format!("{}{}", rep_char.repeat(len), value)) + } + + fn center(self, len: usize, rep: OptionalArg, vm: &VirtualMachine) -> PyResult { + let value = &self.value; + let rep_char = PyStringRef::get_fill_char(&rep, vm)?; + let left_buff: usize = (len - value.len()) / 2; + let right_buff = len - value.len() - left_buff; + Ok(format!( + "{}{}{}", + rep_char.repeat(left_buff), + value, + rep_char.repeat(right_buff) + )) + } + + fn expandtabs(self, tab_stop: OptionalArg, _vm: &VirtualMachine) -> String { + let tab_stop = tab_stop.into_option().unwrap_or(8 as usize); + let mut expanded_str = String::new(); + let mut tab_size = tab_stop; + let mut col_count = 0 as usize; + for ch in self.value.chars() { + // 0x0009 is tab + if ch == 0x0009 as char { + let num_spaces = tab_size - col_count; + col_count += num_spaces; + let expand = " ".repeat(num_spaces); + expanded_str.push_str(&expand); + } else { + expanded_str.push(ch); + col_count += 1; + } + if col_count >= tab_size { + tab_size += tab_stop; + } + } + expanded_str + } + + fn isidentifier(self, _vm: &VirtualMachine) -> bool { + let value = &self.value; + // a string is not an identifier if it has whitespace or starts with a number + if !value.chars().any(|c| c.is_ascii_whitespace()) + && !value.chars().nth(0).unwrap().is_digit(10) + { + for c in value.chars() { + if c != "_".chars().nth(0).unwrap() && !c.is_digit(10) && !c.is_alphabetic() { + return false; + } + } + true + } else { + false } } - formatted.push(quote_char); - Ok(vm.ctx.new_str(formatted)) } -fn str_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (s2, None)] - ); - if objtype::isinstance(s2, &vm.ctx.str_type()) { - Ok(vm - .ctx - .new_str(format!("{}{}", get_value(&s), get_value(&s2)))) - } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", s.borrow(), s2.borrow()))) +impl PyValue for PyString { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.str_type() } } -fn str_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +impl IntoPyObject for String { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_str(self)) + } +} + +#[rustfmt::skip] // to avoid line splitting +pub fn init(context: &PyContext) { + let str_type = &context.str_type; + let str_doc = "str(object='') -> str\n\ + str(bytes_or_buffer[, encoding[, errors]]) -> str\n\ + \n\ + Create a new string object from the given object. If encoding or\n\ + errors is specified, then the object must expose a data buffer\n\ + that will be decoded using the given encoding and error handler.\n\ + Otherwise, returns the result of object.__str__() (if defined)\n\ + or repr(object).\n\ + encoding defaults to sys.getdefaultencoding().\n\ + errors defaults to 'strict'."; + context.set_attr(&str_type, "__add__", context.new_rustfunc(PyStringRef::add)); + context.set_attr(&str_type, "__eq__", context.new_rustfunc(PyStringRef::eq)); + context.set_attr(&str_type, "__contains__", context.new_rustfunc(PyStringRef::contains)); + context.set_attr(&str_type, "__getitem__", context.new_rustfunc(PyStringRef::getitem)); + context.set_attr(&str_type, "__gt__", context.new_rustfunc(PyStringRef::gt)); + context.set_attr(&str_type, "__ge__", context.new_rustfunc(PyStringRef::ge)); + context.set_attr(&str_type, "__lt__", context.new_rustfunc(PyStringRef::lt)); + context.set_attr(&str_type, "__le__", context.new_rustfunc(PyStringRef::le)); + context.set_attr(&str_type, "__hash__", context.new_rustfunc(PyStringRef::hash)); + context.set_attr(&str_type, "__len__", context.new_rustfunc(PyStringRef::len)); + context.set_attr(&str_type, "__mul__", context.new_rustfunc(PyStringRef::mul)); + context.set_attr(&str_type, "__new__", context.new_rustfunc(str_new)); + context.set_attr(&str_type, "__str__", context.new_rustfunc(PyStringRef::str)); + context.set_attr(&str_type, "__repr__", context.new_rustfunc(PyStringRef::repr)); + context.set_attr(&str_type, "format", context.new_rustfunc(str_format)); + context.set_attr(&str_type, "lower", context.new_rustfunc(PyStringRef::lower)); + context.set_attr(&str_type, "casefold", context.new_rustfunc(PyStringRef::casefold)); + context.set_attr(&str_type, "upper", context.new_rustfunc(PyStringRef::upper)); + context.set_attr(&str_type, "capitalize", context.new_rustfunc(PyStringRef::capitalize)); + context.set_attr(&str_type, "split", context.new_rustfunc(PyStringRef::split)); + context.set_attr(&str_type, "rsplit", context.new_rustfunc(PyStringRef::rsplit)); + context.set_attr(&str_type, "strip", context.new_rustfunc(PyStringRef::strip)); + context.set_attr(&str_type, "lstrip", context.new_rustfunc(PyStringRef::lstrip)); + context.set_attr(&str_type, "rstrip", context.new_rustfunc(PyStringRef::rstrip)); + context.set_attr(&str_type, "endswith", context.new_rustfunc(PyStringRef::endswith)); + context.set_attr(&str_type, "startswith", context.new_rustfunc(PyStringRef::startswith)); + context.set_attr(&str_type, "isalnum", context.new_rustfunc(PyStringRef::isalnum)); + context.set_attr(&str_type, "isnumeric", context.new_rustfunc(PyStringRef::isnumeric)); + context.set_attr(&str_type, "isdigit", context.new_rustfunc(PyStringRef::isdigit)); + context.set_attr(&str_type, "isdecimal", context.new_rustfunc(PyStringRef::isdecimal)); + context.set_attr(&str_type, "title", context.new_rustfunc(PyStringRef::title)); + context.set_attr(&str_type, "swapcase", context.new_rustfunc(PyStringRef::swapcase)); + context.set_attr(&str_type, "isalpha", context.new_rustfunc(PyStringRef::isalpha)); + context.set_attr(&str_type, "replace", context.new_rustfunc(PyStringRef::replace)); + context.set_attr(&str_type, "isspace", context.new_rustfunc(PyStringRef::isspace)); + context.set_attr(&str_type, "isupper", context.new_rustfunc(PyStringRef::isupper)); + context.set_attr(&str_type, "islower", context.new_rustfunc(PyStringRef::islower)); + context.set_attr(&str_type, "isascii", context.new_rustfunc(PyStringRef::isascii)); + context.set_attr(&str_type, "splitlines", context.new_rustfunc(PyStringRef::splitlines)); + context.set_attr(&str_type, "join", context.new_rustfunc(PyStringRef::join)); + context.set_attr(&str_type, "find", context.new_rustfunc(PyStringRef::find)); + context.set_attr(&str_type, "rfind", context.new_rustfunc(PyStringRef::rfind)); + context.set_attr(&str_type, "index", context.new_rustfunc(PyStringRef::index)); + context.set_attr(&str_type, "rindex", context.new_rustfunc(PyStringRef::rindex)); + context.set_attr(&str_type, "partition", context.new_rustfunc(PyStringRef::partition)); + context.set_attr(&str_type, "rpartition", context.new_rustfunc(PyStringRef::rpartition)); + context.set_attr(&str_type, "istitle", context.new_rustfunc(PyStringRef::istitle)); + context.set_attr(&str_type, "count", context.new_rustfunc(PyStringRef::count)); + context.set_attr(&str_type, "zfill", context.new_rustfunc(PyStringRef::zfill)); + context.set_attr(&str_type, "ljust", context.new_rustfunc(PyStringRef::ljust)); + context.set_attr(&str_type, "rjust", context.new_rustfunc(PyStringRef::rjust)); + context.set_attr(&str_type, "center", context.new_rustfunc(PyStringRef::center)); + context.set_attr(&str_type, "expandtabs", context.new_rustfunc(PyStringRef::expandtabs)); + context.set_attr(&str_type, "isidentifier", context.new_rustfunc(PyStringRef::isidentifier)); + context.set_attr(&str_type, "__doc__", context.new_str(str_doc.to_string())); +} + +pub fn get_value(obj: &PyObjectRef) -> String { + obj.payload::().unwrap().value.clone() +} + +pub fn borrow_value(obj: &PyObjectRef) -> &str { + &obj.payload::().unwrap().value +} + +fn count_char(s: &str, c: char) -> usize { + s.chars().filter(|x| *x == c).count() +} + +fn str_format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if args.args.is_empty() { return Err( vm.new_type_error("descriptor 'format' of 'str' object needs an argument".to_string()) @@ -279,11 +726,7 @@ fn str_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn call_object_format( - vm: &mut VirtualMachine, - argument: PyObjectRef, - format_spec: &str, -) -> PyResult { +fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: &str) -> PyResult { let returned_type = vm.ctx.new_str(format_spec.to_string()); let result = vm.call_method(&argument, "__format__", vec![returned_type])?; if !objtype::isinstance(&result, &vm.ctx.str_type()) { @@ -295,7 +738,7 @@ fn call_object_format( } fn perform_format( - vm: &mut VirtualMachine, + vm: &VirtualMachine, format_string: &FormatString, arguments: &PyFuncArgs, ) -> PyResult { @@ -346,691 +789,24 @@ fn perform_format( Ok(vm.ctx.new_str(final_string)) } -fn str_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.str_type()))]); - let value = get_value(zelf); - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - value.hash(&mut hasher); - let hash = hasher.finish(); - Ok(vm.ctx.new_int(hash)) -} - -fn str_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let sv = get_value(s); - Ok(vm.ctx.new_int(sv.chars().count())) -} - -fn str_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (s2, None)] - ); - if objtype::isinstance(s2, &vm.ctx.int_type()) { - let value1 = get_value(&s); - let value2 = objint::get_value(s2).to_i32().unwrap(); - let mut result = String::new(); - for _x in 0..value2 { - result.push_str(value1.as_str()); - } - Ok(vm.ctx.new_str(result)) - } else { - Err(vm.new_type_error(format!( - "Cannot multiply {} and {}", - s.borrow(), - s2.borrow() - ))) - } -} - -fn str_upper(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s).to_uppercase(); - Ok(vm.ctx.new_str(value)) -} - -fn str_lower(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s).to_lowercase(); - Ok(vm.ctx.new_str(value)) -} - -fn str_capitalize(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - let (first_part, lower_str) = value.split_at(1); - let capitalized = format!("{}{}", first_part.to_uppercase(), lower_str); - Ok(vm.ctx.new_str(capitalized)) -} - -fn str_rsplit(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type()))], - optional = [ - (pat, Some(vm.ctx.str_type())), - (num, Some(vm.ctx.int_type())) - ] - ); - let value = get_value(&s); - let pat = match pat { - Some(s) => get_value(&s), - None => " ".to_string(), - }; - let num_splits = match num { - Some(n) => objint::get_value(&n).to_usize().unwrap(), - None => value.split(&pat).count(), - }; - let elements = value - .rsplitn(num_splits + 1, &pat) - .map(|o| vm.ctx.new_str(o.to_string())) - .collect(); - Ok(vm.ctx.new_list(elements)) -} - -fn str_split(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type()))], - optional = [ - (pat, Some(vm.ctx.str_type())), - (num, Some(vm.ctx.int_type())) - ] - ); - let value = get_value(&s); - let pat = match pat { - Some(s) => get_value(&s), - None => " ".to_string(), - }; - let num_splits = match num { - Some(n) => objint::get_value(&n).to_usize().unwrap(), - None => value.split(&pat).count(), - }; - let elements = value - .splitn(num_splits + 1, &pat) - .map(|o| vm.ctx.new_str(o.to_string())) - .collect(); - Ok(vm.ctx.new_list(elements)) -} - -fn str_strip(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s).trim().to_string(); - Ok(vm.ctx.new_str(value)) -} - -fn str_lstrip(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s).trim_start().to_string(); - Ok(vm.ctx.new_str(value)) -} - -fn str_rstrip(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s).trim_end().to_string(); - Ok(vm.ctx.new_str(value)) -} - -fn str_endswith(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (pat, Some(vm.ctx.str_type()))] - ); - let value = get_value(&s); - let pat = get_value(&pat); - Ok(vm.ctx.new_bool(value.ends_with(pat.as_str()))) -} - -fn str_isidentifier(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - let mut is_identifier: bool = true; - // a string is not an identifier if it has whitespace or starts with a number - if !value.chars().any(|c| c.is_ascii_whitespace()) - && !value.chars().nth(0).unwrap().is_digit(10) - { - for c in value.chars() { - if c != "_".chars().nth(0).unwrap() && !c.is_digit(10) && !c.is_alphabetic() { - is_identifier = false; - } - } - } else { - is_identifier = false; - } - Ok(vm.ctx.new_bool(is_identifier)) -} - -// cpython's isspace ignores whitespace, including \t and \n, etc, unless the whole string is empty -// which is why isspace is using is_ascii_whitespace. Same for isupper & islower -fn str_isspace(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - Ok(vm - .ctx - .new_bool(!value.is_empty() && value.chars().all(|c| c.is_ascii_whitespace()))) -} - -fn str_isupper(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - Ok(vm.ctx.new_bool( - !value.is_empty() - && value - .chars() - .filter(|x| !x.is_ascii_whitespace()) - .all(|c| c.is_uppercase()), - )) -} - -fn str_islower(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - Ok(vm.ctx.new_bool( - !value.is_empty() - && value - .chars() - .filter(|x| !x.is_ascii_whitespace()) - .all(|c| c.is_lowercase()), - )) -} - -// doesn't implement keep new line delimiter just yet -fn str_splitlines(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let elements = get_value(&s) - .split('\n') - .map(|e| vm.ctx.new_str(e.to_string())) - .collect(); - Ok(vm.ctx.new_list(elements)) -} - -fn str_zfill(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (len, Some(vm.ctx.int_type()))] - ); - let value = get_value(&s); - let len = objint::get_value(&len).to_usize().unwrap(); - let new_str = if len <= value.len() { - value - } else { - format!("{}{}", "0".repeat(len - value.len()), value) - }; - Ok(vm.ctx.new_str(new_str)) -} - -fn str_join(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (iterable, None)] - ); - let value = get_value(&s); - let elements: Vec = vm - .extract_elements(iterable)? - .iter() - .map(|w| get_value(&w)) - .collect(); - let joined = elements.join(&value); - Ok(vm.ctx.new_str(joined)) -} - -fn str_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (sub, Some(vm.ctx.str_type()))], - optional = [ - (start, Some(vm.ctx.int_type())), - (end, Some(vm.ctx.int_type())) - ] - ); - let value = get_value(&s); - let sub = get_value(&sub); - let (start, end) = match get_slice(start, end, value.len()) { - Ok((start, end)) => (start, end), - Err(e) => return Err(vm.new_index_error(e)), - }; - let num_occur: usize = value[start..end].matches(&sub).count(); - Ok(vm.ctx.new_int(num_occur)) -} - -fn str_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (sub, Some(vm.ctx.str_type()))], - optional = [ - (start, Some(vm.ctx.int_type())), - (end, Some(vm.ctx.int_type())) - ] - ); - let value = get_value(&s); - let sub = get_value(&sub); - let (start, end) = match get_slice(start, end, value.len()) { - Ok((start, end)) => (start, end), - Err(e) => return Err(vm.new_index_error(e)), - }; - let ind: usize = match value[start..=end].find(&sub) { - Some(num) => num, - None => { - return Err(vm.new_value_error("substring not found".to_string())); - } - }; - Ok(vm.ctx.new_int(ind)) -} - -fn str_find(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (sub, Some(vm.ctx.str_type()))], - optional = [ - (start, Some(vm.ctx.int_type())), - (end, Some(vm.ctx.int_type())) - ] - ); - let value = get_value(&s); - let sub = get_value(&sub); - let (start, end) = match get_slice(start, end, value.len()) { - Ok((start, end)) => (start, end), - Err(e) => return Err(vm.new_index_error(e)), - }; - let ind: i128 = match value[start..=end].find(&sub) { - Some(num) => num as i128, - None => -1 as i128, - }; - Ok(vm.ctx.new_int(ind)) -} - -// casefold is much more aggressive than lower -fn str_casefold(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - let folded_str: String = caseless::default_case_fold_str(&value); - Ok(vm.ctx.new_str(folded_str)) -} - -fn str_swapcase(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - let mut swapped_str = String::with_capacity(value.len()); - for c in value.chars() { - // to_uppercase returns an iterator, to_ascii_uppercase returns the char - if c.is_lowercase() { - swapped_str.push(c.to_ascii_uppercase()); - } else if c.is_uppercase() { - swapped_str.push(c.to_ascii_lowercase()); - } else { - swapped_str.push(c); - } - } - Ok(vm.ctx.new_str(swapped_str)) -} - -fn str_replace(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (s, Some(vm.ctx.str_type())), - (old, Some(vm.ctx.str_type())), - (rep, Some(vm.ctx.str_type())) - ], - optional = [(n, Some(vm.ctx.int_type()))] - ); - let s = get_value(&s); - let old_str = get_value(&old); - let rep_str = get_value(&rep); - let num_rep: usize = match n { - Some(num) => objint::get_value(&num).to_usize().unwrap(), - None => 1, - }; - let new_str = s.replacen(&old_str, &rep_str, num_rep); - Ok(vm.ctx.new_str(new_str)) -} - -fn str_expandtabs(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type()))], - optional = [(size, Some(vm.ctx.int_type()))] - ); - let value = get_value(&s); - let tab_stop = match size { - Some(num) => objint::get_value(&num).to_usize().unwrap(), - None => 8 as usize, - }; - let mut expanded_str = String::new(); - let mut tab_size = tab_stop; - let mut col_count = 0 as usize; - for ch in value.chars() { - // 0x0009 is tab - if ch == 0x0009 as char { - let num_spaces = tab_size - col_count; - col_count += num_spaces; - let expand = " ".repeat(num_spaces); - expanded_str.push_str(&expand); - } else { - expanded_str.push(ch); - col_count += 1; - } - if col_count >= tab_size { - tab_size += tab_stop; - } - } - Ok(vm.ctx.new_str(expanded_str)) -} - -fn str_rpartition(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (sub, Some(vm.ctx.str_type()))] - ); - let value = get_value(&s); - let sub = get_value(&sub); - let mut new_tup = Vec::new(); - if value.contains(&sub) { - new_tup = value - .rsplitn(2, &sub) - .map(|s| vm.ctx.new_str(s.to_string())) - .collect(); - new_tup.swap(0, 1); // so it's in the right order - new_tup.insert(1, vm.ctx.new_str(sub)); - } else { - new_tup.push(vm.ctx.new_str(value)); - new_tup.push(vm.ctx.new_str("".to_string())); - new_tup.push(vm.ctx.new_str("".to_string())); - } - Ok(vm.ctx.new_tuple(new_tup)) -} - -fn str_partition(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (sub, Some(vm.ctx.str_type()))] - ); - let value = get_value(&s); - let sub = get_value(&sub); - let mut new_tup = Vec::new(); - if value.contains(&sub) { - new_tup = value - .splitn(2, &sub) - .map(|s| vm.ctx.new_str(s.to_string())) - .collect(); - new_tup.insert(1, vm.ctx.new_str(sub)); - } else { - new_tup.push(vm.ctx.new_str(value)); - new_tup.push(vm.ctx.new_str("".to_string())); - new_tup.push(vm.ctx.new_str("".to_string())); - } - Ok(vm.ctx.new_tuple(new_tup)) -} - -fn str_title(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - Ok(vm.ctx.new_str(make_title(&get_value(&s)))) -} - -fn str_rjust(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (num, Some(vm.ctx.int_type()))], - optional = [(rep, Some(vm.ctx.str_type()))] - ); - let value = get_value(&s); - let num = objint::get_value(&num).to_usize().unwrap(); - let rep = match rep { - Some(st) => { - let rep_str = get_value(&st); - if rep_str.len() == 1 { - rep_str - } else { - return Err(vm.new_type_error( - "The fill character must be exactly one character long".to_string(), - )); - } - } - None => " ".to_string(), - }; - let new_str = format!("{}{}", rep.repeat(num), value); - Ok(vm.ctx.new_str(new_str)) -} - -fn str_ljust(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (num, Some(vm.ctx.int_type()))], - optional = [(rep, Some(vm.ctx.str_type()))] - ); - let value = get_value(&s); - let num = objint::get_value(&num).to_usize().unwrap(); - let rep = match rep { - Some(st) => { - let rep_str = get_value(&st); - if rep_str.len() == 1 { - rep_str - } else { - return Err(vm.new_type_error( - "The fill character must be exactly one character long".to_string(), - )); - } - } - None => " ".to_string(), - }; - let new_str = format!("{}{}", value, rep.repeat(num)); - Ok(vm.ctx.new_str(new_str)) -} - -fn str_istitle(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - - let is_titled = if value.is_empty() { - false - } else { - value.split(' ').all(|word| word == make_title(word)) - }; - - Ok(vm.ctx.new_bool(is_titled)) -} - -fn str_center(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (len, Some(vm.ctx.int_type()))], - optional = [(chars, Some(vm.ctx.str_type()))] - ); - let value = get_value(&s); - let len = objint::get_value(&len).to_usize().unwrap(); - let rep_char = match chars { - Some(c) => get_value(&c), - None => " ".to_string(), - }; - let left_buff: usize = (len - value.len()) / 2; - let right_buff = len - value.len() - left_buff; - let new_str = format!( - "{}{}{}", - rep_char.repeat(left_buff), - value, - rep_char.repeat(right_buff) - ); - Ok(vm.ctx.new_str(new_str)) -} - -fn str_startswith(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (pat, Some(vm.ctx.str_type()))] - ); - let value = get_value(&s); - let pat = get_value(&pat); - Ok(vm.ctx.new_bool(value.starts_with(pat.as_str()))) -} - -fn str_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (s, Some(vm.ctx.str_type())), - (needle, Some(vm.ctx.str_type())) - ] - ); - let value = get_value(&s); - let needle = get_value(&needle); - Ok(vm.ctx.new_bool(value.contains(needle.as_str()))) -} - -fn str_isalnum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - Ok(vm - .ctx - .new_bool(!value.is_empty() && value.chars().all(|c| c.is_alphanumeric()))) -} - -fn str_isascii(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - Ok(vm - .ctx - .new_bool(!value.is_empty() && value.chars().all(|c| c.is_ascii()))) -} - -fn str_rindex(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (sub, Some(vm.ctx.str_type()))], - optional = [ - (start, Some(vm.ctx.int_type())), - (end, Some(vm.ctx.int_type())) - ] - ); - let value = get_value(&s); - let sub = get_value(&sub); - let (start, end) = match get_slice(start, end, value.len()) { - Ok((start, end)) => (start, end), - Err(e) => return Err(vm.new_index_error(e)), - }; - let ind: i64 = match value[start..=end].rfind(&sub) { - Some(num) => num as i64, - None => { - return Err(vm.new_value_error("substring not found".to_string())); - } - }; - Ok(vm.ctx.new_int(ind)) -} - -fn str_rfind(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (sub, Some(vm.ctx.str_type()))], - optional = [ - (start, Some(vm.ctx.int_type())), - (end, Some(vm.ctx.int_type())) - ] - ); - let value = get_value(&s); - let sub = get_value(&sub); - let (start, end) = match get_slice(start, end, value.len()) { - Ok((start, end)) => (start, end), - Err(e) => return Err(vm.new_index_error(e)), - }; - let ind = match value[start..=end].rfind(&sub) { - Some(num) => num as i128, - None => -1 as i128, - }; - Ok(vm.ctx.new_int(ind)) -} - -fn str_isnumeric(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - Ok(vm - .ctx - .new_bool(!value.is_empty() && value.chars().all(|c| c.is_numeric()))) -} - -fn str_isalpha(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - Ok(vm - .ctx - .new_bool(!value.is_empty() && value.chars().all(|c| c.is_alphanumeric()))) -} - -fn str_isdigit(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s); - // python's isdigit also checks if exponents are digits, these are the unicodes for exponents - let valid_unicodes: [u16; 10] = [ - 0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079, - ]; - - let is_digit = if value.is_empty() { - false - } else { - value - .chars() - .filter(|c| !c.is_digit(10)) - .all(|c| valid_unicodes.contains(&(c as u16))) - }; - - Ok(vm.ctx.new_bool(is_digit)) -} - -fn str_isdecimal(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - - let value = get_value(&s); - - let is_decimal = if !value.is_empty() { - value.chars().all(|c| c.is_ascii_digit()) - } else { - false - }; - - Ok(vm.ctx.new_bool(is_decimal)) -} - -fn str_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (needle, None)] - ); - let value = get_value(&s); - subscript(vm, &value, needle.clone()) -} - // TODO: should with following format // class str(object='') // class str(object=b'', encoding='utf-8', errors='strict') -fn str_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - if args.args.len() == 1 { - return Ok(vm.new_str("".to_string())); - } - - if args.args.len() > 2 { - panic!("str expects exactly one parameter"); +fn str_new( + cls: PyClassRef, + object: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let string = match object { + OptionalArg::Present(ref input) => vm.to_str(input)?.into_object(), + OptionalArg::Missing => vm.new_str("".to_string()), }; - - vm.to_str(&args.args[1]) + if string.typ().is(&cls) { + TryFromObject::try_from_object(vm, string) + } else { + let payload = string.payload::().unwrap(); + payload.clone().into_ref_with_type(vm, cls) + } } impl PySliceableSequence for String { @@ -1095,7 +871,7 @@ fn to_graphemes>(value: S) -> Vec { .collect() } -pub fn subscript(vm: &mut VirtualMachine, value: &str, b: PyObjectRef) -> PyResult { +pub fn subscript(vm: &VirtualMachine, value: &str, b: PyObjectRef) -> PyResult { if objtype::isinstance(&b, &vm.ctx.int_type()) { match objint::get_value(&b).to_i32() { Some(pos) => { @@ -1110,38 +886,43 @@ pub fn subscript(vm: &mut VirtualMachine, value: &str, b: PyObjectRef) -> PyResu Err(vm.new_index_error("cannot fit 'int' into an index-sized integer".to_string())) } } + } else if b.payload::().is_some() { + let string = value.to_string().get_slice_items(vm, &b)?; + Ok(vm.new_str(string)) } else { - match (*b.borrow()).payload { - PyObjectPayload::Slice { .. } => { - let string = value.to_string().get_slice_items(vm, &b)?; - Ok(vm.new_str(string)) - } - _ => panic!( - "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", - value, b - ), - } + panic!( + "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", + value, b + ) } } // help get optional string indices -fn get_slice( - start: Option<&std::rc::Rc>>, - end: Option<&std::rc::Rc>>, +fn adjust_indices( + start: OptionalArg, + end: OptionalArg, len: usize, -) -> Result<(usize, usize), String> { - let start_idx = match start { - Some(int) => objint::get_value(&int).to_usize().unwrap(), - None => 0 as usize, - }; - let end_idx = match end { - Some(int) => objint::get_value(&int).to_usize().unwrap(), - None => len - 1, - }; - if start_idx >= usize::min_value() && start_idx < end_idx && end_idx < len { - Ok((start_idx, end_idx)) +) -> Option<(usize, usize)> { + let mut start = start.into_option().unwrap_or(0); + let mut end = end.into_option().unwrap_or(len as isize); + if end > len as isize { + end = len as isize; + } else if end < 0 { + end += len as isize; + if end < 0 { + end = 0; + } + } + if start < 0 { + start += len as isize; + if start < 0 { + start = 0; + } + } + if start > end { + None } else { - Err("provided index is not valid".to_string()) + Some((start as usize, end as usize)) } } diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 79c516d4c0..2b2be02414 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -6,10 +6,29 @@ https://github.com/python/cpython/blob/50b48572d9a90c5bb36e2bef6179548ea927a35a/ */ -use super::objtype; -use crate::pyobject::{PyContext, PyFuncArgs, PyResult, TypeProtocol}; +use crate::frame::NameProtocol; +use crate::function::PyFuncArgs; +use crate::obj::objstr; +use crate::obj::objtype::PyClass; +use crate::pyobject::{ + DictProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, +}; use crate::vm::VirtualMachine; +use super::objtype; + +#[derive(Debug)] +pub struct PySuper { + obj: PyObjectRef, + typ: PyObjectRef, +} + +impl PyValue for PySuper { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.super_type() + } +} + pub fn init(context: &PyContext) { let super_type = &context.super_type; @@ -27,7 +46,12 @@ pub fn init(context: &PyContext) { def cmeth(cls, arg):\n \ super().cmeth(arg)\n"; - context.set_attr(&super_type, "__init__", context.new_rustfunc(super_init)); + context.set_attr(&super_type, "__new__", context.new_rustfunc(super_new)); + context.set_attr( + &super_type, + "__getattribute__", + context.new_rustfunc(super_getattribute), + ); context.set_attr( &super_type, "__doc__", @@ -35,24 +59,61 @@ pub fn init(context: &PyContext) { ); } -fn super_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("super.__init__ {:?}", args.args); +fn super_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, - required = [(inst, None)], + required = [ + (super_obj, Some(vm.ctx.super_type())), + (name_str, Some(vm.ctx.str_type())) + ] + ); + + let inst = super_obj.payload::().unwrap().obj.clone(); + let typ = super_obj.payload::().unwrap().typ.clone(); + + match typ.payload::() { + Some(PyClass { ref mro, .. }) => { + for class in mro { + if let Ok(item) = vm.get_attribute(class.as_object().clone(), name_str.clone()) { + return Ok(vm.ctx.new_bound_method(item, inst.clone())); + } + } + Err(vm.new_attribute_error(format!( + "{} has no attribute '{}'", + inst, + objstr::get_value(name_str) + ))) + } + _ => panic!("not Class"), + } +} + +fn super_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + trace!("super.__new__ {:?}", args.args); + arg_check!( + vm, + args, + required = [(cls, None)], optional = [(py_type, None), (py_obj, None)] ); + if !objtype::issubclass(cls, &vm.ctx.super_type()) { + return Err(vm.new_type_error(format!("{:?} is not a subtype of super", cls))); + } + // Get the type: let py_type = if let Some(ty) = py_type { ty.clone() } else { - // TODO: implement complex logic here.... - unimplemented!("TODO: get frame and determine instance and class?"); - // let frame = vm.get_current_frame(); - // - // vm.get_none() + match vm.current_scope().load_cell(vm, "__class__") { + Some(obj) => obj.clone(), + _ => { + return Err(vm.new_type_error( + "super must be called with 1 argument or from inside class method".to_string(), + )); + } + } }; // Check type argument: @@ -68,7 +129,20 @@ fn super_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let py_obj = if let Some(obj) = py_obj { obj.clone() } else { - vm.get_none() + let frame = vm.current_frame(); + if let Some(first_arg) = frame.code.arg_names.get(0) { + match vm.get_locals().get_item(first_arg) { + Some(obj) => obj.clone(), + _ => { + return Err(vm + .new_type_error(format!("super arguement {} was not supplied", first_arg))); + } + } + } else { + return Err(vm.new_type_error( + "super must be called with 1 argument or from inside class method".to_string(), + )); + } }; // Check obj type: @@ -78,9 +152,11 @@ fn super_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { )); } - // TODO: how to store those types? - vm.ctx.set_attr(inst, "type", py_type); - vm.ctx.set_attr(inst, "obj", py_obj); - - Ok(vm.get_none()) + Ok(PyObject::new( + PySuper { + obj: py_obj, + typ: py_type, + }, + cls.clone(), + )) } diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 81e3b99598..7438e1c90a 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -1,328 +1,253 @@ +use std::cell::{Cell, RefCell}; +use std::fmt; +use std::hash::{Hash, Hasher}; + +use crate::function::OptionalArg; +use crate::pyobject::{ + IdProtocol, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, +}; +use crate::vm::{ReprGuard, VirtualMachine}; + use super::objbool; use super::objint; use super::objsequence::{ get_elements, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, }; -use super::objstr; -use super::objtype; -use crate::pyobject::{PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyResult, TypeProtocol}; -use crate::vm::{ReprGuard, VirtualMachine}; -use std::hash::{Hash, Hasher}; - -fn tuple_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] - ); - - let result = if objtype::isinstance(other, &vm.ctx.tuple_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_lt(vm, &zelf, &other)? - } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '<'", - zelf.borrow(), - other.borrow() - ))); - }; - - Ok(vm.ctx.new_bool(result)) -} - -fn tuple_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] - ); - - let result = if objtype::isinstance(other, &vm.ctx.tuple_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_gt(vm, &zelf, &other)? - } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '>'", - zelf.borrow(), - other.borrow() - ))); - }; - - Ok(vm.ctx.new_bool(result)) -} - -fn tuple_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] - ); - - let result = if objtype::isinstance(other, &vm.ctx.tuple_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_ge(vm, &zelf, &other)? - } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '>='", - zelf.borrow(), - other.borrow() - ))); - }; +use super::objtype::{self, PyClassRef}; - Ok(vm.ctx.new_bool(result)) +#[derive(Default)] +pub struct PyTuple { + // TODO: shouldn't be public + // TODO: tuples are immutable, remove this RefCell + pub elements: RefCell>, } -fn tuple_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] - ); - - let result = if objtype::isinstance(other, &vm.ctx.tuple_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_le(vm, &zelf, &other)? - } else { - return Err(vm.new_type_error(format!( - "Cannot compare {} and {} using '<='", - zelf.borrow(), - other.borrow() - ))); - }; - - Ok(vm.ctx.new_bool(result)) -} - -fn tuple_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] - ); - - if objtype::isinstance(other, &vm.ctx.tuple_type()) { - let e1 = get_elements(zelf); - let e2 = get_elements(other); - let elements = e1.iter().chain(e2.iter()).cloned().collect(); - Ok(vm.ctx.new_tuple(elements)) - } else { - Err(vm.new_type_error(format!( - "Cannot add {} and {}", - zelf.borrow(), - other.borrow() - ))) +impl fmt::Debug for PyTuple { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: implement more informational, non-recursive Debug formatter + f.write_str("tuple") } } -fn tuple_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.tuple_type())), (value, None)] - ); - let elements = get_elements(zelf); - let mut count: usize = 0; - for element in elements.iter() { - let is_eq = vm._eq(element.clone(), value.clone())?; - if objbool::boolval(vm, is_eq)? { - count += 1; +impl From> for PyTuple { + fn from(elements: Vec) -> Self { + PyTuple { + elements: RefCell::new(elements), } } - Ok(vm.context().new_int(count)) -} - -fn tuple_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] - ); - - let result = if objtype::isinstance(other, &vm.ctx.tuple_type()) { - let zelf = get_elements(zelf); - let other = get_elements(other); - seq_equal(vm, &zelf, &other)? - } else { - false - }; - Ok(vm.ctx.new_bool(result)) } -fn tuple_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.tuple_type()))]); - let elements = get_elements(zelf); - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - for element in elements.iter() { - let element_hash = objint::get_value(&vm.call_method(element, "__hash__", vec![])?); - element_hash.hash(&mut hasher); +impl PyValue for PyTuple { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.tuple_type() } - let hash = hasher.finish(); - Ok(vm.ctx.new_int(hash)) } -fn tuple_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(tuple, Some(vm.ctx.tuple_type()))]); +pub type PyTupleRef = PyRef; - let iter_obj = PyObject::new( - PyObjectPayload::Iterator { - position: 0, - iterated_obj: tuple.clone(), - }, - vm.ctx.iter_type(), - ); - - Ok(iter_obj) -} - -fn tuple_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.tuple_type()))]); - let elements = get_elements(zelf); - Ok(vm.context().new_int(elements.len())) -} +impl PyTupleRef { + fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.tuple_type()) { + let zelf = self.elements.borrow(); + let other = get_elements(&other); + let res = seq_lt(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) + } + } -fn tuple_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None)], - optional = [(iterable, None)] - ); + fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.tuple_type()) { + let zelf = self.elements.borrow(); + let other = get_elements(&other); + let res = seq_gt(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) + } + } - if !objtype::issubclass(cls, &vm.ctx.tuple_type()) { - return Err(vm.new_type_error(format!("{} is not a subtype of tuple", cls.borrow()))); + fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.tuple_type()) { + let zelf = self.elements.borrow(); + let other = get_elements(&other); + let res = seq_ge(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) + } } - let elements = if let Some(iterable) = iterable { - vm.extract_elements(iterable)? - } else { - vec![] - }; + fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.tuple_type()) { + let zelf = self.elements.borrow(); + let other = get_elements(&other); + let res = seq_le(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) + } + } - Ok(PyObject::new( - PyObjectPayload::Sequence { elements }, - cls.clone(), - )) -} + fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.tuple_type()) { + let e1 = self.elements.borrow(); + let e2 = get_elements(&other); + let elements = e1.iter().chain(e2.iter()).cloned().collect(); + Ok(vm.ctx.new_tuple(elements)) + } else { + Ok(vm.ctx.not_implemented()) + } + } -fn tuple_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.tuple_type()))]); + fn count(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let mut count: usize = 0; + for element in self.elements.borrow().iter() { + if element.is(&needle) { + count += 1; + } else { + let is_eq = vm._eq(element.clone(), needle.clone())?; + if objbool::boolval(vm, is_eq)? { + count += 1; + } + } + } + Ok(count) + } - let s = if let Some(_guard) = ReprGuard::enter(zelf) { - let elements = get_elements(zelf); + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.tuple_type()) { + let zelf = &self.elements.borrow(); + let other = get_elements(&other); + let res = seq_equal(vm, &zelf, &other)?; + Ok(vm.new_bool(res)) + } else { + Ok(vm.ctx.not_implemented()) + } + } - let mut str_parts = vec![]; - for elem in elements.iter() { - let s = vm.to_repr(elem)?; - str_parts.push(objstr::get_value(&s)); + fn hash(self, vm: &VirtualMachine) -> PyResult { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + for element in self.elements.borrow().iter() { + let hash_result = vm.call_method(element, "__hash__", vec![])?; + let element_hash = objint::get_value(&hash_result); + element_hash.hash(&mut hasher); } + Ok(hasher.finish()) + } - if str_parts.len() == 1 { - format!("({},)", str_parts[0]) - } else { - format!("({})", str_parts.join(", ")) + fn iter(self, _vm: &VirtualMachine) -> PyIteratorValue { + PyIteratorValue { + position: Cell::new(0), + iterated_obj: self.into_object(), } - } else { - "(...)".to_string() - }; - Ok(vm.new_str(s)) -} + } -fn tuple_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (zelf, Some(vm.ctx.tuple_type())), - (product, Some(vm.ctx.int_type())) - ] - ); + fn len(self, _vm: &VirtualMachine) -> usize { + self.elements.borrow().len() + } - let new_elements = seq_mul(&get_elements(zelf), product); + fn repr(self, vm: &VirtualMachine) -> PyResult { + let s = if let Some(_guard) = ReprGuard::enter(self.as_object()) { + let mut str_parts = vec![]; + for elem in self.elements.borrow().iter() { + let s = vm.to_repr(elem)?; + str_parts.push(s.value.clone()); + } - Ok(vm.ctx.new_tuple(new_elements)) -} + if str_parts.len() == 1 { + format!("({},)", str_parts[0]) + } else { + format!("({})", str_parts.join(", ")) + } + } else { + "(...)".to_string() + }; + Ok(s) + } -fn tuple_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(tuple, Some(vm.ctx.tuple_type())), (needle, None)] - ); - get_item(vm, tuple, &get_elements(&tuple), needle.clone()) -} + fn mul(self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { + let new_elements = seq_mul(&self.elements.borrow(), counter); + vm.ctx.new_tuple(new_elements) + } -pub fn tuple_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(tuple, Some(vm.ctx.tuple_type())), (needle, None)] - ); - for (index, element) in get_elements(tuple).iter().enumerate() { - let py_equal = vm._eq(needle.clone(), element.clone())?; - if objbool::get_value(&py_equal) { - return Ok(vm.context().new_int(index)); + fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + get_item( + vm, + self.as_object(), + &self.elements.borrow(), + needle.clone(), + ) + } + + fn index(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + for (index, element) in self.elements.borrow().iter().enumerate() { + if element.is(&needle) { + return Ok(index); + } + let is_eq = vm._eq(needle.clone(), element.clone())?; + if objbool::boolval(vm, is_eq)? { + return Ok(index); + } } + Err(vm.new_value_error("tuple.index(x): x not in tuple".to_string())) } - Err(vm.new_value_error("tuple.index(x): x not in tuple".to_string())) -} -pub fn tuple_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(tuple, Some(vm.ctx.tuple_type())), (needle, None)] - ); - for element in get_elements(tuple).iter() { - match vm._eq(needle.clone(), element.clone()) { - Ok(value) => { - if objbool::get_value(&value) { - return Ok(vm.new_bool(true)); - } + fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + for element in self.elements.borrow().iter() { + if element.is(&needle) { + return Ok(true); + } + let is_eq = vm._eq(needle.clone(), element.clone())?; + if objbool::boolval(vm, is_eq)? { + return Ok(true); } - Err(_) => return Err(vm.new_type_error("".to_string())), } + Ok(false) } +} + +fn tuple_new( + cls: PyClassRef, + iterable: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + if !objtype::issubclass(cls.as_object(), &vm.ctx.tuple_type()) { + return Err(vm.new_type_error(format!("{} is not a subtype of tuple", cls))); + } + + let elements = if let OptionalArg::Present(iterable) = iterable { + vm.extract_elements(&iterable)? + } else { + vec![] + }; - Ok(vm.new_bool(false)) + PyTuple::from(elements).into_ref_with_type(vm, cls) } +#[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { let tuple_type = &context.tuple_type; let tuple_doc = "tuple() -> empty tuple tuple(iterable) -> tuple initialized from iterable's items If the argument is a tuple, the return value is the same object."; - context.set_attr(&tuple_type, "__add__", context.new_rustfunc(tuple_add)); - context.set_attr(&tuple_type, "__eq__", context.new_rustfunc(tuple_eq)); - context.set_attr( - &tuple_type, - "__contains__", - context.new_rustfunc(tuple_contains), - ); - context.set_attr( - &tuple_type, - "__getitem__", - context.new_rustfunc(tuple_getitem), - ); - context.set_attr(&tuple_type, "__hash__", context.new_rustfunc(tuple_hash)); - context.set_attr(&tuple_type, "__iter__", context.new_rustfunc(tuple_iter)); - context.set_attr(&tuple_type, "__len__", context.new_rustfunc(tuple_len)); + context.set_attr(&tuple_type, "__add__", context.new_rustfunc(PyTupleRef::add)); + context.set_attr(&tuple_type, "__eq__", context.new_rustfunc(PyTupleRef::eq)); + context.set_attr(&tuple_type,"__contains__",context.new_rustfunc(PyTupleRef::contains)); + context.set_attr(&tuple_type,"__getitem__",context.new_rustfunc(PyTupleRef::getitem)); + context.set_attr(&tuple_type, "__hash__", context.new_rustfunc(PyTupleRef::hash)); + context.set_attr(&tuple_type, "__iter__", context.new_rustfunc(PyTupleRef::iter)); + context.set_attr(&tuple_type, "__len__", context.new_rustfunc(PyTupleRef::len)); context.set_attr(&tuple_type, "__new__", context.new_rustfunc(tuple_new)); - context.set_attr(&tuple_type, "__mul__", context.new_rustfunc(tuple_mul)); - context.set_attr(&tuple_type, "__repr__", context.new_rustfunc(tuple_repr)); - context.set_attr(&tuple_type, "count", context.new_rustfunc(tuple_count)); - context.set_attr(&tuple_type, "__lt__", context.new_rustfunc(tuple_lt)); - context.set_attr(&tuple_type, "__le__", context.new_rustfunc(tuple_le)); - context.set_attr(&tuple_type, "__gt__", context.new_rustfunc(tuple_gt)); - context.set_attr(&tuple_type, "__ge__", context.new_rustfunc(tuple_ge)); - context.set_attr( - &tuple_type, - "__doc__", - context.new_str(tuple_doc.to_string()), - ); - context.set_attr(&tuple_type, "index", context.new_rustfunc(tuple_index)); + context.set_attr(&tuple_type, "__mul__", context.new_rustfunc(PyTupleRef::mul)); + context.set_attr(&tuple_type, "__repr__", context.new_rustfunc(PyTupleRef::repr)); + context.set_attr(&tuple_type, "count", context.new_rustfunc(PyTupleRef::count)); + context.set_attr(&tuple_type, "__lt__", context.new_rustfunc(PyTupleRef::lt)); + context.set_attr(&tuple_type, "__le__", context.new_rustfunc(PyTupleRef::le)); + context.set_attr(&tuple_type, "__gt__", context.new_rustfunc(PyTupleRef::gt)); + context.set_attr(&tuple_type, "__ge__", context.new_rustfunc(PyTupleRef::ge)); + context.set_attr(&tuple_type,"__doc__",context.new_str(tuple_doc.to_string())); + context.set_attr(&tuple_type, "index", context.new_rustfunc(PyTupleRef::index)); } diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index aa7618e38b..d5648f07be 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -1,109 +1,176 @@ -use super::objdict; -use super::objstr; -use super::objtype; // Required for arg_check! to use isinstance +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt; + +use crate::function::PyFuncArgs; use crate::pyobject::{ - AttributeProtocol, IdProtocol, PyAttributes, PyContext, PyFuncArgs, PyObject, PyObjectPayload, - PyObjectRef, PyResult, TypeProtocol, + AttributeProtocol, FromPyObjectRef, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, + PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; -use std::cell::RefCell; -use std::collections::HashMap; -/* - * The magical type type - */ +use super::objdict; +use super::objlist::PyList; +use super::objproperty::PropertyBuilder; +use super::objstr::{self, PyStringRef}; +use super::objtuple::PyTuple; + +#[derive(Clone, Debug)] +pub struct PyClass { + pub name: String, + pub mro: Vec, +} -pub fn create_type(type_type: PyObjectRef, object_type: PyObjectRef, _dict_type: PyObjectRef) { - (*type_type.borrow_mut()).payload = PyObjectPayload::Class { - name: String::from("type"), - dict: RefCell::new(PyAttributes::new()), - mro: vec![object_type], - }; - (*type_type.borrow_mut()).typ = Some(type_type.clone()); +impl fmt::Display for PyClass { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.name, f) + } } -pub fn init(context: &PyContext) { - let type_type = &context.type_type; +pub type PyClassRef = PyRef; - let type_doc = "type(object_or_name, bases, dict)\n\ - type(object) -> the object's type\n\ - type(name, bases, dict) -> a new type"; +impl PyValue for PyClass { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.type_type() + } +} - context.set_attr(&type_type, "__call__", context.new_rustfunc(type_call)); - context.set_attr(&type_type, "__new__", context.new_rustfunc(type_new)); - context.set_attr( - &type_type, - "__mro__", - context.new_member_descriptor(type_mro), - ); - context.set_attr( - &type_type, - "__class__", - context.new_member_descriptor(type_new), - ); - context.set_attr(&type_type, "__repr__", context.new_rustfunc(type_repr)); - context.set_attr( - &type_type, - "__prepare__", - context.new_rustfunc(type_prepare), - ); - context.set_attr( - &type_type, - "__getattribute__", - context.new_rustfunc(type_getattribute), - ); - context.set_attr(&type_type, "__doc__", context.new_str(type_doc.to_string())); +impl IdProtocol for PyClassRef { + fn get_id(&self) -> usize { + self.as_object().get_id() + } } -fn type_mro(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (cls, Some(vm.ctx.type_type())), - (_typ, Some(vm.ctx.type_type())) - ] - ); - match _mro(cls.clone()) { - Some(mro) => Ok(vm.context().new_tuple(mro)), - None => Err(vm.new_type_error("Only classes have an MRO.".to_string())), +impl TypeProtocol for PyClassRef { + fn type_ref(&self) -> &PyObjectRef { + &self.as_object().type_ref() } } -fn _mro(cls: PyObjectRef) -> Option> { - match cls.borrow().payload { - PyObjectPayload::Class { ref mro, .. } => { - let mut mro = mro.clone(); - mro.insert(0, cls.clone()); - Some(mro) +struct IterMro<'a> { + cls: &'a PyClassRef, + offset: Option, +} + +impl<'a> Iterator for IterMro<'a> { + type Item = &'a PyClassRef; + + fn next(&mut self) -> Option { + match self.offset { + None => { + self.offset = Some(0); + Some(&self.cls) + } + Some(offset) => { + if offset < self.cls.mro.len() { + self.offset = Some(offset + 1); + Some(&self.cls.mro[offset]) + } else { + None + } + } } - _ => None, } } -pub fn base_classes(obj: &PyObjectRef) -> Vec { - _mro(obj.typ()).unwrap() +impl PyClassRef { + fn iter_mro(&self) -> IterMro { + IterMro { + cls: self, + offset: None, + } + } + + fn mro(self, _vm: &VirtualMachine) -> PyTuple { + let elements: Vec = + _mro(&self).iter().map(|x| x.as_object().clone()).collect(); + PyTuple::from(elements) + } + + fn set_mro(self, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_attribute_error("read-only attribute".to_string())) + } + + fn dir(self, vm: &VirtualMachine) -> PyList { + let attributes = get_attributes(self); + let attributes: Vec = attributes + .keys() + .map(|k| vm.ctx.new_str(k.to_string())) + .collect(); + PyList::from(attributes) + } + + fn instance_check(self, obj: PyObjectRef, _vm: &VirtualMachine) -> bool { + isinstance(&obj, self.as_object()) + } + + fn subclass_check(self, subclass: PyObjectRef, _vm: &VirtualMachine) -> bool { + issubclass(&subclass, self.as_object()) + } + + fn repr(self, _vm: &VirtualMachine) -> String { + format!("", self.name) + } + + fn prepare(_name: PyStringRef, _bases: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + vm.new_dict() + } +} + +/* + * The magical type type + */ + +pub fn init(ctx: &PyContext) { + let type_doc = "type(object_or_name, bases, dict)\n\ + type(object) -> the object's type\n\ + type(name, bases, dict) -> a new type"; + + extend_class!(&ctx, &ctx.type_type, { + "__call__" => ctx.new_rustfunc(type_call), + "__new__" => ctx.new_rustfunc(type_new), + "__mro__" => + PropertyBuilder::new(ctx) + .add_getter(PyClassRef::mro) + .add_setter(PyClassRef::set_mro) + .create(), + "__repr__" => ctx.new_rustfunc(PyClassRef::repr), + "__prepare__" => ctx.new_rustfunc(PyClassRef::prepare), + "__getattribute__" => ctx.new_rustfunc(type_getattribute), + "__instancecheck__" => ctx.new_rustfunc(PyClassRef::instance_check), + "__subclasscheck__" => ctx.new_rustfunc(PyClassRef::subclass_check), + "__doc__" => ctx.new_str(type_doc.to_string()), + "__dir__" => ctx.new_rustfunc(PyClassRef::dir), + }); } +fn _mro(cls: &PyClassRef) -> Vec { + cls.iter_mro().cloned().collect() +} + +/// Determines if `obj` actually an instance of `cls`, this doesn't call __instancecheck__, so only +/// use this if `cls` is known to have not overridden the base __instancecheck__ magic method. pub fn isinstance(obj: &PyObjectRef, cls: &PyObjectRef) -> bool { - let mro = _mro(obj.typ()).unwrap(); - mro.into_iter().any(|c| c.is(&cls)) + issubclass(obj.type_ref(), &cls) } -pub fn issubclass(typ: &PyObjectRef, cls: &PyObjectRef) -> bool { - let mro = _mro(typ.clone()).unwrap(); - mro.into_iter().any(|c| c.is(&cls)) +/// Determines if `subclass` is actually a subclass of `cls`, this doesn't call __subclasscheck__, +/// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic +/// method. +pub fn issubclass(subclass: &PyObjectRef, cls: &PyObjectRef) -> bool { + let mro = &subclass.payload::().unwrap().mro; + subclass.is(cls) || mro.iter().any(|c| c.is(cls)) } pub fn get_type_name(typ: &PyObjectRef) -> String { - if let PyObjectPayload::Class { name, .. } = &typ.borrow().payload { + if let Some(PyClass { name, .. }) = &typ.payload::() { name.clone() } else { panic!("Cannot get type_name of non-type type {:?}", typ); } } -pub fn type_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +pub fn type_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { debug!("type.__new__ {:?}", args); if args.args.len() == 2 { arg_check!( @@ -123,21 +190,35 @@ pub fn type_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { (dict, Some(vm.ctx.dict_type())) ] ); - let mut bases = vm.extract_elements(bases)?; - bases.push(vm.context().object()); - let name = objstr::get_value(name); - new( - typ.clone(), - &name, - bases, - objdict::py_dict_to_attributes(dict), - ) + type_new_class(vm, typ, name, bases, dict) } else { Err(vm.new_type_error(format!(": type_new: {:?}", args))) } } -pub fn type_call(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { +pub fn type_new_class( + vm: &VirtualMachine, + typ: &PyObjectRef, + name: &PyObjectRef, + bases: &PyObjectRef, + dict: &PyObjectRef, +) -> PyResult { + let mut bases: Vec = vm + .extract_elements(bases)? + .iter() + .map(|x| FromPyObjectRef::from_pyobj(x)) + .collect(); + bases.push(FromPyObjectRef::from_pyobj(&vm.ctx.object())); + let name = objstr::get_value(name); + new( + typ.clone(), + &name, + bases, + objdict::py_dict_to_attributes(dict), + ) +} + +pub fn type_call(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { debug!("type_call: {:?}", args); let cls = args.shift(); let new = cls.get_attr("__new__").unwrap(); @@ -153,7 +234,7 @@ pub fn type_call(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { Ok(obj) } -pub fn type_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +pub fn type_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -170,13 +251,7 @@ pub fn type_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult let attr_class = attr.typ(); if attr_class.has_attr("__set__") { if let Some(descriptor) = attr_class.get_attr("__get__") { - return vm.invoke( - descriptor, - PyFuncArgs { - args: vec![attr, cls.clone(), mcl], - kwargs: vec![], - }, - ); + return vm.invoke(descriptor, vec![attr, cls.clone(), mcl]); } } } @@ -185,13 +260,7 @@ pub fn type_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult let attr_class = attr.typ(); if let Some(descriptor) = attr_class.get_attr("__get__") { let none = vm.get_none(); - return vm.invoke( - descriptor, - PyFuncArgs { - args: vec![attr, none, cls.clone()], - kwargs: vec![], - }, - ); + return vm.invoke(descriptor, vec![attr, none, cls.clone()]); } } @@ -200,49 +269,31 @@ pub fn type_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult } else if let Some(attr) = mcl.get_attr(&name) { vm.call_get_descriptor(attr, cls.clone()) } else if let Some(getter) = cls.get_attr("__getattr__") { - vm.invoke( - getter, - PyFuncArgs { - args: vec![mcl, name_str.clone()], - kwargs: vec![], - }, - ) + vm.invoke(getter, vec![mcl, name_str.clone()]) } else { - let attribute_error = vm.context().exceptions.attribute_error.clone(); - Err(vm.new_exception( - attribute_error, - format!("{} has no attribute '{}'", cls.borrow(), name), - )) + Err(vm.new_attribute_error(format!("{} has no attribute '{}'", cls, name))) } } -pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { +pub fn get_attributes(cls: PyClassRef) -> PyAttributes { // Gather all members here: let mut attributes = PyAttributes::new(); - // Get class attributes: - let mut base_classes = objtype::base_classes(obj); + let mut base_classes: Vec<&PyClassRef> = cls.iter_mro().collect(); base_classes.reverse(); + for bc in base_classes { - if let PyObjectPayload::Class { dict, .. } = &bc.borrow().payload { + if let Some(ref dict) = &bc.as_object().dict { for (name, value) in dict.borrow().iter() { attributes.insert(name.to_string(), value.clone()); } } } - // Get instance attributes: - if let PyObjectPayload::Instance { dict } = &obj.borrow().payload { - for (name, value) in dict.borrow().iter() { - attributes.insert(name.to_string(), value.clone()); - } - } attributes } -fn take_next_base( - mut bases: Vec>, -) -> Option<(PyObjectRef, Vec>)> { +fn take_next_base(mut bases: Vec>) -> Option<(PyClassRef, Vec>)> { let mut next = None; bases = bases.into_iter().filter(|x| !x.is_empty()).collect(); @@ -269,11 +320,11 @@ fn take_next_base( None } -fn linearise_mro(mut bases: Vec>) -> Option> { +fn linearise_mro(mut bases: Vec>) -> Option> { debug!("Linearising MRO: {:?}", bases); let mut result = vec![]; loop { - if (&bases).iter().all(|x| x.is_empty()) { + if (&bases).iter().all(Vec::is_empty) { break; } match take_next_base(bases) { @@ -290,37 +341,29 @@ fn linearise_mro(mut bases: Vec>) -> Option> { pub fn new( typ: PyObjectRef, name: &str, - bases: Vec, + bases: Vec, dict: HashMap, ) -> PyResult { - let mros = bases.into_iter().map(|x| _mro(x).unwrap()).collect(); + let mros = bases.into_iter().map(|x| _mro(&x)).collect(); let mro = linearise_mro(mros).unwrap(); - Ok(PyObject::new( - PyObjectPayload::Class { + Ok(PyObject { + payload: Box::new(PyClass { name: String::from(name), - dict: RefCell::new(dict), mro, - }, + }), + dict: Some(RefCell::new(dict)), typ, - )) -} - -fn type_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.type_type()))]); - let type_name = get_type_name(&obj); - Ok(vm.new_str(format!("", type_name))) -} - -fn type_prepare(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { - Ok(vm.new_dict()) + } + .into_ref()) } #[cfg(test)] mod tests { + use super::FromPyObjectRef; use super::{linearise_mro, new}; - use super::{HashMap, IdProtocol, PyContext, PyObjectRef}; + use super::{HashMap, IdProtocol, PyClassRef, PyContext}; - fn map_ids(obj: Option>) -> Option> { + fn map_ids(obj: Option>) -> Option> { match obj { Some(vec) => Some(vec.into_iter().map(|x| x.get_id()).collect()), None => None, @@ -330,12 +373,15 @@ mod tests { #[test] fn test_linearise() { let context = PyContext::new(); - let object = context.object; - let type_type = context.type_type; + let object: PyClassRef = FromPyObjectRef::from_pyobj(&context.object); + let type_type = &context.type_type; let a = new(type_type.clone(), "A", vec![object.clone()], HashMap::new()).unwrap(); let b = new(type_type.clone(), "B", vec![object.clone()], HashMap::new()).unwrap(); + let a: PyClassRef = FromPyObjectRef::from_pyobj(&a); + let b: PyClassRef = FromPyObjectRef::from_pyobj(&b); + assert_eq!( map_ids(linearise_mro(vec![ vec![object.clone()], diff --git a/vm/src/obj/objweakref.rs b/vm/src/obj/objweakref.rs new file mode 100644 index 0000000000..e6dd1e1e69 --- /dev/null +++ b/vm/src/obj/objweakref.rs @@ -0,0 +1,49 @@ +use crate::obj::objtype::PyClassRef; +use crate::pyobject::PyValue; +use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyRef, PyResult}; +use crate::vm::VirtualMachine; + +use std::rc::{Rc, Weak}; + +#[derive(Debug)] +pub struct PyWeak { + referent: Weak, +} + +impl PyWeak { + pub fn downgrade(obj: PyObjectRef) -> PyWeak { + PyWeak { + referent: Rc::downgrade(&obj), + } + } + + pub fn upgrade(&self) -> Option { + self.referent.upgrade() + } +} + +impl PyValue for PyWeak { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.weakref_type() + } +} + +pub type PyWeakRef = PyRef; + +impl PyWeakRef { + // TODO callbacks + fn create(cls: PyClassRef, referent: PyObjectRef, vm: &VirtualMachine) -> PyResult { + PyWeak::downgrade(referent).into_ref_with_type(vm, cls) + } + + fn call(self, vm: &VirtualMachine) -> PyObjectRef { + self.referent.upgrade().unwrap_or_else(|| vm.get_none()) + } +} + +pub fn init(context: &PyContext) { + extend_class!(context, &context.weakref_type, { + "__new__" => context.new_rustfunc(PyWeakRef::create), + "__call__" => context.new_rustfunc(PyWeakRef::call) + }); +} diff --git a/vm/src/obj/objzip.rs b/vm/src/obj/objzip.rs index 43005f4168..0a3c5453a4 100644 --- a/vm/src/obj/objzip.rs +++ b/vm/src/obj/objzip.rs @@ -1,8 +1,21 @@ +use crate::function::PyFuncArgs; +use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::vm::VirtualMachine; + use super::objiter; -use crate::pyobject::{PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyResult, TypeProtocol}; -use crate::vm::VirtualMachine; // Required for arg_check! to use isinstance -fn zip_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +#[derive(Debug)] +pub struct PyZip { + iterators: Vec, +} + +impl PyValue for PyZip { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.zip_type() + } +} + +fn zip_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { no_kwargs!(vm, args); let cls = &args.args[0]; let iterables = &args.args[1..]; @@ -10,16 +23,13 @@ fn zip_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { .iter() .map(|iterable| objiter::get_iter(vm, iterable)) .collect::, _>>()?; - Ok(PyObject::new( - PyObjectPayload::ZipIterator { iterators }, - cls.clone(), - )) + Ok(PyObject::new(PyZip { iterators }, cls.clone())) } -fn zip_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn zip_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zip, Some(vm.ctx.zip_type()))]); - if let PyObjectPayload::ZipIterator { ref mut iterators } = zip.borrow_mut().payload { + if let Some(PyZip { ref iterators }) = zip.payload() { if iterators.is_empty() { Err(objiter::new_stop_iteration(vm)) } else { @@ -38,6 +48,8 @@ fn zip_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn init(context: &PyContext) { let zip_type = &context.zip_type; objiter::iter_type_init(context, zip_type); - context.set_attr(zip_type, "__new__", context.new_rustfunc(zip_new)); - context.set_attr(zip_type, "__next__", context.new_rustfunc(zip_next)); + extend_class!(context, zip_type, { + "__new__" => context.new_rustfunc(zip_new), + "__next__" => context.new_rustfunc(zip_next) + }); } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 0158028d02..d6e84b653b 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1,43 +1,57 @@ +use std::any::Any; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::fmt; +use std::marker::PhantomData; +use std::mem; +use std::ops::Deref; +use std::ptr; +use std::rc::Rc; + +use num_bigint::BigInt; +use num_complex::Complex64; +use num_traits::{One, Zero}; + use crate::bytecode; use crate::exceptions; -use crate::frame::Frame; +use crate::frame::{Frame, Scope}; +use crate::function::{IntoPyNativeFunc, PyFuncArgs}; use crate::obj::objbool; +use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objbytearray; use crate::obj::objbytes; +use crate::obj::objclassmethod; use crate::obj::objcode; -use crate::obj::objcomplex; -use crate::obj::objdict; +use crate::obj::objcomplex::{self, PyComplex}; +use crate::obj::objdict::{self, PyDict}; +use crate::obj::objellipsis; use crate::obj::objenumerate; use crate::obj::objfilter; -use crate::obj::objfloat; +use crate::obj::objfloat::{self, PyFloat}; use crate::obj::objframe; -use crate::obj::objfunction; +use crate::obj::objfunction::{self, PyFunction, PyMethod}; use crate::obj::objgenerator; -use crate::obj::objint; +use crate::obj::objint::{self, PyInt}; use crate::obj::objiter; -use crate::obj::objlist; +use crate::obj::objlist::{self, PyList}; use crate::obj::objmap; use crate::obj::objmemory; +use crate::obj::objmodule::{self, PyModule}; use crate::obj::objnone; use crate::obj::objobject; use crate::obj::objproperty; +use crate::obj::objproperty::PropertyBuilder; use crate::obj::objrange; -use crate::obj::objset; +use crate::obj::objset::{self, PySet}; use crate::obj::objslice; +use crate::obj::objstaticmethod; use crate::obj::objstr; use crate::obj::objsuper; -use crate::obj::objtuple; -use crate::obj::objtype; +use crate::obj::objtuple::{self, PyTuple}; +use crate::obj::objtype::{self, PyClass, PyClassRef}; +use crate::obj::objweakref; use crate::obj::objzip; use crate::vm::VirtualMachine; -use num_bigint::BigInt; -use num_bigint::ToBigInt; -use num_complex::Complex64; -use num_traits::{One, Zero}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::fmt; -use std::rc::{Rc, Weak}; /* Python objects and references. @@ -53,26 +67,17 @@ Basically reference counting, but then done by rust. * Good reference: https://github.com/ProgVal/pythonvm-rust/blob/master/src/objects/mod.rs */ -/* -The PyRef type implements -https://doc.rust-lang.org/std/cell/index.html#introducing-mutability-inside-of-something-immutable -*/ -pub type PyRef = Rc>; - /// The `PyObjectRef` is one of the most used types. It is a reference to a /// python object. A single python object can have multiple references, and /// this reference counting is accounted for by this type. Use the `.clone()` /// method to create a new reference and increment the amount of references /// to the python object by 1. -pub type PyObjectRef = PyRef; - -/// Same as PyObjectRef, except for being a weak reference. -pub type PyObjectWeakRef = Weak>; +pub type PyObjectRef = Rc; /// Use this type for function which return a python object or and exception. /// Both the python object and the python exception are `PyObjectRef` types /// since exceptions are also python objects. -pub type PyResult = Result; // A valid value, or an exception +pub type PyResult = Result; // A valid value, or an exception /// For attributes we do not use a dict, but a hashmap. This is probably /// faster, unordered, and only supports strings as keys. @@ -81,33 +86,23 @@ pub type PyAttributes = HashMap; impl fmt::Display for PyObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::TypeProtocol; - match &self.payload { - PyObjectPayload::Module { name, .. } => write!(f, "module '{}'", name), - PyObjectPayload::Class { name, .. } => { - let type_name = objtype::get_type_name(&self.typ()); - // We don't have access to a vm, so just assume that if its parent's name - // is type, it's a type - if type_name == "type" { - write!(f, "type object '{}'", name) - } else { - write!(f, "'{}' object", type_name) - } + if let Some(PyClass { ref name, .. }) = self.payload::() { + let type_name = objtype::get_type_name(&self.typ()); + // We don't have access to a vm, so just assume that if its parent's name + // is type, it's a type + if type_name == "type" { + return write!(f, "type object '{}'", name); + } else { + return write!(f, "'{}' object", type_name); } - _ => write!(f, "'{}' object", objtype::get_type_name(&self.typ())), } - } -} -/* - // Idea: implement the iterator trait upon PyObjectRef -impl Iterator for (VirtualMachine, PyObjectRef) { - type Item = char; - - fn next(&mut self) -> Option { - // call method ("_next__") + if let Some(PyModule { ref name, .. }) = self.payload::() { + return write!(f, "module '{}'", name); + } + write!(f, "'{}' object", objtype::get_type_name(&self.typ())) } } -*/ #[derive(Debug)] pub struct PyContext { @@ -117,6 +112,7 @@ pub struct PyContext { pub classmethod_type: PyObjectRef, pub code_type: PyObjectRef, pub dict_type: PyObjectRef, + pub ellipsis_type: PyObjectRef, pub enumerate_type: PyObjectRef, pub filter_type: PyObjectRef, pub float_type: PyObjectRef, @@ -132,6 +128,7 @@ pub struct PyContext { pub map_type: PyObjectRef, pub memoryview_type: PyObjectRef, pub none: PyObjectRef, + pub ellipsis: PyObjectRef, pub not_implemented: PyObjectRef, pub tuple_type: PyObjectRef, pub set_type: PyObjectRef, @@ -145,112 +142,134 @@ pub struct PyContext { pub function_type: PyObjectRef, pub builtin_function_or_method_type: PyObjectRef, pub property_type: PyObjectRef, + pub readonly_property_type: PyObjectRef, pub module_type: PyObjectRef, pub bound_method_type: PyObjectRef, - pub member_descriptor_type: PyObjectRef, + pub weakref_type: PyObjectRef, pub object: PyObjectRef, pub exceptions: exceptions::ExceptionZoo, } -/* - * So a scope is a linked list of scopes. - * When a name is looked up, it is check in its scope. - */ +pub fn create_type(name: &str, type_type: &PyObjectRef, base: &PyObjectRef) -> PyObjectRef { + let dict = PyAttributes::new(); + objtype::new( + type_type.clone(), + name, + vec![FromPyObjectRef::from_pyobj(base)], + dict, + ) + .unwrap() +} + #[derive(Debug)] -pub struct Scope { - pub locals: PyObjectRef, // Variables - // TODO: pub locals: RefCell, // Variables - pub parent: Option, // Parent scope +pub struct PyNotImplemented; + +impl PyValue for PyNotImplemented { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.not_implemented().typ() + } } -fn _nothing() -> PyObjectRef { - PyObject { - payload: PyObjectPayload::None, - typ: None, +#[derive(Debug)] +pub struct PyEllipsis; + +impl PyValue for PyEllipsis { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.ellipsis_type.clone() } - .into_ref() } -pub fn create_type( - name: &str, - type_type: &PyObjectRef, - base: &PyObjectRef, - _dict_type: &PyObjectRef, -) -> PyObjectRef { - let dict = PyAttributes::new(); - objtype::new(type_type.clone(), name, vec![base.clone()], dict).unwrap() +fn init_type_hierarchy() -> (PyObjectRef, PyObjectRef) { + // `type` inherits from `object` + // and both `type` and `object are instances of `type`. + // to produce this circular dependency, we need an unsafe block. + // (and yes, this will never get dropped. TODO?) + unsafe { + let object_type = PyObject { + typ: mem::uninitialized(), // ! + dict: Some(RefCell::new(PyAttributes::new())), + payload: Box::new(PyClass { + name: String::from("object"), + mro: vec![], + }), + } + .into_ref(); + + let type_type = PyObject { + typ: mem::uninitialized(), // ! + dict: Some(RefCell::new(PyAttributes::new())), + payload: Box::new(PyClass { + name: String::from("type"), + mro: vec![FromPyObjectRef::from_pyobj(&object_type)], + }), + } + .into_ref(); + + let object_type_ptr = PyObjectRef::into_raw(object_type.clone()) as *mut PyObject; + let type_type_ptr = PyObjectRef::into_raw(type_type.clone()) as *mut PyObject; + ptr::write(&mut (*object_type_ptr).typ, type_type.clone()); + ptr::write(&mut (*type_type_ptr).typ, type_type.clone()); + + (type_type, object_type) + } } // Basic objects: impl PyContext { pub fn new() -> Self { - let type_type = _nothing(); - let object_type = _nothing(); - let dict_type = _nothing(); - - objtype::create_type(type_type.clone(), object_type.clone(), dict_type.clone()); - objobject::create_object(type_type.clone(), object_type.clone(), dict_type.clone()); - objdict::create_type(type_type.clone(), object_type.clone(), dict_type.clone()); - - let module_type = create_type("module", &type_type, &object_type, &dict_type); - let classmethod_type = create_type("classmethod", &type_type, &object_type, &dict_type); - let staticmethod_type = create_type("staticmethod", &type_type, &object_type, &dict_type); - let function_type = create_type("function", &type_type, &object_type, &dict_type); - let builtin_function_or_method_type = create_type( - "builtin_function_or_method", - &type_type, - &object_type, - &dict_type, - ); - let property_type = create_type("property", &type_type, &object_type, &dict_type); - let super_type = create_type("super", &type_type, &object_type, &dict_type); - let generator_type = create_type("generator", &type_type, &object_type, &dict_type); - let bound_method_type = create_type("method", &type_type, &object_type, &dict_type); - let member_descriptor_type = - create_type("member_descriptor", &type_type, &object_type, &dict_type); - let str_type = create_type("str", &type_type, &object_type, &dict_type); - let list_type = create_type("list", &type_type, &object_type, &dict_type); - let set_type = create_type("set", &type_type, &object_type, &dict_type); - let frozenset_type = create_type("frozenset", &type_type, &object_type, &dict_type); - let int_type = create_type("int", &type_type, &object_type, &dict_type); - let float_type = create_type("float", &type_type, &object_type, &dict_type); - let frame_type = create_type("frame", &type_type, &object_type, &dict_type); - let complex_type = create_type("complex", &type_type, &object_type, &dict_type); - let bytes_type = create_type("bytes", &type_type, &object_type, &dict_type); - let bytearray_type = create_type("bytearray", &type_type, &object_type, &dict_type); - let tuple_type = create_type("tuple", &type_type, &object_type, &dict_type); - let iter_type = create_type("iter", &type_type, &object_type, &dict_type); - let enumerate_type = create_type("enumerate", &type_type, &object_type, &dict_type); - let filter_type = create_type("filter", &type_type, &object_type, &dict_type); - let map_type = create_type("map", &type_type, &object_type, &dict_type); - let zip_type = create_type("zip", &type_type, &object_type, &dict_type); - let bool_type = create_type("bool", &type_type, &int_type, &dict_type); - let memoryview_type = create_type("memoryview", &type_type, &object_type, &dict_type); - let code_type = create_type("code", &type_type, &int_type, &dict_type); - let range_type = create_type("range", &type_type, &object_type, &dict_type); - let slice_type = create_type("slice", &type_type, &object_type, &dict_type); - let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type, &dict_type); + let (type_type, object_type) = init_type_hierarchy(); + + let dict_type = create_type("dict", &type_type, &object_type); + let module_type = create_type("module", &type_type, &object_type); + let classmethod_type = create_type("classmethod", &type_type, &object_type); + let staticmethod_type = create_type("staticmethod", &type_type, &object_type); + let function_type = create_type("function", &type_type, &object_type); + let builtin_function_or_method_type = + create_type("builtin_function_or_method", &type_type, &object_type); + let property_type = create_type("property", &type_type, &object_type); + let readonly_property_type = create_type("readonly_property", &type_type, &object_type); + let super_type = create_type("super", &type_type, &object_type); + let weakref_type = create_type("ref", &type_type, &object_type); + let generator_type = create_type("generator", &type_type, &object_type); + let bound_method_type = create_type("method", &type_type, &object_type); + let str_type = create_type("str", &type_type, &object_type); + let list_type = create_type("list", &type_type, &object_type); + let set_type = create_type("set", &type_type, &object_type); + let frozenset_type = create_type("frozenset", &type_type, &object_type); + let int_type = create_type("int", &type_type, &object_type); + let float_type = create_type("float", &type_type, &object_type); + let frame_type = create_type("frame", &type_type, &object_type); + let complex_type = create_type("complex", &type_type, &object_type); + let bytes_type = create_type("bytes", &type_type, &object_type); + let bytearray_type = create_type("bytearray", &type_type, &object_type); + let tuple_type = create_type("tuple", &type_type, &object_type); + let iter_type = create_type("iter", &type_type, &object_type); + let ellipsis_type = create_type("EllipsisType", &type_type, &object_type); + let enumerate_type = create_type("enumerate", &type_type, &object_type); + let filter_type = create_type("filter", &type_type, &object_type); + let map_type = create_type("map", &type_type, &object_type); + let zip_type = create_type("zip", &type_type, &object_type); + let bool_type = create_type("bool", &type_type, &int_type); + let memoryview_type = create_type("memoryview", &type_type, &object_type); + let code_type = create_type("code", &type_type, &int_type); + let range_type = create_type("range", &type_type, &object_type); + let slice_type = create_type("slice", &type_type, &object_type); + let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type); let none = PyObject::new( - PyObjectPayload::None, - create_type("NoneType", &type_type, &object_type, &dict_type), + objnone::PyNone, + create_type("NoneType", &type_type, &object_type), ); + let ellipsis = PyObject::new(PyEllipsis, ellipsis_type.clone()); + let not_implemented = PyObject::new( - PyObjectPayload::NotImplemented, - create_type("NotImplementedType", &type_type, &object_type, &dict_type), + PyNotImplemented, + create_type("NotImplementedType", &type_type, &object_type), ); - let true_value = PyObject::new( - PyObjectPayload::Integer { value: One::one() }, - bool_type.clone(), - ); - let false_value = PyObject::new( - PyObjectPayload::Integer { - value: Zero::zero(), - }, - bool_type.clone(), - ); + let true_value = PyObject::new(PyInt::new(BigInt::one()), bool_type.clone()); + let false_value = PyObject::new(PyInt::new(BigInt::zero()), bool_type.clone()); let context = PyContext { bool_type, memoryview_type, @@ -270,12 +289,14 @@ impl PyContext { false_value, tuple_type, iter_type, + ellipsis_type, enumerate_type, filter_type, map_type, zip_type, dict_type, none, + ellipsis, not_implemented, str_type, range_type, @@ -285,10 +306,11 @@ impl PyContext { builtin_function_or_method_type, super_type, property_type, + readonly_property_type, generator_type, module_type, bound_method_type, - member_descriptor_type, + weakref_type, type_type, exceptions, }; @@ -299,6 +321,8 @@ impl PyContext { objobject::init(&context); objdict::init(&context); objfunction::init(&context); + objstaticmethod::init(&context); + objclassmethod::init(&context); objgenerator::init(&context); objint::init(&context); objfloat::init(&context); @@ -313,6 +337,7 @@ impl PyContext { objsuper::init(&context); objtuple::init(&context); objiter::init(&context); + objellipsis::init(&context); objenumerate::init(&context); objfilter::init(&context); objmap::init(&context); @@ -320,7 +345,9 @@ impl PyContext { objbool::init(&context); objcode::init(&context); objframe::init(&context); + objweakref::init(&context); objnone::init(&context); + objmodule::init(&context); exceptions::init(&context); context } @@ -361,6 +388,10 @@ impl PyContext { self.list_type.clone() } + pub fn module_type(&self) -> PyObjectRef { + self.module_type.clone() + } + pub fn set_type(&self) -> PyObjectRef { self.set_type.clone() } @@ -429,6 +460,10 @@ impl PyContext { self.property_type.clone() } + pub fn readonly_property_type(&self) -> PyObjectRef { + self.readonly_property_type.clone() + } + pub fn classmethod_type(&self) -> PyObjectRef { self.classmethod_type.clone() } @@ -444,9 +479,11 @@ impl PyContext { pub fn bound_method_type(&self) -> PyObjectRef { self.bound_method_type.clone() } - pub fn member_descriptor_type(&self) -> PyObjectRef { - self.member_descriptor_type.clone() + + pub fn weakref_type(&self) -> PyObjectRef { + self.weakref_type.clone() } + pub fn type_type(&self) -> PyObjectRef { self.type_type.clone() } @@ -454,9 +491,15 @@ impl PyContext { pub fn none(&self) -> PyObjectRef { self.none.clone() } + + pub fn ellipsis(&self) -> PyObjectRef { + self.ellipsis.clone() + } + pub fn not_implemented(&self) -> PyObjectRef { self.not_implemented.clone() } + pub fn object(&self) -> PyObjectRef { self.object.clone() } @@ -465,36 +508,28 @@ impl PyContext { self.new_instance(self.object(), None) } - pub fn new_int(&self, i: T) -> PyObjectRef { - PyObject::new( - PyObjectPayload::Integer { - value: i.to_bigint().unwrap(), - }, - self.int_type(), - ) + pub fn new_int>(&self, i: T) -> PyObjectRef { + PyObject::new(PyInt::new(i), self.int_type()) } - pub fn new_float(&self, i: f64) -> PyObjectRef { - PyObject::new(PyObjectPayload::Float { value: i }, self.float_type()) + pub fn new_float(&self, value: f64) -> PyObjectRef { + PyObject::new(PyFloat::from(value), self.float_type()) } - pub fn new_complex(&self, i: Complex64) -> PyObjectRef { - PyObject::new(PyObjectPayload::Complex { value: i }, self.complex_type()) + pub fn new_complex(&self, value: Complex64) -> PyObjectRef { + PyObject::new(PyComplex::from(value), self.complex_type()) } pub fn new_str(&self, s: String) -> PyObjectRef { - PyObject::new(PyObjectPayload::String { value: s }, self.str_type()) + PyObject::new(objstr::PyString { value: s }, self.str_type()) } pub fn new_bytes(&self, data: Vec) -> PyObjectRef { - PyObject::new(PyObjectPayload::Bytes { value: data }, self.bytes_type()) + PyObject::new(objbytes::PyBytes::new(data), self.bytes_type()) } pub fn new_bytearray(&self, data: Vec) -> PyObjectRef { - PyObject::new( - PyObjectPayload::Bytes { value: data }, - self.bytearray_type(), - ) + PyObject::new(objbytearray::PyByteArray::new(data), self.bytearray_type()) } pub fn new_bool(&self, b: bool) -> PyObjectRef { @@ -506,147 +541,105 @@ impl PyContext { } pub fn new_tuple(&self, elements: Vec) -> PyObjectRef { - PyObject::new(PyObjectPayload::Sequence { elements }, self.tuple_type()) + PyObject::new(PyTuple::from(elements), self.tuple_type()) } pub fn new_list(&self, elements: Vec) -> PyObjectRef { - PyObject::new(PyObjectPayload::Sequence { elements }, self.list_type()) + PyObject::new(PyList::from(elements), self.list_type()) } pub fn new_set(&self) -> PyObjectRef { // Initialized empty, as calling __hash__ is required for adding each object to the set // which requires a VM context - this is done in the objset code itself. - let elements: HashMap = HashMap::new(); - PyObject::new(PyObjectPayload::Set { elements }, self.set_type()) + PyObject::new(PySet::default(), self.set_type()) } pub fn new_dict(&self) -> PyObjectRef { - PyObject::new( - PyObjectPayload::Dict { - elements: HashMap::new(), - }, - self.dict_type(), - ) + PyObject::new(PyDict::default(), self.dict_type()) } pub fn new_class(&self, name: &str, base: PyObjectRef) -> PyObjectRef { - objtype::new(self.type_type(), name, vec![base], PyAttributes::new()).unwrap() + objtype::new( + self.type_type(), + name, + vec![FromPyObjectRef::from_pyobj(&base)], + PyAttributes::new(), + ) + .unwrap() } - pub fn new_scope(&self, parent: Option) -> PyObjectRef { - let locals = self.new_dict(); - let scope = Scope { locals, parent }; - PyObject { - payload: PyObjectPayload::Scope { scope }, - typ: None, - } - .into_ref() + pub fn new_scope(&self) -> Scope { + Scope::new(None, self.new_dict()) } - pub fn new_module(&self, name: &str, scope: PyObjectRef) -> PyObjectRef { + pub fn new_module(&self, name: &str, dict: PyObjectRef) -> PyObjectRef { PyObject::new( - PyObjectPayload::Module { + PyModule { name: name.to_string(), - dict: scope.clone(), + dict, }, self.module_type.clone(), ) } - pub fn new_rustfunc PyResult>( - &self, - function: F, - ) -> PyObjectRef { + pub fn new_rustfunc(&self, f: F) -> PyObjectRef + where + F: IntoPyNativeFunc, + { PyObject::new( - PyObjectPayload::RustFunction { - function: Box::new(function), - }, + PyBuiltinFunction::new(f.into_func()), self.builtin_function_or_method_type(), ) } - pub fn new_rustfunc_from_box( - &self, - function: Box PyResult>, - ) -> PyObjectRef { - PyObject::new( - PyObjectPayload::RustFunction { function }, - self.builtin_function_or_method_type(), - ) + pub fn new_frame(&self, code: PyObjectRef, scope: Scope) -> PyObjectRef { + PyObject::new(Frame::new(code, scope), self.frame_type()) } - pub fn new_frame(&self, frame: Frame) -> PyObjectRef { - PyObject::new(PyObjectPayload::Frame { frame }, self.frame_type()) - } - - pub fn new_property PyResult>( - &self, - function: F, - ) -> PyObjectRef { - let fget = self.new_rustfunc(function); - let py_obj = self.new_instance(self.property_type(), None); - self.set_attr(&py_obj, "fget", fget.clone()); - py_obj + pub fn new_property(&self, f: F) -> PyObjectRef + where + F: IntoPyNativeFunc, + { + PropertyBuilder::new(self).add_getter(f).create() } pub fn new_code_object(&self, code: bytecode::CodeObject) -> PyObjectRef { - PyObject::new(PyObjectPayload::Code { code }, self.code_type()) + PyObject::new(objcode::PyCode::new(code), self.code_type()) } pub fn new_function( &self, code_obj: PyObjectRef, - scope: PyObjectRef, + scope: Scope, defaults: PyObjectRef, ) -> PyObjectRef { PyObject::new( - PyObjectPayload::Function { - code: code_obj, - scope, - defaults, - }, + PyFunction::new(code_obj, scope, defaults), self.function_type(), ) } pub fn new_bound_method(&self, function: PyObjectRef, object: PyObjectRef) -> PyObjectRef { - PyObject::new( - PyObjectPayload::BoundMethod { function, object }, - self.bound_method_type(), - ) - } - - pub fn new_member_descriptor PyResult>( - &self, - function: F, - ) -> PyObjectRef { - let mut dict = PyAttributes::new(); - dict.insert("function".to_string(), self.new_rustfunc(function)); - self.new_instance(self.member_descriptor_type(), Some(dict)) + PyObject::new(PyMethod::new(object, function), self.bound_method_type()) } pub fn new_instance(&self, class: PyObjectRef, dict: Option) -> PyObjectRef { - let dict = if let Some(dict) = dict { - dict - } else { - PyAttributes::new() - }; - PyObject::new( - PyObjectPayload::Instance { - dict: RefCell::new(dict), - }, - class, - ) + let dict = dict.unwrap_or_default(); + PyObject { + typ: class, + dict: Some(RefCell::new(dict)), + payload: Box::new(objobject::PyInstance), + } + .into_ref() } // Item set/get: pub fn set_item(&self, obj: &PyObjectRef, key: &str, v: PyObjectRef) { - match obj.borrow_mut().payload { - PyObjectPayload::Dict { ref mut elements } => { - let key = self.new_str(key.to_string()); - objdict::set_item_in_content(elements, &key, &v); - } - ref k => panic!("TODO {:?}", k), + if let Some(dict) = obj.payload::() { + let key = self.new_str(key.to_string()); + objdict::set_item_in_content(&mut dict.entries.borrow_mut(), &key, &v); + } else { + unimplemented!() }; } @@ -658,19 +651,16 @@ impl PyContext { } pub fn set_attr(&self, obj: &PyObjectRef, attr_name: &str, value: PyObjectRef) { - match obj.borrow().payload { - PyObjectPayload::Module { ref dict, .. } => self.set_attr(dict, attr_name, value), - PyObjectPayload::Instance { ref dict } | PyObjectPayload::Class { ref dict, .. } => { - dict.borrow_mut().insert(attr_name.to_string(), value); - } - PyObjectPayload::Scope { ref scope } => { - self.set_item(&scope.locals, attr_name, value); - } - ref payload => unimplemented!("set_attr unimplemented for: {:?}", payload), + if let Some(PyModule { ref dict, .. }) = obj.payload::() { + dict.set_item(self, attr_name, value) + } else if let Some(ref dict) = obj.dict { + dict.borrow_mut().insert(attr_name.to_string(), value); + } else { + unimplemented!("set_attr unimplemented for: {:?}", obj); }; } - pub fn unwrap_constant(&mut self, value: &bytecode::Constant) -> PyObjectRef { + pub fn unwrap_constant(&self, value: &bytecode::Constant) -> PyObjectRef { match *value { bytecode::Constant::Integer { ref value } => self.new_int(value.clone()), bytecode::Constant::Float { ref value } => self.new_float(*value), @@ -678,7 +668,7 @@ impl PyContext { bytecode::Constant::String { ref value } => self.new_str(value.clone()), bytecode::Constant::Bytes { ref value } => self.new_bytes(value.clone()), bytecode::Constant::Boolean { ref value } => self.new_bool(value.clone()), - bytecode::Constant::Code { ref code } => self.new_code_object(code.clone()), + bytecode::Constant::Code { ref code } => self.new_code_object(*code.clone()), bytecode::Constant::Tuple { ref elements } => { let elements = elements .iter() @@ -687,78 +677,146 @@ impl PyContext { self.new_tuple(elements) } bytecode::Constant::None => self.none(), + bytecode::Constant::Ellipsis => self.ellipsis(), } } } +impl Default for PyContext { + fn default() -> Self { + PyContext::new() + } +} + /// This is an actual python object. It consists of a `typ` which is the /// python class, and carries some rust payload optionally. This rust /// payload can be a rust float or rust int in case of float and int objects. pub struct PyObject { - pub payload: PyObjectPayload, - pub typ: Option, - // pub dict: HashMap, // __dict__ member + pub typ: PyObjectRef, + pub dict: Option>, // __dict__ member + pub payload: Box, } -pub trait IdProtocol { - fn get_id(&self) -> usize; - fn is(&self, other: &PyObjectRef) -> bool; +/// A reference to a Python object. +/// +/// Note that a `PyRef` can only deref to a shared / immutable reference. +/// It is the payload type's responsibility to handle (possibly concurrent) +/// mutability with locks or concurrent data structures if required. +/// +/// A `PyRef` can be directly returned from a built-in function to handle +/// situations (such as when implementing in-place methods such as `__iadd__`) +/// where a reference to the same object must be returned. +#[derive(Clone, Debug)] +pub struct PyRef { + // invariant: this obj must always have payload of type T + obj: PyObjectRef, + _payload: PhantomData, } -impl IdProtocol for PyObjectRef { - fn get_id(&self) -> usize { - self.as_ptr() as usize +impl PyRef { + pub fn as_object(&self) -> &PyObjectRef { + &self.obj + } + pub fn into_object(self) -> PyObjectRef { + self.obj } - fn is(&self, other: &PyObjectRef) -> bool { - self.get_id() == other.get_id() + pub fn typ(&self) -> PyClassRef { + PyRef { + obj: self.obj.typ(), + _payload: PhantomData, + } } } -pub trait FromPyObjectRef { - fn from_pyobj(obj: &PyObjectRef) -> Self; +impl Deref for PyRef +where + T: PyValue, +{ + type Target = T; + + fn deref(&self) -> &T { + self.obj.payload().expect("unexpected payload for type") + } } -pub trait TypeProtocol { - fn typ(&self) -> PyObjectRef; +impl TryFromObject for PyRef +where + T: PyValue, +{ + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + if objtype::isinstance(&obj, &T::class(vm)) { + Ok(PyRef { + obj, + _payload: PhantomData, + }) + } else { + let class = T::class(vm); + let expected_type = vm.to_pystr(&class)?; + let actual_type = vm.to_pystr(&obj.typ())?; + Err(vm.new_type_error(format!( + "Expected type {}, not {}", + expected_type, actual_type, + ))) + } + } } -impl TypeProtocol for PyObjectRef { - fn typ(&self) -> PyObjectRef { - self.borrow().typ() +impl IntoPyObject for PyRef { + fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult { + Ok(self.obj) } } -impl TypeProtocol for PyObject { - fn typ(&self) -> PyObjectRef { - match self.typ { - Some(ref typ) => typ.clone(), - None => panic!("Object {:?} doesn't have a type!", self), - } +impl fmt::Display for PyRef +where + T: PyValue + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value: &T = self.obj.payload().expect("unexpected payload for type"); + fmt::Display::fmt(value, f) } } -pub trait ParentProtocol { - fn has_parent(&self) -> bool; - fn get_parent(&self) -> PyObjectRef; +pub trait IdProtocol { + fn get_id(&self) -> usize; + fn is(&self, other: &T) -> bool + where + T: IdProtocol, + { + self.get_id() == other.get_id() + } } -impl ParentProtocol for PyObjectRef { - fn has_parent(&self) -> bool { - match self.borrow().payload { - PyObjectPayload::Scope { ref scope } => scope.parent.is_some(), - _ => panic!("Only scopes have parent (not {:?}", self), - } +impl IdProtocol for PyObjectRef { + fn get_id(&self) -> usize { + &*self as &PyObject as *const PyObject as usize } +} - fn get_parent(&self) -> PyObjectRef { - match self.borrow().payload { - PyObjectPayload::Scope { ref scope } => match scope.parent { - Some(ref value) => value.clone(), - None => panic!("OMG"), - }, - _ => panic!("TODO"), - } +pub trait FromPyObjectRef { + fn from_pyobj(obj: &PyObjectRef) -> Self; +} + +pub trait TypeProtocol { + fn typ(&self) -> PyObjectRef { + self.type_ref().clone() + } + fn type_pyref(&self) -> PyClassRef { + FromPyObjectRef::from_pyobj(self.type_ref()) + } + fn type_ref(&self) -> &PyObjectRef; +} + +impl TypeProtocol for PyObjectRef { + fn type_ref(&self) -> &PyObjectRef { + (**self).type_ref() + } +} + +impl TypeProtocol for PyObject { + fn type_ref(&self) -> &PyObjectRef { + &self.typ } } @@ -768,51 +826,60 @@ pub trait AttributeProtocol { } fn class_get_item(class: &PyObjectRef, attr_name: &str) -> Option { - let class = class.borrow(); - match class.payload { - PyObjectPayload::Class { ref dict, .. } => dict.borrow().get(attr_name).cloned(), - _ => panic!("Only classes should be in MRO!"), + if let Some(ref dict) = class.dict { + dict.borrow().get(attr_name).cloned() + } else { + panic!("Only classes should be in MRO!"); } } fn class_has_item(class: &PyObjectRef, attr_name: &str) -> bool { - let class = class.borrow(); - match class.payload { - PyObjectPayload::Class { ref dict, .. } => dict.borrow().contains_key(attr_name), - _ => panic!("Only classes should be in MRO!"), + if let Some(ref dict) = class.dict { + dict.borrow().contains_key(attr_name) + } else { + panic!("Only classes should be in MRO!"); } } impl AttributeProtocol for PyObjectRef { fn get_attr(&self, attr_name: &str) -> Option { - let obj = self.borrow(); - match obj.payload { - PyObjectPayload::Module { ref dict, .. } => dict.get_item(attr_name), - PyObjectPayload::Class { ref mro, .. } => { - if let Some(item) = class_get_item(self, attr_name) { + if let Some(PyClass { ref mro, .. }) = self.payload::() { + if let Some(item) = class_get_item(self, attr_name) { + return Some(item); + } + for class in mro { + if let Some(item) = class_get_item(class.as_object(), attr_name) { return Some(item); } - for class in mro { - if let Some(item) = class_get_item(class, attr_name) { - return Some(item); - } - } - None } - PyObjectPayload::Instance { ref dict } => dict.borrow().get(attr_name).cloned(), - _ => None, + return None; + } + + if let Some(PyModule { ref dict, .. }) = self.payload::() { + return dict.get_item(attr_name); + } + + if let Some(ref dict) = self.dict { + dict.borrow().get(attr_name).cloned() + } else { + None } } fn has_attr(&self, attr_name: &str) -> bool { - let obj = self.borrow(); - match obj.payload { - PyObjectPayload::Module { ref dict, .. } => dict.contains_key(attr_name), - PyObjectPayload::Class { ref mro, .. } => { - class_has_item(self, attr_name) || mro.iter().any(|d| class_has_item(d, attr_name)) - } - PyObjectPayload::Instance { ref dict } => dict.borrow().contains_key(attr_name), - _ => false, + if let Some(PyClass { ref mro, .. }) = self.payload::() { + return class_has_item(self, attr_name) + || mro.iter().any(|d| class_has_item(d.as_object(), attr_name)); + } + + if let Some(PyModule { ref dict, .. }) = self.payload::() { + return dict.contains_key(attr_name); + } + + if let Some(ref dict) = self.dict { + dict.borrow().contains_key(attr_name) + } else { + false } } } @@ -821,35 +888,55 @@ pub trait DictProtocol { fn contains_key(&self, k: &str) -> bool; fn get_item(&self, k: &str) -> Option; fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)>; + fn set_item(&self, ctx: &PyContext, key: &str, v: PyObjectRef); + fn del_item(&self, key: &str); } impl DictProtocol for PyObjectRef { fn contains_key(&self, k: &str) -> bool { - match self.borrow().payload { - PyObjectPayload::Dict { ref elements } => { - objdict::content_contains_key_str(elements, k) - } - PyObjectPayload::Scope { ref scope } => scope.locals.contains_key(k), - ref payload => unimplemented!("TODO {:?}", payload), + if let Some(dict) = self.payload::() { + objdict::content_contains_key_str(&dict.entries.borrow(), k) + } else { + unimplemented!() } } fn get_item(&self, k: &str) -> Option { - match self.borrow().payload { - PyObjectPayload::Dict { ref elements } => objdict::content_get_key_str(elements, k), - PyObjectPayload::Scope { ref scope } => scope.locals.get_item(k), - _ => panic!("TODO"), + if let Some(dict) = self.payload::() { + objdict::content_get_key_str(&dict.entries.borrow(), k) + } else if let Some(PyModule { ref dict, .. }) = self.payload::() { + dict.get_item(k) + } else { + panic!("TODO {:?}", k) } } fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { - match self.borrow().payload { - PyObjectPayload::Dict { .. } => objdict::get_key_value_pairs(self), - PyObjectPayload::Module { ref dict, .. } => dict.get_key_value_pairs(), - PyObjectPayload::Scope { ref scope } => scope.locals.get_key_value_pairs(), - _ => panic!("TODO"), + if self.payload_is::() { + objdict::get_key_value_pairs(self) + } else if let Some(PyModule { ref dict, .. }) = self.payload::() { + dict.get_key_value_pairs() + } else { + panic!("TODO") + } + } + + // Item set/get: + fn set_item(&self, ctx: &PyContext, key: &str, v: PyObjectRef) { + if let Some(dict) = self.payload::() { + let key = ctx.new_str(key.to_string()); + objdict::set_item_in_content(&mut dict.entries.borrow_mut(), &key, &v); + } else if let Some(PyModule { ref dict, .. }) = self.payload::() { + dict.set_item(ctx, key, v); + } else { + panic!("TODO {:?}", self); } } + + fn del_item(&self, key: &str) { + let mut elements = objdict::get_mut_elements(self); + elements.remove(key).unwrap(); + } } pub trait BufferProtocol { @@ -872,213 +959,255 @@ impl fmt::Debug for PyObject { } } -/// The `PyFuncArgs` struct is one of the most used structs then creating -/// a rust function that can be called from python. It holds both positional -/// arguments, as well as keyword arguments passed to the function. -#[derive(Debug, Default, Clone)] -pub struct PyFuncArgs { - pub args: Vec, - pub kwargs: Vec<(String, PyObjectRef)>, +/// An iterable Python object. +/// +/// `PyIterable` implements `FromArgs` so that a built-in function can accept +/// an object that is required to conform to the Python iterator protocol. +/// +/// PyIterable can optionally perform type checking and conversions on iterated +/// objects using a generic type parameter that implements `TryFromObject`. +pub struct PyIterable { + method: PyObjectRef, + _item: std::marker::PhantomData, +} + +impl PyIterable { + /// Returns an iterator over this sequence of objects. + /// + /// This operation may fail if an exception is raised while invoking the + /// `__iter__` method of the iterable object. + pub fn iter<'a>(&self, vm: &'a VirtualMachine) -> PyResult> { + let iter_obj = vm.invoke( + self.method.clone(), + PyFuncArgs { + args: vec![], + kwargs: vec![], + }, + )?; + + Ok(PyIterator { + vm, + obj: iter_obj, + _item: std::marker::PhantomData, + }) + } } -impl PyFuncArgs { - pub fn new(mut args: Vec, kwarg_names: Vec) -> PyFuncArgs { - let mut kwargs = vec![]; - for name in kwarg_names.iter().rev() { - kwargs.push((name.clone(), args.pop().unwrap())); +pub struct PyIterator<'a, T> { + vm: &'a VirtualMachine, + obj: PyObjectRef, + _item: std::marker::PhantomData, +} + +impl<'a, T> Iterator for PyIterator<'a, T> +where + T: TryFromObject, +{ + type Item = PyResult; + + fn next(&mut self) -> Option { + match self.vm.call_method(&self.obj, "__next__", vec![]) { + Ok(value) => Some(T::try_from_object(self.vm, value)), + Err(err) => { + let stop_ex = self.vm.ctx.exceptions.stop_iteration.clone(); + if objtype::isinstance(&err, &stop_ex) { + None + } else { + Some(Err(err)) + } + } } - PyFuncArgs { args, kwargs } } +} - pub fn insert(&self, item: PyObjectRef) -> PyFuncArgs { - let mut args = PyFuncArgs { - args: self.args.clone(), - kwargs: self.kwargs.clone(), - }; - args.args.insert(0, item); - args +impl TryFromObject for PyIterable +where + T: TryFromObject, +{ + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + Ok(PyIterable { + method: vm.get_method(obj, "__iter__")?, + _item: std::marker::PhantomData, + }) } +} - pub fn shift(&mut self) -> PyObjectRef { - self.args.remove(0) +impl TryFromObject for PyObjectRef { + fn try_from_object(_vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + Ok(obj) } +} - pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef { - for (arg_name, arg_value) in self.kwargs.iter() { - if arg_name == key { - return arg_value.clone(); - } +impl TryFromObject for Option { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + if vm.get_none().is(&obj) { + Ok(None) + } else { + T::try_from_object(vm, obj).map(Some) } - default.clone() } +} - pub fn get_optional_kwarg(&self, key: &str) -> Option { - for (arg_name, arg_value) in self.kwargs.iter() { - if arg_name == key { - return Some(arg_value.clone()); - } - } - None +/// Allows coercion of a types into PyRefs, so that we can write functions that can take +/// refs, pyobject refs or basic types. +pub trait TryIntoRef { + fn try_into_ref(self, vm: &VirtualMachine) -> PyResult>; +} + +impl TryIntoRef for PyRef { + fn try_into_ref(self, _vm: &VirtualMachine) -> PyResult> { + Ok(self) } } -/// Rather than determining the type of a python object, this enum is more -/// a holder for the rust payload of a python object. It is more a carrier -/// of rust data for a particular python object. Determine the python type -/// by using for example the `.typ()` method on a python object. -pub enum PyObjectPayload { - String { - value: String, - }, - Integer { - value: BigInt, - }, - Float { - value: f64, - }, - Complex { - value: Complex64, - }, - Bytes { - value: Vec, - }, - Sequence { - elements: Vec, - }, - Dict { - elements: objdict::DictContentType, - }, - Set { - elements: HashMap, - }, - Iterator { - position: usize, - iterated_obj: PyObjectRef, - }, - EnumerateIterator { - counter: BigInt, - iterator: PyObjectRef, - }, - FilterIterator { - predicate: PyObjectRef, - iterator: PyObjectRef, - }, - MapIterator { - mapper: PyObjectRef, - iterators: Vec, - }, - ZipIterator { - iterators: Vec, - }, - Slice { - start: Option, - stop: Option, - step: Option, - }, - Range { - range: objrange::RangeType, - }, - MemoryView { - obj: PyObjectRef, - }, - Code { - code: bytecode::CodeObject, - }, - Frame { - frame: Frame, - }, - Function { - code: PyObjectRef, - scope: PyObjectRef, - defaults: PyObjectRef, - }, - Generator { - frame: Frame, - }, - BoundMethod { - function: PyObjectRef, - object: PyObjectRef, - }, - Scope { - scope: Scope, - }, - Module { - name: String, - dict: PyObjectRef, - }, - None, - NotImplemented, - Class { - name: String, - dict: RefCell, - mro: Vec, - }, - WeakRef { - referent: PyObjectWeakRef, - }, - Instance { - dict: RefCell, - }, - RustFunction { - function: Box PyResult>, - }, +impl TryIntoRef for PyObjectRef +where + T: PyValue, +{ + fn try_into_ref(self, vm: &VirtualMachine) -> PyResult> { + TryFromObject::try_from_object(vm, self) + } } -impl fmt::Debug for PyObjectPayload { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - PyObjectPayload::String { ref value } => write!(f, "str \"{}\"", value), - PyObjectPayload::Integer { ref value } => write!(f, "int {}", value), - PyObjectPayload::Float { ref value } => write!(f, "float {}", value), - PyObjectPayload::Complex { ref value } => write!(f, "complex {}", value), - PyObjectPayload::Bytes { ref value } => write!(f, "bytes/bytearray {:?}", value), - PyObjectPayload::MemoryView { ref obj } => write!(f, "bytes/bytearray {:?}", obj), - PyObjectPayload::Sequence { .. } => write!(f, "list or tuple"), - PyObjectPayload::Dict { .. } => write!(f, "dict"), - PyObjectPayload::Set { .. } => write!(f, "set"), - PyObjectPayload::WeakRef { .. } => write!(f, "weakref"), - PyObjectPayload::Range { .. } => write!(f, "range"), - PyObjectPayload::Iterator { .. } => write!(f, "iterator"), - PyObjectPayload::EnumerateIterator { .. } => write!(f, "enumerate"), - PyObjectPayload::FilterIterator { .. } => write!(f, "filter"), - PyObjectPayload::MapIterator { .. } => write!(f, "map"), - PyObjectPayload::ZipIterator { .. } => write!(f, "zip"), - PyObjectPayload::Slice { .. } => write!(f, "slice"), - PyObjectPayload::Code { ref code } => write!(f, "code: {:?}", code), - PyObjectPayload::Function { .. } => write!(f, "function"), - PyObjectPayload::Generator { .. } => write!(f, "generator"), - PyObjectPayload::BoundMethod { - ref function, - ref object, - } => write!(f, "bound-method: {:?} of {:?}", function, object), - PyObjectPayload::Module { .. } => write!(f, "module"), - PyObjectPayload::Scope { .. } => write!(f, "scope"), - PyObjectPayload::None => write!(f, "None"), - PyObjectPayload::NotImplemented => write!(f, "NotImplemented"), - PyObjectPayload::Class { ref name, .. } => write!(f, "class {:?}", name), - PyObjectPayload::Instance { .. } => write!(f, "instance"), - PyObjectPayload::RustFunction { .. } => write!(f, "rust function"), - PyObjectPayload::Frame { .. } => write!(f, "frame"), - } +/// Implemented by any type that can be created from a Python object. +/// +/// Any type that implements `TryFromObject` is automatically `FromArgs`, and +/// so can be accepted as a argument to a built-in function. +pub trait TryFromObject: Sized { + /// Attempt to convert a Python object to a value of this type. + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult; +} + +/// Implemented by any type that can be returned from a built-in Python function. +/// +/// `IntoPyObject` has a blanket implementation for any built-in object payload, +/// and should be implemented by many primitive Rust types, allowing a built-in +/// function to simply return a `bool` or a `usize` for example. +pub trait IntoPyObject { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult; +} + +impl IntoPyObject for PyObjectRef { + fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult { + Ok(self) + } +} + +impl IntoPyObject for PyResult +where + T: IntoPyObject, +{ + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + self.and_then(|res| T::into_pyobject(res, vm)) + } +} + +// Allows a built-in function to return any built-in object payload without +// explicitly implementing `IntoPyObject`. +impl IntoPyObject for T +where + T: PyValue + Sized, +{ + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(PyObject::new(self, T::class(vm))) + } +} + +// TODO: This is a workaround and shouldn't exist. +// Each iterable type should have its own distinct iterator type. +#[derive(Debug)] +pub struct PyIteratorValue { + pub position: Cell, + pub iterated_obj: PyObjectRef, +} + +impl PyValue for PyIteratorValue { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.iter_type() } } impl PyObject { - pub fn new( - payload: PyObjectPayload, - /* dict: PyObjectRef,*/ typ: PyObjectRef, - ) -> PyObjectRef { + pub fn new(payload: T, typ: PyObjectRef) -> PyObjectRef { + PyObject { + typ, + dict: Some(RefCell::new(PyAttributes::new())), + payload: Box::new(payload), + } + .into_ref() + } + + pub fn new_without_dict(payload: T, typ: PyObjectRef) -> PyObjectRef { PyObject { - payload, - typ: Some(typ), - // dict: HashMap::new(), // dict, + typ, + dict: None, + payload: Box::new(payload), } .into_ref() } // Move this object into a reference object, transferring ownership. pub fn into_ref(self) -> PyObjectRef { - Rc::new(RefCell::new(self)) + Rc::new(self) + } + + #[inline] + pub fn payload(&self) -> Option<&T> { + self.payload.as_any().downcast_ref() + } + + #[inline] + pub fn payload_is(&self) -> bool { + self.payload.as_any().is::() + } +} + +pub trait PyValue: fmt::Debug + Sized + 'static { + fn class(vm: &VirtualMachine) -> PyObjectRef; + + fn into_ref(self, vm: &VirtualMachine) -> PyRef { + PyRef { + obj: PyObject::new(self, Self::class(vm)), + _payload: PhantomData, + } + } + + fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyClassRef) -> PyResult> { + let class = Self::class(vm); + if objtype::issubclass(&cls.obj, &class) { + Ok(PyRef { + obj: PyObject::new(self, cls.obj), + _payload: PhantomData, + }) + } else { + let subtype = vm.to_pystr(&cls.obj)?; + let basetype = vm.to_pystr(&class)?; + Err(vm.new_type_error(format!("{} is not a subtype of {}", subtype, basetype))) + } + } +} + +pub trait PyObjectPayload: Any + fmt::Debug + 'static { + fn as_any(&self) -> &dyn Any; +} + +impl PyObjectPayload for T { + #[inline] + fn as_any(&self) -> &dyn Any { + self + } +} + +impl FromPyObjectRef for PyRef { + fn from_pyobj(obj: &PyObjectRef) -> Self { + if obj.payload_is::() { + PyRef { + obj: obj.clone(), + _payload: PhantomData, + } + } else { + panic!("Error getting inner type: {:?}", obj.typ) + } } } diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 682b297c6a..a3010a5199 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -3,14 +3,16 @@ //! This module makes use of the parser logic, and translates all ast nodes //! into python ast.AST objects. -extern crate rustpython_parser; +use std::ops::Deref; -use self::rustpython_parser::{ast, parser}; -use crate::obj::objstr; -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; -use crate::VirtualMachine; use num_complex::Complex64; -use std::ops::Deref; + +use rustpython_parser::{ast, parser}; + +use crate::function::PyFuncArgs; +use crate::obj::objstr; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::vm::VirtualMachine; /* * Idea: maybe we can create a sort of struct with some helper functions? @@ -82,6 +84,7 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj args, body, decorator_list, + returns, } => { let node = create_node(ctx, "FunctionDef"); @@ -96,6 +99,13 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj let py_decorator_list = expressions_to_ast(ctx, decorator_list); ctx.set_attr(&node, "decorator_list", py_decorator_list); + + let py_returns = if let Some(hint) = returns { + expression_to_ast(ctx, hint) + } else { + ctx.none() + }; + ctx.set_attr(&node, "returns", py_returns); node } ast::Statement::Continue => create_node(ctx, "Continue"), @@ -395,6 +405,7 @@ fn expression_to_ast(ctx: &PyContext, expression: &ast::Expression) -> PyObjectR node } + ast::Expression::Ellipsis => create_node(ctx, "Ellipsis"), ast::Expression::List { elements } => { let node = create_node(ctx, "List"); @@ -537,17 +548,28 @@ fn parameters_to_ast(ctx: &PyContext, args: &ast::Parameters) -> PyObjectRef { ctx.set_attr( &node, "args", - ctx.new_list( - args.args - .iter() - .map(|a| ctx.new_str(a.to_string())) - .collect(), - ), + ctx.new_list(args.args.iter().map(|a| parameter_to_ast(ctx, a)).collect()), ); node } +fn parameter_to_ast(ctx: &PyContext, parameter: &ast::Parameter) -> PyObjectRef { + let node = create_node(ctx, "arg"); + + let py_arg = ctx.new_str(parameter.arg.to_string()); + ctx.set_attr(&node, "arg", py_arg); + + let py_annotation = if let Some(annotation) = ¶meter.annotation { + expression_to_ast(ctx, annotation) + } else { + ctx.none() + }; + ctx.set_attr(&node, "annotation", py_annotation); + + node +} + fn comprehension_to_ast(ctx: &PyContext, comprehension: &ast::Comprehension) -> PyObjectRef { let node = create_node(ctx, "comprehension"); @@ -590,7 +612,7 @@ fn string_to_ast(ctx: &PyContext, string: &ast::StringGroup) -> PyObjectRef { } } -fn ast_parse(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn ast_parse(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(source, Some(vm.ctx.str_type()))]); let source_string = objstr::get_value(source); @@ -601,24 +623,11 @@ fn ast_parse(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(ast_node) } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - // TODO: maybe we can use some clever macro to generate this? - let ast_mod = ctx.new_module("ast", ctx.new_scope(None)); - - ctx.set_attr(&ast_mod, "parse", ctx.new_rustfunc(ast_parse)); - - ctx.set_attr( - &ast_mod, - "Module", - ctx.new_class("_ast.Module", ctx.object()), - ); - - ctx.set_attr( - &ast_mod, - "FunctionDef", - ctx.new_class("_ast.FunctionDef", ctx.object()), - ); - ctx.set_attr(&ast_mod, "Call", ctx.new_class("_ast.Call", ctx.object())); - - ast_mod +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + py_module!(ctx, "ast", { + "parse" => ctx.new_rustfunc(ast_parse), + "Module" => py_class!(ctx, "_ast.Module", ctx.object(), {}), + "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ctx.object(), {}), + "Call" => py_class!(ctx, "_ast.Call", ctx.object(), {}) + }) } diff --git a/vm/src/stdlib/dis.rs b/vm/src/stdlib/dis.rs index 5e1a30b033..04209d8b3b 100644 --- a/vm/src/stdlib/dis.rs +++ b/vm/src/stdlib/dis.rs @@ -1,8 +1,20 @@ +use crate::function::PyFuncArgs; use crate::obj::objcode; -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; -fn dis_disassemble(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn dis_dis(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(obj, None)]); + + // Method or function: + if let Ok(co) = vm.get_attribute(obj.clone(), "__code__") { + return dis_disassemble(vm, PyFuncArgs::new(vec![co], vec![])); + } + + dis_disassemble(vm, PyFuncArgs::new(vec![obj.clone()], vec![])) +} + +fn dis_disassemble(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(co, Some(vm.ctx.code_type()))]); let code = objcode::get_value(co); @@ -10,8 +22,9 @@ fn dis_disassemble(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "dis", { + "dis" => ctx.new_rustfunc(dis_dis), "disassemble" => ctx.new_rustfunc(dis_disassemble) }) } diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 0d7a46ee00..1ce303c5f3 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -2,27 +2,26 @@ * I/O core tools. */ -//library imports +use std::cell::RefCell; use std::collections::HashSet; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; +use std::path::PathBuf; -//3rd party imports use num_bigint::ToBigInt; use num_traits::ToPrimitive; -//custom imports use super::os; +use crate::function::PyFuncArgs; +use crate::import; +use crate::obj::objbytearray::PyByteArray; use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objstr; - use crate::pyobject::{ - AttributeProtocol, BufferProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, - PyResult, TypeProtocol, + BufferProtocol, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; - use crate::vm::VirtualMachine; fn compute_c_flag(mode: &str) -> u16 { @@ -35,36 +34,79 @@ fn compute_c_flag(mode: &str) -> u16 { } } -fn string_io_init(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { - // arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); +#[derive(Debug)] +struct PyStringIO { + data: RefCell, +} + +type PyStringIORef = PyRef; + +impl PyValue for PyStringIO { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.class("io", "StringIO") + } +} + +impl PyStringIORef { + fn write(self, data: objstr::PyStringRef, _vm: &VirtualMachine) { + let data = data.value.clone(); + self.data.borrow_mut().push_str(&data); + } + + fn getvalue(self, _vm: &VirtualMachine) -> String { + self.data.borrow().clone() + } +} + +fn string_io_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(cls, None)]); + + Ok(PyObject::new( + PyStringIO { + data: RefCell::new(String::default()), + }, + cls.clone(), + )) +} + +fn bytes_io_init(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { // TODO Ok(vm.get_none()) } -fn string_io_getvalue(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn bytes_io_getvalue(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args); // TODO Ok(vm.get_none()) } -fn bytes_io_init(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { - // TODO - Ok(vm.get_none()) +fn io_base_cm_enter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(instance, None)]); + Ok(instance.clone()) } -fn bytes_io_getvalue(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args); - // TODO +fn io_base_cm_exit(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + // The context manager protocol requires these, but we don't use them + required = [ + (_instance, None), + (_exception_type, None), + (_exception_value, None), + (_traceback, None) + ] + ); Ok(vm.get_none()) } -fn buffered_io_base_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn buffered_io_base_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(buffered, None), (raw, None)]); vm.ctx.set_attr(&buffered, "raw", raw.clone()); Ok(vm.get_none()) } -fn buffered_reader_read(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn buffered_reader_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(buffered, None)]); let buff_size = 8 * 1024; let buffer = vm.ctx.new_bytearray(vec![0; buff_size]); @@ -79,24 +121,22 @@ fn buffered_reader_read(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //to obtain buff_size many bytes. Exit when less than buff_size many //bytes are returned (when the end of the file is reached). while length == buff_size { - let raw_read = vm.get_method(raw.clone(), &"readinto".to_string()).unwrap(); - vm.invoke(raw_read, PyFuncArgs::new(vec![buffer.clone()], vec![])) + vm.call_method(&raw, "readinto", vec![buffer.clone()]) .map_err(|_| vm.new_value_error("IO Error".to_string()))?; //Copy bytes from the buffer vector into the results vector - if let PyObjectPayload::Bytes { ref mut value } = buffer.borrow_mut().payload { - result.extend(value.iter().cloned()); + if let Some(bytes) = buffer.payload::() { + result.extend_from_slice(&bytes.value.borrow()); }; - let len = vm.get_method(buffer.clone(), &"__len__".to_string()); - let py_len = vm.invoke(len.unwrap(), PyFuncArgs::default()); - length = objint::get_value(&py_len.unwrap()).to_usize().unwrap(); + let py_len = vm.call_method(&buffer, "__len__", PyFuncArgs::default())?; + length = objint::get_value(&py_len).to_usize().unwrap(); } Ok(vm.ctx.new_bytes(result)) } -fn file_io_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn file_io_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -122,9 +162,9 @@ fn file_io_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn file_io_read(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn file_io_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(file_io, None)]); - let py_name = file_io.get_attr("name").unwrap(); + let py_name = vm.get_attribute(file_io.clone(), "name")?; let f = match File::open(objstr::get_value(&py_name)) { Ok(v) => Ok(v), Err(_) => Err(vm.new_type_error("Error opening file".to_string())), @@ -146,7 +186,7 @@ fn file_io_read(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bytes(bytes)) } -fn file_io_readinto(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn file_io_readinto(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(file_io, None), (obj, None)]); if !obj.readonly() { @@ -156,22 +196,22 @@ fn file_io_readinto(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } //extract length of buffer - let len_method = vm.get_method(obj.clone(), &"__len__".to_string()); - let py_length = vm.invoke(len_method.unwrap(), PyFuncArgs::default()); - let length = objint::get_value(&py_length.unwrap()).to_u64().unwrap(); + let py_length = vm.call_method(obj, "__len__", PyFuncArgs::default())?; + let length = objint::get_value(&py_length).to_u64().unwrap(); - let file_no = file_io.get_attr("fileno").unwrap(); + let file_no = vm.get_attribute(file_io.clone(), "fileno")?; let raw_fd = objint::get_value(&file_no).to_i64().unwrap(); //extract unix file descriptor. let handle = os::rust_file(raw_fd); let mut f = handle.take(length); - if let PyObjectPayload::Bytes { ref mut value } = obj.borrow_mut().payload { + if let Some(bytes) = obj.payload::() { //TODO: Implement for MemoryView - value.clear(); - match f.read_to_end(&mut *value) { + let mut value_mut = bytes.value.borrow_mut(); + value_mut.clear(); + match f.read_to_end(&mut value_mut) { Ok(_) => {} Err(_) => return Err(vm.new_value_error("Error reading from Take".to_string())), } @@ -182,14 +222,14 @@ fn file_io_readinto(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -fn file_io_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn file_io_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, required = [(file_io, None), (obj, Some(vm.ctx.bytes_type()))] ); - let file_no = file_io.get_attr("fileno").unwrap(); + let file_no = vm.get_attribute(file_io.clone(), "fileno")?; let raw_fd = objint::get_value(&file_no).to_i64().unwrap(); //unsafe block - creates file handle from the UNIX file descriptor @@ -197,9 +237,10 @@ fn file_io_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //to support windows - i.e. raw file_handles let mut handle = os::rust_file(raw_fd); - match obj.borrow_mut().payload { - PyObjectPayload::Bytes { ref mut value } => { - match handle.write(&value[..]) { + match obj.payload::() { + Some(bytes) => { + let value_mut = bytes.value.borrow(); + match handle.write(&value_mut[..]) { Ok(len) => { //reset raw fd on the FileIO object let updated = os::raw_file_number(handle); @@ -211,11 +252,11 @@ fn file_io_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Err(_) => Err(vm.new_value_error("Error Writing Bytes to Handle".to_string())), } } - _ => Err(vm.new_value_error("Expected Bytes Object".to_string())), + None => Err(vm.new_value_error("Expected Bytes Object".to_string())), } } -fn buffered_writer_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn buffered_writer_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -223,13 +264,12 @@ fn buffered_writer_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult ); let raw = vm.ctx.get_attr(&buffered, "raw").unwrap(); - let raw_write = vm.get_method(raw.clone(), &"write".to_string()).unwrap(); //This should be replaced with a more appropriate chunking implementation - vm.invoke(raw_write, PyFuncArgs::new(vec![obj.clone()], vec![])) + vm.call_method(&raw, "write", vec![obj.clone()]) } -fn text_io_wrapper_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn text_io_wrapper_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -240,13 +280,12 @@ fn text_io_wrapper_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -fn text_io_base_read(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn text_io_base_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(text_io_base, None)]); let raw = vm.ctx.get_attr(&text_io_base, "buffer").unwrap(); - let read = vm.get_method(raw.clone(), &"read".to_string()); - if let Ok(bytes) = vm.invoke(read.unwrap(), PyFuncArgs::default()) { + if let Ok(bytes) = vm.call_method(&raw, "read", PyFuncArgs::default()) { let value = objbytes::get_value(&bytes).to_vec(); //format bytes into string @@ -257,7 +296,7 @@ fn text_io_base_read(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -265,7 +304,7 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(mode, Some(vm.ctx.str_type()))] ); - let module = mk_module(&vm.ctx); + let module = import::import_module(vm, PathBuf::default(), "io").unwrap(); //mode is optional: 'rt' is the default mode (open from reading text) let rust_mode = mode.map_or("rt".to_string(), |m| objstr::get_value(m)); @@ -302,10 +341,7 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //Construct a FileIO (subclass of RawIOBase) //This is subsequently consumed by a Buffered Class. - let file_args = PyFuncArgs::new( - vec![file.clone(), vm.ctx.new_str(modes[0].to_string())], - vec![], - ); + let file_args = vec![file.clone(), vm.ctx.new_str(modes[0].to_string())]; let file_io = vm.invoke(file_io_class, file_args)?; //Create Buffered class to consume FileIO. The type of buffered class depends on @@ -313,26 +349,17 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //There are 3 possible classes here, each inheriting from the RawBaseIO // creating || writing || appending => BufferedWriter let buffered = if rust_mode.contains('w') { - vm.invoke( - buffered_writer_class, - PyFuncArgs::new(vec![file_io.clone()], vec![]), - ) + vm.invoke(buffered_writer_class, vec![file_io.clone()]) // reading => BufferedReader } else { - vm.invoke( - buffered_reader_class, - PyFuncArgs::new(vec![file_io.clone()], vec![]), - ) + vm.invoke(buffered_reader_class, vec![file_io.clone()]) //TODO: updating => PyBufferedRandom }; if rust_mode.contains('t') { //If the mode is text this buffer type is consumed on construction of //a TextIOWrapper which is subsequently returned. - vm.invoke( - text_io_wrapper_class, - PyFuncArgs::new(vec![buffered.unwrap()], vec![]), - ) + vm.invoke(text_io_wrapper_class, vec![buffered.unwrap()]) } else { // If the mode is binary this Buffered class is returned directly at // this point. @@ -341,102 +368,72 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module(&"io".to_string(), ctx.new_scope(None)); - - ctx.set_attr(&py_mod, "open", ctx.new_rustfunc(io_open)); - +pub fn make_module(ctx: &PyContext) -> PyObjectRef { //IOBase the abstract base class of the IO Module - let io_base = ctx.new_class("IOBase", ctx.object()); - ctx.set_attr(&py_mod, "IOBase", io_base.clone()); + let io_base = py_class!(ctx, "IOBase", ctx.object(), { + "__enter__" => ctx.new_rustfunc(io_base_cm_enter), + "__exit__" => ctx.new_rustfunc(io_base_cm_exit) + }); // IOBase Subclasses - let raw_io_base = ctx.new_class("RawIOBase", ctx.object()); - ctx.set_attr(&py_mod, "RawIOBase", raw_io_base.clone()); - - let buffered_io_base = { - let buffered_io_base = ctx.new_class("BufferedIOBase", io_base.clone()); - ctx.set_attr( - &buffered_io_base, - "__init__", - ctx.new_rustfunc(buffered_io_base_init), - ); - buffered_io_base - }; - ctx.set_attr(&py_mod, "BufferedIOBase", buffered_io_base.clone()); + let raw_io_base = py_class!(ctx, "RawIOBase", ctx.object(), {}); + + let buffered_io_base = py_class!(ctx, "BufferedIOBase", io_base.clone(), { + "__init__" => ctx.new_rustfunc(buffered_io_base_init) + }); //TextIO Base has no public constructor - let text_io_base = { - let text_io_base = ctx.new_class("TextIOBase", io_base.clone()); - ctx.set_attr(&text_io_base, "read", ctx.new_rustfunc(text_io_base_read)); - text_io_base - }; - ctx.set_attr(&py_mod, "TextIOBase", text_io_base.clone()); + let text_io_base = py_class!(ctx, "TextIOBase", io_base.clone(), { + "read" => ctx.new_rustfunc(text_io_base_read) + }); // RawBaseIO Subclasses - let file_io = { - let file_io = ctx.new_class("FileIO", raw_io_base.clone()); - ctx.set_attr(&file_io, "__init__", ctx.new_rustfunc(file_io_init)); - ctx.set_attr(&file_io, "name", ctx.str_type()); - ctx.set_attr(&file_io, "read", ctx.new_rustfunc(file_io_read)); - ctx.set_attr(&file_io, "readinto", ctx.new_rustfunc(file_io_readinto)); - ctx.set_attr(&file_io, "write", ctx.new_rustfunc(file_io_write)); - file_io - }; - ctx.set_attr(&py_mod, "FileIO", file_io.clone()); + let file_io = py_class!(ctx, "FileIO", raw_io_base.clone(), { + "__init__" => ctx.new_rustfunc(file_io_init), + "name" => ctx.str_type(), + "read" => ctx.new_rustfunc(file_io_read), + "readinto" => ctx.new_rustfunc(file_io_readinto), + "write" => ctx.new_rustfunc(file_io_write) + }); // BufferedIOBase Subclasses - let buffered_reader = { - let buffered_reader = ctx.new_class("BufferedReader", buffered_io_base.clone()); - ctx.set_attr( - &buffered_reader, - "read", - ctx.new_rustfunc(buffered_reader_read), - ); - buffered_reader - }; - ctx.set_attr(&py_mod, "BufferedReader", buffered_reader.clone()); - - let buffered_writer = { - let buffered_writer = ctx.new_class("BufferedWriter", buffered_io_base.clone()); - ctx.set_attr( - &buffered_writer, - "write", - ctx.new_rustfunc(buffered_writer_write), - ); - buffered_writer - }; - ctx.set_attr(&py_mod, "BufferedWriter", buffered_writer.clone()); + let buffered_reader = py_class!(ctx, "BufferedReader", buffered_io_base.clone(), { + "read" => ctx.new_rustfunc(buffered_reader_read) + }); + + let buffered_writer = py_class!(ctx, "BufferedWriter", buffered_io_base.clone(), { + "write" => ctx.new_rustfunc(buffered_writer_write) + }); //TextIOBase Subclass - let text_io_wrapper = { - let text_io_wrapper = ctx.new_class("TextIOWrapper", text_io_base.clone()); - ctx.set_attr( - &text_io_wrapper, - "__init__", - ctx.new_rustfunc(text_io_wrapper_init), - ); - text_io_wrapper - }; - ctx.set_attr(&py_mod, "TextIOWrapper", text_io_wrapper.clone()); + let text_io_wrapper = py_class!(ctx, "TextIOWrapper", text_io_base.clone(), { + "__init__" => ctx.new_rustfunc(text_io_wrapper_init) + }); //StringIO: in-memory text - let string_io = { - let string_io = ctx.new_class("StringIO", text_io_base.clone()); - ctx.set_attr(&string_io, "__init__", ctx.new_rustfunc(string_io_init)); - ctx.set_attr(&string_io, "getvalue", ctx.new_rustfunc(string_io_getvalue)); - string_io - }; - ctx.set_attr(&py_mod, "StringIO", string_io); + let string_io = py_class!(ctx, "StringIO", text_io_base.clone(), { + "__new__" => ctx.new_rustfunc(string_io_new), + "write" => ctx.new_rustfunc(PyStringIORef::write), + "getvalue" => ctx.new_rustfunc(PyStringIORef::getvalue) + }); //BytesIO: in-memory bytes - let bytes_io = { - let bytes_io = ctx.new_class("BytesIO", buffered_io_base.clone()); - ctx.set_attr(&bytes_io, "__init__", ctx.new_rustfunc(bytes_io_init)); - ctx.set_attr(&bytes_io, "getvalue", ctx.new_rustfunc(bytes_io_getvalue)); - bytes_io - }; - ctx.set_attr(&py_mod, "BytesIO", bytes_io); - - py_mod + let bytes_io = py_class!(ctx, "BytesIO", buffered_io_base.clone(), { + "__init__" => ctx.new_rustfunc(bytes_io_init), + "getvalue" => ctx.new_rustfunc(bytes_io_getvalue) + }); + + py_module!(ctx, "io", { + "open" => ctx.new_rustfunc(io_open), + "IOBase" => io_base.clone(), + "RawIOBase" => raw_io_base.clone(), + "BufferedIOBase" => buffered_io_base.clone(), + "TextIOBase" => text_io_base.clone(), + "FileIO" => file_io.clone(), + "BufferedReader" => buffered_reader.clone(), + "BufferedWriter" => buffered_writer.clone(), + "TextIOWrapper" => text_io_wrapper.clone(), + "StringIO" => string_io, + "BytesIO" => bytes_io, + }) } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 53507e81d7..da373a955c 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -5,10 +5,14 @@ use serde::de::{DeserializeSeed, Visitor}; use serde::ser::{SerializeMap, SerializeSeq}; use serde_json; -use crate::obj::{objbool, objdict, objfloat, objint, objsequence, objstr, objtype}; +use crate::function::PyFuncArgs; +use crate::obj::{ + objbool, objdict, objfloat, objint, objsequence, + objstr::{self, PyString}, + objtype, +}; use crate::pyobject::{ - create_type, DictProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, - TypeProtocol, + create_type, DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol, }; use crate::VirtualMachine; use num_traits::cast::ToPrimitive; @@ -65,7 +69,7 @@ impl<'s> serde::Serialize for PyObjectSerializer<'s> { map.serialize_entry(&key, &self.clone_with_object(&e.1))?; } map.end() - } else if let PyObjectPayload::None = self.pyobject.borrow().payload { + } else if self.pyobject.is(&self.vm.get_none()) { serializer.serialize_none() } else { Err(serde::ser::Error::custom(format!( @@ -167,11 +171,11 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { // than wrapping the given object up and then unwrapping it to determine whether or // not it is a string while let Some((key_obj, value)) = access.next_entry_seed(self.clone(), self.clone())? { - let key = match key_obj.borrow().payload { - PyObjectPayload::String { ref value } => value.clone(), + let key: String = match key_obj.payload::() { + Some(PyString { ref value }) => value.clone(), _ => unimplemented!("map keys must be strings"), }; - self.vm.ctx.set_item(&dict, &key, value); + dict.set_item(&self.vm.ctx, &key, value); } Ok(dict) } @@ -184,60 +188,58 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { } } +pub fn ser_pyobject(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { + let serializer = PyObjectSerializer { pyobject: obj, vm }; + serde_json::to_string(&serializer).map_err(|err| vm.new_type_error(err.to_string())) +} + +pub fn de_pyobject(vm: &VirtualMachine, s: &str) -> PyResult { + let de = PyObjectDeserializer { vm }; + // TODO: Support deserializing string sub-classes + de.deserialize(&mut serde_json::Deserializer::from_str(s)) + .map_err(|err| { + let json_decode_error = vm + .sys_module + .get_item("modules") + .unwrap() + .get_item("json") + .unwrap() + .get_item("JSONDecodeError") + .unwrap(); + let exc = vm.new_exception(json_decode_error, format!("{}", err)); + vm.ctx.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())); + vm.ctx.set_attr(&exc, "colno", vm.ctx.new_int(err.column())); + exc + }) +} + /// Implement json.dumps -fn json_dumps(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn json_dumps(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: Implement non-trivial serialisation case arg_check!(vm, args, required = [(obj, None)]); - let res = { - let serializer = PyObjectSerializer { pyobject: obj, vm }; - serde_json::to_string(&serializer) - }; - let string = res.map_err(|err| vm.new_type_error(format!("{}", err)))?; + let string = ser_pyobject(vm, obj)?; Ok(vm.context().new_str(string)) } /// Implement json.loads -fn json_loads(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn json_loads(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: Implement non-trivial deserialization case arg_check!(vm, args, required = [(string, Some(vm.ctx.str_type()))]); - let res = { - let de = PyObjectDeserializer { vm }; - // TODO: Support deserializing string sub-classes - de.deserialize(&mut serde_json::Deserializer::from_str(&objstr::get_value( - &string, - ))) - }; - - res.map_err(|err| { - let json_decode_error = vm - .sys_module - .get_item("modules") - .unwrap() - .get_item("json") - .unwrap() - .get_item("JSONDecodeError") - .unwrap(); - let exc = vm.new_exception(json_decode_error, format!("{}", err)); - vm.ctx.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())); - vm.ctx.set_attr(&exc, "colno", vm.ctx.new_int(err.column())); - exc - }) -} - -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let json_mod = ctx.new_module("json", ctx.new_scope(None)); - ctx.set_attr(&json_mod, "dumps", ctx.new_rustfunc(json_dumps)); - ctx.set_attr(&json_mod, "loads", ctx.new_rustfunc(json_loads)); + de_pyobject(vm, &objstr::get_value(&string)) +} +pub fn make_module(ctx: &PyContext) -> PyObjectRef { // TODO: Make this a proper type with a constructor let json_decode_error = create_type( "JSONDecodeError", &ctx.type_type, &ctx.exceptions.exception_type, - &ctx.dict_type, ); - ctx.set_attr(&json_mod, "JSONDecodeError", json_decode_error); - json_mod + py_module!(ctx, "json", { + "dumps" => ctx.new_rustfunc(json_dumps), + "loads" => ctx.new_rustfunc(json_loads), + "JSONDecodeError" => json_decode_error + }) } diff --git a/vm/src/stdlib/keyword.rs b/vm/src/stdlib/keyword.rs index 5d353fffad..fe27399c55 100644 --- a/vm/src/stdlib/keyword.rs +++ b/vm/src/stdlib/keyword.rs @@ -2,13 +2,14 @@ * Testing if a string is a keyword. */ -extern crate rustpython_parser; -use self::rustpython_parser::lexer; +use rustpython_parser::lexer; + +use crate::function::PyFuncArgs; use crate::obj::objstr; -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; -use crate::VirtualMachine; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::vm::VirtualMachine; -fn keyword_iskeyword(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn keyword_iskeyword(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); let s = objstr::get_value(s); let keywords = lexer::get_keywords(); @@ -17,18 +18,16 @@ fn keyword_iskeyword(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(value) } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module("keyword", ctx.new_scope(None)); - - ctx.set_attr(&py_mod, "iskeyword", ctx.new_rustfunc(keyword_iskeyword)); - +pub fn make_module(ctx: &PyContext) -> PyObjectRef { let keyword_kwlist = ctx.new_list( lexer::get_keywords() .keys() .map(|k| ctx.new_str(k.to_string())) .collect(), ); - ctx.set_attr(&py_mod, "kwlist", keyword_kwlist); - py_mod + py_module!(ctx, "keyword", { + "iskeyword" => ctx.new_rustfunc(keyword_iskeyword), + "kwlist" => keyword_kwlist + }) } diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index 8caa59e6de..5af599cb7d 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -3,17 +3,18 @@ * */ -use crate::obj::objfloat; -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; -use crate::VirtualMachine; use statrs::function::erf::{erf, erfc}; use statrs::function::gamma::{gamma, ln_gamma}; -use std; + +use crate::function::PyFuncArgs; +use crate::obj::objfloat; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::vm::VirtualMachine; // Helper macro: macro_rules! make_math_func { ( $fname:ident, $fun:ident ) => { - fn $fname(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + fn $fname(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let value = objfloat::make_float(vm, value)?; let value = value.$fun(); @@ -26,19 +27,19 @@ macro_rules! make_math_func { // Number theory functions: make_math_func!(math_fabs, abs); -fn math_isfinite(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_isfinite(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let value = objfloat::make_float(vm, value)?.is_finite(); Ok(vm.ctx.new_bool(value)) } -fn math_isinf(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_isinf(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let value = objfloat::make_float(vm, value)?.is_infinite(); Ok(vm.ctx.new_bool(value)) } -fn math_isnan(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_isnan(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let value = objfloat::make_float(vm, value)?.is_nan(); Ok(vm.ctx.new_bool(value)) @@ -48,7 +49,7 @@ fn math_isnan(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { make_math_func!(math_exp, exp); make_math_func!(math_expm1, exp_m1); -fn math_log(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_log(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(x, None)], optional = [(base, None)]); let x = objfloat::make_float(vm, x)?; match base { @@ -60,7 +61,7 @@ fn math_log(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn math_log1p(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_log1p(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(x, None)]); let x = objfloat::make_float(vm, x)?; Ok(vm.ctx.new_float((x + 1.0).ln())) @@ -69,7 +70,7 @@ fn math_log1p(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { make_math_func!(math_log2, log2); make_math_func!(math_log10, log10); -fn math_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(x, None), (y, None)]); let x = objfloat::make_float(vm, x)?; let y = objfloat::make_float(vm, y)?; @@ -83,7 +84,7 @@ make_math_func!(math_acos, acos); make_math_func!(math_asin, asin); make_math_func!(math_atan, atan); -fn math_atan2(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_atan2(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(y, None), (x, None)]); let y = objfloat::make_float(vm, y)?; let x = objfloat::make_float(vm, x)?; @@ -92,7 +93,7 @@ fn math_atan2(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { make_math_func!(math_cos, cos); -fn math_hypot(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_hypot(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(x, None), (y, None)]); let x = objfloat::make_float(vm, x)?; let y = objfloat::make_float(vm, y)?; @@ -102,13 +103,13 @@ fn math_hypot(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { make_math_func!(math_sin, sin); make_math_func!(math_tan, tan); -fn math_degrees(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_degrees(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let x = objfloat::make_float(vm, value)?; Ok(vm.ctx.new_float(x * (180.0 / std::f64::consts::PI))) } -fn math_radians(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_radians(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let x = objfloat::make_float(vm, value)?; Ok(vm.ctx.new_float(x * (std::f64::consts::PI / 180.0))) @@ -123,7 +124,7 @@ make_math_func!(math_sinh, sinh); make_math_func!(math_tanh, tanh); // Special functions: -fn math_erf(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_erf(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let x = objfloat::make_float(vm, value)?; @@ -134,7 +135,7 @@ fn math_erf(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn math_erfc(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_erfc(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let x = objfloat::make_float(vm, value)?; @@ -145,7 +146,7 @@ fn math_erfc(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn math_gamma(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_gamma(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let x = objfloat::make_float(vm, value)?; @@ -158,7 +159,7 @@ fn math_gamma(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn math_lgamma(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn math_lgamma(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(value, None)]); let x = objfloat::make_float(vm, value)?; @@ -171,7 +172,7 @@ fn math_lgamma(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "math", { // Number theory functions: "fabs" => ctx.new_rustfunc(math_fabs), diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index a0dcd9bff6..b0b61119c3 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -1,11 +1,13 @@ mod ast; mod dis; -mod json; +pub(crate) mod json; mod keyword; mod math; +mod platform; mod pystruct; mod random; mod re; +pub mod socket; mod string; mod time_module; mod tokenize; @@ -20,32 +22,34 @@ mod os; use crate::pyobject::{PyContext, PyObjectRef}; -pub type StdlibInitFunc = fn(&PyContext) -> PyObjectRef; +pub type StdlibInitFunc = Box PyObjectRef>; pub fn get_module_inits() -> HashMap { let mut modules = HashMap::new(); - modules.insert("ast".to_string(), ast::mk_module as StdlibInitFunc); - modules.insert("dis".to_string(), dis::mk_module as StdlibInitFunc); - modules.insert("json".to_string(), json::mk_module as StdlibInitFunc); - modules.insert("keyword".to_string(), keyword::mk_module as StdlibInitFunc); - modules.insert("math".to_string(), math::mk_module as StdlibInitFunc); - modules.insert("re".to_string(), re::mk_module as StdlibInitFunc); - modules.insert("random".to_string(), random::mk_module as StdlibInitFunc); - modules.insert("string".to_string(), string::mk_module as StdlibInitFunc); - modules.insert("struct".to_string(), pystruct::mk_module as StdlibInitFunc); - modules.insert("time".to_string(), time_module::mk_module as StdlibInitFunc); modules.insert( - "tokenize".to_string(), - tokenize::mk_module as StdlibInitFunc, + "ast".to_string(), + Box::new(ast::make_module) as StdlibInitFunc, ); - modules.insert("types".to_string(), types::mk_module as StdlibInitFunc); - modules.insert("_weakref".to_string(), weakref::mk_module as StdlibInitFunc); + modules.insert("dis".to_string(), Box::new(dis::make_module)); + modules.insert("json".to_string(), Box::new(json::make_module)); + modules.insert("keyword".to_string(), Box::new(keyword::make_module)); + modules.insert("math".to_string(), Box::new(math::make_module)); + modules.insert("platform".to_string(), Box::new(platform::make_module)); + modules.insert("re".to_string(), Box::new(re::make_module)); + modules.insert("random".to_string(), Box::new(random::make_module)); + modules.insert("string".to_string(), Box::new(string::make_module)); + modules.insert("struct".to_string(), Box::new(pystruct::make_module)); + modules.insert("time".to_string(), Box::new(time_module::make_module)); + modules.insert("tokenize".to_string(), Box::new(tokenize::make_module)); + modules.insert("types".to_string(), Box::new(types::make_module)); + modules.insert("_weakref".to_string(), Box::new(weakref::make_module)); // disable some modules on WASM #[cfg(not(target_arch = "wasm32"))] { - modules.insert("io".to_string(), io::mk_module as StdlibInitFunc); - modules.insert("os".to_string(), os::mk_module as StdlibInitFunc); + modules.insert("io".to_string(), Box::new(io::make_module)); + modules.insert("os".to_string(), Box::new(os::make_module)); + modules.insert("socket".to_string(), Box::new(socket::make_module)); } modules diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index ef45d2d84a..42b4160ca8 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,18 +1,13 @@ -//library imports use std::fs::File; use std::fs::OpenOptions; use std::io::ErrorKind; -// use std::env; -//3rd party imports use num_traits::cast::ToPrimitive; -//custom imports +use crate::function::PyFuncArgs; use crate::obj::objint; use crate::obj::objstr; -// use crate::obj::objdict; - -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; #[cfg(unix)] @@ -55,7 +50,7 @@ pub fn raw_file_number(handle: File) -> i64 { unimplemented!(); } -pub fn os_close(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +pub fn os_close(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(fileno, Some(vm.ctx.int_type()))]); let raw_fileno = objint::get_value(&fileno); @@ -68,7 +63,7 @@ pub fn os_close(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -pub fn os_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -101,7 +96,7 @@ pub fn os_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_int(raw_file_number(handle))) } -fn os_error(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -118,18 +113,18 @@ fn os_error(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Err(vm.new_os_error(msg)) } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module(&"os".to_string(), ctx.new_scope(None)); - ctx.set_attr(&py_mod, "open", ctx.new_rustfunc(os_open)); - ctx.set_attr(&py_mod, "close", ctx.new_rustfunc(os_close)); - ctx.set_attr(&py_mod, "error", ctx.new_rustfunc(os_error)); - - ctx.set_attr(&py_mod, "O_RDONLY", ctx.new_int(0)); - ctx.set_attr(&py_mod, "O_WRONLY", ctx.new_int(1)); - ctx.set_attr(&py_mod, "O_RDWR", ctx.new_int(2)); - ctx.set_attr(&py_mod, "O_NONBLOCK", ctx.new_int(4)); - ctx.set_attr(&py_mod, "O_APPEND", ctx.new_int(8)); - ctx.set_attr(&py_mod, "O_CREAT", ctx.new_int(512)); +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + let py_mod = py_module!(ctx, "os", { + "open" => ctx.new_rustfunc(os_open), + "close" => ctx.new_rustfunc(os_close), + "error" => ctx.new_rustfunc(os_error), + "O_RDONLY" => ctx.new_int(0), + "O_WRONLY" => ctx.new_int(1), + "O_RDWR" => ctx.new_int(2), + "O_NONBLOCK" => ctx.new_int(4), + "O_APPEND" => ctx.new_int(8), + "O_CREAT" => ctx.new_int(512) + }); if cfg!(windows) { ctx.set_attr(&py_mod, "name", ctx.new_str("nt".to_string())); diff --git a/vm/src/stdlib/platform.rs b/vm/src/stdlib/platform.rs new file mode 100644 index 0000000000..a87383a723 --- /dev/null +++ b/vm/src/stdlib/platform.rs @@ -0,0 +1,28 @@ +use crate::function::PyFuncArgs; +use crate::pyobject::{PyContext, PyObjectRef, PyResult}; +use crate::vm::VirtualMachine; + +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + py_module!(ctx, "platform", { + "python_compiler" => ctx.new_rustfunc(platform_python_compiler), + "python_implementation" => ctx.new_rustfunc(platform_python_implementation), + "python_version" => ctx.new_rustfunc(platform_python_version), + }) +} + +fn platform_python_implementation(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args); + Ok(vm.new_str("RustPython".to_string())) +} + +fn platform_python_version(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args); + // TODO: fetch version from somewhere. + Ok(vm.new_str("4.0.0".to_string())) +} + +fn platform_python_compiler(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args); + let version = rustc_version_runtime::version_meta(); + Ok(vm.new_str(format!("rustc {}", version.semver))) +} diff --git a/vm/src/stdlib/pystruct.rs b/vm/src/stdlib/pystruct.rs index 8c12674cf2..9dadc520a9 100644 --- a/vm/src/stdlib/pystruct.rs +++ b/vm/src/stdlib/pystruct.rs @@ -7,15 +7,16 @@ * https://docs.rs/byteorder/1.2.6/byteorder/ */ -extern crate byteorder; -use self::byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Cursor, Read, Write}; -use crate::obj::{objbool, objbytes, objfloat, objint, objstr, objtype}; -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; -use crate::VirtualMachine; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use num_bigint::BigInt; use num_traits::ToPrimitive; -use std::io::{Cursor, Read, Write}; + +use crate::function::PyFuncArgs; +use crate::obj::{objbool, objbytes, objfloat, objint, objstr, objtype}; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::VirtualMachine; #[derive(Debug)] struct FormatCode { @@ -42,35 +43,23 @@ fn parse_format_string(fmt: String) -> Vec { codes } -fn get_int(vm: &mut VirtualMachine, arg: &PyObjectRef) -> Result { +fn get_int(vm: &VirtualMachine, arg: &PyObjectRef) -> PyResult { objint::to_int(vm, arg, 10) } -fn pack_i8( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_i8(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_i8().unwrap(); data.write_i8(v).unwrap(); Ok(()) } -fn pack_u8( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_u8(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_u8().unwrap(); data.write_u8(v).unwrap(); Ok(()) } -fn pack_bool( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_bool(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { if objtype::isinstance(&arg, &vm.ctx.bool_type()) { let v = if objbool::get_value(arg) { 1 } else { 0 }; data.write_u8(v).unwrap(); @@ -80,71 +69,43 @@ fn pack_bool( } } -fn pack_i16( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_i16(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_i16().unwrap(); data.write_i16::(v).unwrap(); Ok(()) } -fn pack_u16( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_u16(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_u16().unwrap(); data.write_u16::(v).unwrap(); Ok(()) } -fn pack_i32( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_i32(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_i32().unwrap(); data.write_i32::(v).unwrap(); Ok(()) } -fn pack_u32( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_u32(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_u32().unwrap(); data.write_u32::(v).unwrap(); Ok(()) } -fn pack_i64( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_i64(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_i64().unwrap(); data.write_i64::(v).unwrap(); Ok(()) } -fn pack_u64( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_u64(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_u64().unwrap(); data.write_u64::(v).unwrap(); Ok(()) } -fn pack_f32( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_f32(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { if objtype::isinstance(&arg, &vm.ctx.float_type()) { let v = objfloat::get_value(arg) as f32; data.write_f32::(v).unwrap(); @@ -154,11 +115,7 @@ fn pack_f32( } } -fn pack_f64( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> Result<(), PyObjectRef> { +fn pack_f64(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { if objtype::isinstance(&arg, &vm.ctx.float_type()) { let v = objfloat::get_value(arg) as f64; data.write_f64::(v).unwrap(); @@ -168,7 +125,7 @@ fn pack_f64( } } -fn struct_pack(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn struct_pack(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if args.args.is_empty() { Err(vm.new_type_error(format!( "Expected at least 1 argument (got: {})", @@ -221,84 +178,84 @@ fn struct_pack(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn unpack_i8(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_i8(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_i8() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_u8(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_u8(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_u8() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_bool(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_bool(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_u8() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_bool(v > 0)), } } -fn unpack_i16(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_i16(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_i16::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_u16(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_u16(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_u16::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_i32(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_i32(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_i32::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_u32(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_u32(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_u32::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_i64(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_i64(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_i64::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_u64(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_u64(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_u64::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_f32(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_f32(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_f32::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_float(f64::from(v))), } } -fn unpack_f64(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { +fn unpack_f64(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_f64::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_float(v)), } } -fn struct_unpack(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn struct_unpack(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -340,11 +297,9 @@ fn struct_unpack(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_tuple(items)) } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module(&"struct".to_string(), ctx.new_scope(None)); - - ctx.set_attr(&py_mod, "pack", ctx.new_rustfunc(struct_pack)); - ctx.set_attr(&py_mod, "unpack", ctx.new_rustfunc(struct_unpack)); - - py_mod +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + py_module!(ctx, "struct", { + "pack" => ctx.new_rustfunc(struct_pack), + "unpack" => ctx.new_rustfunc(struct_unpack) + }) } diff --git a/vm/src/stdlib/random.rs b/vm/src/stdlib/random.rs index 85cd52a587..82c1f410c3 100644 --- a/vm/src/stdlib/random.rs +++ b/vm/src/stdlib/random.rs @@ -1,13 +1,13 @@ //! Random module. -extern crate rand; +use rand::distributions::{Distribution, Normal}; +use crate::function::PyFuncArgs; use crate::obj::objfloat; -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; -use crate::stdlib::random::rand::distributions::{Distribution, Normal}; -use crate::VirtualMachine; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::vm::VirtualMachine; -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "random", { "guass" => ctx.new_rustfunc(random_gauss), "normalvariate" => ctx.new_rustfunc(random_normalvariate), @@ -16,12 +16,12 @@ pub fn mk_module(ctx: &PyContext) -> PyObjectRef { }) } -fn random_gauss(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn random_gauss(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: is this the same? random_normalvariate(vm, args) } -fn random_normalvariate(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn random_normalvariate(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -38,7 +38,7 @@ fn random_normalvariate(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(py_value) } -fn random_random(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn random_random(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args); let value = rand::random::(); let py_value = vm.ctx.new_float(value); @@ -47,7 +47,7 @@ fn random_random(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { /* * TODO: enable this function: -fn random_weibullvariate(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn random_weibullvariate(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(alpha, Some(vm.ctx.float_type())), (beta, Some(vm.ctx.float_type()))]); let alpha = objfloat::get_value(alpha); let beta = objfloat::get_value(beta); diff --git a/vm/src/stdlib/re.rs b/vm/src/stdlib/re.rs index 0c0cef934f..863e24c406 100644 --- a/vm/src/stdlib/re.rs +++ b/vm/src/stdlib/re.rs @@ -5,20 +5,47 @@ * system. */ -extern crate regex; -use self::regex::Regex; +use std::path::PathBuf; +use regex::{Match, Regex}; + +use crate::function::PyFuncArgs; +use crate::import; use crate::obj::objstr; -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; -use crate::VirtualMachine; +use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::vm::VirtualMachine; + +impl PyValue for Regex { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.class("re", "Pattern") + } +} -fn re_match(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - // TODO: - error!("TODO: implement match"); - re_search(vm, args) +/// Create the python `re` module with all its members. +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + let match_type = py_class!(ctx, "Match", ctx.object(), { + "start" => ctx.new_rustfunc(match_start), + "end" => ctx.new_rustfunc(match_end) + }); + + let pattern_type = py_class!(ctx, "Pattern", ctx.object(), { + "match" => ctx.new_rustfunc(pattern_match), + "search" => ctx.new_rustfunc(pattern_search) + }); + + py_module!(ctx, "re", { + "compile" => ctx.new_rustfunc(re_compile), + "Match" => match_type, + "match" => ctx.new_rustfunc(re_match), + "Pattern" => pattern_type, + "search" => ctx.new_rustfunc(re_search) + }) } -fn re_search(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +/// Implement re.match +/// See also: +/// https://docs.python.org/3/library/re.html#re.match +fn re_match(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -27,36 +54,156 @@ fn re_search(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { (string, Some(vm.ctx.str_type())) ] ); + let regex = make_regex(vm, pattern)?; + let search_text = objstr::get_value(string); + + do_match(vm, ®ex, search_text) +} - let pattern_str = objstr::get_value(&pattern); - let search_text = objstr::get_value(&string); +/// Implement re.search +/// See also: +/// https://docs.python.org/3/library/re.html#re.search +fn re_search(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (pattern, Some(vm.ctx.str_type())), + (string, Some(vm.ctx.str_type())) + ] + ); + + // let pattern_str = objstr::get_value(&pattern); + let regex = make_regex(vm, pattern)?; + let search_text = objstr::get_value(string); + + do_search(vm, ®ex, search_text) +} + +fn do_match(vm: &VirtualMachine, regex: &Regex, search_text: String) -> PyResult { + // TODO: implement match! + do_search(vm, regex, search_text) +} + +fn do_search(vm: &VirtualMachine, regex: &Regex, search_text: String) -> PyResult { + match regex.find(&search_text) { + None => Ok(vm.get_none()), + Some(result) => create_match(vm, &result), + } +} + +fn make_regex(vm: &VirtualMachine, pattern: &PyObjectRef) -> PyResult { + let pattern_str = objstr::get_value(pattern); match Regex::new(&pattern_str) { - Ok(regex) => { - // Now use regex to search: - match regex.find(&search_text) { - None => Ok(vm.get_none()), - Some(result) => { - // Return match object: - // TODO: implement match object - // TODO: how to refer to match object defined in this - // module? - Ok(vm.ctx.new_str(result.as_str().to_string())) - } - } - } + Ok(regex) => Ok(regex), Err(err) => Err(vm.new_value_error(format!("Error in regex: {:?}", err))), } } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module("re", ctx.new_scope(None)); +/// Inner data for a match object. +#[derive(Debug)] +struct PyMatch { + start: usize, + end: usize, +} - let match_type = ctx.new_class("Match", ctx.object()); - ctx.set_attr(&py_mod, "Match", match_type); +impl PyValue for PyMatch { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.class("re", "Match") + } +} - ctx.set_attr(&py_mod, "match", ctx.new_rustfunc(re_match)); - ctx.set_attr(&py_mod, "search", ctx.new_rustfunc(re_search)); +/// Take a found regular expression and convert it to proper match object. +fn create_match(vm: &VirtualMachine, match_value: &Match) -> PyResult { + // Return match object: + // TODO: implement match object + // TODO: how to refer to match object defined in this + let module = import::import_module(vm, PathBuf::default(), "re").unwrap(); + let match_class = vm.ctx.get_attr(&module, "Match").unwrap(); + + // let mo = vm.invoke(match_class, PyFuncArgs::default())?; + // let txt = vm.ctx.new_str(result.as_str().to_string()); + // vm.ctx.set_attr(&mo, "str", txt); + let match_value = PyMatch { + start: match_value.start(), + end: match_value.end(), + }; + + Ok(PyObject::new(match_value, match_class.clone())) +} - py_mod +/// Compile a regular expression into a Pattern object. +/// See also: +/// https://docs.python.org/3/library/re.html#re.compile +fn re_compile(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(pattern, Some(vm.ctx.str_type()))] // TODO: flags=0 + ); + + let regex = make_regex(vm, pattern)?; + // TODO: retrieval of this module is akward: + let module = import::import_module(vm, PathBuf::default(), "re").unwrap(); + let pattern_class = vm.ctx.get_attr(&module, "Pattern").unwrap(); + + Ok(PyObject::new(regex, pattern_class.clone())) +} + +fn pattern_match(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, None), (text, Some(vm.ctx.str_type()))] + ); + + let regex = get_regex(zelf); + let search_text = objstr::get_value(text); + do_match(vm, ®ex, search_text) +} + +fn pattern_search(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, None), (text, Some(vm.ctx.str_type()))] + ); + + let regex = get_regex(zelf); + let search_text = objstr::get_value(text); + do_search(vm, ®ex, search_text) +} + +/// Returns start of match +/// see: https://docs.python.org/3/library/re.html#re.Match.start +fn match_start(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, None)]); + // TODO: implement groups + let m = get_match(zelf); + Ok(vm.new_int(m.start)) +} + +fn match_end(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, None)]); + // TODO: implement groups + let m = get_match(zelf); + Ok(vm.new_int(m.end)) +} + +/// Retrieve inner rust regex from python object: +fn get_regex(obj: &PyObjectRef) -> &Regex { + // TODO: Regex shouldn't be stored in payload directly, create newtype wrapper + if let Some(regex) = obj.payload::() { + return regex; + } + panic!("Inner error getting regex {:?}", obj); +} + +/// Retrieve inner rust match from python object: +fn get_match(obj: &PyObjectRef) -> &PyMatch { + if let Some(value) = obj.payload::() { + return value; + } + panic!("Inner error getting match {:?}", obj); } diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs new file mode 100644 index 0000000000..4e75cfa417 --- /dev/null +++ b/vm/src/stdlib/socket.rs @@ -0,0 +1,444 @@ +use std::cell::RefCell; +use std::io; +use std::io::Read; +use std::io::Write; +use std::net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs, UdpSocket}; +use std::ops::Deref; + +use crate::function::PyFuncArgs; +use crate::obj::objbytes; +use crate::obj::objint; +use crate::obj::objsequence::get_elements; +use crate::obj::objstr; +use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::vm::VirtualMachine; + +use num_traits::ToPrimitive; + +#[derive(Debug, Copy, Clone)] +enum AddressFamily { + Unix = 1, + Inet = 2, + Inet6 = 3, +} + +impl AddressFamily { + fn from_i32(vm: &VirtualMachine, value: i32) -> Result { + match value { + 1 => Ok(AddressFamily::Unix), + 2 => Ok(AddressFamily::Inet), + 3 => Ok(AddressFamily::Inet6), + _ => Err(vm.new_os_error(format!("Unknown address family value: {}", value))), + } + } +} + +#[derive(Debug, Copy, Clone)] +enum SocketKind { + Stream = 1, + Dgram = 2, +} + +impl SocketKind { + fn from_i32(vm: &VirtualMachine, value: i32) -> Result { + match value { + 1 => Ok(SocketKind::Stream), + 2 => Ok(SocketKind::Dgram), + _ => Err(vm.new_os_error(format!("Unknown socket kind value: {}", value))), + } + } +} + +#[derive(Debug)] +enum Connection { + TcpListener(TcpListener), + TcpStream(TcpStream), + UdpSocket(UdpSocket), +} + +impl Connection { + fn accept(&mut self) -> io::Result<(TcpStream, SocketAddr)> { + match self { + Connection::TcpListener(con) => con.accept(), + _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), + } + } + + fn local_addr(&self) -> io::Result { + match self { + Connection::TcpListener(con) => con.local_addr(), + Connection::UdpSocket(con) => con.local_addr(), + Connection::TcpStream(con) => con.local_addr(), + } + } + + fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + match self { + Connection::UdpSocket(con) => con.recv_from(buf), + _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), + } + } + + fn send_to(&self, buf: &[u8], addr: A) -> io::Result { + match self { + Connection::UdpSocket(con) => con.send_to(buf, addr), + _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), + } + } +} + +impl Read for Connection { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + Connection::TcpStream(con) => con.read(buf), + Connection::UdpSocket(con) => con.recv(buf), + _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), + } + } +} + +impl Write for Connection { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + Connection::TcpStream(con) => con.write(buf), + Connection::UdpSocket(con) => con.send(buf), + _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), + } + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[derive(Debug)] +pub struct Socket { + address_family: AddressFamily, + socket_kind: SocketKind, + con: RefCell>, +} + +impl PyValue for Socket { + fn class(_vm: &VirtualMachine) -> PyObjectRef { + // TODO + unimplemented!() + } +} + +impl Socket { + fn new(address_family: AddressFamily, socket_kind: SocketKind) -> Socket { + Socket { + address_family, + socket_kind, + con: RefCell::new(None), + } + } +} + +fn get_socket<'a>(obj: &'a PyObjectRef) -> impl Deref + 'a { + obj.payload::().unwrap() +} + +fn socket_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (cls, None), + (family_int, Some(vm.ctx.int_type())), + (kind_int, Some(vm.ctx.int_type())) + ] + ); + + let address_family = + AddressFamily::from_i32(vm, objint::get_value(family_int).to_i32().unwrap())?; + let kind = SocketKind::from_i32(vm, objint::get_value(kind_int).to_i32().unwrap())?; + + Ok(PyObject::new( + Socket::new(address_family, kind), + cls.clone(), + )) +} + +fn socket_connect(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, None), (address, Some(vm.ctx.tuple_type()))] + ); + + let address_string = get_address_string(vm, address)?; + + let socket = get_socket(zelf); + + match socket.socket_kind { + SocketKind::Stream => match TcpStream::connect(address_string) { + Ok(stream) => { + socket + .con + .borrow_mut() + .replace(Connection::TcpStream(stream)); + Ok(vm.get_none()) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + SocketKind::Dgram => { + if let Some(Connection::UdpSocket(con)) = socket.con.borrow().as_ref() { + match con.connect(address_string) { + Ok(_) => Ok(vm.get_none()), + Err(s) => Err(vm.new_os_error(s.to_string())), + } + } else { + Err(vm.new_type_error("".to_string())) + } + } + } +} + +fn socket_bind(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, None), (address, Some(vm.ctx.tuple_type()))] + ); + + let address_string = get_address_string(vm, address)?; + + let socket = get_socket(zelf); + + match socket.socket_kind { + SocketKind::Stream => match TcpListener::bind(address_string) { + Ok(stream) => { + socket + .con + .borrow_mut() + .replace(Connection::TcpListener(stream)); + Ok(vm.get_none()) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + SocketKind::Dgram => match UdpSocket::bind(address_string) { + Ok(dgram) => { + socket + .con + .borrow_mut() + .replace(Connection::UdpSocket(dgram)); + Ok(vm.get_none()) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + } +} + +fn get_address_string(vm: &VirtualMachine, address: &PyObjectRef) -> Result { + let args = PyFuncArgs { + args: get_elements(address).to_vec(), + kwargs: vec![], + }; + arg_check!( + vm, + args, + required = [ + (host, Some(vm.ctx.str_type())), + (port, Some(vm.ctx.int_type())) + ] + ); + + Ok(format!( + "{}:{}", + objstr::get_value(host), + objint::get_value(port).to_string() + )) +} + +fn socket_listen(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(_zelf, None), (_num, Some(vm.ctx.int_type()))] + ); + Ok(vm.get_none()) +} + +fn socket_accept(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, None)]); + + let socket = get_socket(zelf); + + let ret = match socket.con.borrow_mut().as_mut() { + Some(v) => v.accept(), + None => return Err(vm.new_type_error("".to_string())), + }; + + let (tcp_stream, addr) = match ret { + Ok((socket, addr)) => (socket, addr), + Err(s) => return Err(vm.new_os_error(s.to_string())), + }; + + let socket = Socket { + address_family: socket.address_family, + socket_kind: socket.socket_kind, + con: RefCell::new(Some(Connection::TcpStream(tcp_stream))), + }; + + let sock_obj = PyObject::new(socket, zelf.typ()); + + let addr_tuple = get_addr_tuple(vm, addr)?; + + Ok(vm.ctx.new_tuple(vec![sock_obj, addr_tuple])) +} + +fn socket_recv(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, None), (bufsize, Some(vm.ctx.int_type()))] + ); + let socket = get_socket(zelf); + + let mut buffer = vec![0u8; objint::get_value(bufsize).to_usize().unwrap()]; + match socket.con.borrow_mut().as_mut() { + Some(v) => match v.read_exact(&mut buffer) { + Ok(_) => (), + Err(s) => return Err(vm.new_os_error(s.to_string())), + }, + None => return Err(vm.new_type_error("".to_string())), + }; + Ok(vm.ctx.new_bytes(buffer)) +} + +fn socket_recvfrom(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, None), (bufsize, Some(vm.ctx.int_type()))] + ); + + let socket = get_socket(zelf); + + let mut buffer = vec![0u8; objint::get_value(bufsize).to_usize().unwrap()]; + let ret = match socket.con.borrow().as_ref() { + Some(v) => v.recv_from(&mut buffer), + None => return Err(vm.new_type_error("".to_string())), + }; + + let addr = match ret { + Ok((_size, addr)) => addr, + Err(s) => return Err(vm.new_os_error(s.to_string())), + }; + + let addr_tuple = get_addr_tuple(vm, addr)?; + + Ok(vm.ctx.new_tuple(vec![vm.ctx.new_bytes(buffer), addr_tuple])) +} + +fn socket_send(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, None), (bytes, Some(vm.ctx.bytes_type()))] + ); + let socket = get_socket(zelf); + + match socket.con.borrow_mut().as_mut() { + Some(v) => match v.write(&objbytes::get_value(&bytes)) { + Ok(_) => (), + Err(s) => return Err(vm.new_os_error(s.to_string())), + }, + None => return Err(vm.new_type_error("".to_string())), + }; + Ok(vm.get_none()) +} + +fn socket_sendto(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (zelf, None), + (bytes, Some(vm.ctx.bytes_type())), + (address, Some(vm.ctx.tuple_type())) + ] + ); + let address_string = get_address_string(vm, address)?; + + let socket = get_socket(zelf); + + match socket.socket_kind { + SocketKind::Dgram => { + if let Some(v) = socket.con.borrow().as_ref() { + return match v.send_to(&objbytes::get_value(&bytes), address_string) { + Ok(_) => Ok(vm.get_none()), + Err(s) => Err(vm.new_os_error(s.to_string())), + }; + } + // Doing implicit bind + match UdpSocket::bind("0.0.0.0:0") { + Ok(dgram) => match dgram.send_to(&objbytes::get_value(&bytes), address_string) { + Ok(_) => { + socket + .con + .borrow_mut() + .replace(Connection::UdpSocket(dgram)); + Ok(vm.get_none()) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + Err(s) => Err(vm.new_os_error(s.to_string())), + } + } + _ => Err(vm.new_not_implemented_error("".to_string())), + } +} + +fn socket_close(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, None)]); + + let socket = get_socket(zelf); + socket.con.borrow_mut().take(); + Ok(vm.get_none()) +} + +fn socket_getsockname(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, None)]); + let socket = get_socket(zelf); + + let addr = match socket.con.borrow().as_ref() { + Some(v) => v.local_addr(), + None => return Err(vm.new_type_error("".to_string())), + }; + + match addr { + Ok(addr) => get_addr_tuple(vm, addr), + Err(s) => Err(vm.new_os_error(s.to_string())), + } +} + +fn get_addr_tuple(vm: &VirtualMachine, addr: SocketAddr) -> PyResult { + let port = vm.ctx.new_int(addr.port()); + let ip = vm.ctx.new_str(addr.ip().to_string()); + + Ok(vm.ctx.new_tuple(vec![ip, port])) +} + +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + let socket = py_class!(ctx, "socket", ctx.object(), { + "__new__" => ctx.new_rustfunc(socket_new), + "connect" => ctx.new_rustfunc(socket_connect), + "recv" => ctx.new_rustfunc(socket_recv), + "send" => ctx.new_rustfunc(socket_send), + "bind" => ctx.new_rustfunc(socket_bind), + "accept" => ctx.new_rustfunc(socket_accept), + "listen" => ctx.new_rustfunc(socket_listen), + "close" => ctx.new_rustfunc(socket_close), + "getsockname" => ctx.new_rustfunc(socket_getsockname), + "sendto" => ctx.new_rustfunc(socket_sendto), + "recvfrom" => ctx.new_rustfunc(socket_recvfrom), + }); + + py_module!(ctx, "socket", { + "AF_INET" => ctx.new_int(AddressFamily::Inet as i32), + "SOCK_STREAM" => ctx.new_int(SocketKind::Stream as i32), + "SOCK_DGRAM" => ctx.new_int(SocketKind::Dgram as i32), + "socket" => socket.clone(), + }) +} diff --git a/vm/src/stdlib/string.rs b/vm/src/stdlib/string.rs index 07f62b4970..157dae035f 100644 --- a/vm/src/stdlib/string.rs +++ b/vm/src/stdlib/string.rs @@ -5,9 +5,7 @@ use crate::pyobject::{PyContext, PyObjectRef}; -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module(&"string".to_string(), ctx.new_scope(None)); - +pub fn make_module(ctx: &PyContext) -> PyObjectRef { let ascii_lowercase = "abcdefghijklmnopqrstuvwxyz".to_string(); let ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".to_string(); let ascii_letters = format!("{}{}", ascii_lowercase, ascii_uppercase); @@ -21,15 +19,15 @@ pub fn mk_module(ctx: &PyContext) -> PyObjectRef { */ // Constants: - ctx.set_attr(&py_mod, "ascii_letters", ctx.new_str(ascii_letters)); - ctx.set_attr(&py_mod, "ascii_lowercase", ctx.new_str(ascii_lowercase)); - ctx.set_attr(&py_mod, "ascii_uppercase", ctx.new_str(ascii_uppercase)); - ctx.set_attr(&py_mod, "digits", ctx.new_str(digits)); - ctx.set_attr(&py_mod, "hexdigits", ctx.new_str(hexdigits)); - ctx.set_attr(&py_mod, "octdigits", ctx.new_str(octdigits)); - // ctx.set_attr(&py_mod, "printable", ctx.new_str(printable)); - ctx.set_attr(&py_mod, "punctuation", ctx.new_str(punctuation)); - // ctx.set_attr(&py_mod, "whitespace", ctx.new_str(whitespace)); - - py_mod + py_module!(ctx, "string", { + "ascii_letters" => ctx.new_str(ascii_letters), + "ascii_lowercase" => ctx.new_str(ascii_lowercase), + "ascii_uppercase" => ctx.new_str(ascii_uppercase), + "digits" => ctx.new_str(digits), + "hexdigits" => ctx.new_str(hexdigits), + "octdigits" => ctx.new_str(octdigits), + // "printable", ctx.new_str(printable) + "punctuation" => ctx.new_str(punctuation) + // "whitespace", ctx.new_str(whitespace) + }) } diff --git a/vm/src/stdlib/time_module.rs b/vm/src/stdlib/time_module.rs index 1f96a4ba43..dfe3ab8e35 100644 --- a/vm/src/stdlib/time_module.rs +++ b/vm/src/stdlib/time_module.rs @@ -1,12 +1,14 @@ //! The python `time` module. -use crate::obj::objfloat; -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; -use crate::VirtualMachine; use std::thread; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -fn time_sleep(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +use crate::function::PyFuncArgs; +use crate::obj::objfloat; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::vm::VirtualMachine; + +fn time_sleep(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(seconds, Some(vm.ctx.float_type()))]); let seconds = objfloat::get_value(seconds); let secs: u64 = seconds.trunc() as u64; @@ -20,7 +22,7 @@ fn duration_to_f64(d: Duration) -> f64 { (d.as_secs() as f64) + (f64::from(d.subsec_nanos()) / 1e9) } -fn time_time(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn time_time(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args); let x = match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(v) => duration_to_f64(v), @@ -30,11 +32,9 @@ fn time_time(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(value) } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module("time", ctx.new_scope(None)); - - ctx.set_attr(&py_mod, "sleep", ctx.new_rustfunc(time_sleep)); - ctx.set_attr(&py_mod, "time", ctx.new_rustfunc(time_time)); - - py_mod +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + py_module!(ctx, "time", { + "sleep" => ctx.new_rustfunc(time_sleep), + "time" => ctx.new_rustfunc(time_time) + }) } diff --git a/vm/src/stdlib/tokenize.rs b/vm/src/stdlib/tokenize.rs index cbd4a78704..0aa5df3d2a 100644 --- a/vm/src/stdlib/tokenize.rs +++ b/vm/src/stdlib/tokenize.rs @@ -2,16 +2,16 @@ * python tokenize module. */ -extern crate rustpython_parser; use std::iter::FromIterator; -use self::rustpython_parser::lexer; +use rustpython_parser::lexer; +use crate::function::PyFuncArgs; use crate::obj::objstr; -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; -use crate::VirtualMachine; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::vm::VirtualMachine; -fn tokenize_tokenize(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn tokenize_tokenize(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(readline, Some(vm.ctx.str_type()))]); let source = objstr::get_value(readline); @@ -25,11 +25,8 @@ fn tokenize_tokenize(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: create main function when called with -m -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module("tokenize", ctx.new_scope(None)); - - // Number theory functions: - ctx.set_attr(&py_mod, "tokenize", ctx.new_rustfunc(tokenize_tokenize)); - - py_mod +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + py_module!(ctx, "tokenize", { + "tokenize" => ctx.new_rustfunc(tokenize_tokenize) + }) } diff --git a/vm/src/stdlib/types.rs b/vm/src/stdlib/types.rs index b5a0997c3f..279cdbdf9e 100644 --- a/vm/src/stdlib/types.rs +++ b/vm/src/stdlib/types.rs @@ -2,11 +2,12 @@ * Dynamic type creation and names for built in types. */ -use crate::obj::{objsequence, objstr, objtype}; -use crate::pyobject::{PyAttributes, PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; +use crate::function::PyFuncArgs; +use crate::obj::objtype; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::VirtualMachine; -fn types_new_class(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn types_new_class(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -14,31 +15,20 @@ fn types_new_class(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(bases, None), (_kwds, None), (_exec_body, None)] ); - let name = objstr::get_value(name); - - let bases = match bases { - Some(b) => { - if objtype::isinstance(b, &vm.ctx.tuple_type()) { - objsequence::get_elements(b).to_vec() - } else { - return Err(vm.new_type_error("Bases must be a tuple".to_string())); - } - } - None => vec![vm.ctx.object()], + let bases: PyObjectRef = match bases { + Some(bases) => bases.clone(), + None => vm.ctx.new_tuple(vec![]), }; - - objtype::new(vm.ctx.type_type(), &name, bases, PyAttributes::new()) + let dict = vm.ctx.new_dict(); + objtype::type_new_class(vm, &vm.ctx.type_type(), name, &bases, &dict) } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module("types", ctx.new_scope(None)); - - // Number theory functions: - ctx.set_attr(&py_mod, "new_class", ctx.new_rustfunc(types_new_class)); - ctx.set_attr(&py_mod, "FunctionType", ctx.function_type()); - ctx.set_attr(&py_mod, "LambdaType", ctx.function_type()); - ctx.set_attr(&py_mod, "CodeType", ctx.code_type()); - ctx.set_attr(&py_mod, "FrameType", ctx.frame_type()); - - py_mod +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + py_module!(ctx, "types", { + "new_class" => ctx.new_rustfunc(types_new_class), + "FunctionType" => ctx.function_type(), + "LambdaType" => ctx.function_type(), + "CodeType" => ctx.code_type(), + "FrameType" => ctx.frame_type() + }) } diff --git a/vm/src/stdlib/weakref.rs b/vm/src/stdlib/weakref.rs index 0fc0878679..5616c3ddfe 100644 --- a/vm/src/stdlib/weakref.rs +++ b/vm/src/stdlib/weakref.rs @@ -5,50 +5,10 @@ //! - [rust weak struct](https://doc.rust-lang.org/std/rc/struct.Weak.html) //! -use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyObjectWeakRef, PyResult, - TypeProtocol, -}; -use crate::VirtualMachine; -use std::rc::Rc; +use super::super::pyobject::{PyContext, PyObjectRef}; -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module("_weakref", ctx.new_scope(None)); - - let py_ref_class = ctx.new_class("ref", ctx.object()); - ctx.set_attr(&py_ref_class, "__new__", ctx.new_rustfunc(ref_new)); - ctx.set_attr(&py_ref_class, "__call__", ctx.new_rustfunc(ref_call)); - ctx.set_attr(&py_mod, "ref", py_ref_class); - py_mod -} - -fn ref_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - // TODO: check first argument for subclass of `ref`. - arg_check!(vm, args, required = [(cls, None), (referent, None)]); - let referent = Rc::downgrade(referent); - Ok(PyObject::new( - PyObjectPayload::WeakRef { referent }, - cls.clone(), - )) -} - -/// Dereference the weakref, and check if we still refer something. -fn ref_call(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - // TODO: check first argument for subclass of `ref`. - arg_check!(vm, args, required = [(cls, None)]); - let referent = get_value(cls); - let py_obj = if let Some(obj) = referent.upgrade() { - obj - } else { - vm.get_none() - }; - Ok(py_obj) -} - -fn get_value(obj: &PyObjectRef) -> PyObjectWeakRef { - if let PyObjectPayload::WeakRef { referent } = &obj.borrow().payload { - referent.clone() - } else { - panic!("Inner error getting weak ref {:?}", obj); - } +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + py_module!(ctx, "_weakref", { + "ref" => ctx.weakref_type() + }) } diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 1e07b4d9b8..8a45bc1c7e 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -1,8 +1,13 @@ -use crate::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; -use crate::vm::VirtualMachine; use std::rc::Rc; use std::{env, mem}; +use num_traits::ToPrimitive; + +use crate::function::PyFuncArgs; +use crate::obj::objint; +use crate::pyobject::{DictProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::vm::VirtualMachine; + /* * The magic sys module. */ @@ -13,28 +18,46 @@ fn argv(ctx: &PyContext) -> PyObjectRef { ctx.new_list(argv) } -fn getframe(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { - if let Some(frame) = &vm.current_frame { - Ok(frame.clone()) - } else { - panic!("Current frame is undefined!") +fn frame_idx(vm: &VirtualMachine, offset: Option<&PyObjectRef>) -> Result { + if let Some(int) = offset { + if let Some(offset) = objint::get_value(&int).to_usize() { + if offset > vm.frames.borrow().len() - 1 { + return Err(vm.new_value_error("call stack is not deep enough".to_string())); + } + return Ok(offset); + } } + Ok(0) +} + +fn getframe(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [], + optional = [(offset, Some(vm.ctx.int_type()))] + ); + + let idx = frame_idx(vm, offset)?; + let idx = vm.frames.borrow().len() - idx - 1; + let frame = &vm.frames.borrow()[idx]; + Ok(frame.clone()) } -fn sys_getrefcount(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn sys_getrefcount(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(object, None)]); let size = Rc::strong_count(&object); Ok(vm.ctx.new_int(size)) } -fn sys_getsizeof(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn sys_getsizeof(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(object, None)]); // TODO: implement default optional argument. - let size = mem::size_of_val(&object.borrow()); + let size = mem::size_of_val(&object); Ok(vm.ctx.new_int(size)) } -pub fn mk_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(ctx: &PyContext, builtins: PyObjectRef) -> PyObjectRef { let path_list = match env::var_os("PYTHONPATH") { Some(paths) => env::split_paths(&paths) .map(|path| { @@ -132,7 +155,8 @@ settrace() -- set the global debug tracing function "_getframe" => ctx.new_rustfunc(getframe), }); - ctx.set_item(&modules, sys_name, sys_mod.clone()); + modules.set_item(&ctx, sys_name, sys_mod.clone()); + modules.set_item(&ctx, "builtins", builtins); ctx.set_attr(&sys_mod, "modules", modules); sys_mod diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 149a5313cf..d8bdda0203 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -6,26 +6,35 @@ extern crate rustpython_parser; +use std::cell::{Ref, RefCell}; use std::collections::hash_map::HashMap; use std::collections::hash_set::HashSet; +use std::rc::Rc; use std::sync::{Mutex, MutexGuard}; use crate::builtins; use crate::bytecode; -use crate::frame::Frame; +use crate::frame::{ExecutionResult, Frame, Scope}; +use crate::function::PyFuncArgs; use crate::obj::objbool; +use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode; +use crate::obj::objframe; +use crate::obj::objfunction::{PyFunction, PyMethod}; use crate::obj::objgenerator; use crate::obj::objiter; +use crate::obj::objlist::PyList; use crate::obj::objsequence; -use crate::obj::objstr; +use crate::obj::objstr::{PyString, PyStringRef}; +use crate::obj::objtuple::PyTuple; use crate::obj::objtype; use crate::pyobject::{ - AttributeProtocol, DictProtocol, IdProtocol, PyContext, PyFuncArgs, PyObjectPayload, - PyObjectRef, PyResult, TypeProtocol, + AttributeProtocol, DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, + TryIntoRef, TypeProtocol, }; use crate::stdlib; use crate::sysmodule; +use num_bigint::BigInt; // use objects::objects; @@ -36,9 +45,9 @@ use crate::sysmodule; pub struct VirtualMachine { pub builtins: PyObjectRef, pub sys_module: PyObjectRef, - pub stdlib_inits: HashMap, + pub stdlib_inits: RefCell>, pub ctx: PyContext, - pub current_frame: Option, + pub frames: RefCell>, pub wasm_id: Option, } @@ -49,26 +58,58 @@ impl VirtualMachine { // Hard-core modules: let builtins = builtins::make_module(&ctx); - let sysmod = sysmodule::mk_module(&ctx); + let sysmod = sysmodule::make_module(&ctx, builtins.clone()); - // Add builtins as builtins module: - let modules = sysmod.get_attr("modules").unwrap(); - ctx.set_item(&modules, "builtins", builtins.clone()); - - let stdlib_inits = stdlib::get_module_inits(); + let stdlib_inits = RefCell::new(stdlib::get_module_inits()); VirtualMachine { builtins, sys_module: sysmod, stdlib_inits, ctx, - current_frame: None, + frames: RefCell::new(vec![]), wasm_id: None, } } - pub fn run_code_obj(&mut self, code: PyObjectRef, scope: PyObjectRef) -> PyResult { - let mut frame = Frame::new(code, scope); - frame.run_frame_full(self) + pub fn run_code_obj(&self, code: PyObjectRef, scope: Scope) -> PyResult { + let frame = self.ctx.new_frame(code, scope); + self.run_frame_full(frame) + } + + pub fn run_frame_full(&self, frame: PyObjectRef) -> PyResult { + match self.run_frame(frame)? { + ExecutionResult::Return(value) => Ok(value), + _ => panic!("Got unexpected result from function"), + } + } + + pub fn run_frame(&self, frame: PyObjectRef) -> PyResult { + self.frames.borrow_mut().push(frame.clone()); + let frame = objframe::get_value(&frame); + let result = frame.run(self); + self.frames.borrow_mut().pop(); + result + } + + pub fn current_frame(&self) -> Ref { + Ref::map(self.frames.borrow(), |frames| { + let index = frames.len() - 1; + let current_frame = &frames[index]; + objframe::get_value(current_frame) + }) + } + + pub fn current_scope(&self) -> Ref { + let frame = self.current_frame(); + Ref::map(frame, |f| &f.scope) + } + + pub fn class(&self, module: &str, class: &str) -> PyObjectRef { + let module = self + .import(module) + .unwrap_or_else(|_| panic!("unable to import {}", module)); + self.get_attribute(module.clone(), class) + .unwrap_or_else(|_| panic!("module {} has no class {}", module, class)) } /// Create a new python string object. @@ -76,6 +117,11 @@ impl VirtualMachine { self.ctx.new_str(s) } + /// Create a new python int object. + pub fn new_int>(&self, i: T) -> PyObjectRef { + self.ctx.new_int(i) + } + /// Create a new python bool object. pub fn new_bool(&self, b: bool) -> PyObjectRef { self.ctx.new_bool(b) @@ -85,27 +131,35 @@ impl VirtualMachine { self.ctx.new_dict() } - pub fn new_exception(&mut self, exc_type: PyObjectRef, msg: String) -> PyObjectRef { + pub fn new_empty_exception(&self, exc_type: PyObjectRef) -> PyResult { + info!("New exception created: no msg"); + let args = PyFuncArgs::default(); + self.invoke(exc_type, args) + } + + pub fn new_exception(&self, exc_type: PyObjectRef, msg: String) -> PyObjectRef { + // TODO: exc_type may be user-defined exception, so we should return PyResult // TODO: maybe there is a clearer way to create an instance: info!("New exception created: {}", msg); let pymsg = self.new_str(msg); let args: Vec = vec![pymsg]; - let args = PyFuncArgs { - args, - kwargs: vec![], - }; // Call function: self.invoke(exc_type, args).unwrap() } - pub fn new_type_error(&mut self, msg: String) -> PyObjectRef { + pub fn new_attribute_error(&self, msg: String) -> PyObjectRef { + let attribute_error = self.ctx.exceptions.attribute_error.clone(); + self.new_exception(attribute_error, msg) + } + + pub fn new_type_error(&self, msg: String) -> PyObjectRef { let type_error = self.ctx.exceptions.type_error.clone(); self.new_exception(type_error, msg) } pub fn new_unsupported_operand_error( - &mut self, + &self, a: PyObjectRef, b: PyObjectRef, op: &str, @@ -118,48 +172,43 @@ impl VirtualMachine { )) } - pub fn new_os_error(&mut self, msg: String) -> PyObjectRef { + pub fn new_os_error(&self, msg: String) -> PyObjectRef { let os_error = self.ctx.exceptions.os_error.clone(); self.new_exception(os_error, msg) } /// Create a new python ValueError object. Useful for raising errors from /// python functions implemented in rust. - pub fn new_value_error(&mut self, msg: String) -> PyObjectRef { + pub fn new_value_error(&self, msg: String) -> PyObjectRef { let value_error = self.ctx.exceptions.value_error.clone(); self.new_exception(value_error, msg) } - pub fn new_key_error(&mut self, msg: String) -> PyObjectRef { + pub fn new_key_error(&self, msg: String) -> PyObjectRef { let key_error = self.ctx.exceptions.key_error.clone(); self.new_exception(key_error, msg) } - pub fn new_index_error(&mut self, msg: String) -> PyObjectRef { + pub fn new_index_error(&self, msg: String) -> PyObjectRef { let index_error = self.ctx.exceptions.index_error.clone(); self.new_exception(index_error, msg) } - pub fn new_not_implemented_error(&mut self, msg: String) -> PyObjectRef { + pub fn new_not_implemented_error(&self, msg: String) -> PyObjectRef { let not_implemented_error = self.ctx.exceptions.not_implemented_error.clone(); self.new_exception(not_implemented_error, msg) } - pub fn new_zero_division_error(&mut self, msg: String) -> PyObjectRef { + pub fn new_zero_division_error(&self, msg: String) -> PyObjectRef { let zero_division_error = self.ctx.exceptions.zero_division_error.clone(); self.new_exception(zero_division_error, msg) } - pub fn new_overflow_error(&mut self, msg: String) -> PyObjectRef { + pub fn new_overflow_error(&self, msg: String) -> PyObjectRef { let overflow_error = self.ctx.exceptions.overflow_error.clone(); self.new_exception(overflow_error, msg) } - pub fn new_scope(&mut self, parent_scope: Option) -> PyObjectRef { - // let parent_scope = self.current_frame_mut().locals.clone(); - self.ctx.new_scope(parent_scope) - } - pub fn get_none(&self) -> PyObjectRef { self.ctx.none() } @@ -173,84 +222,74 @@ impl VirtualMachine { } pub fn get_locals(&self) -> PyObjectRef { - // let scope = &self.frames.last().unwrap().locals; - // scope.clone() - // TODO: fix this! - self.get_none() - /* - match (*scope).payload { - PyObjectPayload::Scope { scope } => { scope.locals.clone() }, - _ => { panic!("Should be scope") }, - } // .clone() - */ + self.current_scope().get_locals().clone() } pub fn context(&self) -> &PyContext { &self.ctx } - pub fn get_builtin_scope(&mut self) -> PyObjectRef { - let a2 = &*self.builtins.borrow(); - match a2.payload { - PyObjectPayload::Module { ref dict, .. } => dict.clone(), - _ => { - panic!("OMG"); - } - } - } - // Container of the virtual machine state: - pub fn to_str(&mut self, obj: &PyObjectRef) -> PyResult { - self.call_method(&obj, "__str__", vec![]) + pub fn to_str(&self, obj: &PyObjectRef) -> PyResult { + let str = self.call_method(&obj, "__str__", vec![])?; + TryFromObject::try_from_object(self, str) } - pub fn to_pystr(&mut self, obj: &PyObjectRef) -> Result { + pub fn to_pystr(&self, obj: &PyObjectRef) -> Result { let py_str_obj = self.to_str(obj)?; - Ok(objstr::get_value(&py_str_obj)) + Ok(py_str_obj.value.clone()) } - pub fn to_repr(&mut self, obj: &PyObjectRef) -> PyResult { - self.call_method(obj, "__repr__", vec![]) + pub fn to_repr(&self, obj: &PyObjectRef) -> PyResult { + let repr = self.call_method(obj, "__repr__", vec![])?; + TryFromObject::try_from_object(self, repr) } - pub fn call_get_descriptor(&mut self, attr: PyObjectRef, obj: PyObjectRef) -> PyResult { + pub fn import(&self, module: &str) -> PyResult { + let builtins_import = self.builtins.get_item("__import__"); + match builtins_import { + Some(func) => self.invoke(func, vec![self.ctx.new_str(module.to_string())]), + None => Err(self.new_exception( + self.ctx.exceptions.import_error.clone(), + "__import__ not found".to_string(), + )), + } + } + + /// Determines if `obj` is an instance of `cls`, either directly, indirectly or virtually via + /// the __instancecheck__ magic method. + pub fn isinstance(&self, obj: &PyObjectRef, cls: &PyObjectRef) -> PyResult { + // cpython first does an exact check on the type, although documentation doesn't state that + // https://github.com/python/cpython/blob/a24107b04c1277e3c1105f98aff5bfa3a98b33a0/Objects/abstract.c#L2408 + if Rc::ptr_eq(&obj.typ(), cls) { + Ok(true) + } else { + let ret = self.call_method(cls, "__instancecheck__", vec![obj.clone()])?; + objbool::boolval(self, ret) + } + } + + /// Determines if `subclass` is a subclass of `cls`, either directly, indirectly or virtually + /// via the __subclasscheck__ magic method. + pub fn issubclass(&self, subclass: &PyObjectRef, cls: &PyObjectRef) -> PyResult { + let ret = self.call_method(cls, "__subclasscheck__", vec![subclass.clone()])?; + objbool::boolval(self, ret) + } + + pub fn call_get_descriptor(&self, attr: PyObjectRef, obj: PyObjectRef) -> PyResult { let attr_class = attr.typ(); if let Some(descriptor) = attr_class.get_attr("__get__") { let cls = obj.typ(); - self.invoke( - descriptor, - PyFuncArgs { - args: vec![attr, obj.clone(), cls], - kwargs: vec![], - }, - ) + self.invoke(descriptor, vec![attr, obj.clone(), cls]) } else { Ok(attr) } } - pub fn call_method( - &mut self, - obj: &PyObjectRef, - method_name: &str, - args: Vec, - ) -> PyResult { - self.call_method_pyargs( - obj, - method_name, - PyFuncArgs { - args, - kwargs: vec![], - }, - ) - } - - pub fn call_method_pyargs( - &mut self, - obj: &PyObjectRef, - method_name: &str, - args: PyFuncArgs, - ) -> PyResult { + pub fn call_method(&self, obj: &PyObjectRef, method_name: &str, args: T) -> PyResult + where + T: Into, + { // This is only used in the vm for magic methods, which use a greatly simplified attribute lookup. let cls = obj.typ(); match cls.get_attr(method_name) { @@ -269,60 +308,89 @@ impl VirtualMachine { } } - pub fn invoke(&mut self, func_ref: PyObjectRef, args: PyFuncArgs) -> PyResult { + pub fn invoke(&self, func_ref: PyObjectRef, args: T) -> PyResult + where + T: Into, + { + let args = args.into(); trace!("Invoke: {:?} {:?}", func_ref, args); - match func_ref.borrow().payload { - PyObjectPayload::RustFunction { ref function } => function(self, args), - PyObjectPayload::Function { - ref code, - ref scope, - ref defaults, - } => self.invoke_python_function(code, scope, defaults, args), - PyObjectPayload::Class { .. } => self.call_method_pyargs(&func_ref, "__call__", args), - PyObjectPayload::BoundMethod { - ref function, - ref object, - } => self.invoke(function.clone(), args.insert(object.clone())), - PyObjectPayload::Instance { .. } => { - self.call_method_pyargs(&func_ref, "__call__", args) - } - ref payload => { - // TODO: is it safe to just invoke __call__ otherwise? - trace!("invoke __call__ for: {:?}", payload); - self.call_method_pyargs(&func_ref, "__call__", args) - } + if let Some(PyFunction { + ref code, + ref scope, + ref defaults, + }) = func_ref.payload() + { + return self.invoke_python_function(code, scope, defaults, args); } + if let Some(PyMethod { + ref function, + ref object, + }) = func_ref.payload() + { + return self.invoke(function.clone(), args.insert(object.clone())); + } + if let Some(PyBuiltinFunction { ref value }) = func_ref.payload() { + return value(self, args); + } + + // TODO: is it safe to just invoke __call__ otherwise? + trace!("invoke __call__ for: {:?}", func_ref.payload); + self.call_method(&func_ref, "__call__", args) } fn invoke_python_function( - &mut self, + &self, code: &PyObjectRef, - scope: &PyObjectRef, + scope: &Scope, defaults: &PyObjectRef, args: PyFuncArgs, ) -> PyResult { let code_object = objcode::get_value(code); - let scope = self.ctx.new_scope(Some(scope.clone())); - self.fill_scope_from_args(&code_object, &scope, args, defaults)?; + let scope = scope.child_scope(&self.ctx); + self.fill_locals_from_args(&code_object, &scope.get_locals(), args, defaults)?; // Construct frame: - let mut frame = Frame::new(code.clone(), scope); + let frame = self.ctx.new_frame(code.clone(), scope); // If we have a generator, create a new generator if code_object.is_generator { - objgenerator::new_generator(self, frame) + Ok(objgenerator::new_generator(frame, self).into_object()) } else { - frame.run_frame_full(self) + self.run_frame_full(frame) } } - fn fill_scope_from_args( - &mut self, + pub fn invoke_with_locals( + &self, + function: PyObjectRef, + cells: PyObjectRef, + locals: PyObjectRef, + ) -> PyResult { + if let Some(PyFunction { + code, + scope, + defaults: _, + }) = &function.payload() + { + let scope = scope + .child_scope_with_locals(cells) + .child_scope_with_locals(locals); + let frame = self.ctx.new_frame(code.clone(), scope); + return self.run_frame_full(frame); + } + panic!( + "invoke_with_locals: expected python function, got: {:?}", + function + ); + } + + fn fill_locals_from_args( + &self, code_object: &bytecode::CodeObject, - scope: &PyObjectRef, + locals: &PyObjectRef, args: PyFuncArgs, defaults: &PyObjectRef, - ) -> Result<(), PyObjectRef> { + ) -> PyResult<()> { let nargs = args.args.len(); let nexpected_args = code_object.arg_names.len(); @@ -342,44 +410,44 @@ impl VirtualMachine { for i in 0..n { let arg_name = &code_object.arg_names[i]; let arg = &args.args[i]; - self.ctx.set_attr(scope, arg_name, arg.clone()); + locals.set_item(&self.ctx, arg_name, arg.clone()); } // Pack other positional arguments in to *args: - if let Some(vararg) = &code_object.varargs { - let mut last_args = vec![]; - for i in n..nargs { - let arg = &args.args[i]; - last_args.push(arg.clone()); - } - let vararg_value = self.ctx.new_tuple(last_args); + match code_object.varargs { + bytecode::Varargs::Named(ref vararg_name) => { + let mut last_args = vec![]; + for i in n..nargs { + let arg = &args.args[i]; + last_args.push(arg.clone()); + } + let vararg_value = self.ctx.new_tuple(last_args); - // If we have a name (not '*' only) then store it: - if let Some(vararg_name) = vararg { - self.ctx.set_attr(scope, vararg_name, vararg_value); + locals.set_item(&self.ctx, vararg_name, vararg_value); } - } else { - // Check the number of positional arguments - if nargs > nexpected_args { - return Err(self.new_type_error(format!( - "Expected {} arguments (got: {})", - nexpected_args, nargs - ))); + bytecode::Varargs::Unnamed => { + // just ignore the rest of the args + } + bytecode::Varargs::None => { + // Check the number of positional arguments + if nargs > nexpected_args { + return Err(self.new_type_error(format!( + "Expected {} arguments (got: {})", + nexpected_args, nargs + ))); + } } } // Do we support `**kwargs` ? - let kwargs = if let Some(kwargs) = &code_object.varkeywords { - let d = self.new_dict(); - - // Store when we have a name: - if let Some(kwargs_name) = kwargs { - self.ctx.set_attr(scope, &kwargs_name, d.clone()); + let kwargs = match code_object.varkeywords { + bytecode::Varargs::Named(ref kwargs_name) => { + let d = self.new_dict(); + locals.set_item(&self.ctx, kwargs_name, d.clone()); + Some(d) } - - Some(d) - } else { - None + bytecode::Varargs::Unnamed => Some(self.new_dict()), + bytecode::Varargs::None => None, }; // Handle keyword arguments @@ -387,15 +455,15 @@ impl VirtualMachine { // Check if we have a parameter with this name: if code_object.arg_names.contains(&name) || code_object.kwonlyarg_names.contains(&name) { - if scope.contains_key(&name) { + if locals.contains_key(&name) { return Err( self.new_type_error(format!("Got multiple values for argument '{}'", name)) ); } - self.ctx.set_attr(scope, &name, value); + locals.set_item(&self.ctx, &name, value); } else if let Some(d) = &kwargs { - self.ctx.set_item(d, &name, value); + d.set_item(&self.ctx, &name, value); } else { return Err( self.new_type_error(format!("Got an unexpected keyword argument '{}'", name)) @@ -406,10 +474,14 @@ impl VirtualMachine { // Add missing positional arguments, if we have fewer positional arguments than the // function definition calls for if nargs < nexpected_args { - let available_defaults = match defaults.borrow().payload { - PyObjectPayload::Sequence { ref elements } => elements.clone(), - PyObjectPayload::None => vec![], - _ => panic!("function defaults not tuple or None"), + let available_defaults = if defaults.is(&self.get_none()) { + vec![] + } else if let Some(list) = defaults.payload::() { + list.elements.borrow().clone() + } else if let Some(tuple) = defaults.payload::() { + tuple.elements.borrow().clone() + } else { + panic!("function defaults not tuple or None"); }; // Given the number of defaults available, check all the arguments for which we @@ -418,7 +490,7 @@ impl VirtualMachine { let mut missing = vec![]; for i in 0..required_args { let variable_name = &code_object.arg_names[i]; - if !scope.contains_key(variable_name) { + if !locals.contains_key(variable_name) { missing.push(variable_name) } } @@ -434,9 +506,12 @@ impl VirtualMachine { // the default if we don't already have a value for (default_index, i) in (required_args..nexpected_args).enumerate() { let arg_name = &code_object.arg_names[i]; - if !scope.contains_key(arg_name) { - self.ctx - .set_attr(scope, arg_name, available_defaults[default_index].clone()); + if !locals.contains_key(arg_name) { + locals.set_item( + &self.ctx, + arg_name, + available_defaults[default_index].clone(), + ); } } }; @@ -444,7 +519,7 @@ impl VirtualMachine { // Check if kw only arguments are all present: let kwdefs: HashMap = HashMap::new(); for arg_name in &code_object.kwonlyarg_names { - if !scope.contains_key(arg_name) { + if !locals.contains_key(arg_name) { if kwdefs.contains_key(arg_name) { // If not yet specified, take the default value unimplemented!(); @@ -461,10 +536,7 @@ impl VirtualMachine { Ok(()) } - pub fn extract_elements( - &mut self, - value: &PyObjectRef, - ) -> Result, PyObjectRef> { + pub fn extract_elements(&self, value: &PyObjectRef) -> PyResult> { // Extract elements from item, if possible: let elements = if objtype::isinstance(value, &self.ctx.tuple_type()) || objtype::isinstance(value, &self.ctx.list_type()) @@ -478,26 +550,35 @@ impl VirtualMachine { } // get_attribute should be used for full attribute access (usually from user code). - pub fn get_attribute(&mut self, obj: PyObjectRef, attr_name: PyObjectRef) -> PyResult { + pub fn get_attribute(&self, obj: PyObjectRef, attr_name: T) -> PyResult + where + T: TryIntoRef, + { + let attr_name = attr_name.try_into_ref(self)?; trace!("vm.__getattribute__: {:?} {:?}", obj, attr_name); - self.call_method(&obj, "__getattribute__", vec![attr_name]) + self.call_method(&obj, "__getattribute__", vec![attr_name.into_object()]) + } + + pub fn set_attr( + &self, + obj: &PyObjectRef, + attr_name: PyObjectRef, + attr_value: PyObjectRef, + ) -> PyResult { + self.call_method(&obj, "__setattr__", vec![attr_name, attr_value]) } - pub fn del_attr(&mut self, obj: &PyObjectRef, attr_name: PyObjectRef) -> PyResult { + pub fn del_attr(&self, obj: &PyObjectRef, attr_name: PyObjectRef) -> PyResult { self.call_method(&obj, "__delattr__", vec![attr_name]) } // get_method should be used for internal access to magic methods (by-passing // the full getattribute look-up. - pub fn get_method(&mut self, obj: PyObjectRef, method_name: &str) -> PyResult { + pub fn get_method(&self, obj: PyObjectRef, method_name: &str) -> PyResult { let cls = obj.typ(); match cls.get_attr(method_name) { Some(method) => self.call_get_descriptor(method, obj.clone()), - None => Err(self.new_type_error(format!( - "{} has no method {:?}", - obj.borrow(), - method_name - ))), + None => Err(self.new_type_error(format!("{} has no method {:?}", obj, method_name))), } } @@ -506,23 +587,17 @@ impl VirtualMachine { /// Otherwise, or if the result is the special `NotImplemented` built-in constant, /// calls `unsupported` to determine fallback value. pub fn call_or_unsupported( - &mut self, + &self, obj: PyObjectRef, arg: PyObjectRef, method: &str, unsupported: F, ) -> PyResult where - F: Fn(&mut VirtualMachine, PyObjectRef, PyObjectRef) -> PyResult, + F: Fn(&VirtualMachine, PyObjectRef, PyObjectRef) -> PyResult, { if let Ok(method) = self.get_method(obj.clone(), method) { - let result = self.invoke( - method, - PyFuncArgs { - args: vec![arg.clone()], - kwargs: vec![], - }, - )?; + let result = self.invoke(method, vec![arg.clone()])?; if !result.is(&self.ctx.not_implemented()) { return Ok(result); } @@ -542,12 +617,12 @@ impl VirtualMachine { /// 2. If above is not implemented, calls `__rand__` with `rhs` and `lhs`. /// 3. If above is not implemented, invokes `unsupported` for the result. pub fn call_or_reflection( - &mut self, + &self, lhs: PyObjectRef, rhs: PyObjectRef, default: &str, reflection: &str, - unsupported: fn(&mut VirtualMachine, PyObjectRef, PyObjectRef) -> PyResult, + unsupported: fn(&VirtualMachine, PyObjectRef, PyObjectRef) -> PyResult, ) -> PyResult { // Try to call the default method self.call_or_unsupported(lhs, rhs, default, move |vm, lhs, rhs| { @@ -556,13 +631,21 @@ impl VirtualMachine { }) } - pub fn _sub(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn serialize(&self, obj: &PyObjectRef) -> PyResult { + crate::stdlib::json::ser_pyobject(self, obj) + } + + pub fn deserialize(&self, s: &str) -> PyResult { + crate::stdlib::json::de_pyobject(self, s) + } + + pub fn _sub(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__sub__", "__rsub__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "-")) }) } - pub fn _isub(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _isub(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__isub__", |vm, a, b| { vm.call_or_reflection(a, b, "__sub__", "__rsub__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "-=")) @@ -570,13 +653,13 @@ impl VirtualMachine { }) } - pub fn _add(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _add(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__add__", "__radd__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "+")) }) } - pub fn _iadd(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _iadd(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__iadd__", |vm, a, b| { vm.call_or_reflection(a, b, "__add__", "__radd__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "+=")) @@ -584,13 +667,13 @@ impl VirtualMachine { }) } - pub fn _mul(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _mul(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__mul__", "__rmul__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "*")) }) } - pub fn _imul(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _imul(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__imul__", |vm, a, b| { vm.call_or_reflection(a, b, "__mul__", "__rmul__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "*=")) @@ -598,13 +681,13 @@ impl VirtualMachine { }) } - pub fn _matmul(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _matmul(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__matmul__", "__rmatmul__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "@")) }) } - pub fn _imatmul(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _imatmul(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__imatmul__", |vm, a, b| { vm.call_or_reflection(a, b, "__matmul__", "__rmatmul__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "@=")) @@ -612,13 +695,13 @@ impl VirtualMachine { }) } - pub fn _truediv(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _truediv(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__truediv__", "__rtruediv__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "/")) }) } - pub fn _itruediv(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _itruediv(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__itruediv__", |vm, a, b| { vm.call_or_reflection(a, b, "__truediv__", "__rtruediv__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "/=")) @@ -626,13 +709,13 @@ impl VirtualMachine { }) } - pub fn _floordiv(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _floordiv(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__floordiv__", "__rfloordiv__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "//")) }) } - pub fn _ifloordiv(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _ifloordiv(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__ifloordiv__", |vm, a, b| { vm.call_or_reflection(a, b, "__floordiv__", "__rfloordiv__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "//=")) @@ -640,13 +723,13 @@ impl VirtualMachine { }) } - pub fn _pow(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _pow(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__pow__", "__rpow__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "**")) }) } - pub fn _ipow(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _ipow(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__ipow__", |vm, a, b| { vm.call_or_reflection(a, b, "__pow__", "__rpow__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "**=")) @@ -654,13 +737,13 @@ impl VirtualMachine { }) } - pub fn _mod(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _mod(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__mod__", "__rmod__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "%")) }) } - pub fn _imod(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _imod(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__imod__", |vm, a, b| { vm.call_or_reflection(a, b, "__mod__", "__rmod__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "%=")) @@ -668,13 +751,13 @@ impl VirtualMachine { }) } - pub fn _lshift(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _lshift(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__lshift__", "__rlshift__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "<<")) }) } - pub fn _ilshift(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _ilshift(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__ilshift__", |vm, a, b| { vm.call_or_reflection(a, b, "__lshift__", "__rlshift__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "<<=")) @@ -682,13 +765,13 @@ impl VirtualMachine { }) } - pub fn _rshift(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _rshift(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__rshift__", "__rrshift__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, ">>")) }) } - pub fn _irshift(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _irshift(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__irshift__", |vm, a, b| { vm.call_or_reflection(a, b, "__rshift__", "__rrshift__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, ">>=")) @@ -696,13 +779,13 @@ impl VirtualMachine { }) } - pub fn _xor(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _xor(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__xor__", "__rxor__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "^")) }) } - pub fn _ixor(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _ixor(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__ixor__", |vm, a, b| { vm.call_or_reflection(a, b, "__xor__", "__rxor__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "^=")) @@ -710,13 +793,13 @@ impl VirtualMachine { }) } - pub fn _or(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _or(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__or__", "__ror__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "|")) }) } - pub fn _ior(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _ior(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__ior__", |vm, a, b| { vm.call_or_reflection(a, b, "__or__", "__ror__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "|=")) @@ -724,13 +807,13 @@ impl VirtualMachine { }) } - pub fn _and(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _and(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__and__", "__rand__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "&")) }) } - pub fn _iand(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _iand(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_unsupported(a, b, "__iand__", |vm, a, b| { vm.call_or_reflection(a, b, "__and__", "__rand__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "&=")) @@ -738,44 +821,50 @@ impl VirtualMachine { }) } - pub fn _eq(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _eq(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__eq__", "__eq__", |vm, a, b| { Ok(vm.new_bool(a.is(&b))) }) } - pub fn _ne(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _ne(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__ne__", "__ne__", |vm, a, b| { let eq = vm._eq(a, b)?; objbool::not(vm, &eq) }) } - pub fn _lt(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _lt(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__lt__", "__gt__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "<")) }) } - pub fn _le(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _le(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__le__", "__ge__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "<=")) }) } - pub fn _gt(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _gt(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__gt__", "__lt__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, ">")) }) } - pub fn _ge(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { + pub fn _ge(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__ge__", "__le__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, ">=")) }) } } +impl Default for VirtualMachine { + fn default() -> Self { + VirtualMachine::new() + } +} + lazy_static! { static ref REPR_GUARDS: Mutex> = { Mutex::new(HashSet::new()) }; } @@ -825,7 +914,7 @@ mod tests { let b = vm.ctx.new_int(12_i32); let res = vm._add(a, b).unwrap(); let value = objint::get_value(&res); - assert_eq!(value, 45_i32.to_bigint().unwrap()); + assert_eq!(*value, 45_i32.to_bigint().unwrap()); } #[test] diff --git a/wasm/demo/package.json b/wasm/demo/package.json index 321bef0902..a03c80aced 100644 --- a/wasm/demo/package.json +++ b/wasm/demo/package.json @@ -4,7 +4,8 @@ "description": "Bindings to the RustPython library for WebAssembly", "main": "index.js", "dependencies": { - "codemirror": "^5.42.0" + "codemirror": "^5.42.0", + "xterm": "^3.8.0" }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "0.2.0", @@ -15,12 +16,15 @@ "raw-loader": "^1.0.0", "webpack": "^4.16.3", "webpack-cli": "^3.1.0", - "webpack-dev-server": "^3.1.5" + "webpack-dev-server": "^3.1.5", + "start-server-and-test": "^1.7.11" }, "scripts": { "dev": "webpack-dev-server -d", "build": "webpack", - "dist": "webpack --mode production" + "dist": "webpack --mode production", + "test": "cd ../tests; pipenv run pytest", + "ci": "start-server-and-test dev http-get://localhost:8080 test" }, "repository": { "type": "git", diff --git a/wasm/demo/snippets/fetch.py b/wasm/demo/snippets/fetch.py new file mode 100644 index 0000000000..f507057b22 --- /dev/null +++ b/wasm/demo/snippets/fetch.py @@ -0,0 +1,12 @@ +from browser import fetch + +def fetch_handler(res): + print(f"headers: {res['headers']}") + +fetch( + "https://httpbin.org/get", + response_format="json", + headers={ + "X-Header-Thing": "rustpython is neat!" + }, +).then(fetch_handler, lambda err: print(f"error: {err}")) diff --git a/wasm/demo/snippets/mandelbrot.py b/wasm/demo/snippets/mandelbrot.py index 8d99aed4f9..2d664fdedf 100644 --- a/wasm/demo/snippets/mandelbrot.py +++ b/wasm/demo/snippets/mandelbrot.py @@ -1,31 +1,43 @@ -# NOTE: will take a while, up to around a minute, to run. -# Expect this page to freeze. - w = 50.0 h = 50.0 -y = 0.0 -while y < h: - x = 0.0 - while x < w: - Zr, Zi, Tr, Ti = 0.0, 0.0, 0.0, 0.0 - Cr = 2 * x / w - 1.5 - Ci = 2 * y / h - 1.0 +def mandel(): + """Print a mandelbrot fractal to the console, yielding after each character + is printed""" + y = 0.0 + while y < h: + x = 0.0 + while x < w: + Zr, Zi, Tr, Ti = 0.0, 0.0, 0.0, 0.0 + Cr = 2 * x / w - 1.5 + Ci = 2 * y / h - 1.0 + + i = 0 + while i < 50 and Tr + Ti <= 4: + Zi = 2 * Zr * Zi + Ci + Zr = Tr - Ti + Cr + Tr = Zr * Zr + Ti = Zi * Zi + i += 1 + + if Tr + Ti <= 4: + print('*', end='') + else: + print('·', end='') - i = 0 - while i < 50 and Tr + Ti <= 4: - Zi = 2 * Zr * Zi + Ci - Zr = Tr - Ti + Cr - Tr = Zr * Zr - Ti = Zi * Zi - i = i + 1 + x += 1 + yield - if Tr + Ti <= 4: - print('*', end='') - else: - print('·', end='') + print() + y += 1 + yield - x = x + 1 +try: from browser import request_animation_frame +except: request_animation_frame = None - print() - y = y + 1 +gen = mandel() +def gen_cb(_time=None): + gen.__next__() + request_animation_frame(gen_cb) +if request_animation_frame: gen_cb() +else: list(gen) diff --git a/wasm/demo/src/index.ejs b/wasm/demo/src/index.ejs index 2d06c3200a..a2a02d214b 100644 --- a/wasm/demo/src/index.ejs +++ b/wasm/demo/src/index.ejs @@ -20,7 +20,7 @@ @@ -30,6 +30,9 @@

Standard Output

+

Interactive shell

+
+

Here's some info regarding the rp.pyEval() function

  • diff --git a/wasm/demo/src/index.js b/wasm/demo/src/index.js index bf5257df2a..9b58afe398 100644 --- a/wasm/demo/src/index.js +++ b/wasm/demo/src/index.js @@ -1,5 +1,6 @@ import './style.css'; import 'codemirror/lib/codemirror.css'; +import 'xterm/dist/xterm.css'; // A dependency graph that contains any wasm must all be imported // asynchronously. This `index.js` file does the single async import, so diff --git a/wasm/demo/src/main.js b/wasm/demo/src/main.js index 598be13f98..f5034f62a1 100644 --- a/wasm/demo/src/main.js +++ b/wasm/demo/src/main.js @@ -2,6 +2,7 @@ import * as rp from '../../lib/pkg'; import CodeMirror from 'codemirror'; import 'codemirror/mode/python/python'; import 'codemirror/addon/comment/comment'; +import { Terminal } from 'xterm'; // so people can play around with it window.rp = rp; @@ -12,7 +13,11 @@ const editor = CodeMirror.fromTextArea(document.getElementById('code'), { 'Cmd-Enter': runCodeFromTextarea, 'Shift-Tab': 'indentLess', 'Ctrl-/': 'toggleComment', - 'Cmd-/': 'toggleComment' + 'Cmd-/': 'toggleComment', + Tab: editor => { + var spaces = Array(editor.getOption('indentUnit') + 1).join(' '); + editor.replaceSelection(spaces); + } }, lineNumbers: true, mode: 'text/x-python', @@ -20,10 +25,10 @@ const editor = CodeMirror.fromTextArea(document.getElementById('code'), { autofocus: true }); -function runCodeFromTextarea() { - const consoleElement = document.getElementById('console'); - const errorElement = document.getElementById('error'); +const consoleElement = document.getElementById('console'); +const errorElement = document.getElementById('error'); +function runCodeFromTextarea() { // Clean the console and errors consoleElement.value = ''; errorElement.textContent = ''; @@ -31,14 +36,25 @@ function runCodeFromTextarea() { const code = editor.getValue(); try { const result = rp.pyEval(code, { - stdout: '#console' + stdout: output => { + const shouldScroll = + consoleElement.scrollHeight - consoleElement.scrollTop === + consoleElement.clientHeight; + consoleElement.value += output; + if (shouldScroll) { + consoleElement.scrollTop = consoleElement.scrollHeight; + } + } }); if (result !== null) { consoleElement.value += `\n${result}\n`; } - } catch (e) { - errorElement.textContent = e; - console.error(e); + } catch (err) { + if (err instanceof WebAssembly.RuntimeError) { + err = window.__RUSTPYTHON_ERROR || err; + } + errorElement.textContent = err; + console.error(err); } } @@ -48,7 +64,7 @@ document const snippets = document.getElementById('snippets'); -snippets.addEventListener('change', () => { +const updateSnippet = () => { const selected = snippets.value; // the require here creates a webpack context; it's fine to use it @@ -57,8 +73,67 @@ snippets.addEventListener('change', () => { const snippet = require(`raw-loader!../snippets/${selected}.py`); editor.setValue(snippet); - runCodeFromTextarea(); -}); +}; + +snippets.addEventListener('change', updateSnippet); + +// Run once for demo (updateSnippet b/c the browser might try to keep the same +// option selected for the `select`, but the textarea won't be updated) +updateSnippet(); + +const prompt = '>>>>> '; + +const term = new Terminal(); +term.open(document.getElementById('terminal')); +term.write(prompt); -runCodeFromTextarea(); // Run once for demo +function removeNonAscii(str) { + if (str === null || str === '') return false; + else str = str.toString(); + + return str.replace(/[^\x20-\x7E]/g, ''); +} + +function printToConsole(data) { + term.write(removeNonAscii(data) + '\r\n'); +} + +const terminalVM = rp.vmStore.init('term_vm'); +terminalVM.setStdout(printToConsole); + +var input = ''; +term.on('data', data => { + const code = data.charCodeAt(0); + if (code == 13) { + // CR + if (input[input.length - 1] == ':') { + input += data; + term.write('\r\n.....'); + } else { + term.write('\r\n'); + try { + terminalVM.execSingle(input); + } catch (err) { + if (err instanceof WebAssembly.RuntimeError) { + err = window.__RUSTPYTHON_ERROR || err; + } + printToConsole(err); + } + term.write(prompt); + input = ''; + } + } else if (code == 127) { + if (input.length > 0) { + term.write('\b \b'); + input = input.slice(0, -1); + } + } else if (code < 32 || code == 127) { + // Control + return; + } else { + // Visible + term.write(data); + input += data; + } +}); diff --git a/wasm/demo/webpack.config.js b/wasm/demo/webpack.config.js index 1f13b99fa9..7ae9ecffda 100644 --- a/wasm/demo/webpack.config.js +++ b/wasm/demo/webpack.config.js @@ -4,6 +4,8 @@ const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); const path = require('path'); const fs = require('fs'); +const interval = setInterval(() => console.log('keepalive'), 1000 * 60 * 5); + module.exports = { entry: './src/index.js', output: { @@ -40,6 +42,13 @@ module.exports = { }), new WasmPackPlugin({ crateDirectory: path.join(__dirname, '../lib') - }) + }), + { + apply(compiler) { + compiler.hooks.done.tap('clearInterval', () => { + clearInterval(interval); + }); + } + } ] }; diff --git a/wasm/example/package.json b/wasm/example/package.json index cee4b7702b..5653448b25 100644 --- a/wasm/example/package.json +++ b/wasm/example/package.json @@ -1,10 +1,8 @@ { "name": "rustpython-wasm-example", "version": "1.0.0", - "//1": "`dependencies.rustpython_wasm` would be the version of the npm", - "//2": "library in a real app", "dependencies": { - "rustpython_wasm": "file:../lib/pkg/" + "rustpython_wasm": "0.1.0-pre-alpha.1" }, "devDependencies": { "raw-loader": "1.0.0", @@ -12,7 +10,8 @@ "webpack-cli": "^3.1.2" }, "scripts": { - "build": "webpack" + "build": "webpack", + "dist": "webpack --mode production" }, "license": "MIT" } diff --git a/wasm/example/src/main.js b/wasm/example/src/main.js index fcd32165e8..0ba7fa42c0 100644 --- a/wasm/example/src/main.js +++ b/wasm/example/src/main.js @@ -1,11 +1,6 @@ -import * as py from 'rustpython_wasm'; +import * as rp from 'rustpython_wasm'; import pyCode from 'raw-loader!./main.py'; -fetch('https://github-trending-api.now.sh/repositories') - .then(r => r.json()) - .then(repos => { - const result = py.pyEval(pyCode, { - vars: { repos } - }); - alert(result); - }); +const vm = rp.vmStore.get('main'); + +vm.exec(pyCode); diff --git a/wasm/example/src/main.py b/wasm/example/src/main.py index e2d590a3f3..5447d078af 100644 --- a/wasm/example/src/main.py +++ b/wasm/example/src/main.py @@ -1,8 +1,12 @@ -repos = js_vars['repos'] +from browser import fetch, alert -star_sum = 0 +def fetch_handler(repos): + star_sum = 0 + for repo in repos: + star_sum += repo['stars'] + alert(f'Average github trending star count: {star_sum / len(repos)}') -for repo in repos: - star_sum += repo['stars'] - -return 'Average github trending star count: ' + str(star_sum / len(repos)) +fetch( + 'https://github-trending-api.now.sh/repositories', + response_format='json', +).then(fetch_handler, lambda err: alert(f"Error: {err}")) \ No newline at end of file diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 103d069d9d..769455aea1 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpython_wasm" -version = "0.1.0" +version = "0.1.0-pre-alpha.1" authors = ["Ryan Liddle "] license = "MIT" description = "A Python-3 (CPython >= 3.5.0) Interpreter written in Rust, compiled to WASM" @@ -18,7 +18,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.3" js-sys = "0.3" futures = "0.1" -console_error_panic_hook = "0.1" +num-traits = "0.2" [dependencies.web-sys] version = "0.3" @@ -26,7 +26,6 @@ features = [ "console", "Document", "Element", - "HtmlTextAreaElement", "Window", "Headers", "Request", diff --git a/wasm/lib/README.md b/wasm/lib/README.md index 6d2f89d932..989e5952e3 100644 --- a/wasm/lib/README.md +++ b/wasm/lib/README.md @@ -7,6 +7,10 @@ A Python-3 (CPython >= 3.5.0) Interpreter written in Rust. [![Contributors](https://img.shields.io/github/contributors/RustPython/RustPython.svg)](https://github.com/RustPython/RustPython/graphs/contributors) [![Gitter](https://badges.gitter.im/RustPython/Lobby.svg)](https://gitter.im/rustpython/Lobby) +# WARNING: this project is still in a pre-alpha state! + +**Using this in a production project is inadvisable. Please only do so if you understand the risks.** + ## Usage ### Check out our [online demo](https://rustpython.github.io/demo/) running on WebAssembly. diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs new file mode 100644 index 0000000000..7fc2a2476e --- /dev/null +++ b/wasm/lib/src/browser_module.rs @@ -0,0 +1,341 @@ +use std::path::PathBuf; + +use futures::Future; +use js_sys::Promise; +use num_traits::cast::ToPrimitive; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::{future_to_promise, JsFuture}; + +use rustpython_vm::function::PyFuncArgs; +use rustpython_vm::import::import_module; +use rustpython_vm::obj::{objint, objstr}; +use rustpython_vm::pyobject::{ + AttributeProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, +}; +use rustpython_vm::VirtualMachine; + +use crate::{convert, vm_class::AccessibleVM, wasm_builtins::window}; + +enum FetchResponseFormat { + Json, + Text, + ArrayBuffer, +} + +impl FetchResponseFormat { + fn from_str(vm: &VirtualMachine, s: &str) -> Result { + match s { + "json" => Ok(FetchResponseFormat::Json), + "text" => Ok(FetchResponseFormat::Text), + "array_buffer" => Ok(FetchResponseFormat::ArrayBuffer), + _ => Err(vm.new_type_error("Unkown fetch response_format".into())), + } + } + fn get_response(&self, response: &web_sys::Response) -> Result { + match self { + FetchResponseFormat::Json => response.json(), + FetchResponseFormat::Text => response.text(), + FetchResponseFormat::ArrayBuffer => response.array_buffer(), + } + } +} + +fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(url, Some(vm.ctx.str_type()))]); + + let promise_type = import_promise_type(vm)?; + + let response_format = + args.get_optional_kwarg_with_type("response_format", vm.ctx.str_type(), vm)?; + let method = args.get_optional_kwarg_with_type("method", vm.ctx.str_type(), vm)?; + let headers = args.get_optional_kwarg_with_type("headers", vm.ctx.dict_type(), vm)?; + let body = args.get_optional_kwarg("body"); + let content_type = args.get_optional_kwarg_with_type("content_type", vm.ctx.str_type(), vm)?; + + let response_format = match response_format { + Some(s) => FetchResponseFormat::from_str(vm, &objstr::get_value(&s))?, + None => FetchResponseFormat::Text, + }; + + let mut opts = web_sys::RequestInit::new(); + + match method { + Some(s) => opts.method(&objstr::get_value(&s)), + None => opts.method("GET"), + }; + + if let Some(body) = body { + opts.body(Some(&convert::py_to_js(vm, body))); + } + + let request = web_sys::Request::new_with_str_and_init(&objstr::get_value(url), &opts) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + + if let Some(headers) = headers { + let h = request.headers(); + for (key, value) in rustpython_vm::obj::objdict::get_key_value_pairs(&headers) { + let key = &vm.to_str(&key)?.value; + let value = &vm.to_str(&value)?.value; + h.set(key, value) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + } + } + + if let Some(content_type) = content_type { + request + .headers() + .set("Content-Type", &objstr::get_value(&content_type)) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + } + + let window = window(); + let request_prom = window.fetch_with_request(&request); + + let future = JsFuture::from(request_prom) + .and_then(move |val| { + let response = val + .dyn_into::() + .expect("val to be of type Response"); + response_format.get_response(&response) + }) + .and_then(JsFuture::from); + + Ok(PyPromise::new_obj(promise_type, future_to_promise(future))) +} + +fn browser_request_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(func, Some(vm.ctx.function_type()))]); + + use std::{cell::RefCell, rc::Rc}; + + // this basic setup for request_animation_frame taken from: + // https://rustwasm.github.io/wasm-bindgen/examples/request-animation-frame.html + + let f = Rc::new(RefCell::new(None)); + let g = f.clone(); + + let func = func.clone(); + + let acc_vm = AccessibleVM::from(vm); + + *g.borrow_mut() = Some(Closure::wrap(Box::new(move |time: f64| { + let stored_vm = acc_vm + .upgrade() + .expect("that the vm is valid from inside of request_animation_frame"); + let vm = &stored_vm.vm; + let func = func.clone(); + let args = vec![vm.ctx.new_float(time)]; + let _ = vm.invoke(func, args); + + let closure = f.borrow_mut().take(); + drop(closure); + }) as Box)); + + let id = window() + .request_animation_frame(&js_sys::Function::from( + g.borrow().as_ref().unwrap().as_ref().clone(), + )) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + + Ok(vm.ctx.new_int(id)) +} + +fn browser_cancel_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(id, Some(vm.ctx.int_type()))]); + + let id = objint::get_value(id).to_i32().ok_or_else(|| { + vm.new_exception( + vm.ctx.exceptions.value_error.clone(), + "Integer too large to convert to i32 for animationFrame id".into(), + ) + })?; + + window() + .cancel_animation_frame(id) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + + Ok(vm.get_none()) +} + +#[derive(Debug)] +pub struct PyPromise { + value: Promise, +} + +impl PyValue for PyPromise { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.class(BROWSER_NAME, "Promise") + } +} + +impl PyPromise { + pub fn new_obj(promise_type: PyObjectRef, value: Promise) -> PyObjectRef { + PyObject::new(PyPromise { value }, promise_type) + } +} + +pub fn get_promise_value(obj: &PyObjectRef) -> Promise { + if let Some(promise) = obj.payload::() { + return promise.value.clone(); + } + panic!("Inner error getting promise") +} + +pub fn import_promise_type(vm: &VirtualMachine) -> PyResult { + match import_module(vm, PathBuf::default(), BROWSER_NAME)?.get_attr("Promise") { + Some(promise) => Ok(promise), + None => Err(vm.new_not_implemented_error("No Promise".to_string())), + } +} + +fn promise_then(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + let promise_type = import_promise_type(vm)?; + arg_check!( + vm, + args, + required = [ + (zelf, Some(promise_type.clone())), + (on_fulfill, Some(vm.ctx.function_type())) + ], + optional = [(on_reject, Some(vm.ctx.function_type()))] + ); + + let on_fulfill = on_fulfill.clone(); + let on_reject = on_reject.cloned(); + + let acc_vm = AccessibleVM::from(vm); + + let promise = get_promise_value(zelf); + + let ret_future = JsFuture::from(promise).then(move |res| { + let stored_vm = &acc_vm + .upgrade() + .expect("that the vm is valid when the promise resolves"); + let vm = &stored_vm.vm; + let ret = match res { + Ok(val) => { + let val = convert::js_to_py(vm, val); + vm.invoke(on_fulfill, PyFuncArgs::new(vec![val], vec![])) + } + Err(err) => { + if let Some(on_reject) = on_reject { + let err = convert::js_to_py(vm, err); + vm.invoke(on_reject, PyFuncArgs::new(vec![err], vec![])) + } else { + return Err(err); + } + } + }; + convert::pyresult_to_jsresult(vm, ret) + }); + + let ret_promise = future_to_promise(ret_future); + + Ok(PyPromise::new_obj(promise_type, ret_promise)) +} + +fn promise_catch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + let promise_type = import_promise_type(vm)?; + arg_check!( + vm, + args, + required = [ + (zelf, Some(promise_type.clone())), + (on_reject, Some(vm.ctx.function_type())) + ] + ); + + let on_reject = on_reject.clone(); + + let acc_vm = AccessibleVM::from(vm); + + let promise = get_promise_value(zelf); + + let ret_future = JsFuture::from(promise).then(move |res| match res { + Ok(val) => Ok(val), + Err(err) => { + let stored_vm = acc_vm + .upgrade() + .expect("that the vm is valid when the promise resolves"); + let vm = &stored_vm.vm; + let err = convert::js_to_py(vm, err); + let res = vm.invoke(on_reject, PyFuncArgs::new(vec![err], vec![])); + convert::pyresult_to_jsresult(vm, res) + } + }); + + let ret_promise = future_to_promise(ret_future); + + Ok(PyPromise::new_obj(promise_type, ret_promise)) +} + +fn browser_alert(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(message, Some(vm.ctx.str_type()))]); + + window() + .alert_with_message(&objstr::get_value(message)) + .expect("alert() not to fail"); + + Ok(vm.get_none()) +} + +fn browser_confirm(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(message, Some(vm.ctx.str_type()))]); + + let result = window() + .confirm_with_message(&objstr::get_value(message)) + .expect("confirm() not to fail"); + + Ok(vm.new_bool(result)) +} + +fn browser_prompt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(message, Some(vm.ctx.str_type()))], + optional = [(default, Some(vm.ctx.str_type()))] + ); + + let result = if let Some(default) = default { + window().prompt_with_message_and_default( + &objstr::get_value(message), + &objstr::get_value(default), + ) + } else { + window().prompt_with_message(&objstr::get_value(message)) + }; + + let result = match result.expect("prompt() not to fail") { + Some(result) => vm.new_str(result), + None => vm.get_none(), + }; + + Ok(result) +} + +const BROWSER_NAME: &str = "browser"; + +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + let promise = py_class!(ctx, "Promise", ctx.object(), { + "then" => ctx.new_rustfunc(promise_then), + "catch" => ctx.new_rustfunc(promise_catch) + }); + + py_module!(ctx, BROWSER_NAME, { + "fetch" => ctx.new_rustfunc(browser_fetch), + "request_animation_frame" => ctx.new_rustfunc(browser_request_animation_frame), + "cancel_animation_frame" => ctx.new_rustfunc(browser_cancel_animation_frame), + "Promise" => promise, + "alert" => ctx.new_rustfunc(browser_alert), + "confirm" => ctx.new_rustfunc(browser_confirm), + "prompt" => ctx.new_rustfunc(browser_prompt), + }) +} + +pub fn setup_browser_module(vm: &VirtualMachine) { + vm.stdlib_inits + .borrow_mut() + .insert(BROWSER_NAME.to_string(), Box::new(make_module)); +} diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index df63c959e6..122ff1763e 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,41 +1,91 @@ -use crate::vm_class::{AccessibleVM, WASMVirtualMachine}; -use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array}; -use rustpython_vm::obj::{objbytes, objtype}; -use rustpython_vm::pyobject::{self, PyFuncArgs, PyObjectRef, PyResult}; -use rustpython_vm::VirtualMachine; +use js_sys::{Array, ArrayBuffer, Object, Promise, Reflect, Uint8Array}; +use num_traits::cast::ToPrimitive; use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; -pub fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { - vm.to_pystr(&py_err) - .unwrap_or_else(|_| "Error, and error getting error message".into()) +use rustpython_vm::function::PyFuncArgs; +use rustpython_vm::obj::{objbytes, objint, objsequence, objtype}; +use rustpython_vm::pyobject::{AttributeProtocol, DictProtocol, PyObjectRef, PyResult}; +use rustpython_vm::VirtualMachine; + +use crate::browser_module; +use crate::vm_class::{AccessibleVM, WASMVirtualMachine}; + +pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { + macro_rules! map_exceptions { + ($py_exc:ident, $msg:expr, { $($py_exc_ty:expr => $js_err_new:expr),*$(,)? }) => { + $(if objtype::isinstance($py_exc, $py_exc_ty) { + JsValue::from($js_err_new($msg)) + } else)* { + JsValue::from(js_sys::Error::new($msg)) + } + }; + } + let msg = match py_err + .get_attr("msg") + .and_then(|msg| vm.to_pystr(&msg).ok()) + { + Some(msg) => msg, + None => return js_sys::Error::new("error getting error").into(), + }; + let js_err = map_exceptions!(py_err,& msg, { + // TypeError is sort of a catch-all for "this value isn't what I thought it was like" + &vm.ctx.exceptions.type_error => js_sys::TypeError::new, + &vm.ctx.exceptions.value_error => js_sys::TypeError::new, + &vm.ctx.exceptions.index_error => js_sys::TypeError::new, + &vm.ctx.exceptions.key_error => js_sys::TypeError::new, + &vm.ctx.exceptions.attribute_error => js_sys::TypeError::new, + &vm.ctx.exceptions.name_error => js_sys::ReferenceError::new, + &vm.ctx.exceptions.syntax_error => js_sys::SyntaxError::new, + }); + if let Some(tb) = py_err.get_attr("__traceback__") { + if objtype::isinstance(&tb, &vm.ctx.list_type()) { + let elements = objsequence::get_elements(&tb).to_vec(); + if let Some(top) = elements.get(0) { + if objtype::isinstance(&top, &vm.ctx.tuple_type()) { + let element = objsequence::get_elements(&top); + + if let Some(lineno) = objint::to_int(vm, &element[1], 10) + .ok() + .and_then(|lineno| lineno.to_u32()) + { + let _ = Reflect::set(&js_err, &"row".into(), &lineno.into()); + } + } + } + } + } + js_err } -pub fn js_py_typeerror(vm: &mut VirtualMachine, js_err: JsValue) -> PyObjectRef { +pub fn js_py_typeerror(vm: &VirtualMachine, js_err: JsValue) -> PyObjectRef { let msg = js_err.unchecked_into::().to_string(); vm.new_type_error(msg.into()) } -pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { +pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { if let Some(ref wasm_id) = vm.wasm_id { if objtype::isinstance(&py_obj, &vm.ctx.function_type()) { let wasm_vm = WASMVirtualMachine { id: wasm_id.clone(), }; - let mut py_obj = Some(py_obj); + let weak_py_obj = wasm_vm.push_held_rc(py_obj).unwrap(); + let closure = move |args: Option, kwargs: Option| -> Result { let py_obj = match wasm_vm.assert_valid() { - Ok(_) => py_obj.clone().expect("py_obj to be valid if VM is valid"), + Ok(_) => weak_py_obj + .upgrade() + .expect("weak_py_obj to be valid if VM is valid"), Err(err) => { - py_obj = None; return Err(err); } }; let acc_vm = AccessibleVM::from(wasm_vm.clone()); - let vm = &mut acc_vm + let stored_vm = acc_vm .upgrade() .expect("acc. VM to be invalid when WASM vm is valid"); - let mut py_func_args = rustpython_vm::pyobject::PyFuncArgs::default(); + let vm = &stored_vm.vm; + let mut py_func_args = PyFuncArgs::default(); if let Some(ref args) = args { for arg in args.values() { py_func_args.args.push(js_to_py(vm, arg?)); @@ -56,12 +106,20 @@ pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { as Box, Option) -> Result>); let func = closure.as_ref().clone(); - // TODO: Come up with a way of managing closure handles + // stores pretty much nothing, it's fine to leak this because if it gets dropped + // the error message is worse closure.forget(); return func; } } + // the browser module might not be injected + if let Ok(promise_type) = browser_module::import_promise_type(vm) { + if objtype::isinstance(&py_obj, &promise_type) { + return browser_module::get_promise_value(&py_obj).into(); + } + } + if objtype::isinstance(&py_obj, &vm.ctx.bytes_type()) || objtype::isinstance(&py_obj, &vm.ctx.bytearray_type()) { @@ -73,18 +131,8 @@ pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { } arr.into() } else { - let dumps = rustpython_vm::import::import( - vm, - std::path::PathBuf::default(), - "json", - &Some("dumps".into()), - ) - .expect("Couldn't get json.dumps function"); - match vm.invoke(dumps, pyobject::PyFuncArgs::new(vec![py_obj], vec![])) { - Ok(value) => { - let json = vm.to_pystr(&value).unwrap(); - js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED) - } + match vm.serialize(&py_obj) { + Ok(json) => js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED), Err(_) => JsValue::UNDEFINED, } } @@ -100,14 +148,20 @@ pub fn object_entries(obj: &Object) -> impl Iterator Result { +pub fn pyresult_to_jsresult(vm: &VirtualMachine, result: PyResult) -> Result { result .map(|value| py_to_js(vm, value)) - .map_err(|err| py_str_err(vm, &err).into()) + .map_err(|err| py_err_to_js_err(vm, &err)) } -pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { +pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { if js_val.is_object() { + if let Some(promise) = js_val.dyn_ref::() { + // the browser module might not be injected + if let Ok(promise_type) = browser_module::import_promise_type(vm) { + return browser_module::PyPromise::new_obj(promise_type, promise.clone()); + } + } if Array::is_array(&js_val) { let js_arr: Array = js_val.into(); let elems = js_arr @@ -134,15 +188,14 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { for pair in object_entries(&Object::from(js_val)) { let (key, val) = pair.expect("iteration over object to not fail"); let py_val = js_to_py(vm, val); - vm.ctx - .set_item(&dict, &String::from(js_sys::JsString::from(key)), py_val); + dict.set_item(&vm.ctx, &String::from(js_sys::JsString::from(key)), py_val); } dict } } else if js_val.is_function() { let func = js_sys::Function::from(js_val); - vm.ctx.new_rustfunc( - move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult { + vm.ctx + .new_rustfunc(move |vm: &VirtualMachine, args: PyFuncArgs| -> PyResult { let func = func.clone(); let this = Object::new(); for (k, v) in args.kwargs { @@ -156,8 +209,7 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { func.apply(&this, &js_args) .map(|val| js_to_py(vm, val)) .map_err(|err| js_to_py(vm, err)) - }, - ) + }) } else if let Some(err) = js_val.dyn_ref::() { let exc_type = match String::from(err.name()).as_str() { "TypeError" => &vm.ctx.exceptions.type_error, @@ -171,22 +223,10 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { // Because `JSON.stringify(undefined)` returns undefined vm.get_none() } else { - let loads = rustpython_vm::import::import( - vm, - std::path::PathBuf::default(), - "json", - &Some("loads".into()), - ) - .expect("json.loads function to be available"); - let json = match js_sys::JSON::stringify(&js_val) { Ok(json) => String::from(json), Err(_) => return vm.get_none(), }; - let py_json = vm.new_str(json); - - vm.invoke(loads, pyobject::PyFuncArgs::new(vec![py_json], vec![])) - // can safely unwrap because we know it's valid JSON - .unwrap() + vm.deserialize(&json).unwrap_or_else(|_| vm.get_none()) } } diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 2c69866e90..35d4595199 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -1,25 +1,45 @@ +pub mod browser_module; pub mod convert; pub mod vm_class; pub mod wasm_builtins; extern crate futures; extern crate js_sys; +#[macro_use] extern crate rustpython_vm; extern crate wasm_bindgen; +extern crate wasm_bindgen_futures; extern crate web_sys; use js_sys::{Object, Reflect, TypeError}; +use std::panic; use wasm_bindgen::prelude::*; pub use crate::vm_class::*; const PY_EVAL_VM_ID: &str = "__py_eval_vm"; -extern crate console_error_panic_hook; +fn panic_hook(info: &panic::PanicInfo) { + // If something errors, just ignore it; we don't want to panic in the panic hook + use js_sys::WebAssembly::RuntimeError; + let window = match web_sys::window() { + Some(win) => win, + None => return, + }; + let msg = &info.to_string(); + let _ = Reflect::set(&window, &"__RUSTPYTHON_ERROR_MSG".into(), &msg.into()); + let error = RuntimeError::new(&msg); + let _ = Reflect::set(&window, &"__RUSTPYTHON_ERROR".into(), &error); + let stack = match Reflect::get(&error, &"stack".into()) { + Ok(stack) => stack, + Err(_) => return, + }; + let _ = Reflect::set(&window, &"__RUSTPYTHON_ERROR_STACK".into(), &stack); +} #[wasm_bindgen(start)] pub fn setup_console_error() { - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + std::panic::set_hook(Box::new(panic_hook)); } // Hack to comment out wasm-bindgen's generated typescript definitons diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 5af38ef39b..9d80854f73 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -1,40 +1,55 @@ -use crate::convert; -use crate::wasm_builtins::{self, setup_wasm_builtins}; -use js_sys::{SyntaxError, TypeError}; -use rustpython_vm::{ - compile, - pyobject::{PyFuncArgs, PyObjectRef, PyRef, PyResult}, - VirtualMachine, -}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::{Rc, Weak}; + +use js_sys::{Object, Reflect, SyntaxError, TypeError}; use wasm_bindgen::prelude::*; +use rustpython_vm::compile; +use rustpython_vm::frame::{NameProtocol, Scope}; +use rustpython_vm::function::PyFuncArgs; +use rustpython_vm::pyobject::{PyContext, PyObjectRef, PyResult}; +use rustpython_vm::VirtualMachine; + +use crate::browser_module::setup_browser_module; +use crate::convert; +use crate::wasm_builtins; + +pub trait HeldRcInner {} + +impl HeldRcInner for T {} + pub(crate) struct StoredVirtualMachine { pub vm: VirtualMachine, - pub scope: PyObjectRef, + pub scope: RefCell, + /// you can put a Rc in here, keep it as a Weak, and it'll be held only for + /// as long as the StoredVM is alive + held_rcs: RefCell>>, } impl StoredVirtualMachine { - fn new(id: String, inject_builtins: bool) -> StoredVirtualMachine { + fn new(id: String, inject_browser_module: bool) -> StoredVirtualMachine { let mut vm = VirtualMachine::new(); - let builtin = vm.get_builtin_scope(); - let scope = vm.context().new_scope(Some(builtin)); - if inject_builtins { - setup_wasm_builtins(&mut vm, &scope); + let scope = vm.ctx.new_scope(); + if inject_browser_module { + setup_browser_module(&vm); } vm.wasm_id = Some(id); - StoredVirtualMachine { vm, scope } + StoredVirtualMachine { + vm, + scope: RefCell::new(scope), + held_rcs: RefCell::new(Vec::new()), + } } } -// It's fine that it's thread local, since WASM doesn't even have threads yet. thread_local! probably -// gets compiled down to a normal-ish static varible, like Atomic* types: +// It's fine that it's thread local, since WASM doesn't even have threads yet. thread_local! +// probably gets compiled down to a normal-ish static varible, like Atomic* types do: // https://rustwasm.github.io/2018/10/24/multithreading-rust-and-wasm.html#atomic-instructions thread_local! { - static STORED_VMS: PyRef>> = Rc::default(); - static ACTIVE_VMS: PyRef> = Rc::default(); + static STORED_VMS: RefCell>> = + RefCell::default(); + static ACTIVE_VMS: RefCell> = RefCell::default(); } #[wasm_bindgen(js_name = vmStore)] @@ -42,13 +57,13 @@ pub struct VMStore; #[wasm_bindgen(js_class = vmStore)] impl VMStore { - pub fn init(id: String, inject_builtins: Option) -> WASMVirtualMachine { + pub fn init(id: String, inject_browser_module: Option) -> WASMVirtualMachine { STORED_VMS.with(|cell| { let mut vms = cell.borrow_mut(); if !vms.contains_key(&id) { let stored_vm = - StoredVirtualMachine::new(id.clone(), inject_builtins.unwrap_or(true)); - vms.insert(id.clone(), Rc::new(RefCell::new(stored_vm))); + StoredVirtualMachine::new(id.clone(), inject_browser_module.unwrap_or(true)); + vms.insert(id.clone(), Rc::new(stored_vm)); } }); WASMVirtualMachine { id } @@ -95,8 +110,8 @@ impl VMStore { } #[derive(Clone)] -pub struct AccessibleVM { - weak: Weak>, +pub(crate) struct AccessibleVM { + weak: Weak, id: String, } @@ -107,35 +122,8 @@ impl AccessibleVM { AccessibleVM { weak, id } } - pub fn from_vm(vm: &VirtualMachine) -> AccessibleVM { - AccessibleVM::from_id( - vm.wasm_id - .clone() - .expect("VM passed to from_vm to have wasm_id be Some()"), - ) - } - - pub fn upgrade(&self) -> Option { - let vm_cell = self.weak.upgrade()?; - let top_level = match vm_cell.try_borrow_mut() { - Ok(mut vm) => { - ACTIVE_VMS.with(|cell| { - cell.borrow_mut().insert(self.id.clone(), &mut vm.vm); - }); - true - } - Err(_) => false, - }; - Some(ACTIVE_VMS.with(|cell| { - let vms = cell.borrow(); - let ptr = vms.get(&self.id).expect("id to be in ACTIVE_VMS"); - let vm = unsafe { &mut **ptr }; - AccessibleVMPtr { - id: self.id.clone(), - top_level, - inner: vm, - } - })) + pub fn upgrade(&self) -> Option> { + self.weak.upgrade() } } @@ -149,31 +137,13 @@ impl From<&WASMVirtualMachine> for AccessibleVM { AccessibleVM::from_id(vm.id.clone()) } } - -pub struct AccessibleVMPtr<'a> { - id: String, - top_level: bool, - inner: &'a mut VirtualMachine, -} - -impl std::ops::Deref for AccessibleVMPtr<'_> { - type Target = VirtualMachine; - fn deref(&self) -> &VirtualMachine { - &self.inner - } -} -impl std::ops::DerefMut for AccessibleVMPtr<'_> { - fn deref_mut(&mut self) -> &mut VirtualMachine { - &mut self.inner - } -} - -impl Drop for AccessibleVMPtr<'_> { - fn drop(&mut self) { - if self.top_level { - // remove the (now invalid) pointer from the map - ACTIVE_VMS.with(|cell| cell.borrow_mut().remove(&self.id)); - } +impl From<&VirtualMachine> for AccessibleVM { + fn from(vm: &VirtualMachine) -> AccessibleVM { + AccessibleVM::from_id( + vm.wasm_id + .clone() + .expect("VM passed to from::() to have wasm_id be Some()"), + ) } } @@ -187,19 +157,18 @@ pub struct WASMVirtualMachine { impl WASMVirtualMachine { pub(crate) fn with_unchecked(&self, f: F) -> R where - F: FnOnce(&mut StoredVirtualMachine) -> R, + F: FnOnce(&StoredVirtualMachine) -> R, { let stored_vm = STORED_VMS.with(|cell| { let mut vms = cell.borrow_mut(); vms.get_mut(&self.id).unwrap().clone() }); - let mut stored_vm = stored_vm.borrow_mut(); - f(&mut stored_vm) + f(&stored_vm) } pub(crate) fn with(&self, f: F) -> Result where - F: FnOnce(&mut StoredVirtualMachine) -> R, + F: FnOnce(&StoredVirtualMachine) -> R, { self.assert_valid()?; Ok(self.with_unchecked(f)) @@ -209,6 +178,17 @@ impl WASMVirtualMachine { STORED_VMS.with(|cell| cell.borrow().contains_key(&self.id)) } + pub(crate) fn push_held_rc( + &self, + rc: Rc, + ) -> Result, JsValue> { + self.with(|stored_vm| { + let weak = Rc::downgrade(&rc); + stored_vm.held_rcs.borrow_mut().push(rc); + weak + }) + } + pub fn assert_valid(&self) -> Result<(), JsValue> { if self.valid() { Ok(()) @@ -230,70 +210,125 @@ impl WASMVirtualMachine { pub fn add_to_scope(&self, name: String, value: JsValue) -> Result<(), JsValue> { self.with( move |StoredVirtualMachine { - ref mut vm, - ref mut scope, + ref vm, ref scope, .. }| { let value = convert::js_to_py(vm, value); - vm.ctx.set_attr(scope, &name, value); + scope.borrow_mut().store_name(&vm, &name, value); }, ) } #[wasm_bindgen(js_name = setStdout)] pub fn set_stdout(&self, stdout: JsValue) -> Result<(), JsValue> { - self.with( - move |StoredVirtualMachine { - ref mut vm, - ref mut scope, - }| { - let print_fn: Box PyResult> = - if let Some(selector) = stdout.as_string() { - Box::new( - move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult { - wasm_builtins::builtin_print_html(vm, args, &selector) - }, - ) - } else if stdout.is_function() { - let func = js_sys::Function::from(stdout); - Box::new( - move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult { - func.call1( - &JsValue::UNDEFINED, - &wasm_builtins::format_print_args(vm, args)?.into(), - ) - .map_err(|err| convert::js_to_py(vm, err))?; - Ok(vm.get_none()) - }, - ) - } else if stdout.is_undefined() || stdout.is_null() { - Box::new(wasm_builtins::builtin_print_console) - } else { - return Err(TypeError::new( - "stdout must be null, a function or a css selector", + self.with(move |StoredVirtualMachine { ref vm, .. }| { + fn error() -> JsValue { + TypeError::new("Unknown stdout option, please pass a function or 'console'").into() + } + let print_fn: Box PyResult> = + if let Some(s) = stdout.as_string() { + match s.as_str() { + "console" => Box::new(wasm_builtins::builtin_print_console), + _ => return Err(error()), + } + } else if stdout.is_function() { + let func = js_sys::Function::from(stdout); + Box::new(move |vm: &VirtualMachine, args: PyFuncArgs| -> PyResult { + func.call1( + &JsValue::UNDEFINED, + &wasm_builtins::format_print_args(vm, args)?.into(), ) - .into()); - }; - vm.ctx - .set_attr(scope, "print", vm.ctx.new_rustfunc_from_box(print_fn)); - Ok(()) - }, - )? + .map_err(|err| convert::js_to_py(vm, err))?; + Ok(vm.get_none()) + }) + } else if stdout.is_undefined() || stdout.is_null() { + fn noop(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { + Ok(vm.get_none()) + } + Box::new(noop) + } else { + return Err(error()); + }; + let rustfunc = vm.ctx.new_rustfunc(print_fn); + vm.ctx.set_attr(&vm.builtins, "print", rustfunc); + Ok(()) + })? + } + + #[wasm_bindgen(js_name = injectModule)] + pub fn inject_module(&self, name: String, module: Object) -> Result<(), JsValue> { + self.with(|StoredVirtualMachine { ref vm, .. }| { + let mut module_items: HashMap = HashMap::new(); + for entry in convert::object_entries(&module) { + let (key, value) = entry?; + let key = Object::from(key).to_string(); + module_items.insert(key.into(), convert::js_to_py(vm, value)); + } + + let mod_name = name.clone(); + + let stdlib_init_fn = move |ctx: &PyContext| { + let py_mod = ctx.new_module(&name, ctx.new_dict()); + for (key, value) in module_items.clone() { + ctx.set_attr(&py_mod, &key, value); + } + py_mod + }; + + vm.stdlib_inits + .borrow_mut() + .insert(mod_name, Box::new(stdlib_init_fn)); + + Ok(()) + })? } fn run(&self, mut source: String, mode: compile::Mode) -> Result { self.assert_valid()?; self.with_unchecked( |StoredVirtualMachine { - ref mut vm, - ref mut scope, + ref vm, ref scope, .. }| { source.push('\n'); let code = - compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()) - .map_err(|err| { - SyntaxError::new(&format!("Error parsing Python code: {}", err)) - })?; - let result = vm.run_code_obj(code, scope.clone()); + compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()); + let code = code.map_err(|err| { + let js_err = SyntaxError::new(&format!("Error parsing Python code: {}", err)); + if let rustpython_vm::error::CompileError::Parse(ref parse_error) = err { + use rustpython_parser::error::ParseError; + if let ParseError::EOF(Some(ref loc)) + | ParseError::ExtraToken((ref loc, ..)) + | ParseError::InvalidToken(ref loc) + | ParseError::UnrecognizedToken((ref loc, ..), _) = parse_error + { + let _ = Reflect::set( + &js_err, + &"row".into(), + &(loc.get_row() as u32).into(), + ); + let _ = Reflect::set( + &js_err, + &"col".into(), + &(loc.get_column() as u32).into(), + ); + } + if let ParseError::ExtraToken((_, _, ref loc)) + | ParseError::UnrecognizedToken((_, _, ref loc), _) = parse_error + { + let _ = Reflect::set( + &js_err, + &"endrow".into(), + &(loc.get_row() as u32).into(), + ); + let _ = Reflect::set( + &js_err, + &"endcol".into(), + &(loc.get_column() as u32).into(), + ); + } + } + js_err + })?; + let result = vm.run_code_obj(code, scope.borrow().clone()); convert::pyresult_to_jsresult(vm, result) }, ) @@ -306,4 +341,9 @@ impl WASMVirtualMachine { pub fn eval(&self, source: String) -> Result { self.run(source, compile::Mode::Eval) } + + #[wasm_bindgen(js_name = execSingle)] + pub fn exec_single(&self, source: String) -> Result { + self.run(source, compile::Mode::Single) + } } diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index 211eedb415..d5bc830dfe 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -2,42 +2,21 @@ //! //! This is required because some feature like I/O works differently in the browser comparing to //! desktop. -//! Implements functions listed here: https://docs.python.org/3/library/builtins.html and some -//! others. +//! Implements functions listed here: https://docs.python.org/3/library/builtins.html. -extern crate futures; -extern crate js_sys; -extern crate wasm_bindgen; -extern crate web_sys; +use js_sys::{self, Array}; +use web_sys::{self, console}; -use crate::convert; -use js_sys::Array; +use rustpython_vm::function::PyFuncArgs; use rustpython_vm::obj::{objstr, objtype}; -use rustpython_vm::pyobject::{IdProtocol, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; +use rustpython_vm::pyobject::{IdProtocol, PyObjectRef, PyResult, TypeProtocol}; use rustpython_vm::VirtualMachine; -use wasm_bindgen::{prelude::*, JsCast}; -use web_sys::{console, HtmlTextAreaElement}; -fn window() -> web_sys::Window { +pub(crate) fn window() -> web_sys::Window { web_sys::window().expect("Window to be available") } -// The HTML id of the textarea element that act as our STDOUT - -pub fn print_to_html(text: &str, selector: &str) -> Result<(), JsValue> { - let document = window().document().expect("Document to be available"); - let element = document - .query_selector(selector)? - .ok_or_else(|| js_sys::TypeError::new("Couldn't get element"))?; - let textarea = element - .dyn_ref::() - .ok_or_else(|| js_sys::TypeError::new("Element must be a textarea"))?; - let value = textarea.value(); - textarea.set_value(&format!("{}{}", value, text)); - Ok(()) -} - -pub fn format_print_args(vm: &mut VirtualMachine, args: PyFuncArgs) -> Result { +pub fn format_print_args(vm: &VirtualMachine, args: PyFuncArgs) -> Result { // Handle 'sep' kwarg: let sep_arg = args .get_optional_kwarg("sep") @@ -89,13 +68,7 @@ pub fn format_print_args(vm: &mut VirtualMachine, args: PyFuncArgs) -> Result PyResult { - let output = format_print_args(vm, args)?; - print_to_html(&output, selector).map_err(|err| convert::js_to_py(vm, err))?; - Ok(vm.get_none()) -} - -pub fn builtin_print_console(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +pub fn builtin_print_console(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let arr = Array::new(); for arg in args.args { arr.push(&vm.to_pystr(&arg)?.into()); @@ -103,8 +76,3 @@ pub fn builtin_print_console(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyRes console::log(&arr); Ok(vm.get_none()) } - -pub fn setup_wasm_builtins(vm: &mut VirtualMachine, scope: &PyObjectRef) { - let ctx = vm.context(); - ctx.set_attr(scope, "print", ctx.new_rustfunc(builtin_print_console)); -} diff --git a/wasm/tests/.travis-runner.sh b/wasm/tests/.travis-runner.sh new file mode 100755 index 0000000000..add96c6b24 --- /dev/null +++ b/wasm/tests/.travis-runner.sh @@ -0,0 +1,23 @@ +#!/bin/sh -eux +# This script is intended to be run in Travis from the root of the repository + +# Install Rust +curl -sSf https://build.travis-ci.org/files/rustup-init.sh | sh -s -- --default-toolchain=$TRAVIS_RUST_VERSION -y +export PATH=$HOME/.cargo/bin:$PATH + +# install wasm-pack +if [ ! -f $HOME/.cargo/bin/wasm-pack ]; then + curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh +fi + +# install geckodriver +wget https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux32.tar.gz +mkdir geckodriver +tar -xzf geckodriver-v0.24.0-linux32.tar.gz -C geckodriver +export PATH=$PATH:$PWD/geckodriver + +# Install pipenv +pip install pipenv +(cd wasm/tests; pipenv install) + +(cd wasm/demo; npm install; npm run ci) diff --git a/wasm/tests/Pipfile b/wasm/tests/Pipfile new file mode 100644 index 0000000000..547282c585 --- /dev/null +++ b/wasm/tests/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +pytest = "*" +selenium = "*" + +[dev-packages] + +[requires] +python_version = "3" diff --git a/wasm/tests/Pipfile.lock b/wasm/tests/Pipfile.lock new file mode 100644 index 0000000000..7f8b97f879 --- /dev/null +++ b/wasm/tests/Pipfile.lock @@ -0,0 +1,87 @@ +{ + "_meta": { + "hash": { + "sha256": "e7ebbd25509f50c886b2e9773e88b011af0cbc4bfcfebb386024be80d2c3aa49" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, + "more-itertools": { + "hashes": [ + "sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40", + "sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1" + ], + "markers": "python_version > '2.7'", + "version": "==6.0.0" + }, + "pluggy": { + "hashes": [ + "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f", + "sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746" + ], + "version": "==0.9.0" + }, + "py": { + "hashes": [ + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + ], + "version": "==1.8.0" + }, + "pytest": { + "hashes": [ + "sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523", + "sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4" + ], + "index": "pypi", + "version": "==4.3.1" + }, + "selenium": { + "hashes": [ + "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c", + "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d" + ], + "index": "pypi", + "version": "==3.141.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" + } + }, + "develop": {} +} diff --git a/wasm/tests/test_demo.py b/wasm/tests/test_demo.py new file mode 100644 index 0000000000..9dce406811 --- /dev/null +++ b/wasm/tests/test_demo.py @@ -0,0 +1,38 @@ +import time + +from selenium import webdriver +from selenium.webdriver.firefox.options import Options +import pytest + +RUN_CODE_TEMPLATE = """ +var output = ""; +save_output = function(text) {{ + output += text +}}; +var vm = window.rp.vmStore.init('test_vm'); +vm.setStdout(save_output); +vm.exec('{}'); +vm.destroy(); +return output; +""" + +@pytest.fixture(scope="module") +def driver(request): + options = Options() + options.add_argument('-headless') + driver = webdriver.Firefox(options=options) + driver.get("http://localhost:8080") + time.sleep(5) + yield driver + driver.close() + + +@pytest.mark.parametrize("script, output", + [ + ("print(5)", "5"), + ("a=5;b=4;print(a+b)", "9") + ] +) +def test_demo(driver, script, output): + script = RUN_CODE_TEMPLATE.format(script) + assert driver.execute_script(script).strip() == output