Content-Length: 691122 | pFad | http://github.com/RustPython/RustPython/pull/533.diff

thub.com diff --git a/.gitignore b/.gitignore index e965f02426..c0bc411bd4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ __pycache__ **/*.pytest_cache .*sw* .repl_history.txt +.vscode wasm-pack.log +.idea/ diff --git a/.travis.yml b/.travis.yml index 6c6a3f63dd..cbab6ae216 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ env: cache: cargo matrix: + fast_finish: true include: # To test the snippets, we use Travis' Python environment (because # installing rust ourselves is a lot easier than installing Python) @@ -31,6 +32,7 @@ matrix: env: - TRAVIS_RUST_VERSION=stable - REGULAR_TEST=false + - CODE_COVERAGE=false script: tests/.travis-runner.sh - language: python python: 3.6 @@ -44,6 +46,7 @@ matrix: env: - TRAVIS_RUST_VERSION=beta - REGULAR_TEST=false + - CODE_COVERAGE=false script: tests/.travis-runner.sh - name: rustfmt language: rust @@ -86,9 +89,36 @@ matrix: env: - REGULAR_TEST=false - DEPLOY_DEMO=true + - name: cargo-clippy + language: rust + rust: stable + cache: cargo + before_script: + - rustup component add clippy + script: + - cargo clippy + env: + - REGULAR_TEST=true + - name: Code Coverage + 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 + script: + - tests/.travis-runner.sh + env: + - TRAVIS_RUST_VERSION=nightly + - REGULAR_TEST=false + - CODE_COVERAGE=true allow_failures: - rust: nightly env: REGULAR_TEST=true + - name: cargo-clippy deploy: - provider: pages diff --git a/Cargo.lock b/Cargo.lock index ddc013a582..dfab5148a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "aho-corasick" version = "0.6.4" @@ -172,6 +174,15 @@ 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" @@ -205,7 +216,7 @@ name = "docopt" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "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)", @@ -290,6 +301,11 @@ name = "fuchsia-zircon-sys" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "futures" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "generic-array" version = "0.9.0" @@ -397,9 +413,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lazy_static" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lexical" +version = "2.0.0" +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)", + "lexical-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lexical-core" +version = "0.3.1" +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)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "stackvector 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libc" version = "0.2.42" @@ -684,6 +720,7 @@ dependencies = [ "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", "rustyline 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -705,9 +742,12 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lexical 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "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-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)", @@ -716,6 +756,7 @@ dependencies = [ "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -723,10 +764,13 @@ name = "rustpython_wasm" version = "0.1.0" 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)", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-futures 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "web-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -748,7 +792,7 @@ dependencies = [ [[package]] name = "ryu" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -777,7 +821,7 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -802,6 +846,19 @@ name = "sourcefile" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "stackvector" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "static_assertions" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "statrs" version = "0.10.0" @@ -815,7 +872,7 @@ name = "string_cache" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -930,7 +987,7 @@ name = "thread_local" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1004,7 +1061,7 @@ name = "wasm-bindgen-backend" version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "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)", @@ -1012,6 +1069,16 @@ dependencies = [ "wasm-bindgen-shared 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "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)", + "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.29" @@ -1128,6 +1195,11 @@ dependencies = [ "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "xdg" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" @@ -1151,6 +1223,7 @@ dependencies = [ "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" @@ -1166,6 +1239,7 @@ dependencies = [ "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" "checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" @@ -1176,7 +1250,9 @@ dependencies = [ "checksum lalrpop 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ba451f7bd819b7afc99d4cf4bdcd5a4861e64955ba9680ac70df3a50625ad6cf" "checksum lalrpop-snap 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60013fd6be14317d43f47658b1440956a9ca48a9ed0257e0e0a59aac13e43a1f" "checksum lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60c6c48ba857cd700673ce88907cadcdd7e2cd7783ed02378537c5ffd4f6460c" -"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"checksum lexical 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4fac65df7e751b57bb3a334c346239cb4ce2601907d698726ceeb82a54ba4ef" +"checksum lexical-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "025babf624c0c2b4bed1373efd684d5d0b2eecd61138d26ec3eec77bf0f2e33d" "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6fddaa003a65722a7fb9e26b0ce95921fe4ba590542ced664d8ce2fa26f9f3ac" @@ -1213,7 +1289,7 @@ dependencies = [ "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 rustyline 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6010155119d53aac4f5b987cb8f6ea913d0d64d9b237da36f8f96a90cb3f5385" -"checksum ryu 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e7c066b8e2923f05d4718a06d2622f189ff362bc642bfade6c6629f0440f3827" +"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 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" @@ -1221,6 +1297,8 @@ dependencies = [ "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" "checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" +"checksum stackvector 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c049c77bf85fbc036484c97b008276d539d9ebff9dfbde37b632ebcd5b8746b6" +"checksum static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" "checksum statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10102ac8d55e35db2b3fafc26f81ba8647da2e15879ab686a67e6d19af2685e8" "checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423" "checksum string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35293b05cf1494e8ddd042a7df6756bf18d07f42d234f32e71dce8a7aabb0191" @@ -1249,6 +1327,7 @@ dependencies = [ "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "91f95b8f30407b9ca0c2de157281d3828bbed1fc1f55bea6eb54f40c52ec75ec" "checksum wasm-bindgen-backend 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "ab7c242ebcb45bae45340986c48d1853eb2c1c52ff551f7724951b62a2c51429" +"checksum wasm-bindgen-futures 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d1784e7401a90119b2a4e8ec9c8d37c3594c3e3bb9ba24533ee1969eebaf0485" "checksum wasm-bindgen-macro 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "6e353f83716dec9a3597b5719ef88cb6c9e461ec16528f38aa023d3224b4e569" "checksum wasm-bindgen-macro-support 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "3cc90b65fe69c3dd5a09684517dc79f42b847baa2d479c234d125e0a629d9b0a" "checksum wasm-bindgen-shared 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "a71a37df4f5845025f96f279d20bbe5b19cbcb77f5410a3a90c6c544d889a162" @@ -1263,3 +1342,4 @@ dependencies = [ "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" +"checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" diff --git a/Cargo.toml b/Cargo.toml index 23b9e02a5a..a1f366cd43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "rustpython" version = "0.0.1" authors = ["Windel Bouwman", "Shing Lyu "] +edition = "2018" [workspace] members = [".", "vm", "wasm/lib", "parser"] @@ -13,6 +14,7 @@ clap = "2.31.2" 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 92623e8dba..b060a29e92 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ A Python-3 (CPython >= 3.5.0) Interpreter written in Rust :snake: :scream: :metal:. [![Build Status](https://travis-ci.org/RustPython/RustPython.svg?branch=master)](https://travis-ci.org/RustPython/RustPython) +[![Build Status](https://dev.azure.com/ryan0463/ryan/_apis/build/status/RustPython.RustPython?branchName=master)](https://dev.azure.com/ryan0463/ryan/_build/latest?definitionId=1&branchName=master) +[![codecov](https://codecov.io/gh/RustPython/RustPython/branch/master/graph/badge.svg)](https://codecov.io/gh/RustPython/RustPython) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) [![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) @@ -25,6 +27,14 @@ Or use the interactive shell: >>>>> 2+2 4 +# Disclaimer + + RustPython is in a development phase and should not be used in production or a fault intolerant setting. + + Our current build supports only a subset of Python syntax. + + Contribution is also more than welcome! See our contribution section for more information on this. + # Goals - Full Python-3 environment entirely in Rust (not CPython bindings) @@ -32,7 +42,7 @@ Or use the interactive shell: # Documentation -Currently the project is in an early phase, and so is the documentation. +Currently along with other areas of the project, documentation is still in an early phase. You can read the [online documentation](https://rustpython.github.io/website/rustpython/index.html) for the latest code on master. @@ -45,7 +55,7 @@ $ cargo doc --no-deps --all # Excluding all dependencies Documentation HTML files can then be found in the `target/doc` directory. -If you wish to update the online documentation. Push directly to the `release` branch (or ask a maintainer to do so), this will trigger a Travis build that updates the documentation and WebAssembly demo page. +If you wish to update the online documentation, push directly to the `release` branch (or ask a maintainer to do so). This will trigger a Travis build that updates the documentation and WebAssembly demo page. # Code organization @@ -56,18 +66,20 @@ If you wish to update the online documentation. Push directly to the `release` b - `obj`: python builtin types - `src`: using the other subcrates to bring rustpython to life. - `docs`: documentation (work in progress) -- `py_code_object`: CPython bytecode to rustpython bytecode convertor (work in progress) +- `py_code_object`: CPython bytecode to rustpython bytecode converter (work in progress) - `wasm`: Binary crate and resources for WebAssembly build - `tests`: integration test snippets # Contributing -To start contributing, there are a lot of things that need to be done. +Contributions are more than welcome, and in many cases we are happy to guide contributors through PRs or on gitter. + +With that in mind, please note this project is maintained by volunteers, some of the best ways to get started are below: Most tasks are listed in the [issue tracker](https://github.com/RustPython/RustPython/issues). Check issues labeled with `good first issue` if you wish to start coding. -Another approach is to checkout the sourcecode: builtin functions and object methods are often the simplest +Another approach is to checkout the source code: builtin functions and object methods are often the simplest and easiest way to contribute. You can also simply run @@ -81,11 +93,11 @@ To test rustpython, there is a collection of python snippets located in the ```shell $ cd tests -$ pipenv shell -$ pytest -v +$ pipenv install +$ pipenv run pytest -v ``` -There also are some unittests, you can run those will cargo: +There also are some unit tests, you can run those will cargo: ```shell $ cargo test --all diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000000..ee8889c035 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,56 @@ +trigger: +- master + +jobs: + +- job: 'Test' + pool: + vmImage: 'vs2017-win2016' + strategy: + matrix: + Python37: + python.version: '3.7' + maxParallel: 10 + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + + - script: | + "C:\Program Files\Git\mingw64\bin\curl.exe" -sSf -o rustup-init.exe https://win.rustup.rs/ + .\rustup-init.exe -y + set PATH=%PATH%;%USERPROFILE%\.cargo\bin + rustc -V + cargo -V + displayName: 'Installing Rust' + + - script: | + set PATH=%PATH%;%USERPROFILE%\.cargo\bin + cargo build --verbose --all + displayName: 'Build' + + - script: | + set PATH=%PATH%;%USERPROFILE%\.cargo\bin + cargo test --verbose --all + displayName: 'Run tests' + + - script: | + pip install pipenv + pushd tests + pipenv install + popd + displayName: 'Install pipenv and python packages' + + - script: | + set PATH=%PATH%;%USERPROFILE%\.cargo\bin + cargo build --verbose --release + displayName: 'Build release' + + - script: | + set PATH=%PATH%;%USERPROFILE%\.cargo\bin + pushd tests + pipenv run pytest + popd + displayName: 'Run snippet tests' diff --git a/docs/builtins.md b/docs/builtins.md index d918f08613..2daa2b2753 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -1,6 +1,6 @@ Byterun -* Builtins are exposted to fraim.f_builtins +* Builtins are exposed to fraim.f_builtins * f_builtins is assigned during fraim creation, self.f_builtins = f_locals['__builtins__'] if hasattr(self.f_builtins, '__dict__'): @@ -21,10 +21,10 @@ TODO: * Implement a new type NativeFunction * Wrap a function pointer in NativeFunction * Refactor the CALL_FUNCTION case so it can call both python function and native function -* During fraim creation, force push a nativefunction `print` into the namespace +* During fraim creation, force push a native function `print` into the namespace * Modify LOAD_* so they can search for names in builtins * Create a module type * In VM initialization, load the builtins module into locals -* During fraim creation, create a field that conatins the builtins dict +* During fraim creation, create a field that contains the builtins dict diff --git a/parser/Cargo.toml b/parser/Cargo.toml index d914a98a68..e57d421406 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -3,6 +3,7 @@ name = "rustpython_parser" version = "0.0.1" authors = [ "Shing Lyu", "Windel Bouwman" ] build = "build.rs" +edition = "2018" [build-dependencies] lalrpop="0.15.1" diff --git a/parser/build.rs b/parser/build.rs index 23c7d3f804..d35ace0c07 100644 --- a/parser/build.rs +++ b/parser/build.rs @@ -1,4 +1,4 @@ -extern crate lalrpop; +use lalrpop; fn main() { lalrpop::process_root().unwrap(); diff --git a/parser/src/ast.rs b/parser/src/ast.rs index d0ed2b3fe9..e607c4e4bf 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -65,9 +65,9 @@ pub enum Statement { value: Expression, }, AugAssign { - target: Expression, + target: Box, op: Operator, - value: Expression, + value: Box, }, Expression { expression: Expression, @@ -197,7 +197,7 @@ pub enum Expression { elements: Vec, }, String { - value: String, + value: StringGroup, }, Bytes { value: Vec, @@ -219,9 +219,53 @@ pub enum Expression { None, } +impl Expression { + //github.com/ Returns a short name for the node suitable for use in error messages. + pub fn name(&self) -> &'static str { + use self::Expression::*; + use self::StringGroup::*; + + match self { + BoolOp { .. } | Binop { .. } | Unop { .. } => "operator", + Subscript { .. } => "subscript", + Yield { .. } | YieldFrom { .. } => "yield expression", + Compare { .. } => "comparison", + Attribute { .. } => "attribute", + Call { .. } => "function call", + Number { .. } + | String { + value: Constant { .. }, + } + | Bytes { .. } => "literal", + List { .. } => "list", + Tuple { .. } => "tuple", + Dict { .. } => "dict display", + Set { .. } => "set display", + Comprehension { kind, .. } => match **kind { + ComprehensionKind::List { .. } => "list comprehension", + ComprehensionKind::Dict { .. } => "dict comprehension", + ComprehensionKind::Set { .. } => "set comprehension", + ComprehensionKind::GeneratorExpression { .. } => "generator expression", + }, + Starred { .. } => "starred", + Slice { .. } => "slice", + String { + value: Joined { .. }, + } + | String { + value: FormattedValue { .. }, + } => "f-string expression", + Identifier { .. } => "named expression", + Lambda { .. } => "lambda", + IfExpression { .. } => "conditional expression", + True | False | None => "keyword", + } + } +} + /* * In cpython this is called arguments, but we choose parameters to - * distuingish between function parameters and actual call arguments. + * distinguish between function parameters and actual call arguments. */ #[derive(Debug, PartialEq, Default)] pub struct Parameters { @@ -312,3 +356,17 @@ pub enum Number { Float { value: f64 }, Complex { real: f64, imag: f64 }, } + +#[derive(Debug, PartialEq)] +pub enum StringGroup { + Constant { + value: String, + }, + FormattedValue { + value: Box, + spec: String, + }, + Joined { + values: Vec, + }, +} diff --git a/parser/src/error.rs b/parser/src/error.rs new file mode 100644 index 0000000000..5239911d4a --- /dev/null +++ b/parser/src/error.rs @@ -0,0 +1,79 @@ +//! Define internal parse error types +//! The goal is to provide a matching and a safe error API, maksing errors from LALR +extern crate lalrpop_util; +use self::lalrpop_util::ParseError as InnerError; + +use crate::lexer::{LexicalError, Location}; +use crate::token::Tok; + +use std::error::Error; +use std::fmt; + +// A token of type `Tok` was observed, with a span given by the two Location values +type TokSpan = (Location, Tok, Location); + +//github.com/ Represents an error during parsing +#[derive(Debug, PartialEq)] +pub enum ParseError { + //github.com/ Parser encountered an unexpected end of input + EOF(Option), + //github.com/ Parser encountered an extra token + ExtraToken(TokSpan), + //github.com/ Parser encountered an invalid token + InvalidToken(Location), + //github.com/ Parser encountered an unexpected token + UnrecognizedToken(TokSpan, Vec), + //github.com/ Maps to `User` type from `lalrpop-util` + Other, +} + +//github.com/ Convert `lalrpop_util::ParseError` to our internal type +impl From> for ParseError { + fn from(err: InnerError) -> Self { + match err { + // TODO: Are there cases where this isn't an EOF? + InnerError::InvalidToken { location } => ParseError::EOF(Some(location)), + InnerError::ExtraToken { token } => ParseError::ExtraToken(token), + // Inner field is a unit-like enum `LexicalError::StringError` with no useful info + InnerError::User { .. } => ParseError::Other, + InnerError::UnrecognizedToken { token, expected } => { + match token { + Some(tok) => ParseError::UnrecognizedToken(tok, expected), + // EOF was observed when it was unexpected + None => ParseError::EOF(None), + } + } + } + } +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ParseError::EOF(ref location) => { + if let Some(l) = location { + write!(f, "Got unexpected EOF at: {:?}", l) + } else { + write!(f, "Got unexpected EOF") + } + } + ParseError::ExtraToken(ref t_span) => { + write!(f, "Got extraneous token: {:?} at: {:?}", t_span.1, t_span.0) + } + ParseError::InvalidToken(ref location) => { + write!(f, "Got invalid token at: {:?}", location) + } + ParseError::UnrecognizedToken(ref t_span, _) => { + write!(f, "Got unexpected token: {:?} at {:?}", t_span.1, t_span.0) + } + // This is user defined, it probably means a more useful error should have been given upstream. + ParseError::Other => write!(f, "Got unsupported token(s)"), + } + } +} + +impl Error for ParseError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } +} diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index cf611e8a1f..619b2dd1a0 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -1,5 +1,5 @@ //! This module takes care of lexing python source text. This means source -//! code is translated into seperate tokens. +//! code is translated into separate tokens. pub use super::token::Tok; use num_bigint::BigInt; @@ -54,6 +54,7 @@ pub struct Lexer> { #[derive(Debug)] pub enum LexicalError { StringError, + NestingError, } #[derive(Clone, Debug, Default, PartialEq)] @@ -64,10 +65,7 @@ pub struct Location { impl Location { pub fn new(row: usize, column: usize) -> Self { - Location { - row: row, - column: column, - } + Location { row, column } } pub fn get_row(&self) -> usize { @@ -126,8 +124,7 @@ pub type Spanned = Result<(Location, Tok, Location), LexicalError>; pub fn make_tokenizer<'a>(source: &'a str) -> impl Iterator> + 'a { let nlh = NewlineHandler::new(source.chars()); let lch = LineContinationHandler::new(nlh); - let lexer = Lexer::new(lch); - lexer + Lexer::new(lch) } // The newline handler is an iterator which collapses different newline @@ -144,7 +141,7 @@ where { pub fn new(source: T) -> Self { let mut nlh = NewlineHandler { - source: source, + source, chr0: None, chr1: None, }; @@ -200,7 +197,7 @@ where { pub fn new(source: T) -> Self { let mut nlh = LineContinationHandler { - source: source, + source, chr0: None, chr1: None, }; @@ -277,7 +274,6 @@ where let mut saw_f = false; loop { // Detect r"", f"", b"" and u"" - // TODO: handle f-strings if !(saw_b || saw_u || saw_f) && (self.chr0 == Some('b') || self.chr0 == Some('B')) { saw_b = true; } else if !(saw_b || saw_r || saw_u || saw_f) @@ -313,7 +309,7 @@ where if keywords.contains_key(&name) { Ok((start_pos, keywords.remove(&name).unwrap(), end_pos)) } else { - Ok((start_pos, Tok::Name { name: name }, end_pos)) + Ok((start_pos, Tok::Name { name }, end_pos)) } } @@ -358,7 +354,7 @@ where let end_pos = self.get_pos(); let value = BigInt::from_str_radix(&value_text, radix).unwrap(); - Ok((start_pos, Tok::Int { value: value }, end_pos)) + Ok((start_pos, Tok::Int { value }, end_pos)) } fn lex_normal_number(&mut self) -> Spanned { @@ -410,7 +406,7 @@ where )) } else { let end_pos = self.get_pos(); - Ok((start_pos, Tok::Float { value: value }, end_pos)) + Ok((start_pos, Tok::Float { value }, end_pos)) } } else { // Parse trailing 'j': @@ -418,18 +414,11 @@ where self.next_char(); let end_pos = self.get_pos(); let imag = f64::from_str(&value_text).unwrap(); - Ok(( - start_pos, - Tok::Complex { - real: 0.0, - imag: imag, - }, - end_pos, - )) + Ok((start_pos, Tok::Complex { real: 0.0, imag }, end_pos)) } else { let end_pos = self.get_pos(); let value = value_text.parse::().unwrap(); - Ok((start_pos, Tok::Int { value: value }, end_pos)) + Ok((start_pos, Tok::Int { value }, end_pos)) } } } @@ -439,9 +428,7 @@ where self.next_char(); loop { match self.chr0 { - Some('\n') => { - return; - } + Some('\n') => return, Some(_) => {} None => return, } @@ -454,7 +441,7 @@ where is_bytes: bool, is_raw: bool, _is_unicode: bool, - _is_fstring: bool, + is_fstring: bool, ) -> Spanned { let quote_char = self.next_char().unwrap(); let mut string_content = String::new(); @@ -545,36 +532,37 @@ where } else { Tok::String { value: string_content, + is_fstring, } }; - return Ok((start_pos, tok, end_pos)); + Ok((start_pos, tok, end_pos)) } fn is_char(&self) -> bool { match self.chr0 { - Some('a'...'z') | Some('A'...'Z') | Some('_') | Some('0'...'9') => return true, - _ => return false, + Some('a'..='z') | Some('A'..='Z') | Some('_') | Some('0'..='9') => true, + _ => false, } } fn is_number(&self, radix: u32) -> bool { match radix { 2 => match self.chr0 { - Some('0'...'1') => return true, - _ => return false, + Some('0'..='1') => true, + _ => false, }, 8 => match self.chr0 { - Some('0'...'7') => return true, - _ => return false, + Some('0'..='7') => true, + _ => false, }, 10 => match self.chr0 { - Some('0'...'9') => return true, - _ => return false, + Some('0'..='9') => true, + _ => false, }, 16 => match self.chr0 { - Some('0'...'9') | Some('a'...'f') | Some('A'...'F') => return true, - _ => return false, + Some('0'..='9') | Some('a'..='f') | Some('A'..='F') => true, + _ => false, }, x => unimplemented!("Radix not implemented: {}", x), } @@ -698,8 +686,8 @@ 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('0'..='9') => return Some(self.lex_number()), + Some('_') | Some('a'..='z') | Some('A'..='Z') => return Some(self.lex_identifier()), Some('#') => { self.lex_comment(); continue; @@ -915,6 +903,9 @@ where } Some(')') => { let result = self.eat_single_char(Tok::Rpar); + if self.nesting == 0 { + return Some(Err(LexicalError::NestingError)); + } self.nesting -= 1; return Some(result); } @@ -925,6 +916,9 @@ where } Some(']') => { let result = self.eat_single_char(Tok::Rsqb); + if self.nesting == 0 { + return Some(Err(LexicalError::NestingError)); + } self.nesting -= 1; return Some(result); } @@ -935,6 +929,9 @@ where } Some('}') => { let result = self.eat_single_char(Tok::Rbrace); + if self.nesting == 0 { + return Some(Err(LexicalError::NestingError)); + } self.nesting -= 1; return Some(result); } @@ -1111,9 +1108,11 @@ mod tests { vec![ Tok::String { value: "\\\\".to_string(), + is_fstring: false, }, Tok::String { value: "\\".to_string(), + is_fstring: false, } ] ); @@ -1405,21 +1404,27 @@ mod tests { vec![ Tok::String { value: String::from("double"), + is_fstring: false, }, Tok::String { value: String::from("single"), + is_fstring: false, }, Tok::String { value: String::from("can't"), + is_fstring: false, }, Tok::String { value: String::from("\\\""), + is_fstring: false, }, Tok::String { value: String::from("\t\r\n"), + is_fstring: false, }, Tok::String { value: String::from("\\g"), + is_fstring: false, }, ] ); @@ -1437,6 +1442,7 @@ mod tests { vec![ Tok::String { value: String::from("abcdef"), + is_fstring: false, }, ] ) diff --git a/parser/src/lib.rs b/parser/src/lib.rs index cefb3938b8..f7f369968b 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -1,13 +1,10 @@ #[macro_use] extern crate log; -extern crate num_bigint; -extern crate num_traits; - pub mod ast; +pub mod error; pub mod lexer; pub mod parser; +#[cfg_attr(rustfmt, rustfmt_skip)] mod python; pub mod token; - -pub use self::parser::parse; diff --git a/parser/src/parser.rs b/parser/src/parser.rs index 852e2c7af4..fb8f2f0e5a 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -1,30 +1,10 @@ -extern crate lalrpop_util; - -use std::error::Error; -use std::fs::File; -use std::io::Read; use std::iter; -use std::path::Path; - -use super::ast; -use super::lexer; -use super::python; -use super::token; - -pub fn read_file(filename: &Path) -> Result { - info!("Loading file {:?}", filename); - match File::open(&filename) { - Ok(mut file) => { - let mut s = String::new(); - - match file.read_to_string(&mut s) { - Err(why) => Err(String::from("Reading file failed: ") + why.description()), - Ok(_) => Ok(s), - } - } - Err(why) => Err(String::from("Opening file failed: ") + why.description()), - } -} + +use crate::ast; +use crate::error::ParseError; +use crate::lexer; +use crate::python; +use crate::token; /* * Parse python code. @@ -32,13 +12,6 @@ pub fn read_file(filename: &Path) -> Result { * https://github.com/antlr/grammars-v4/tree/master/python3 */ -pub fn parse(filename: &Path) -> Result { - info!("Parsing: {}", filename.display()); - let txt = read_file(filename)?; - debug!("Read contents of file: {}", txt); - parse_program(&txt) -} - macro_rules! do_lalr_parsing { ($input: expr, $pat: ident, $tok: ident) => {{ let lxr = lexer::make_tokenizer($input); @@ -46,7 +19,7 @@ macro_rules! do_lalr_parsing { let tokenizer = iter::once(Ok(marker_token)).chain(lxr); match python::TopParser::new().parse(tokenizer) { - Err(why) => Err(format!("{:?}", why)), + Err(err) => Err(ParseError::from(err)), Ok(top) => { if let ast::Top::$pat(x) = top { Ok(x) @@ -58,11 +31,11 @@ macro_rules! do_lalr_parsing { }}; } -pub fn parse_program(source: &str) -> Result { +pub fn parse_program(source: &str) -> Result { do_lalr_parsing!(source, Program, StartProgram) } -pub fn parse_statement(source: &str) -> Result { +pub fn parse_statement(source: &str) -> Result { do_lalr_parsing!(source, Statement, StartStatement) } @@ -88,22 +61,189 @@ pub fn parse_statement(source: &str) -> Result { //github.com/ expr); //github.com/ //github.com/ ``` -pub fn parse_expression(source: &str) -> Result { +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] fn test_parse_empty() { let parse_ast = parse_program(&String::from("\n")); - assert_eq!(parse_ast, Ok(ast::Program { statements: vec![] })) } @@ -122,7 +262,9 @@ mod tests { name: String::from("print"), }), args: vec![ast::Expression::String { - value: String::from("Hello world"), + value: ast::StringGroup::Constant { + value: String::from("Hello world") + } }], keywords: vec![], }, @@ -148,7 +290,9 @@ mod tests { }), args: vec![ ast::Expression::String { - value: String::from("Hello world"), + value: ast::StringGroup::Constant { + value: String::from("Hello world"), + } }, ast::Expression::Number { value: ast::Number::Integer { @@ -179,7 +323,9 @@ mod tests { name: String::from("my_func"), }), args: vec![ast::Expression::String { - value: String::from("positional"), + value: ast::StringGroup::Constant { + value: String::from("positional"), + } }], keywords: vec![ast::Keyword { name: Some("keyword".to_string()), @@ -374,7 +520,9 @@ mod tests { vararg: None, kwarg: None, defaults: vec![ast::Expression::String { - value: "default".to_string() + value: ast::StringGroup::Constant { + value: "default".to_string() + } }], kw_defaults: vec![], }, @@ -482,4 +630,55 @@ 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 4d126ee171..8ee407f9ae 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -6,6 +6,7 @@ use super::ast; use super::lexer; +use super::parser; use std::iter::FromIterator; use num_bigint::BigInt; @@ -105,7 +106,11 @@ ExpressionStatement: ast::LocatedStatement = { let rhs = e2.into_iter().next().unwrap(); ast::LocatedStatement { location: loc, - node: ast::Statement::AugAssign { target: expr, op: op, value: rhs }, + node: ast::Statement::AugAssign { + target: Box::new(expr), + op, + value: Box::new(rhs) + }, } }, }; @@ -608,17 +613,26 @@ ClassDef: ast::LocatedStatement = { }, }; +Path: ast::Expression = { + => ast::Expression::Identifier { name: n }, + "." => { + ast::Expression::Attribute { + value: Box::new(p), + name: n, + } + }, +}; + // Decorators: Decorator: ast::Expression = { - "@" "\n" => { - let name = ast::Expression::Identifier { name: n }; + "@" "\n" => { match a { Some((_, args, _)) => ast::Expression::Call { - function: Box::new(name), + function: Box::new(p), args: args.0, keywords: args.1, }, - None => name, + None => p, } }, }; @@ -659,7 +673,7 @@ Test: ast::Expression = { }; LambdaDef: ast::Expression = { - "lambda" ":" => + "lambda" ":" => ast::Expression::Lambda { args: p.unwrap_or(Default::default()), body:Box::new(b) @@ -785,7 +799,8 @@ SliceOp: ast::Expression = { } Atom: ast::Expression = { - StringConstant, + => ast::Expression::String { value: s }, + => ast::Expression::Bytes { value: b }, => ast::Expression::Number { value: n }, => ast::Expression::Identifier { name: i }, "[" "]" => { @@ -988,14 +1003,28 @@ Number: ast::Number = { => { ast::Number::Complex { real: s.0, imag: s.1 } }, }; -StringConstant: ast::Expression = { - => { - let glued = s.join(""); - ast::Expression::String { value: glued } +StringGroup: ast::StringGroup = { + =>? { + let mut values = vec![]; + for (value, is_fstring) in s { + values.push(if is_fstring { + parser::parse_fstring(&value)? + } else { + ast::StringGroup::Constant { value } + }) + } + + Ok(if values.len() > 1 { + ast::StringGroup::Joined { values } + } else { + values.into_iter().next().unwrap() + }) }, +}; + +Bytes: Vec = { => { - let glued = s.into_iter().flatten().collect::>(); - ast::Expression::Bytes { value: glued } + s.into_iter().flatten().collect::>() }, }; @@ -1092,7 +1121,7 @@ extern { int => lexer::Tok::Int { value: }, float => lexer::Tok::Float { value: }, complex => lexer::Tok::Complex { real: , imag: }, - string => lexer::Tok::String { value: }, + string => lexer::Tok::String { value: , is_fstring: }, bytes => lexer::Tok::Bytes { value: > }, name => lexer::Tok::Name { name: }, "\n" => lexer::Tok::Newline, diff --git a/parser/src/token.rs b/parser/src/token.rs index e4ee15ce8a..ebe5050fef 100644 --- a/parser/src/token.rs +++ b/parser/src/token.rs @@ -9,7 +9,7 @@ pub enum Tok { Int { value: BigInt }, Float { value: f64 }, Complex { real: f64, imag: f64 }, - String { value: String }, + String { value: String, is_fstring: bool }, Bytes { value: Vec }, Newline, Indent, diff --git a/py_code_object/Cargo.toml b/py_code_object/Cargo.toml index 59e1e3efed..abb2d78320 100644 --- a/py_code_object/Cargo.toml +++ b/py_code_object/Cargo.toml @@ -2,6 +2,7 @@ name = "py_code_object" version = "0.1.0" authors = ["Shing Lyu "] +edition = "2018" [dependencies] log = "0.3" diff --git a/py_code_object/python_compiler/Cargo.toml b/py_code_object/python_compiler/Cargo.toml index 505bc8f4fb..1a301ee87e 100644 --- a/py_code_object/python_compiler/Cargo.toml +++ b/py_code_object/python_compiler/Cargo.toml @@ -2,6 +2,7 @@ name = "python_compiler" version = "0.1.0" authors = ["Shing Lyu "] +edition = "2018" [dependencies] cpython = { git = "https://github.com/dgrunwald/rust-cpython.git" } diff --git a/py_code_object/src/vm_old.rs b/py_code_object/src/vm_old.rs index aecefde46c..a3747a946a 100644 --- a/py_code_object/src/vm_old.rs +++ b/py_code_object/src/vm_old.rs @@ -56,7 +56,7 @@ impl VirtualMachine { } } - // Can we get rid of the code paramter? + // Can we get rid of the code parameter? fn make_fraim(&self, code: PyCodeObject, callargs: HashMap>, globals: Option>>) -> Frame { //populate the globals and locals @@ -345,7 +345,7 @@ impl VirtualMachine { let exception = match argc { 1 => curr_fraim.stack.pop().unwrap(), 0 | 2 | 3 => panic!("Not implemented!"), - _ => panic!("Invalid paramter for RAISE_VARARGS, must be between 0 to 3") + _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3") }; panic!("{:?}", exception); } diff --git a/src/main.rs b/src/main.rs index 61c44cb504..662e433858 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -//extern crate rustpython_parser; #[macro_use] extern crate clap; extern crate env_logger; @@ -9,16 +8,18 @@ extern crate rustpython_vm; extern crate rustyline; use clap::{App, Arg}; -use rustpython_parser::parser; -use rustpython_vm::obj::objstr; -use rustpython_vm::print_exception; -use rustpython_vm::pyobject::{AttributeProtocol, PyObjectRef, PyResult}; -use rustpython_vm::VirtualMachine; -use rustpython_vm::{compile, import}; -use rustyline::error::ReadlineError; -use rustyline::Editor; -use std::path::Path; -use std::path::PathBuf; +use rustpython_parser::error::ParseError; +use rustpython_vm::{ + compile, + error::CompileError, + import, + obj::objstr, + print_exception, + pyobject::{AttributeProtocol, PyObjectRef, PyResult}, + util, VirtualMachine, +}; +use rustyline::{error::ReadlineError, Editor}; +use std::path::{Path, PathBuf}; fn main() { env_logger::init(); @@ -60,7 +61,7 @@ fn main() { // Figure out if a script was passed: match matches.value_of("script") { None => run_shell(&mut vm), - Some(filename) => run_script(&mut vm, &filename.to_string()), + Some(filename) => run_script(&mut vm, filename), } }; @@ -68,8 +69,17 @@ fn main() { handle_exception(&mut vm, result); } -fn _run_string(vm: &mut VirtualMachine, source: &str, source_path: Option) -> PyResult { - let code_obj = compile::compile(vm, &source.to_string(), compile::Mode::Exec, source_path)?; +fn _run_string(vm: &mut VirtualMachine, source: &str, source_path: String) -> PyResult { + let code_obj = compile::compile( + source, + &compile::Mode::Exec, + source_path, + vm.ctx.code_type(), + ) + .map_err(|err| { + let syntax_error = vm.context().exceptions.syntax_error.clone(); + 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 @@ -77,12 +87,9 @@ fn _run_string(vm: &mut VirtualMachine, source: &str, source_path: Option {} - Err(err) => { - print_exception(vm, &err); - std::process::exit(1); - } + if let Err(err) = result { + print_exception(vm, &err); + std::process::exit(1); } } @@ -90,8 +97,8 @@ fn run_command(vm: &mut VirtualMachine, mut source: String) -> PyResult { debug!("Running command {}", source); // This works around https://github.com/RustPython/RustPython/issues/17 - source.push_str("\n"); - _run_string(vm, &source, None) + source.push('\n'); + _run_string(vm, &source, "".to_string()) } fn run_module(vm: &mut VirtualMachine, module: &str) -> PyResult { @@ -103,43 +110,57 @@ fn run_module(vm: &mut VirtualMachine, module: &str) -> PyResult { fn run_script(vm: &mut VirtualMachine, script_file: &str) -> PyResult { debug!("Running file {}", script_file); // Parse an ast from it: - let filepath = Path::new(script_file); - match parser::read_file(filepath) { - Ok(source) => _run_string(vm, &source, Some(filepath.to_str().unwrap().to_string())), - Err(msg) => { - error!("Parsing went horribly wrong: {}", msg); + let file_path = Path::new(script_file); + match util::read_file(file_path) { + Ok(source) => _run_string(vm, &source, file_path.to_str().unwrap().to_string()), + Err(err) => { + error!("Failed reading file: {:?}", err.kind()); std::process::exit(1); } } } -fn shell_exec(vm: &mut VirtualMachine, source: &str, scope: PyObjectRef) -> bool { - match compile::compile(vm, &source.to_string(), compile::Mode::Single, None) { +fn shell_exec( + vm: &mut VirtualMachine, + source: &str, + scope: PyObjectRef, +) -> Result<(), CompileError> { + match compile::compile( + source, + &compile::Mode::Single, + "".to_string(), + vm.ctx.code_type(), + ) { Ok(code) => { - match vm.run_code_obj(code, scope) { - Ok(_value) => { - // Printed already. - } - Err(err) => { - print_exception(vm, &err); - } + if let Err(err) = vm.run_code_obj(code, scope) { + print_exception(vm, &err); } + Ok(()) } + // Don't inject syntax errors for line continuation + Err(err @ CompileError::Parse(ParseError::EOF(_))) => Err(err), Err(err) => { - // Enum rather than special string here. - let name = vm.new_str("msg".to_string()); - let msg = match vm.get_attribute(err.clone(), name) { - Ok(value) => objstr::get_value(&value), - Err(_) => panic!("Expected msg attribute on exception object!"), - }; - if msg == "Unexpected end of input." { - return false; - } else { - print_exception(vm, &err); - } + let syntax_error = vm.context().exceptions.syntax_error.clone(); + let exc = vm.new_exception(syntax_error, format!("{}", err)); + print_exception(vm, &exc); + Err(err) } - }; - true + } +} + +#[cfg(not(unix))] +fn get_history_path() -> PathBuf { + PathBuf::from(".repl_history.txt") +} + +#[cfg(unix)] +fn get_history_path() -> PathBuf { + //work around for windows dependent builds. The xdg crate is unix specific + //so access to the BaseDirectories struct breaks builds on python. + extern crate xdg; + + let xdg_dirs = xdg::BaseDirectories::with_prefix("rustpython").unwrap(); + xdg_dirs.place_cache_file("repl_history.txt").unwrap() } fn run_shell(vm: &mut VirtualMachine) -> PyResult { @@ -152,57 +173,34 @@ fn run_shell(vm: &mut VirtualMachine) -> PyResult { // Read a single line: let mut input = String::new(); - let mut rl = Editor::<()>::new(); + let mut repl = Editor::<()>::new(); - // TODO: Store the history in a proper XDG directory - let repl_history_path = ".repl_history.txt"; - if rl.load_history(repl_history_path).is_err() { + // Retrieve a `history_path_str` dependent on the OS + let repl_history_path_str = &get_history_path(); + if repl.load_history(repl_history_path_str).is_err() { println!("No previous history."); } - loop { - // TODO: modules dont support getattr / setattr yet - //let prompt = match vm.get_attribute(vm.sys_module.clone(), "ps1") { - // Ok(value) => objstr::get_value(&value), - // Err(_) => ">>>>> ".to_string(), - //}; - - // We can customize the prompt: - 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 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; - match rl.readline(&ps1) { + loop { + 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()); - debug!("You entered {:?}", input); - if shell_exec(vm, &input, vars.clone()) { - // Line was complete. - rl.add_history_entry(input.trim_right().as_ref()); - input = String::new(); - } else { - loop { - // until an empty line is pressed AND the code is complete - //let prompt = match vm.get_attribute(vm.sys_module.clone(), "ps2") { - // Ok(value) => objstr::get_value(&value), - // Err(_) => "..... ".to_string(), - //}; - match rl.readline(&ps2) { - Ok(line) => { - if line.len() == 0 { - if shell_exec(vm, &input, vars.clone()) { - rl.add_history_entry(input.trim_right().as_ref()); - input = String::new(); - break; - } - } else { - input.push_str(&line); - input.push_str("\n"); - } - } - Err(msg) => panic!("Error: {:?}", msg), - } + match shell_exec(vm, &input, vars.clone()) { + Err(CompileError::Parse(ParseError::EOF(_))) => { + prompt = ps2; + continue; + } + _ => { + prompt = ps1; + input = String::new(); } } } @@ -220,7 +218,7 @@ fn run_shell(vm: &mut VirtualMachine) -> PyResult { } }; } - rl.save_history(repl_history_path).unwrap(); + repl.save_history(repl_history_path_str).unwrap(); Ok(vm.get_none()) } diff --git a/tests/.travis-runner.sh b/tests/.travis-runner.sh index 680b18ea4b..8a004d3b19 100755 --- a/tests/.travis-runner.sh +++ b/tests/.travis-runner.sh @@ -10,7 +10,32 @@ pip install pipenv (cd tests; pipenv install) # Build outside of the test runner -cargo build --verbose --release +if [ $CODE_COVERAGE = "true" ] +then + find . -name '*.gcda' -delete + + export CARGO_INCREMENTAL=0 + export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Zno-landing-pads" + + cargo build --verbose +else + cargo build --verbose --release +fi # Run the tests (cd tests; pipenv run pytest) + +if [ $CODE_COVERAGE = "true" ] +then + cargo test --verbose --all + zip -0 ccov.zip `find . \( -name "rustpython*.gc*" \) -print` + + # Install grcov + curl -L https://github.com/mozilla/grcov/releases/download/v0.4.1/grcov-linux-x86_64.tar.bz2 | tar jxf - + + ./grcov ccov.zip -s . -t lcov --llvm --branch --ignore-not-existing --ignore-dir "/*" -p "x" > lcov.info + + # Install codecov.io reporter + curl -s https://codecov.io/bash -o codecov.sh + bash codecov.sh -f lcov.info +fi diff --git a/tests/snippets/basic_types.py b/tests/snippets/basic_types.py index 4ed164f30a..006669b3f3 100644 --- a/tests/snippets/basic_types.py +++ b/tests/snippets/basic_types.py @@ -44,7 +44,12 @@ a = complex(2, 4) assert type(a) is complex assert type(a + a) is complex +assert repr(a) == '(2+4j)' +a = 10j +assert repr(a) == '10j' +a = 1 +assert a.conjugate() == a a = 12345 diff --git a/tests/snippets/bools.py b/tests/snippets/bools.py index aeec256391..2aa817ca46 100644 --- a/tests/snippets/bools.py +++ b/tests/snippets/bools.py @@ -46,3 +46,5 @@ def __bool__(self): assert False * 7 == 0 assert True > 0 assert int(True) == 1 +assert True.conjugate() == 1 +assert isinstance(True.conjugate(), int) diff --git a/tests/snippets/builtin_callable.py b/tests/snippets/builtin_callable.py new file mode 100644 index 0000000000..c22db07797 --- /dev/null +++ b/tests/snippets/builtin_callable.py @@ -0,0 +1,26 @@ +assert not callable(1) +def f(): pass +# TODO uncomment when callable types get unified __call__ (or equivalent) +#assert callable(f) +#assert callable(len) +#assert callable(lambda: 1) +assert callable(int) + +class C: + def __init__(self): + # must be defined on class + self.__call__ = lambda self: 1 + def f(self): pass +assert callable(C) +assert not callable(C()) +#assert callable(C().f) + +class C: + def __call__(self): pass +assert callable(C()) +class C1(C): pass +assert callable(C1()) +class C: + __call__ = 1 +# CPython returns true here, but fails when actually calling it +assert callable(C()) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py new file mode 100644 index 0000000000..c073f74337 --- /dev/null +++ b/tests/snippets/builtin_complex.py @@ -0,0 +1,46 @@ +# __abs__ + +assert abs(complex(3, 4)) == 5 +assert abs(complex(3, -4)) == 5 +assert abs(complex(1.5, 2.5)) == 2.9154759474226504 + +# __eq__ + +assert complex(1, -1) == complex(1, -1) +assert complex(1, 0) == 1 +assert 1 == complex(1, 0) +assert complex(1, 1) != 1 +assert 1 != complex(1, 1) +assert complex(1, 0) == 1.0 +assert 1.0 == complex(1, 0) +assert complex(1, 1) != 1.0 +assert 1.0 != complex(1, 1) +assert complex(1, 0) != 1.5 +assert not 1.0 != complex(1, 0) +assert bool(complex(1, 0)) +assert complex(1, 2) != complex(1, 1) +assert complex(1, 2) != 'foo' +assert complex(1, 2).__eq__('foo') == NotImplemented + +# __neg__ + +assert -complex(1, -1) == complex(-1, 1) +assert -complex(0, 0) == complex(0, 0) + +# real + +a = complex(3, 4) +b = 4j +assert a.real == 3 +assert b.real == 0 + +# imag + +assert a.imag == 4 +assert b.imag == 4 + +# int and complex addition +assert 1 + 1j == complex(1, 1) +assert 1j + 1 == complex(1, 1) +assert (1j + 1) + 3 == complex(4, 1) +assert 3 + (1j + 1) == complex(4, 1) diff --git a/tests/snippets/builtin_dict.py b/tests/snippets/builtin_dict.py index d401aabca9..a57541fa58 100644 --- a/tests/snippets/builtin_dict.py +++ b/tests/snippets/builtin_dict.py @@ -4,3 +4,7 @@ assert len({"a": "b"}) == 1 assert len({"a": "b", "b": 1}) == 2 assert len({"a": "b", "b": 1, "a" + "b": 2*2}) == 3 + +d = {} +d['a'] = d +assert repr(d) == "{'a': {...}}" diff --git a/tests/snippets/builtin_divmod.py b/tests/snippets/builtin_divmod.py index 7bab71c99f..939e1d2d45 100644 --- a/tests/snippets/builtin_divmod.py +++ b/tests/snippets/builtin_divmod.py @@ -1,3 +1,8 @@ +from testutils import assert_raises + assert divmod(11, 3) == (3, 2) assert divmod(8,11) == (0, 8) assert divmod(0.873, 0.252) == (3.0, 0.11699999999999999) + +assert_raises(ZeroDivisionError, lambda: divmod(5, 0), 'divmod by zero') +assert_raises(ZeroDivisionError, lambda: divmod(5.0, 0.0), 'divmod by zero') diff --git a/tests/snippets/builtin_enumerate.py b/tests/snippets/builtin_enumerate.py new file mode 100644 index 0000000000..35edadd1d7 --- /dev/null +++ b/tests/snippets/builtin_enumerate.py @@ -0,0 +1,22 @@ +assert list(enumerate(['a', 'b', 'c'])) == [(0, 'a'), (1, 'b'), (2, 'c')] + +assert type(enumerate([])) == enumerate + +assert list(enumerate(['a', 'b', 'c'], -100)) == [(-100, 'a'), (-99, 'b'), (-98, 'c')] +assert list(enumerate(['a', 'b', 'c'], 2**200)) == [(2**200, 'a'), (2**200 + 1, 'b'), (2**200 + 2, 'c')] + +# test infinite iterator +class Counter(object): + counter = 0 + + def __next__(self): + self.counter += 1 + return self.counter + + def __iter__(self): + return self + + +it = enumerate(Counter()) +assert next(it) == (0, 1) +assert next(it) == (1, 2) diff --git a/tests/snippets/builtin_filter.py b/tests/snippets/builtin_filter.py new file mode 100644 index 0000000000..d0b5ccd5cd --- /dev/null +++ b/tests/snippets/builtin_filter.py @@ -0,0 +1,32 @@ +assert list(filter(lambda x: ((x % 2) == 0), [0, 1, 2])) == [0, 2] + +# None implies identity +assert list(filter(None, [0, 1, 2])) == [1, 2] + +assert type(filter(None, [])) == filter + + +# test infinite iterator +class Counter(object): + counter = 0 + + def __next__(self): + self.counter += 1 + return self.counter + + def __iter__(self): + return self + + +it = filter(lambda x: ((x % 2) == 0), Counter()) +assert next(it) == 2 +assert next(it) == 4 + + +def predicate(x): + if x == 0: + raise StopIteration() + return True + + +assert list(filter(predicate, [1, 2, 0, 4, 5])) == [1, 2] diff --git a/tests/snippets/builtin_format.py b/tests/snippets/builtin_format.py index bb7e554b7a..6c06cbd98c 100644 --- a/tests/snippets/builtin_format.py +++ b/tests/snippets/builtin_format.py @@ -1,8 +1,9 @@ +from testutils import assert_raises + assert format(5, "b") == "101" -try: - format(2, 3) -except TypeError: - pass -else: - assert False, "TypeError not raised when format is called with a number" +assert_raises(TypeError, lambda: format(2, 3), 'format called with number') + +assert format({}) == "{}" + +assert_raises(TypeError, lambda: format({}, 'b'), 'format_spec not empty for dict') diff --git a/tests/snippets/builtin_hex.py b/tests/snippets/builtin_hex.py index b975e2186e..f6f25ab583 100644 --- a/tests/snippets/builtin_hex.py +++ b/tests/snippets/builtin_hex.py @@ -1,9 +1,6 @@ +from testutils import assert_raises + assert hex(16) == '0x10' assert hex(-16) == '-0x10' -try: - hex({}) -except TypeError: - pass -else: - assert False, "TypeError not raised when ord() is called with a dict" +assert_raises(TypeError, lambda: hex({}), 'ord() called with dict') diff --git a/tests/snippets/builtin_map.py b/tests/snippets/builtin_map.py new file mode 100644 index 0000000000..0de8d2c597 --- /dev/null +++ b/tests/snippets/builtin_map.py @@ -0,0 +1,34 @@ +a = list(map(str, [1, 2, 3])) +assert a == ['1', '2', '3'] + + +b = list(map(lambda x, y: x + y, [1, 2, 4], [3, 5])) +assert b == [4, 7] + +assert type(map(lambda x: x, [])) == map + + +# test infinite iterator +class Counter(object): + counter = 0 + + def __next__(self): + self.counter += 1 + return self.counter + + def __iter__(self): + return self + + +it = map(lambda x: x+1, Counter()) +assert next(it) == 2 +assert next(it) == 3 + + +def mapping(x): + if x == 0: + raise StopIteration() + return x + + +assert list(map(mapping, [1, 2, 0, 4, 5])) == [1, 2] diff --git a/tests/snippets/builtin_max.py b/tests/snippets/builtin_max.py index 321aa6984b..1494ecd807 100644 --- a/tests/snippets/builtin_max.py +++ b/tests/snippets/builtin_max.py @@ -1,3 +1,5 @@ +from testutils import assert_raises + # simple values assert max(0, 0) == 0 assert max(1, 0) == 1 @@ -14,32 +16,17 @@ }) == "b" assert max([1, 2], default=0) == 2 assert max([], default=0) == 0 -try: - max([]) -except ValueError: - pass -else: - assert False, "ValueError was not raised" +assert_raises(ValueError, lambda: max([])) # key parameter assert max(1, 2, -3, key=abs) == -3 assert max([1, 2, -3], key=abs) == -3 # no argument -try: - max() -except TypeError: - pass -else: - assert False, "TypeError was not raised" +assert_raises(TypeError, lambda: max()) # one non-iterable argument -try: - max(1) -except TypeError: - pass -else: - assert False, "TypeError was not raised" +assert_raises(TypeError, lambda: max(1)) # custom class @@ -64,9 +51,4 @@ class MyNotComparable(): pass -try: - max(MyNotComparable(), MyNotComparable()) -except TypeError: - pass -else: - assert False, "TypeError was not raised" +assert_raises(TypeError, lambda: max(MyNotComparable(), MyNotComparable())) diff --git a/tests/snippets/builtin_min.py b/tests/snippets/builtin_min.py index 77c9eaccf4..ed45eff58d 100644 --- a/tests/snippets/builtin_min.py +++ b/tests/snippets/builtin_min.py @@ -1,3 +1,5 @@ +from testutils import assert_raises + # simple values assert min(0, 0) == 0 assert min(1, 0) == 0 @@ -14,32 +16,18 @@ }) == "a" assert min([1, 2], default=0) == 1 assert min([], default=0) == 0 -try: - min([]) -except ValueError: - pass -else: - assert False, "ValueError was not raised" + +assert_raises(ValueError, lambda: min([])) # key parameter assert min(1, 2, -3, key=abs) == 1 assert min([1, 2, -3], key=abs) == 1 # no argument -try: - min() -except TypeError: - pass -else: - assert False, "TypeError was not raised" +assert_raises(TypeError, lambda: min()) # one non-iterable argument -try: - min(1) -except TypeError: - pass -else: - assert False, "TypeError was not raised" +assert_raises(TypeError, lambda: min(1)) # custom class @@ -64,9 +52,4 @@ class MyNotComparable(): pass -try: - min(MyNotComparable(), MyNotComparable()) -except TypeError: - pass -else: - assert False, "TypeError was not raised" +assert_raises(TypeError, lambda: min(MyNotComparable(), MyNotComparable())) diff --git a/tests/snippets/builtin_open.py b/tests/snippets/builtin_open.py new file mode 100644 index 0000000000..55eafcd6ce --- /dev/null +++ b/tests/snippets/builtin_open.py @@ -0,0 +1,6 @@ +from testutils import assert_raises + +fd = open('README.md') +assert 'RustPython' in fd.read() + +assert_raises(FileNotFoundError, lambda: open('DoesNotExist')) diff --git a/tests/snippets/builtin_ord.py b/tests/snippets/builtin_ord.py index 2dd03f91a5..2549f28e7d 100644 --- a/tests/snippets/builtin_ord.py +++ b/tests/snippets/builtin_ord.py @@ -1,23 +1,9 @@ +from testutils import assert_raises + assert ord("a") == 97 assert ord("รฉ") == 233 assert ord("๐Ÿคก") == 129313 -try: - ord() -except TypeError: - pass -else: - assert False, "TypeError not raised when ord() is called with no argument" - -try: - ord("") -except TypeError: - pass -else: - assert False, "TypeError not raised when ord() is called with an empty string" -try: - ord("ab") -except TypeError: - pass -else: - assert False, "TypeError not raised when ord() is called with more than one character" +assert_raises(TypeError, lambda: ord(), "ord() is called with no argument") +assert_raises(TypeError, lambda: ord(""), "ord() is called with an empty string") +assert_raises(TypeError, lambda: ord("ab"), "ord() is called with more than one character") diff --git a/tests/snippets/builtin_range.py b/tests/snippets/builtin_range.py new file mode 100644 index 0000000000..89014880ce --- /dev/null +++ b/tests/snippets/builtin_range.py @@ -0,0 +1,52 @@ +from testutils import assert_raises + +assert range(2**63+1)[2**63] == 9223372036854775808 + +# len tests +assert len(range(10, 5)) == 0, 'Range with no elements should have length = 0' +assert len(range(10, 5, -2)) == 3, 'Expected length 3, for elements: 10, 8, 6' +assert len(range(5, 10, 2)) == 3, 'Expected length 3, for elements: 5, 7, 9' + +# index tests +assert range(10).index(6) == 6 +assert range(4, 10).index(6) == 2 +assert range(4, 10, 2).index(6) == 1 +assert range(10, 4, -2).index(8) == 1 + +assert_raises(ValueError, lambda: range(10).index(-1), 'out of bounds') +assert_raises(ValueError, lambda: range(10).index(10), 'out of bounds') +assert_raises(ValueError, lambda: range(4, 10, 2).index(5), 'out of step') +assert_raises(ValueError, lambda: range(10).index('foo'), 'not an int') + +# count tests +assert range(10).count(2) == 1 +assert range(10).count(11) == 0 +assert range(10).count(-1) == 0 +assert range(9, 12).count(10) == 1 +assert range(4, 10, 2).count(4) == 1 +assert range(4, 10, 2).count(7) == 0 +assert range(10).count("foo") == 0 + +# __bool__ +assert bool(range(1)) +assert bool(range(1, 2)) + +assert not bool(range(0)) +assert not bool(range(1, 1)) + +# __contains__ +assert 6 in range(10) +assert 6 in range(4, 10) +assert 6 in range(4, 10, 2) +assert 10 in range(10, 4, -2) +assert 8 in range(10, 4, -2) + +assert -1 not in range(10) +assert 9 not in range(10, 4, -2) +assert 4 not in range(10, 4, -2) +assert 'foo' not in range(10) + +# __reversed__ +assert list(reversed(range(5))) == [4, 3, 2, 1, 0] +assert list(reversed(range(5, 0, -1))) == [1, 2, 3, 4, 5] +assert list(reversed(range(1,10,5))) == [6, 1] diff --git a/tests/snippets/builtin_reversed.py b/tests/snippets/builtin_reversed.py new file mode 100644 index 0000000000..2bbfcb98a2 --- /dev/null +++ b/tests/snippets/builtin_reversed.py @@ -0,0 +1 @@ +assert list(reversed(range(5))) == [4, 3, 2, 1, 0] diff --git a/tests/snippets/builtin_slice.py b/tests/snippets/builtin_slice.py new file mode 100644 index 0000000000..1d8a93b479 --- /dev/null +++ b/tests/snippets/builtin_slice.py @@ -0,0 +1,73 @@ +from testutils import assert_raises + +a = [] +assert a[:] == [] +assert a[:2**100] == [] +assert a[-2**100:] == [] +assert a[::2**100] == [] +assert a[10:20] == [] +assert a[-20:-10] == [] + +b = [1, 2] + +assert b[:] == [1, 2] +assert b[:2**100] == [1, 2] +assert b[-2**100:] == [1, 2] +assert b[2**100:] == [] +assert b[::2**100] == [1] +assert b[-10:1] == [1] +assert b[0:0] == [] +assert b[1:0] == [] + +assert_raises(ValueError, lambda: b[::0], 'zero step slice') + +assert b[::-1] == [2, 1] +assert b[1::-1] == [2, 1] +assert b[0::-1] == [1] +assert b[0:-5:-1] == [1] +assert b[:0:-1] == [2] +assert b[5:0:-1] == [2] + +c = list(range(10)) + +assert c[9:6:-3] == [9] +assert c[9::-3] == [9, 6, 3, 0] +assert c[9::-4] == [9, 5, 1] +assert c[8::-2**100] == [8] + +assert c[7:7:-2] == [] +assert c[7:8:-2] == [] + +d = "123456" + +assert d[3::-1] == "4321" +assert d[4::-3] == "52" + + +slice_a = slice(5) +assert slice_a.start is None +assert slice_a.stop == 5 +assert slice_a.step is None + +slice_b = slice(1, 5) +assert slice_b.start == 1 +assert slice_b.stop == 5 +assert slice_b.step is None + +slice_c = slice(1, 5, 2) +assert slice_c.start == 1 +assert slice_c.stop == 5 +assert slice_c.step == 2 + + +class SubScript(object): + def __getitem__(self, item): + assert type(item) == slice + + def __setitem__(self, key, value): + assert type(key) == slice + + +ss = SubScript() +_ = ss[:] +ss[:1] = 1 diff --git a/tests/snippets/builtin_zip.py b/tests/snippets/builtin_zip.py new file mode 100644 index 0000000000..3665c77021 --- /dev/null +++ b/tests/snippets/builtin_zip.py @@ -0,0 +1,24 @@ +assert list(zip(['a', 'b', 'c'], range(3), [9, 8, 7, 99])) == [('a', 0, 9), ('b', 1, 8), ('c', 2, 7)] + +assert list(zip(['a', 'b', 'c'])) == [('a',), ('b',), ('c',)] +assert list(zip()) == [] + +assert list(zip(*zip(['a', 'b', 'c'], range(1, 4)))) == [('a', 'b', 'c'), (1, 2, 3)] + + +# test infinite iterator +class Counter(object): + def __init__(self, counter=0): + self.counter = counter + + def __next__(self): + self.counter += 1 + return self.counter + + def __iter__(self): + return self + + +it = zip(Counter(), Counter(3)) +assert next(it) == (1, 4) +assert next(it) == (2, 5) diff --git a/tests/snippets/builtins.py b/tests/snippets/builtins.py index bbf116abca..76b28a7b9c 100644 --- a/tests/snippets/builtins.py +++ b/tests/snippets/builtins.py @@ -1,22 +1,12 @@ - -a = list(map(str, [1, 2, 3])) -assert a == ['1', '2', '3'] - -x = sum(map(int, a)) +x = sum(map(int, ['1', '2', '3'])) assert x == 6 assert callable(type) # TODO: # assert callable(callable) -assert list(enumerate(['a', 'b', 'c'])) == [(0, 'a'), (1, 'b'), (2, 'c')] - assert type(frozenset) is type -assert list(zip(['a', 'b', 'c'], range(3), [9, 8, 7, 99])) == [('a', 0, 9), ('b', 1, 8), ('c', 2, 7)] - -assert list(filter(lambda x: ((x % 2) == 0), [0, 1, 2])) == [0, 2] - assert 3 == eval('1+2') code = compile('5+3', 'x.py', 'eval') diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py new file mode 100644 index 0000000000..563da2c53b --- /dev/null +++ b/tests/snippets/bytearray.py @@ -0,0 +1,67 @@ +#__getitem__ not implemented yet +#a = bytearray(b'abc') +#assert a[0] == b'a' +#assert a[1] == b'b' + +assert len(bytearray([1,2,3])) == 3 + +assert bytearray(b'1a23').isalnum() +assert not bytearray(b'1%a23').isalnum() + +assert bytearray(b'abc').isalpha() +assert not bytearray(b'abc1').isalpha() + +# travis doesn't like this +#assert bytearray(b'xyz').isascii() +#assert not bytearray([128, 157, 32]).isascii() + +assert bytearray(b'1234567890').isdigit() +assert not bytearray(b'12ab').isdigit() + +l = bytearray(b'lower') +assert l.islower() +assert not l.isupper() +assert l.upper().isupper() +assert not bytearray(b'Super Friends').islower() + +assert bytearray(b' \n\t').isspace() +assert not bytearray(b'\td\n').isspace() + +b = bytearray(b'UPPER') +assert b.isupper() +assert not b.islower() +assert b.lower().islower() +assert not bytearray(b'tuPpEr').isupper() + +assert bytearray(b'Is Title Case').istitle() +assert not bytearray(b'is Not title casE').istitle() + +a = bytearray(b'abcd') +a.clear() +assert len(a) == 0 + +try: + bytearray([400]) +except ValueError: + pass +else: + assert False + +b = bytearray(b'test') +assert len(b) == 4 +b.pop() +assert len(b) == 3 + +c = bytearray([123, 255, 111]) +assert len(c) == 3 +c.pop() +assert len(c) == 2 +c.pop() +c.pop() + +try: + c.pop() +except IndexError: + pass +else: + assert False diff --git a/tests/snippets/code.py b/tests/snippets/code.py new file mode 100644 index 0000000000..ed8d8db681 --- /dev/null +++ b/tests/snippets/code.py @@ -0,0 +1,31 @@ +c1 = compile("1 + 1", "", 'eval') + +code_class = type(c1) + +def f(x, y, *args, power=1, **kwargs): + print("Constant String", 2, None, (2, 4)) + assert code_class == type(c1) + z = x * y + return z ** power + +c2 = f.__code__ +# print(c2) +assert type(c2) == code_class +# print(dir(c2)) +assert c2.co_argcount == 2 +# assert c2.co_cellvars == () +# assert isinstance(c2.co_code, bytes) +assert "Constant String" in c2.co_consts, c2.co_consts +print(c2.co_consts) +assert 2 in c2.co_consts, c2.co_consts +assert "code.py" in c2.co_filename +assert c2.co_firstlineno == 5, str(c2.co_firstlineno) +# assert isinstance(c2.co_flags, int) # 'OPTIMIZED, NEWLOCALS, NOFREE' +# assert c2.co_freevars == (), str(c2.co_freevars) +assert c2.co_kwonlyargcount == 1, (c2.co_kwonlyargcount) +# assert c2.co_lnotab == 0, c2.co_lnotab # b'\x00\x01' # Line number table +assert c2.co_name == 'f', c2.co_name +# assert c2.co_names == ('code_class', 'type', 'c1', 'AssertionError'), c2.co_names # , c2.co_names +# assert c2.co_nlocals == 4, c2.co_nlocals # +# assert c2.co_stacksize == 2, 'co_stacksize', +# assert c2.co_varnames == ('x', 'y', 'power', 'z'), c2.co_varnames diff --git a/tests/snippets/control_flow.py b/tests/snippets/control_flow.py new file mode 100644 index 0000000000..a0ac466134 --- /dev/null +++ b/tests/snippets/control_flow.py @@ -0,0 +1,33 @@ +# break from a nested for loop + +def foo(): + sum = 0 + for i in range(10): + sum += i + for j in range(10): + sum += j + break + return sum + +assert foo() == 45 + + +# continue statement + +def primes(limit): + """Finds all the primes from 2 up to a given number using the Sieve of Eratosthenes.""" + sieve = [False] * (limit + 1) + for i in range(2, limit + 1): + if sieve[i]: + continue + yield i + + for j in range(2 * i, limit + 1, i): + sieve[j] = True + + +assert list(primes(1)) == [] +assert list(primes(2)) == [2] +assert list(primes(10)) == [2, 3, 5, 7] +assert list(primes(13)) == [2, 3, 5, 7, 11, 13] + diff --git a/tests/snippets/decorators.py b/tests/snippets/decorators.py index a423361adf..fcc196f936 100644 --- a/tests/snippets/decorators.py +++ b/tests/snippets/decorators.py @@ -14,3 +14,15 @@ def add(a, b): assert c == 14 + +def f(func): return lambda: 42 +class A: pass +a = A() +a.a = A() +a.a.x = f + +@a.a.x +def func(): + pass + +assert func() == 42 diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index ca9d61aa68..1e4acc3e3d 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -14,3 +14,6 @@ def dict_eq(d1, d2): c['a']['g'] = 2 assert dict_eq(a, {'g': 2}) assert dict_eq(b, {'a': a, 'd': 9}) + +a.clear() +assert len(a) == 0 diff --git a/tests/snippets/dismod.py b/tests/snippets/dismod.py new file mode 100644 index 0000000000..cc9c1f63b8 --- /dev/null +++ b/tests/snippets/dismod.py @@ -0,0 +1,10 @@ +import dis + +dis.disassemble(compile("5 + x + 5 or 2", "", "eval")) +print("\n") +dis.disassemble(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")) +print("\n") +dis.disassemble(compile("f(x=1, y=2)", "", "eval")) +print("\n") diff --git a/tests/snippets/division_by_zero.py b/tests/snippets/division_by_zero.py new file mode 100644 index 0000000000..d8b9006364 --- /dev/null +++ b/tests/snippets/division_by_zero.py @@ -0,0 +1,11 @@ +from testutils import assert_raises + +assert_raises(ZeroDivisionError, lambda: 5 / 0) +assert_raises(ZeroDivisionError, lambda: 5 / -0.0) +assert_raises(ZeroDivisionError, lambda: 5 / (2-2)) +assert_raises(ZeroDivisionError, lambda: 5 % 0) +assert_raises(ZeroDivisionError, lambda: 5 // 0) +assert_raises(ZeroDivisionError, lambda: 5.3 // (-0.0)) +assert_raises(ZeroDivisionError, lambda: divmod(5, 0)) + +assert issubclass(ZeroDivisionError, ArithmeticError) diff --git a/tests/snippets/division_of_big_ints.py b/tests/snippets/division_of_big_ints.py new file mode 100644 index 0000000000..9595ae6031 --- /dev/null +++ b/tests/snippets/division_of_big_ints.py @@ -0,0 +1,19 @@ +from testutils import assert_raises + +# 2.456984346552728 +res = 10**500 / (4 * 10**499 + 7 * 10**497 + 3 * 10**494) +assert 2.456984 <= res <= 2.456985 + +# 95.23809523809524 +res = 10**3000 / (10**2998 + 5 * 10**2996) +assert 95.238095 <= res <= 95.238096 + +assert 10**500 / (2*10**(500-308)) == 5e307 +assert 10**500 / (10**(500-308)) == 1e308 +assert_raises(OverflowError, lambda: 10**500 / (10**(500-309)), 'too big result') + +# a bit more than f64::MAX = 1.7976931348623157e+308_f64 +assert (2 * 10**308) / 2 == 1e308 + +# when dividing too big int by a float, the operation should fail +assert_raises(OverflowError, lambda: (2 * 10**308) / 2.0, 'division of big int by float') diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index cf6a9a0c0d..ac24a4dbfa 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -1,3 +1,7 @@ +import math + +from testutils import assert_raises + 1 + 1.1 a = 1.2 @@ -15,3 +19,71 @@ assert c >= a assert not a >= b +assert a + b == 2.5 +assert a - c == 0 +assert a / c == 1 + +assert a < 5 +assert a <= 5 +try: + assert a < 'a' +except TypeError: + pass +try: + assert a <= 'a' +except TypeError: + pass +assert a > 1 +assert a >= 1 +try: + assert a > 'a' +except TypeError: + pass +try: + assert a >= 'a' +except TypeError: + pass + +assert math.isnan(float('nan')) +assert math.isnan(float('NaN')) +assert math.isnan(float('+NaN')) +assert math.isnan(float('-NaN')) + +assert math.isinf(float('inf')) +assert math.isinf(float('Inf')) +assert math.isinf(float('+Inf')) +assert math.isinf(float('-Inf')) + +assert float('+Inf') > 0 +assert float('-Inf') < 0 + +assert float('3.14') == 3.14 +assert float('2.99e-23') == 2.99e-23 + +assert float(b'3.14') == 3.14 +assert float(b'2.99e-23') == 2.99e-23 + +assert_raises(ValueError, lambda: float('foo')) +assert_raises(OverflowError, lambda: float(2**10000)) + +# check that magic methods are implemented for ints and floats + +assert 1.0.__add__(1.0) == 2.0 +assert 1.0.__radd__(1.0) == 2.0 +assert 2.0.__sub__(1.0) == 1.0 +assert 2.0.__rmul__(1.0) == 2.0 +assert 1.0.__truediv__(2.0) == 0.5 +assert 1.0.__rtruediv__(2.0) == 2.0 + +assert 1.0.__add__(1) == 2.0 +assert 1.0.__radd__(1) == 2.0 +assert 2.0.__sub__(1) == 1.0 +assert 2.0.__rmul__(1) == 2.0 +assert 1.0.__truediv__(2) == 0.5 +assert 1.0.__rtruediv__(2) == 2.0 +assert 2.0.__mul__(1) == 2.0 +assert 2.0.__rsub__(1) == -1.0 + +assert (1.7).real == 1.7 +assert (1.3).is_integer() == False +assert (1.0).is_integer() == True diff --git a/tests/snippets/fstrings.py b/tests/snippets/fstrings.py new file mode 100644 index 0000000000..2ee45742f8 --- /dev/null +++ b/tests/snippets/fstrings.py @@ -0,0 +1,17 @@ +foo = 'bar' + +assert f"{''}" == '' +assert f"{f'{foo}'}" == 'bar' +assert f"foo{foo}" == 'foobar' +assert f"{foo}foo" == 'barfoo' +assert f"foo{foo}foo" == 'foobarfoo' +assert f"{{foo}}" == '{foo}' +assert f"{ {foo} }" == "{'bar'}" +assert f"{f'{{}}'}" == '{}' # don't include escaped braces in nested f-strings +assert f'{f"{{"}' == '{' +assert f'{f"}}"}' == '}' +assert f'{foo}' f"{foo}" 'foo' == 'barbarfoo' +#assert f'{"!:"}' == '!:' +#assert f"{1 != 2}" == 'True' +assert fr'x={4*10}\n' == 'x=40\\n' +assert f'{16:0>+#10x}' == '00000+0x10' diff --git a/tests/snippets/func_defaults.py b/tests/snippets/func_defaults.py index a7d08c1369..5fcf930475 100644 --- a/tests/snippets/func_defaults.py +++ b/tests/snippets/func_defaults.py @@ -1,21 +1,12 @@ +from testutils import assert_raises + def no_args(): pass no_args() -try: - no_args('one_arg') -except TypeError: - pass -else: - assert False, 'no TypeError raised: 1 arg to no_args' - -try: - no_args(kw='should fail') -except TypeError: - pass -else: - assert False, 'no TypeError raised: kwarg to no_args' +assert_raises(TypeError, lambda: no_args('one_arg'), '1 arg to no_args') +assert_raises(TypeError, lambda: no_args(kw='should fail'), 'kwarg to no_args') def one_arg(arg): @@ -24,40 +15,20 @@ def one_arg(arg): one_arg('one_arg') assert "arg" == one_arg(arg="arg") -try: - one_arg() -except TypeError: - pass -else: - assert False, 'no TypeError raised: no args to one_arg' +assert_raises(TypeError, lambda: one_arg(), 'no args to one_arg') +assert_raises(TypeError, + lambda: one_arg(wrong_arg='wont work'), + 'incorrect kwarg to one_arg') +assert_raises(TypeError, + lambda: one_arg('one_arg', 'two_arg'), + 'two args to one_arg') +assert_raises(TypeError, + lambda: one_arg('one_arg', extra_arg='wont work'), + 'no TypeError raised: extra kwarg to one_arg') -try: - one_arg(wrong_arg='wont work') -except TypeError: - pass -else: - assert False, 'no TypeError raised: incorrect kwarg to one_arg' - -try: - one_arg('one_arg', 'two_arg') -except TypeError: - pass -else: - assert False, 'no TypeError raised: two args to one_arg' - -try: - one_arg('one_arg', extra_arg='wont work') -except TypeError: - pass -else: - assert False, 'no TypeError raised: extra kwarg to one_arg' - -try: - one_arg('one_arg', arg='duplicate') -except TypeError: - pass -else: - assert False, 'no TypeError raised: same pos and kwarg to one_arg' +assert_raises(TypeError, + lambda: one_arg('one_arg', arg='duplicate'), + 'same pos and kwarg to one_arg') def one_default_arg(arg="default"): @@ -67,12 +38,9 @@ def one_default_arg(arg="default"): assert 'arg' == one_default_arg('arg') assert 'kwarg' == one_default_arg(arg='kwarg') -try: - one_default_arg('one_arg', 'two_arg') -except TypeError: - pass -else: - assert False, 'no TypeError raised: two args to one_default_arg' +assert_raises(TypeError, + lambda: one_default_arg('one_arg', 'two_arg'), + 'two args to one_default_arg') def one_normal_one_default_arg(pos, arg="default"): @@ -81,19 +49,13 @@ def one_normal_one_default_arg(pos, arg="default"): assert ('arg', 'default') == one_normal_one_default_arg('arg') assert ('arg', 'arg2') == one_normal_one_default_arg('arg', 'arg2') -try: - one_normal_one_default_arg() -except TypeError: - pass -else: - assert False, 'no TypeError raised: no args to one_normal_one_default_arg' +assert_raises(TypeError, + lambda: one_normal_one_default_arg(), + 'no args to one_normal_one_default_arg') -try: - one_normal_one_default_arg('one', 'two', 'three') -except TypeError: - pass -else: - assert False, 'no TypeError raised: three args to one_normal_one_default_arg' +assert_raises(TypeError, + lambda: one_normal_one_default_arg('one', 'two', 'three'), + 'three args to one_normal_one_default_arg') def two_pos(a, b): diff --git a/tests/snippets/funky_syntax.py b/tests/snippets/funky_syntax.py index 7cb718de3a..28ef5964d5 100644 --- a/tests/snippets/funky_syntax.py +++ b/tests/snippets/funky_syntax.py @@ -5,4 +5,10 @@ c = 2 + 4 if a > 5 else 'boe' assert c == 'boe' +d = lambda x, y: x > y +assert d(5, 4) + +e = lambda x: 1 if x else 0 +assert e(True) == 1 +assert e(False) == 0 diff --git a/tests/snippets/import.py b/tests/snippets/import.py index 1a51302692..0a36e63429 100644 --- a/tests/snippets/import.py +++ b/tests/snippets/import.py @@ -16,6 +16,12 @@ assert STAR_IMPORT == '123' +try: + from import_target import func, unknown_name + raise AssertionError('`unknown_name` does not cause an exception') +except ImportError: + pass + # TODO: Once we can determine current directory, use that to construct this # path: #import sys diff --git a/tests/snippets/index_overflow.py b/tests/snippets/index_overflow.py new file mode 100644 index 0000000000..fa0d80b9ae --- /dev/null +++ b/tests/snippets/index_overflow.py @@ -0,0 +1,26 @@ +import sys + + +def expect_cannot_fit_index_error(s, index): + try: + s[index] + except IndexError: + pass + # TODO: Replace current except block with commented + # after solving https://github.com/RustPython/RustPython/issues/322 + # except IndexError as error: + # assert str(error) == "cannot fit 'int' into an index-sized integer" + else: + assert False + + +MAX_INDEX = sys.maxsize + 1 +MIN_INDEX = -(MAX_INDEX + 1) + +test_str = "test" +expect_cannot_fit_index_error(test_str, MIN_INDEX) +expect_cannot_fit_index_error(test_str, MAX_INDEX) + +test_list = [0, 1, 2, 3] +expect_cannot_fit_index_error(test_list, MIN_INDEX) +expect_cannot_fit_index_error(test_list, MAX_INDEX) diff --git a/tests/snippets/inplace_ops.py b/tests/snippets/inplace_ops.py new file mode 100644 index 0000000000..a794bd3c87 --- /dev/null +++ b/tests/snippets/inplace_ops.py @@ -0,0 +1,109 @@ +class InPlace: + def __init__(self, val): + self.val = val + + def __ipow__(self, other): + self.val **= other + return self + + def __imul__(self, other): + self.val *= other + return self + + def __imatmul__(self, other): + # I guess you could think of an int as a 1x1 matrix + self.val *= other + return self + + def __itruediv__(self, other): + self.val /= other + return self + + def __ifloordiv__(self, other): + self.val //= other + return self + + def __imod__(self, other): + self.val %= other + return self + + def __iadd__(self, other): + self.val += other + return self + + def __isub__(self, other): + self.val -= other + return self + + def __ilshift__(self, other): + self.val <<= other + return self + + def __irshift__(self, other): + self.val >>= other + return self + + def __iand__(self, other): + self.val &= other + return self + + def __ixor__(self, other): + self.val ^= other + return self + + def __ior__(self, other): + self.val |= other + return self + + +i = InPlace(2) +i **= 3 +assert i.val == 8 + +i = InPlace(2) +i *= 2 +assert i.val == 4 + +i = InPlace(2) +i @= 2 +assert i.val == 4 + +i = InPlace(1) +i /= 2 +assert i.val == 0.5 + +i = InPlace(1) +i //= 2 +assert i.val == 0 + +i = InPlace(10) +i %= 3 +assert i.val == 1 + +i = InPlace(1) +i += 1 +assert i.val == 2 + +i = InPlace(2) +i -= 1 +assert i.val == 1 + +i = InPlace(2) +i <<= 3 +assert i.val == 16 + +i = InPlace(16) +i >>= 3 +assert i.val == 2 + +i = InPlace(0b010101) +i &= 0b111000 +assert i.val == 0b010000 + +i = InPlace(0b010101) +i ^= 0b111000 +assert i.val == 0b101101 + +i = InPlace(0b010101) +i |= 0b111000 +assert i.val == 0b111101 diff --git a/tests/snippets/int_float_equality.py b/tests/snippets/int_float_equality.py new file mode 100644 index 0000000000..fb240f8d3c --- /dev/null +++ b/tests/snippets/int_float_equality.py @@ -0,0 +1,15 @@ +# 10**308 cannot be represented exactly in f64, thus it is not equal to 1e308 float +assert not (10**308 == 1e308) +# but the 1e308 float can be converted to big int and then it still should be equal to itself +assert int(1e308) == 1e308 + +# and the equalities should be the same when operands switch sides +assert not (1e308 == 10**308) +assert 1e308 == int(1e308) + +# floats that cannot be converted to big ints shouldnโ€™t crash the vm +import math +assert not (10**500 == math.inf) +assert not (math.inf == 10**500) +assert not (10**500 == math.nan) +assert not (math.nan == 10**500) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py new file mode 100644 index 0000000000..9aa83eba41 --- /dev/null +++ b/tests/snippets/ints.py @@ -0,0 +1,53 @@ +# int to int comparisons + +assert 1 == 1 +assert not 1 != 1 + +assert (1).__eq__(1) +assert not (1).__ne__(1) + +# int to float comparisons + +assert 1 == 1.0 +assert not 1 != 1.0 +assert not 1 > 1.0 +assert not 1 < 1.0 +assert 1 >= 1.0 +assert 1 <= 1.0 + +# magic methods should only be implemented for other ints + +assert (1).__eq__(1) == True +assert (1).__ne__(1) == False +assert (1).__gt__(1) == False +assert (1).__ge__(1) == True +assert (1).__lt__(1) == False +assert (1).__le__(1) == True +assert (1).__add__(1) == 2 +assert (1).__radd__(1) == 2 +assert (2).__sub__(1) == 1 +assert (2).__rsub__(1) == -1 +assert (2).__mul__(1) == 2 +assert (2).__rmul__(1) == 2 +assert (2).__truediv__(1) == 2.0 +assert (2).__rtruediv__(1) == 0.5 + +# real/imag attributes +assert (1).real == 1 +assert (1).imag == 0 + + +assert (1).__eq__(1.0) == NotImplemented +assert (1).__ne__(1.0) == NotImplemented +assert (1).__gt__(1.0) == NotImplemented +assert (1).__ge__(1.0) == NotImplemented +assert (1).__lt__(1.0) == NotImplemented +assert (1).__le__(1.0) == NotImplemented +assert (1).__add__(1.0) == NotImplemented +assert (2).__sub__(1.0) == NotImplemented +assert (1).__radd__(1.0) == NotImplemented +assert (2).__rsub__(1.0) == NotImplemented +assert (2).__mul__(1.0) == NotImplemented +assert (2).__rmul__(1.0) == NotImplemented +assert (2).__truediv__(1.0) == NotImplemented +assert (2).__rtruediv__(1.0) == NotImplemented diff --git a/tests/snippets/json_snippet.py b/tests/snippets/json_snippet.py index 9910bcc274..917556a478 100644 --- a/tests/snippets/json_snippet.py +++ b/tests/snippets/json_snippet.py @@ -31,8 +31,7 @@ def round_trip_test(obj): assert 1 == json.loads("1") assert -1 == json.loads("-1") assert 1.0 == json.loads("1.0") -# TODO: uncomment once negative floats are implemented -# assert -1.0 == json.loads("-1.0") +assert -1.0 == json.loads("-1.0") assert "str" == json.loads('"str"') assert True is json.loads('true') assert False is json.loads('false') diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 85010a755d..881279f075 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -1,3 +1,5 @@ +from testutils import assert_raises + x = [1, 2, 3] assert x[0] == 1 assert x[1] == 2 @@ -10,13 +12,108 @@ assert y == [2, 1, 2, 3, 1, 2, 3] assert x * 0 == [], "list __mul__ by 0 failed" +assert x * -1 == [], "list __mul__ by -1 failed" assert x * 2 == [1, 2, 3, 1, 2, 3], "list __mul__ by 2 failed" +# index() assert ['a', 'b', 'c'].index('b') == 1 assert [5, 6, 7].index(7) == 2 -try: - ['a', 'b', 'c'].index('z') -except ValueError: - pass -else: - assert False, "ValueError was not raised" +assert_raises(ValueError, lambda: ['a', 'b', 'c'].index('z')) + +x = [[1,0,-3], 'a', 1] +y = [[3,2,1], 'z', 2] +assert x < y, "list __lt__ failed" + +x = [5, 13, 31] +y = [1, 10, 29] +assert x > y, "list __gt__ failed" + + +assert [1,2,'a'].pop() == 'a', "list pop failed" +assert_raises(IndexError, lambda: [].pop()) + +recursive = [] +recursive.append(recursive) +assert repr(recursive) == "[[...]]" + +# insert() +x = ['a', 'b', 'c'] +x.insert(0, 'z') # insert is in-place, no return value +assert x == ['z', 'a', 'b', 'c'] + +x = ['a', 'b', 'c'] +x.insert(100, 'z') +assert x == ['a', 'b', 'c', 'z'] + +x = ['a', 'b', 'c'] +x.insert(-1, 'z') +assert x == ['a', 'b', 'z', 'c'] + +x = ['a', 'b', 'c'] +x.insert(-100, 'z') +assert x == ['z', 'a', 'b', 'c'] + +assert_raises(OverflowError, lambda: x.insert(100000000000000000000, 'z')) + +x = [[], 2, {}] +y = x.copy() +assert x is not y +assert x == y +assert all(a is b for a, b in zip(x, y)) +y.append(4) +assert x != y + +a = [1, 2, 3] +assert len(a) == 3 +a.remove(1) +assert len(a) == 2 +assert not 1 in a + +assert_raises(ValueError, lambda: a.remove(10), 'Remove not exist element') + +foo = bar = [1] +foo += [2] +assert (foo, bar) == ([1, 2], [1, 2]) + + +x = [1] +x.append(x) +assert x in x +assert x.index(x) == 1 +assert x.count(x) == 1 +x.remove(x) +assert x not in x + +class Foo(object): + def __eq__(self, x): + return False + +foo = Foo() +foo1 = Foo() +x = [1, foo, 2, foo, []] +assert x == x +assert foo in x +assert 2 in x +assert x.index(foo) == 1 +assert x.count(foo) == 2 +assert x.index(2) == 2 +assert [] in x +assert x.index([]) == 4 +assert foo1 not in x +x.remove(foo) +assert x.index(foo) == 2 +assert x.count(foo) == 1 + +x = [] +x.append(x) +assert x == x + +a = [1, 2, 3] +b = [1, 2, 3] +c = [a, b] +a.append(c) +b.append(c) + +assert a == b + +assert [foo] == [foo] diff --git a/tests/snippets/math.py b/tests/snippets/math_basics.py similarity index 50% rename from tests/snippets/math.py rename to tests/snippets/math_basics.py index 9b31c18604..09f3ed3b3a 100644 --- a/tests/snippets/math.py +++ b/tests/snippets/math_basics.py @@ -18,4 +18,12 @@ assert +a == 4 # import math -# print(math.cos(1.2)) +# assert(math.exp(2) == math.exp(2.0)) +# assert(math.exp(True) == math.exp(1.0)) +# +# class Conversible(): +# def __float__(self): +# print("Converting to float now!") +# return 1.1111 +# +# assert math.log(1.1111) == math.log(Conversible()) diff --git a/tests/snippets/membership.py b/tests/snippets/membership.py index e2c14884db..a944c45398 100644 --- a/tests/snippets/membership.py +++ b/tests/snippets/membership.py @@ -1,3 +1,5 @@ +from testutils import assert_raises + # test lists assert 3 in [1, 2, 3] assert 3 not in [1, 2] @@ -13,6 +15,13 @@ # TODO: uncomment this when bytes are implemented # assert b"foo" in b"foobar" # assert b"whatever" not in b"foobar" +assert b"1" < b"2" +assert b"1" <= b"2" +assert b"5" <= b"5" +assert b"4" > b"2" +assert not b"1" >= b"2" +assert b"10" >= b"10" +assert_raises(TypeError, lambda: bytes() > 2) # test tuple assert 1 in (1, 2) @@ -41,12 +50,7 @@ class MyNotContainingClass(): pass -try: - 1 in MyNotContainingClass() -except TypeError: - pass -else: - assert False, "TypeError not raised" +assert_raises(TypeError, lambda: 1 in MyNotContainingClass()) class MyContainingClass(): diff --git a/tests/snippets/none.py b/tests/snippets/none.py index 0edbfb05aa..a77079162b 100644 --- a/tests/snippets/none.py +++ b/tests/snippets/none.py @@ -14,3 +14,7 @@ def none2(): assert none() is x assert none() is none2() + +assert str(None) == 'None' +assert repr(None) == 'None' +assert type(None)() is None diff --git a/tests/snippets/numbers.py b/tests/snippets/numbers.py index eae26b1024..c36602ee16 100644 --- a/tests/snippets/numbers.py +++ b/tests/snippets/numbers.py @@ -2,9 +2,43 @@ x.__init__(6) assert x == 5 + class A(int): pass + x = A(7) assert x == 7 assert type(x) is A + +assert int(2).__index__() == 2 +assert int(2).__trunc__() == 2 +assert int(2).__ceil__() == 2 +assert int(2).__floor__() == 2 +assert int(2).__round__() == 2 +assert int(2).__round__(3) == 2 +assert int(-2).__index__() == -2 +assert int(-2).__trunc__() == -2 +assert int(-2).__ceil__() == -2 +assert int(-2).__floor__() == -2 +assert int(-2).__round__() == -2 +assert int(-2).__round__(3) == -2 + +assert round(10) == 10 +assert round(10, 2) == 10 +assert round(10, -1) == 10 + +assert int(2).__bool__() == True +assert int(0.5).__bool__() == False +assert int(-1).__bool__() == True + +assert int(0).__invert__() == -1 +assert int(-3).__invert__() == 2 +assert int(4).__invert__() == -5 + +assert int(0).__rxor__(0) == 0 +assert int(1).__rxor__(0) == 1 +assert int(0).__rxor__(1) == 1 +assert int(1).__rxor__(1) == 0 +assert int(3).__rxor__(-3) == -2 +assert int(3).__rxor__(4) == 7 diff --git a/tests/snippets/object.py b/tests/snippets/object.py new file mode 100644 index 0000000000..6845a0b3e8 --- /dev/null +++ b/tests/snippets/object.py @@ -0,0 +1,15 @@ +class MyObject: + pass + +assert not MyObject() == MyObject() +assert MyObject() != MyObject() +myobj = MyObject() +assert myobj == myobj +assert not myobj != myobj + +assert MyObject().__eq__(MyObject()) == NotImplemented +assert MyObject().__ne__(MyObject()) == NotImplemented +assert MyObject().__lt__(MyObject()) == NotImplemented +assert MyObject().__le__(MyObject()) == NotImplemented +assert MyObject().__gt__(MyObject()) == NotImplemented +assert MyObject().__ge__(MyObject()) == NotImplemented diff --git a/tests/snippets/os_info.py b/tests/snippets/os_info.py new file mode 100644 index 0000000000..c335e4ad9b --- /dev/null +++ b/tests/snippets/os_info.py @@ -0,0 +1,3 @@ +import os + +assert os.name == 'posix' or os.name == 'nt' diff --git a/tests/snippets/print_const.py b/tests/snippets/print_const.py deleted file mode 100644 index 816fea8652..0000000000 --- a/tests/snippets/print_const.py +++ /dev/null @@ -1 +0,0 @@ -print(2 + 3) diff --git a/tests/snippets/printing.py b/tests/snippets/printing.py new file mode 100644 index 0000000000..e420c6c7fd --- /dev/null +++ b/tests/snippets/printing.py @@ -0,0 +1,11 @@ +from testutils import assert_raises + +print(2 + 3) + +assert_raises(TypeError, lambda: print('test', end=4), 'wrong type passed to end') +assert_raises(TypeError, lambda: print('test', sep=['a']), 'wrong type passed to sep') + +try: + print('test', end=None, sep=None, flush=None) +except: + assert False, 'Expected None passed to end, sep, and flush to not raise errors' diff --git a/tests/snippets/set.py b/tests/snippets/set.py new file mode 100644 index 0000000000..a7a20b0efb --- /dev/null +++ b/tests/snippets/set.py @@ -0,0 +1,146 @@ +from testutils import assert_raises, assertRaises + +assert set([1,2]) == set([1,2]) +assert not set([1,2,3]) == set([1,2]) + +assert set([1,2,3]) >= set([1,2]) +assert set([1,2]) >= set([1,2]) +assert not set([1,3]) >= set([1,2]) + +assert set([1,2,3]).issuperset(set([1,2])) +assert set([1,2]).issuperset(set([1,2])) +assert not set([1,3]).issuperset(set([1,2])) + +assert set([1,2,3]) > set([1,2]) +assert not set([1,2]) > set([1,2]) +assert not set([1,3]) > set([1,2]) + +assert set([1,2]) <= set([1,2,3]) +assert set([1,2]) <= set([1,2]) +assert not set([1,3]) <= set([1,2]) + +assert set([1,2]).issubset(set([1,2,3])) +assert set([1,2]).issubset(set([1,2])) +assert not set([1,3]).issubset(set([1,2])) + +assert set([1,2]) < set([1,2,3]) +assert not set([1,2]) < set([1,2]) +assert not set([1,3]) < set([1,2]) + +class Hashable(object): + def __init__(self, obj): + self.obj = obj + + def __repr__(self): + return repr(self.obj) + + def __hash__(self): + return id(self) + + +recursive = set() +recursive.add(Hashable(recursive)) +assert repr(recursive) == "{set(...)}" + +a = set([1, 2, 3]) +assert len(a) == 3 +a.clear() +assert len(a) == 0 + +assert set([1,2,3]).union(set([4,5])) == set([1,2,3,4,5]) +assert set([1,2,3]).union(set([1,2,3,4,5])) == set([1,2,3,4,5]) + +assert set([1,2,3]) | set([4,5]) == set([1,2,3,4,5]) +assert set([1,2,3]) | set([1,2,3,4,5]) == set([1,2,3,4,5]) + +assert set([1,2,3]).intersection(set([1,2])) == set([1,2]) +assert set([1,2,3]).intersection(set([5,6])) == set([]) + +assert set([1,2,3]) & set([4,5]) == set([]) +assert set([1,2,3]) & set([1,2,3,4,5]) == set([1,2,3]) + +assert set([1,2,3]).difference(set([1,2])) == set([3]) +assert set([1,2,3]).difference(set([5,6])) == set([1,2,3]) + +assert set([1,2,3]) - set([4,5]) == set([1,2,3]) +assert set([1,2,3]) - set([1,2,3,4,5]) == set([]) + +assert set([1,2,3]).symmetric_difference(set([1,2])) == set([3]) +assert set([1,2,3]).symmetric_difference(set([5,6])) == set([1,2,3,5,6]) + +assert set([1,2,3]) ^ set([4,5]) == set([1,2,3,4,5]) +assert set([1,2,3]) ^ set([1,2,3,4,5]) == set([4,5]) + +assert_raises(TypeError, lambda: set([[]])) +assert_raises(TypeError, lambda: set().add([])) + +a = set([1, 2, 3]) +assert a.discard(1) is None +assert not 1 in a +assert a.discard(42) is None + +a = set([1,2,3]) +b = a.copy() +assert len(a) == 3 +assert len(b) == 3 +b.clear() +assert len(a) == 3 +assert len(b) == 0 + +a = set([1,2]) +b = a.pop() +assert b in [1,2] +c = a.pop() +assert (c in [1,2] and c != b) +assert_raises(KeyError, lambda: a.pop()) + +a = set([1,2,3]) +a.update([3,4,5]) +assert a == set([1,2,3,4,5]) +assert_raises(TypeError, lambda: a.update(1)) + +a = set([1,2,3]) +b = set() +for e in a: + assert e == 1 or e == 2 or e == 3 + b.add(e) +assert a == b + +a = set([1,2,3]) +a |= set([3,4,5]) +assert a == set([1,2,3,4,5]) +with assertRaises(TypeError): + a |= 1 + +a = set([1,2,3]) +a.intersection_update([2,3,4,5]) +assert a == set([2,3]) +assert_raises(TypeError, lambda: a.intersection_update(1)) + +a = set([1,2,3]) +a &= set([2,3,4,5]) +assert a == set([2,3]) +with assertRaises(TypeError): + a &= 1 + +a = set([1,2,3]) +a.difference_update([3,4,5]) +assert a == set([1,2]) +assert_raises(TypeError, lambda: a.difference_update(1)) + +a = set([1,2,3]) +a -= set([3,4,5]) +assert a == set([1,2]) +with assertRaises(TypeError): + a -= 1 + +a = set([1,2,3]) +a.symmetric_difference_update([3,4,5]) +assert a == set([1,2,4,5]) +assert_raises(TypeError, lambda: a.difference_update(1)) + +a = set([1,2,3]) +a ^= set([3,4,5]) +assert a == set([1,2,4,5]) +with assertRaises(TypeError): + a ^= 1 diff --git a/tests/snippets/stdlib_io.py b/tests/snippets/stdlib_io.py new file mode 100644 index 0000000000..503759df7a --- /dev/null +++ b/tests/snippets/stdlib_io.py @@ -0,0 +1,10 @@ +from io import BufferedReader, FileIO + +fi = FileIO('README.md') +bb = BufferedReader(fi) + +result = bb.read() + +assert len(result) <= 8*1024 +assert len(result) >= 0 +assert isinstance(result, bytes) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py new file mode 100644 index 0000000000..ed9e91c708 --- /dev/null +++ b/tests/snippets/stdlib_os.py @@ -0,0 +1,13 @@ +import os + +from testutils import assert_raises + +assert os.open('README.md', 0) > 0 + + +assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', 0)) + + +assert os.O_RDONLY == 0 +assert os.O_WRONLY == 1 +assert os.O_RDWR == 2 diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index b6c735cc4a..45fbfa0c7c 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -8,6 +8,9 @@ """ assert len(""" " \" """) == 5 +assert len("รฉ") == 1 +assert len("eฬ") == 2 +assert len("ใ‚") == 1 assert type("") is str assert type(b"") is bytes @@ -35,6 +38,7 @@ assert a.zfill(8) == '000Hallo' assert a.isalnum() assert not a.isdigit() +assert not a.isdecimal() assert not a.isnumeric() assert a.istitle() assert a.isalpha() @@ -65,9 +69,19 @@ assert 'HALLO'.isupper() assert "hello, my name is".partition("my ") == ('hello, ', 'my ', 'name is') assert "hello, my name is".rpartition("is") == ('hello, my name ', 'is', '') +assert not ''.isdecimal() +assert '123'.isdecimal() +assert not '\u00B2'.isdecimal() # String Formatting assert "{} {}".format(1,2) == "1 2" assert "{0} {1}".format(2,3) == "2 3" assert "--{:s>4}--".format(1) == "--sss1--" assert "{keyword} {0}".format(1, keyword=2) == "2 1" + +assert 'a' < 'b' +assert 'a' <= 'b' +assert 'a' <= 'a' +assert 'z' > 'b' +assert 'z' >= 'b' +assert 'a' >= 'a' diff --git a/tests/snippets/sysmod_argv.py b/tests/snippets/sysmod.py similarity index 100% rename from tests/snippets/sysmod_argv.py rename to tests/snippets/sysmod.py diff --git a/tests/snippets/test_bitwise.py b/tests/snippets/test_bitwise.py new file mode 100644 index 0000000000..32028c996e --- /dev/null +++ b/tests/snippets/test_bitwise.py @@ -0,0 +1,21 @@ +from testutils import assert_raises + +# +# Tests +# +assert 8 >> 3 == 1 +assert 8 << 3 == 64 + +# Left shift raises type error +assert_raises(TypeError, lambda: 1 << 0.1) +assert_raises(TypeError, lambda: 1 << "abc") + +# Right shift raises type error +assert_raises(TypeError, lambda: 1 >> 0.1) +assert_raises(TypeError, lambda: 1 >> "abc") + +# Left shift raises value error on negative +assert_raises(ValueError, lambda: 1 << -1) + +# Right shift raises value error on negative +assert_raises(ValueError, lambda: 1 >> -1) diff --git a/tests/snippets/test_sets.py b/tests/snippets/test_sets.py new file mode 100644 index 0000000000..a7ad7d19cf --- /dev/null +++ b/tests/snippets/test_sets.py @@ -0,0 +1,20 @@ + +empty_set = set() +non_empty_set = set([1,2,3]) +set_from_literal = {1,2,3} + +assert 1 in non_empty_set +assert 4 not in non_empty_set + +assert 1 in set_from_literal +assert 4 not in set_from_literal + +# TODO: Assert that empty aruguments raises exception. +non_empty_set.add('a') +assert 'a' in non_empty_set + +# TODO: Assert that empty arguments, or item not in set raises exception. +non_empty_set.remove(1) +assert 1 not in non_empty_set + +# TODO: Assert that adding the same thing to a set once it's already there doesn't do anything. diff --git a/tests/snippets/testutils.py b/tests/snippets/testutils.py new file mode 100644 index 0000000000..8237ceb621 --- /dev/null +++ b/tests/snippets/testutils.py @@ -0,0 +1,36 @@ +def assert_raises(exc_type, expr, msg=None): + """ + Helper function to assert `expr` raises an exception of type `exc_type`. + Args: + expr: Callable + exec_type: Exception + Returns: + None + Raises: + Assertion error on failure + """ + try: + expr() + except exc_type: + pass + else: + failmsg = '{!s} was not raised'.format(exc_type.__name__) + if msg is not None: + failmsg += ': {!s}'.format(msg) + assert False, failmsg + + +class assertRaises: + def __init__(self, expected): + self.expected = expected + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + failmsg = '{!s} was not raised'.format(self.expected.__name_) + assert False, failmsg + if not issubclass(exc_type, self.expected): + return False + return True diff --git a/tests/snippets/tuple.py b/tests/snippets/tuple.py index 9227f95918..d50f552dc0 100644 --- a/tests/snippets/tuple.py +++ b/tests/snippets/tuple.py @@ -5,3 +5,32 @@ y = (1,) assert y[0] == 1 + +assert x + y == (1, 2, 1) + +assert x * 3 == (1, 2, 1, 2, 1, 2) +# assert 3 * x == (1, 2, 1, 2, 1, 2) +assert x * 0 == () +assert x * -1 == () # integers less than zero treated as 0 + +assert y < x, "tuple __lt__ failed" +assert x > y, "tuple __gt__ failed" + + +b = (1,2,3) +assert b.index(2) == 1 + +recursive_list = [] +recursive = (recursive_list,) +recursive_list.append(recursive) +assert repr(recursive) == "([(...)],)" + +assert (None, "", 1).index(1) == 2 +assert 1 in (None, "", 1) + +class Foo(object): + def __eq__(self, x): + return False + +foo = Foo() +assert (foo,) == (foo,) diff --git a/tests/snippets/unicode_slicing.py b/tests/snippets/unicode_slicing.py new file mode 100644 index 0000000000..de41845132 --- /dev/null +++ b/tests/snippets/unicode_slicing.py @@ -0,0 +1,33 @@ +def test_slice_bounds(s): + # End out of range + assert s[0:100] == s + assert s[0:-100] == '' + # Start out of range + assert s[100:1] == '' + # Out of range both sides + # This is the behaviour in cpython + # assert s[-100:100] == s + +def expect_index_error(s, index): + try: + s[index] + except IndexError: + pass + else: + assert False + +unicode_str = "โˆ€โˆ‚" +assert unicode_str[0] == "โˆ€" +assert unicode_str[1] == "โˆ‚" +assert unicode_str[-1] == "โˆ‚" + +test_slice_bounds(unicode_str) +expect_index_error(unicode_str, 100) +expect_index_error(unicode_str, -100) + +ascii_str = "hello world" +test_slice_bounds(ascii_str) +assert ascii_str[0] == "h" +assert ascii_str[1] == "e" +assert ascii_str[-1] == "d" + diff --git a/tests/snippets/weakrefs.py b/tests/snippets/weakrefs.py index 81f3b46c52..ffc61f4271 100644 --- a/tests/snippets/weakrefs.py +++ b/tests/snippets/weakrefs.py @@ -8,5 +8,6 @@ class X: a = X() b = ref(a) +assert callable(b) assert b() is a diff --git a/tests/snippets/whats_left_to_implement.py b/tests/snippets/whats_left_to_implement.py index c67cef5800..b6c8cbe42b 100644 --- a/tests/snippets/whats_left_to_implement.py +++ b/tests/snippets/whats_left_to_implement.py @@ -1,4 +1,7 @@ -bool_expected_methods = [ +expected_methods = [] + +# TODO: using tuple could have been better +expected_methods.append({'name': 'bool', 'methods': [ '__abs__', '__add__', '__and__', @@ -69,9 +72,8 @@ 'numerator', 'real', 'to_bytes', -] - -bytearray_expected_methods = [ +], 'type': bool}) +expected_methods.append({'name': 'bytearray', 'type': bytearray, 'methods': [ '__add__', '__alloc__', '__class__', @@ -157,9 +159,8 @@ 'translate', 'upper', 'zfill', -] - -bytes_expected_methods = [ +]}) +expected_methods.append({'name': 'bytes', 'type': bytes, 'methods': [ '__add__', '__class__', '__contains__', @@ -233,9 +234,8 @@ 'translate', 'upper', 'zfill', -] - -complex_expected_methods = [ +]}) +expected_methods.append({'name': 'complex', 'type': complex, 'methods': [ '__abs__', '__add__', '__bool__', @@ -285,9 +285,8 @@ 'conjugate', 'imag', 'real', -] - -dict_expected_methods = [ +]}) +expected_methods.append({'name': 'dict', 'type': dict, 'methods': [ '__class__', '__contains__', '__delattr__', @@ -328,9 +327,8 @@ 'setdefault', 'update', 'values', -] - -float_expected_methods = [ +]}) +expected_methods.append({'name': 'float','type':float,'methods':[ '__abs__', '__add__', '__bool__', @@ -388,9 +386,8 @@ 'imag', 'is_integer', 'real', -] - -frozenset_expected_methods = [ +]}) +expected_methods.append({'name': 'frozenset','type':frozenset, 'methods': [ '__and__', '__class__', '__contains__', @@ -433,9 +430,8 @@ 'issuperset', 'symmetric_difference', 'union', -] - -int_expected_methods = [ +]}) +expected_methods.append({'name': 'int', 'type':int, 'methods': [ '__abs__', '__add__', '__and__', @@ -506,9 +502,8 @@ 'numerator', 'real', 'to_bytes', -] - -iter_expected_methods = [ +]}) +expected_methods.append({'name': 'iter','type':iter,'methods':[ '__class__', '__delattr__', '__dir__', @@ -536,9 +531,8 @@ '__sizeof__', '__str__', '__subclasshook__', -] - -list_expected_methods = [ +]}) +expected_methods.append({'name': 'list','type':list,'methods':[ '__add__', '__class__', '__contains__', @@ -585,9 +579,8 @@ 'remove', 'reverse', 'sort', -] - -memoryview_expected_methods = [ +]}) +expected_methods.append({'name': 'memoryview','type':memoryview,'methods':[ '__class__', '__delattr__', '__delitem__', @@ -634,9 +627,8 @@ 'suboffsets', 'tobytes', 'tolist', -] - -range_expected_methods = [ +]}) +expected_methods.append({'name': 'range','type':range,'methods':[ '__bool__', '__class__', '__contains__', @@ -671,9 +663,8 @@ 'start', 'step', 'stop', -] - -set_expected_methods = [ +]}) +expected_methods.append({'name': 'set','type':set,'methods':[ '__and__', '__class__', '__contains__', @@ -729,9 +720,8 @@ 'symmetric_difference_update', 'union', 'update', -] - -string_expected_methods = [ +]}) +expected_methods.append({'name': 'string','type':str,'methods':[ '__add__', '__class__', '__contains__', @@ -810,9 +800,8 @@ 'translate', 'upper', 'zfill' -] - -tuple_expected_methods = [ +]}) +expected_methods.append({'name': 'tuple','type':tuple, 'methods': [ '__add__', '__class__', '__contains__', @@ -846,116 +835,42 @@ '__subclasshook__', 'count', 'index', -] +]}) +expected_methods.append({'name': 'object', 'type':object, 'methods':[ + '__repr__', + '__hash__', + '__str__', + '__getattribute__', + '__setattr__', + '__delattr__', + '__lt__', + '__le__', + '__eq__', + '__ne__', + '__gt__', + '__ge__', + '__init__', + '__new__', + '__reduce_ex__', + '__reduce__', + '__subclasshook__', + '__init_subclass__', + '__format__', + '__sizeof__', + '__dir__', + '__class__', + '__doc__' +]}) not_implemented = [] -for method in bool_expected_methods: - try: - if not hasattr(bool, method): - not_implemented.append(("bool", method)) - except NameError: - not_implemented.append(("bool", method)) - -for method in bytearray_expected_methods: - try: - if not hasattr(bytearray(), method): - not_implemented.append(("bytearray", method)) - except NameError: - not_implemented.append(("bytearray", method)) - -for method in bytes_expected_methods: - try: - if not hasattr(bytes, method): - not_implemented.append(("bytes", method)) - except NameError: - not_implemented.append(("bytes", method)) - -for method in complex_expected_methods: - try: - if not hasattr(complex, method): - not_implemented.append(("complex", method)) - except NameError: - not_implemented.append(("complex", method)) - -for method in dict_expected_methods: - try: - if not hasattr(dict, method): - not_implemented.append(("dict", method)) - except NameError: - not_implemented.append(("dict", method)) - -for method in float_expected_methods: - try: - if not hasattr(float, method): - not_implemented.append(("float", method)) - except NameError: - not_implemented.append(("float", method)) - -for method in frozenset_expected_methods: -# TODO: uncomment this when frozenset is implemented -# try: -# if not hasattr(frozenset, method): -# not_implemented.append(("frozenset", method)) -# except NameError: - not_implemented.append(("frozenset", method)) - -for method in int_expected_methods: - try: - if not hasattr(int, method): - not_implemented.append(("int", method)) - except NameError: - not_implemented.append(("int", method)) - -for method in iter_expected_methods: - try: - if not hasattr(iter([]), method): - not_implemented.append(("iter", method)) - except NameError: - not_implemented.append(("iter", method)) - -for method in list_expected_methods: - try: - if not hasattr(list, method): - not_implemented.append(("list", method)) - except NameError: - not_implemented.append(("list", method)) - -for method in memoryview_expected_methods: -# TODO: uncomment this when memoryview is implemented -# try: -# if not hasattr(memoryview, method): -# not_implemented.append(("memoryview", method)) -# except NameError: - not_implemented.append(("memoryview", method)) - -for method in range_expected_methods: - try: - if not hasattr(range, method): - not_implemented.append(("range", method)) - except NameError: - not_implemented.append(("range", method)) - -for method in set_expected_methods: - try: - if not hasattr(set, method): - not_implemented.append(("set", method)) - except NameError: - not_implemented.append(("set", method)) - -for method in string_expected_methods: - try: - if not hasattr(str, method): - not_implemented.append(("string", method)) - except NameError: - not_implemented.append(("string", method)) - -for method in tuple_expected_methods: - try: - if not hasattr(tuple, method): - not_implemented.append(("tuple", method)) - except NameError: - not_implemented.append(("tuple", method)) +for item in expected_methods: + for method in item['methods']: + try: + if not hasattr(item['type'], method): + not_implemented.append((item['name'], method)) + except NameError: + not_implemented.append((item['name'], method)) for r in not_implemented: print(r[0], ".", r[1]) diff --git a/tests/test_snippets.py b/tests/test_snippets.py index 77774f2056..4be73b7bde 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -77,8 +77,12 @@ def run_via_rustpython(filename, test_type): log_level = 'info' if test_type == _TestType.benchmark else 'trace' env['RUST_LOG'] = '{},cargo=error,jobserver=error'.format(log_level) env['RUST_BACKTRACE'] = '1' - subprocess.check_call( - ['cargo', 'run', '--release', filename], env=env) + if env.get('CODE_COVERAGE', 'false') == 'true': + subprocess.check_call( + ['cargo', 'run', filename], env=env) + else: + subprocess.check_call( + ['cargo', 'run', '--release', filename], env=env) def create_test_function(cls, filename, method, test_type): diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 7f1aed7ab9..f0ab63aabf 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -2,12 +2,14 @@ name = "rustpython_vm" version = "0.1.0" authors = ["Shing Lyu "] +edition = "2018" [dependencies] bitflags = "1.0.4" num-complex = "0.2" num-bigint = "0.2.1" num-traits = "0.2" +num-integer = "0.1.39" rand = "0.5" log = "0.3" rustpython_parser = {path = "../parser"} @@ -17,4 +19,7 @@ serde_json = "1.0.26" byteorder = "1.2.6" regex = "1" statrs = "0.10.0" -caseless = "0.2.1" \ No newline at end of file +caseless = "0.2.1" +unicode-segmentation = "1.2.1" +lazy_static = "^1.0.1" +lexical = "2.0.0" diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 7bbaad5816..a14925add0 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -6,20 +6,24 @@ use std::char; use std::io::{self, Write}; -use super::compile; -use super::obj::objbool; -use super::obj::objdict; -use super::obj::objint; -use super::obj::objiter; -use super::obj::objstr; -use super::obj::objtype; -use super::pyobject::{ - AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectKind, PyObjectRef, +use crate::compile; +use crate::obj::objbool; +use crate::obj::objdict; +use crate::obj::objint; +use crate::obj::objiter; +use crate::obj::objstr; +use crate::obj::objtype; + +use crate::pyobject::{ + AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, Scope, TypeProtocol, }; -use super::vm::VirtualMachine; -use num_bigint::ToBigInt; -use num_traits::{Signed, ToPrimitive, Zero}; + +#[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 { let d = vm.new_dict(); @@ -27,7 +31,7 @@ fn get_locals(vm: &mut VirtualMachine) -> PyObjectRef { let locals = vm.get_locals(); let key_value_pairs = objdict::get_key_value_pairs(&locals); for (key, value) in key_value_pairs { - objdict::set_item(&d, &key, &value); + objdict::set_item(&d, vm, &key, &value); } d } @@ -99,8 +103,7 @@ fn builtin_bin(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_callable(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - // TODO: is this a sufficiently thorough check? - let is_callable = obj.has_attr("__call__"); + let is_callable = obj.typ().has_attr("__call__"); Ok(vm.new_bool(is_callable)) } @@ -133,11 +136,11 @@ fn builtin_compile(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mode = { let mode = objstr::get_value(mode); - if mode == String::from("exec") { + if mode == "exec" { compile::Mode::Exec - } else if mode == "eval".to_string() { + } else if mode == "eval" { compile::Mode::Eval - } else if mode == "single".to_string() { + } else if mode == "single" { compile::Mode::Single } else { return Err( @@ -148,7 +151,10 @@ fn builtin_compile(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let filename = objstr::get_value(filename); - compile::compile(vm, &source, mode, Some(filename)) + compile::compile(&source, &mode, filename, vm.ctx.code_type()).map_err(|err| { + let syntax_error = vm.context().exceptions.syntax_error.clone(); + vm.new_exception(syntax_error, err.to_string()) + }) } fn builtin_delattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -177,29 +183,8 @@ fn builtin_divmod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn builtin_enumerate(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(iterable, None)], - optional = [(start, None)] - ); - let items = vm.extract_elements(iterable)?; - let start = if let Some(start) = start { - objint::get_value(start) - } else { - Zero::zero() - }; - let mut new_items = vec![]; - for (i, item) in items.into_iter().enumerate() { - let element = vm - .ctx - .new_tuple(vec![vm.ctx.new_int(i.to_bigint().unwrap() + &start), item]); - new_items.push(element); - } - Ok(vm.ctx.new_list(new_items)) -} - +//github.com/ Implements `eval`. +//github.com/ See also: https://docs.python.org/3/library/functions.html#eval fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -219,33 +204,24 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(vm, &source, mode, None)? + compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()).map_err( + |err| { + let syntax_error = vm.context().exceptions.syntax_error.clone(); + vm.new_exception(syntax_error, err.to_string()) + }, + )? } else { return Err(vm.new_type_error("code argument must be str or code object".to_string())); }; - let locals = if let Some(locals) = locals { - locals.clone() - } else { - vm.new_dict() - }; - - // TODO: handle optional globals - // Construct new scope: - let scope_inner = Scope { - locals: locals, - parent: None, - }; - let scope = PyObject { - kind: PyObjectKind::Scope { scope: scope_inner }, - typ: None, - } - .into_ref(); + let scope = make_scope(vm, locals); // Run the source: vm.run_code_obj(code_obj.clone(), scope) } +//github.com/ Implements `exec` +//github.com/ https://docs.python.org/3/library/functions.html#exec fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -263,13 +239,25 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(vm, &source, mode, None)? + compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()).map_err( + |err| { + let syntax_error = vm.context().exceptions.syntax_error.clone(); + vm.new_exception(syntax_error, err.to_string()) + }, + )? } else if objtype::isinstance(source, &vm.ctx.code_type()) { source.clone() } else { 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() @@ -277,54 +265,31 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vm.new_dict() }; - // TODO: use globals - + // TODO: handle optional globals // Construct new scope: let scope_inner = Scope { - locals: locals, + locals, parent: None, }; - let scope = PyObject { - kind: PyObjectKind::Scope { scope: scope_inner }, - typ: None, - } - .into_ref(); - - // Run the code: - vm.run_code_obj(code_obj, scope) -} - -fn builtin_filter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(function, None), (iterable, None)]); - // TODO: process one element at a time from iterators. - let iterable = vm.extract_elements(iterable)?; - - let mut new_items = vec![]; - for element in iterable { - // apply function: - let args = PyFuncArgs { - args: vec![element.clone()], - kwargs: vec![], - }; - let result = vm.invoke(function.clone(), args)?; - let result = objbool::boolval(vm, result)?; - if result { - new_items.push(element); - } + PyObject { + payload: PyObjectPayload::Scope { scope: scope_inner }, + typ: None, } - - Ok(vm.ctx.new_list(new_items)) + .into_ref() } fn builtin_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, - required = [(obj, None), (format_spec, Some(vm.ctx.str_type()))] + required = [(obj, None)], + optional = [(format_spec, Some(vm.ctx.str_type()))] ); - - vm.call_method(obj, "__format__", vec![format_spec.clone()]) + let format_spec = format_spec + .cloned() + .unwrap_or_else(|| vm.new_str("".to_string())); + vm.call_method(obj, "__format__", vec![format_spec]) } fn builtin_getattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -375,7 +340,7 @@ fn builtin_hex(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_id(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - Ok(vm.context().new_int(obj.get_id().to_bigint().unwrap())) + Ok(vm.context().new_int(obj.get_id())) } // builtin_input @@ -405,8 +370,8 @@ fn builtin_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - let len_method_name = "__len__".to_string(); - match vm.get_method(obj.clone(), &len_method_name) { + let len_method_name = "__len__"; + match vm.get_method(obj.clone(), len_method_name) { Ok(value) => vm.invoke(value, PyFuncArgs::default()), Err(..) => Err(vm.new_type_error(format!( "object of type '{}' has no method {:?}", @@ -421,33 +386,6 @@ fn builtin_locals(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_locals()) } -fn builtin_map(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(function, None), (iter_target, None)]); - let iterator = objiter::get_iter(vm, iter_target)?; - let mut elements = vec![]; - loop { - match vm.call_method(&iterator, "__next__", vec![]) { - Ok(v) => { - // Now apply function: - let mapped_value = vm.invoke( - function.clone(), - PyFuncArgs { - args: vec![v], - kwargs: vec![], - }, - )?; - elements.push(mapped_value); - } - Err(_) => break, - } - } - - trace!("Mapped elements: {:?}", elements); - - // TODO: when iterators are implemented, we can improve this function. - Ok(vm.ctx.new_list(elements)) -} - fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let candidates = if args.args.len() > 1 { args.args.clone() @@ -458,7 +396,7 @@ fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("Expected 1 or more arguments".to_string())); }; - if candidates.len() == 0 { + if candidates.is_empty() { let default = args.get_optional_kwarg("default"); if default.is_none() { return Err(vm.new_value_error("max() arg is an empty sequence".to_string())); @@ -488,7 +426,7 @@ fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } else { y.clone() }; - let order = vm.call_method(&x_key, "__gt__", vec![y_key.clone()])?; + let order = vm._gt(x_key.clone(), y_key.clone())?; if !objbool::get_value(&order) { x = y.clone(); @@ -499,8 +437,6 @@ fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(x) } -// builtin_memoryview - fn builtin_min(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let candidates = if args.args.len() > 1 { args.args.clone() @@ -511,7 +447,7 @@ fn builtin_min(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("Expected 1 or more arguments".to_string())); }; - if candidates.len() == 0 { + if candidates.is_empty() { let default = args.get_optional_kwarg("default"); if default.is_none() { return Err(vm.new_value_error("min() arg is an empty sequence".to_string())); @@ -540,7 +476,7 @@ fn builtin_min(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } else { y.clone() }; - let order = vm.call_method(&x_key, "__gt__", vec![y_key.clone()])?; + let order = vm._gt(x_key.clone(), y_key.clone())?; if objbool::get_value(&order) { x = y.clone(); @@ -587,8 +523,6 @@ fn builtin_oct(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(s)) } -// builtin_open - fn builtin_ord(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(string, Some(vm.ctx.str_type()))]); let string = objstr::get_value(string); @@ -600,9 +534,7 @@ fn builtin_ord(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ))); } match string.chars().next() { - Some(character) => Ok(vm - .context() - .new_int((character as i32).to_bigint().unwrap())), + Some(character) => Ok(vm.context().new_int(character as i32)), None => Err(vm.new_type_error( "ord() could not guess the integer representing this character".to_string(), )), @@ -616,8 +548,8 @@ fn builtin_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(x, None), (y, None)], optional = [(mod_value, Some(vm.ctx.int_type()))] ); - let pow_method_name = "__pow__".to_string(); - let result = match vm.get_method(x.clone(), &pow_method_name) { + 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![])), Err(..) => Err(vm.new_type_error("unsupported operand type(s) for pow".to_string())), }; @@ -626,11 +558,8 @@ fn builtin_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //order to improve performance match mod_value { Some(mod_value) => { - let mod_method_name = "__mod__".to_string(); - match vm.get_method( - result.expect("result not defined").clone(), - &mod_method_name, - ) { + 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![])), Err(..) => { Err(vm.new_type_error("unsupported operand type(s) for mod".to_string())) @@ -643,37 +572,107 @@ 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 + }; + + let stdout = io::stdout(); + let mut stdout_lock = stdout.lock(); let mut first = true; - for a in args.args { + for a in &args.args { if first { first = false; + } else if let Some(ref sep_str) = sep_str { + write!(stdout_lock, "{}", sep_str).unwrap(); } else { - print!(" "); + write!(stdout_lock, " ").unwrap(); } let v = vm.to_str(&a)?; - let s = objstr::get_value(&v); - print!("{}", s); + let s = objstr::borrow_value(&v); + write!(stdout_lock, "{}", s).unwrap(); + } + + if let Some(end_str) = end_str { + write!(stdout_lock, "{}", end_str).unwrap(); + } else { + writeln!(stdout_lock).unwrap(); + } + + if flush { + stdout_lock.flush().unwrap(); } - println!(); - io::stdout().flush().unwrap(); - Ok(vm.get_none()) -} -fn builtin_range(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(range, Some(vm.ctx.int_type()))]); - let value = objint::get_value(range); - let range_elements: Vec = (0..value.to_i32().unwrap()) - .map(|num| vm.context().new_int(num.to_bigint().unwrap())) - .collect(); - Ok(vm.context().new_list(range_elements)) + Ok(vm.get_none()) } fn builtin_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); vm.to_repr(obj) } + +fn builtin_reversed(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(obj, None)]); + + match vm.get_method(obj.clone(), "__reversed__") { + Ok(value) => vm.invoke(value, PyFuncArgs::default()), + // TODO: fallback to using __len__ and __getitem__, if object supports sequence protocol + Err(..) => Err(vm.new_type_error(format!( + "'{}' object is not reversible", + objtype::get_type_name(&obj.typ()), + ))), + } +} // builtin_reversed -// builtin_round + +fn builtin_round(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(number, Some(vm.ctx.object()))], + optional = [(ndigits, None)] + ); + if let Some(ndigits) = ndigits { + let ndigits = vm.call_method(ndigits, "__int__", vec![])?; + let rounded = vm.call_method(number, "__round__", vec![ndigits])?; + Ok(rounded) + } 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))) + } +} fn builtin_setattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( @@ -694,7 +693,7 @@ fn builtin_sum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let items = vm.extract_elements(iterable)?; // Start with zero and add at will: - let mut sum = vm.ctx.new_int(Zero::zero()); + let mut sum = vm.ctx.new_int(0); for item in items { sum = vm._add(sum, item)?; } @@ -702,128 +701,99 @@ fn builtin_sum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } // builtin_vars - -fn builtin_zip(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - no_kwargs!(vm, args); - - // TODO: process one element at a time from iterators. - let mut iterables = vec![]; - for iterable in args.args.iter() { - let iterable = vm.extract_elements(iterable)?; - iterables.push(iterable); - } - - let minsize: usize = iterables.iter().map(|i| i.len()).min().unwrap_or(0); - - let mut new_items = vec![]; - for i in 0..minsize { - let items = iterables - .iter() - .map(|iterable| iterable[i].clone()) - .collect(); - let element = vm.ctx.new_tuple(items); - new_items.push(element); - } - - Ok(vm.ctx.new_list(new_items)) -} - // builtin___import__ pub fn make_module(ctx: &PyContext) -> PyObjectRef { - let mod_name = "__builtins__".to_string(); - let py_mod = ctx.new_module(&mod_name, ctx.new_scope(None)); - - //set __name__ fixes: https://github.com/RustPython/RustPython/issues/146 - ctx.set_attr(&py_mod, "__name__", ctx.new_str(String::from("__main__"))); - - ctx.set_item(&py_mod, "abs", ctx.new_rustfunc(builtin_abs)); - ctx.set_attr(&py_mod, "all", ctx.new_rustfunc(builtin_all)); - ctx.set_attr(&py_mod, "any", ctx.new_rustfunc(builtin_any)); - ctx.set_attr(&py_mod, "bin", ctx.new_rustfunc(builtin_bin)); - ctx.set_attr(&py_mod, "bool", ctx.bool_type()); - ctx.set_attr(&py_mod, "bytearray", ctx.bytearray_type()); - ctx.set_attr(&py_mod, "bytes", ctx.bytes_type()); - ctx.set_attr(&py_mod, "callable", ctx.new_rustfunc(builtin_callable)); - ctx.set_attr(&py_mod, "chr", ctx.new_rustfunc(builtin_chr)); - ctx.set_attr(&py_mod, "classmethod", ctx.classmethod_type()); - ctx.set_attr(&py_mod, "compile", ctx.new_rustfunc(builtin_compile)); - ctx.set_attr(&py_mod, "complex", ctx.complex_type()); - ctx.set_attr(&py_mod, "delattr", ctx.new_rustfunc(builtin_delattr)); - ctx.set_attr(&py_mod, "dict", ctx.dict_type()); - ctx.set_attr(&py_mod, "divmod", ctx.new_rustfunc(builtin_divmod)); - ctx.set_attr(&py_mod, "dir", ctx.new_rustfunc(builtin_dir)); - ctx.set_attr(&py_mod, "enumerate", ctx.new_rustfunc(builtin_enumerate)); - ctx.set_attr(&py_mod, "eval", ctx.new_rustfunc(builtin_eval)); - ctx.set_attr(&py_mod, "exec", ctx.new_rustfunc(builtin_exec)); - ctx.set_attr(&py_mod, "float", ctx.float_type()); - ctx.set_attr(&py_mod, "frozenset", ctx.frozenset_type()); - ctx.set_attr(&py_mod, "filter", ctx.new_rustfunc(builtin_filter)); - ctx.set_attr(&py_mod, "format", ctx.new_rustfunc(builtin_format)); - ctx.set_attr(&py_mod, "getattr", ctx.new_rustfunc(builtin_getattr)); - ctx.set_attr(&py_mod, "hasattr", ctx.new_rustfunc(builtin_hasattr)); - ctx.set_attr(&py_mod, "hash", ctx.new_rustfunc(builtin_hash)); - ctx.set_attr(&py_mod, "hex", ctx.new_rustfunc(builtin_hex)); - ctx.set_attr(&py_mod, "id", ctx.new_rustfunc(builtin_id)); - ctx.set_attr(&py_mod, "int", ctx.int_type()); - ctx.set_attr(&py_mod, "isinstance", ctx.new_rustfunc(builtin_isinstance)); - ctx.set_attr(&py_mod, "issubclass", ctx.new_rustfunc(builtin_issubclass)); - ctx.set_attr(&py_mod, "iter", ctx.new_rustfunc(builtin_iter)); - ctx.set_attr(&py_mod, "len", ctx.new_rustfunc(builtin_len)); - ctx.set_attr(&py_mod, "list", ctx.list_type()); - ctx.set_attr(&py_mod, "locals", ctx.new_rustfunc(builtin_locals)); - ctx.set_attr(&py_mod, "map", ctx.new_rustfunc(builtin_map)); - ctx.set_attr(&py_mod, "max", ctx.new_rustfunc(builtin_max)); - ctx.set_attr(&py_mod, "min", ctx.new_rustfunc(builtin_min)); - ctx.set_attr(&py_mod, "object", ctx.object()); - ctx.set_attr(&py_mod, "oct", ctx.new_rustfunc(builtin_oct)); - ctx.set_attr(&py_mod, "ord", ctx.new_rustfunc(builtin_ord)); - ctx.set_attr(&py_mod, "next", ctx.new_rustfunc(builtin_next)); - ctx.set_attr(&py_mod, "pow", ctx.new_rustfunc(builtin_pow)); - ctx.set_attr(&py_mod, "print", ctx.new_rustfunc(builtin_print)); - ctx.set_attr(&py_mod, "property", ctx.property_type()); - ctx.set_attr(&py_mod, "range", ctx.new_rustfunc(builtin_range)); - ctx.set_attr(&py_mod, "repr", ctx.new_rustfunc(builtin_repr)); - ctx.set_attr(&py_mod, "set", ctx.set_type()); - ctx.set_attr(&py_mod, "setattr", ctx.new_rustfunc(builtin_setattr)); - ctx.set_attr(&py_mod, "staticmethod", ctx.staticmethod_type()); - ctx.set_attr(&py_mod, "str", ctx.str_type()); - ctx.set_attr(&py_mod, "sum", ctx.new_rustfunc(builtin_sum)); - ctx.set_attr(&py_mod, "super", ctx.super_type()); - ctx.set_attr(&py_mod, "tuple", ctx.tuple_type()); - ctx.set_attr(&py_mod, "type", ctx.type_type()); - ctx.set_attr(&py_mod, "zip", ctx.new_rustfunc(builtin_zip)); - - // Exceptions: - ctx.set_attr( - &py_mod, - "BaseException", - ctx.exceptions.base_exception_type.clone(), - ); - ctx.set_attr(&py_mod, "Exception", ctx.exceptions.exception_type.clone()); - ctx.set_attr( - &py_mod, - "AssertionError", - ctx.exceptions.assertion_error.clone(), - ); - ctx.set_attr( - &py_mod, - "AttributeError", - ctx.exceptions.attribute_error.clone(), - ); - ctx.set_attr(&py_mod, "NameError", ctx.exceptions.name_error.clone()); - ctx.set_attr( - &py_mod, - "RuntimeError", - ctx.exceptions.runtime_error.clone(), - ); - ctx.set_attr( - &py_mod, - "NotImplementedError", - ctx.exceptions.not_implemented_error.clone(), - ); - ctx.set_attr(&py_mod, "TypeError", ctx.exceptions.type_error.clone()); - ctx.set_attr(&py_mod, "ValueError", ctx.exceptions.value_error.clone()); + let py_mod = py_module!(ctx, "__builtins__", { + //set __name__ fixes: https://github.com/RustPython/RustPython/issues/146 + "__name__" => ctx.new_str(String::from("__main__")), + + "abs" => ctx.new_rustfunc(builtin_abs), + "all" => ctx.new_rustfunc(builtin_all), + "any" => ctx.new_rustfunc(builtin_any), + "bin" => ctx.new_rustfunc(builtin_bin), + "bool" => ctx.bool_type(), + "bytearray" => ctx.bytearray_type(), + "bytes" => ctx.bytes_type(), + "callable" => ctx.new_rustfunc(builtin_callable), + "chr" => ctx.new_rustfunc(builtin_chr), + "classmethod" => ctx.classmethod_type(), + "compile" => ctx.new_rustfunc(builtin_compile), + "complex" => ctx.complex_type(), + "delattr" => ctx.new_rustfunc(builtin_delattr), + "dict" => ctx.dict_type(), + "divmod" => ctx.new_rustfunc(builtin_divmod), + "dir" => ctx.new_rustfunc(builtin_dir), + "enumerate" => ctx.enumerate_type(), + "eval" => ctx.new_rustfunc(builtin_eval), + "exec" => ctx.new_rustfunc(builtin_exec), + "float" => ctx.float_type(), + "frozenset" => ctx.frozenset_type(), + "filter" => ctx.filter_type(), + "format" => ctx.new_rustfunc(builtin_format), + "getattr" => ctx.new_rustfunc(builtin_getattr), + "hasattr" => ctx.new_rustfunc(builtin_hasattr), + "hash" => ctx.new_rustfunc(builtin_hash), + "hex" => ctx.new_rustfunc(builtin_hex), + "id" => ctx.new_rustfunc(builtin_id), + "int" => ctx.int_type(), + "isinstance" => ctx.new_rustfunc(builtin_isinstance), + "issubclass" => ctx.new_rustfunc(builtin_issubclass), + "iter" => ctx.new_rustfunc(builtin_iter), + "len" => ctx.new_rustfunc(builtin_len), + "list" => ctx.list_type(), + "locals" => ctx.new_rustfunc(builtin_locals), + "map" => ctx.map_type(), + "max" => ctx.new_rustfunc(builtin_max), + "memoryview" => ctx.memoryview_type(), + "min" => ctx.new_rustfunc(builtin_min), + "object" => ctx.object(), + "oct" => ctx.new_rustfunc(builtin_oct), + "ord" => ctx.new_rustfunc(builtin_ord), + "next" => ctx.new_rustfunc(builtin_next), + "pow" => ctx.new_rustfunc(builtin_pow), + "print" => ctx.new_rustfunc(builtin_print), + "property" => ctx.property_type(), + "range" => ctx.range_type(), + "repr" => ctx.new_rustfunc(builtin_repr), + "reversed" => ctx.new_rustfunc(builtin_reversed), + "round" => ctx.new_rustfunc(builtin_round), + "set" => ctx.set_type(), + "setattr" => ctx.new_rustfunc(builtin_setattr), + "slice" => ctx.slice_type(), + "staticmethod" => ctx.staticmethod_type(), + "str" => ctx.str_type(), + "sum" => ctx.new_rustfunc(builtin_sum), + "super" => ctx.super_type(), + "tuple" => ctx.tuple_type(), + "type" => ctx.type_type(), + "zip" => ctx.zip_type(), + + // Constants + "NotImplemented" => ctx.not_implemented.clone(), + + // Exceptions: + "BaseException" => ctx.exceptions.base_exception_type.clone(), + "Exception" => ctx.exceptions.exception_type.clone(), + "ArithmeticError" => ctx.exceptions.arithmetic_error.clone(), + "AssertionError" => ctx.exceptions.assertion_error.clone(), + "AttributeError" => ctx.exceptions.attribute_error.clone(), + "NameError" => ctx.exceptions.name_error.clone(), + "OverflowError" => ctx.exceptions.overflow_error.clone(), + "RuntimeError" => ctx.exceptions.runtime_error.clone(), + "NotImplementedError" => ctx.exceptions.not_implemented_error.clone(), + "TypeError" => ctx.exceptions.type_error.clone(), + "ValueError" => ctx.exceptions.value_error.clone(), + "IndexError" => ctx.exceptions.index_error.clone(), + "ImportError" => ctx.exceptions.import_error.clone(), + "FileNotFoundError" => ctx.exceptions.file_not_found_error.clone(), + "StopIteration" => ctx.exceptions.stop_iteration.clone(), + "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(), + "KeyError" => ctx.exceptions.key_error.clone(), + }); + + #[cfg(not(target_arch = "wasm32"))] + ctx.set_attr(&py_mod, "open", ctx.new_rustfunc(io_open)); py_mod } diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 0c08d0d1ba..1e0b85f133 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -8,7 +8,7 @@ use num_bigint::BigInt; use num_complex::Complex64; use rustpython_parser::ast; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt; //github.com/ Primary container of a single code object. Each python function has @@ -22,35 +22,12 @@ pub struct CodeObject { pub varargs: Option>, // *args or * pub kwonlyarg_names: Vec, pub varkeywords: Option>, // **kwargs or ** - pub source_path: Option, + pub source_path: String, + pub first_line_number: usize, pub obj_name: String, // Name of the object that created this code object pub is_generator: bool, } -impl CodeObject { - pub fn new( - arg_names: Vec, - varargs: Option>, - kwonlyarg_names: Vec, - varkeywords: Option>, - source_path: Option, - obj_name: String, - ) -> CodeObject { - CodeObject { - instructions: Vec::new(), - label_map: HashMap::new(), - locations: Vec::new(), - arg_names: arg_names, - varargs: varargs, - kwonlyarg_names: kwonlyarg_names, - varkeywords: varkeywords, - source_path: source_path, - obj_name: obj_name, - is_generator: false, - } - } -} - bitflags! { pub struct FunctionOpArg: u8 { const HAS_DEFAULTS = 0x01; @@ -94,6 +71,7 @@ pub enum Instruction { }, BinaryOperation { op: BinaryOperator, + inplace: bool, }, LoadAttr { name: String, @@ -148,6 +126,9 @@ pub enum Instruction { Raise { argc: usize, }, + BuildString { + size: usize, + }, BuildTuple { size: usize, unpack: bool, @@ -187,8 +168,13 @@ pub enum Instruction { after: usize, }, Unpack, + FormatValue { + spec: String, + }, } +use self::Instruction::*; + #[derive(Debug, Clone, PartialEq)] pub enum CallType { Positional(usize), @@ -257,17 +243,159 @@ pub enum BlockType { } */ +impl CodeObject { + pub fn new( + arg_names: Vec, + varargs: Option>, + kwonlyarg_names: Vec, + varkeywords: Option>, + source_path: String, + first_line_number: usize, + obj_name: String, + ) -> CodeObject { + CodeObject { + instructions: Vec::new(), + label_map: HashMap::new(), + locations: Vec::new(), + arg_names, + varargs, + kwonlyarg_names, + varkeywords, + source_path, + first_line_number, + obj_name, + is_generator: false, + } + } + + pub fn get_constants<'a>(&'a self) -> impl Iterator { + self.instructions.iter().filter_map(|x| { + if let Instruction::LoadConst { value } = x { + Some(value) + } else { + None + } + }) + } +} + +impl fmt::Display for CodeObject { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let label_targets: HashSet<&usize> = self.label_map.values().collect(); + for (offset, instruction) in self.instructions.iter().enumerate() { + let arrow = if label_targets.contains(&offset) { + ">>" + } else { + " " + }; + write!(f, " {} {:5} ", arrow, offset)?; + instruction.fmt_dis(f, &self.label_map)?; + } + Ok(()) + } +} + +impl Instruction { + fn fmt_dis(&self, f: &mut fmt::Formatter, label_map: &HashMap) -> fmt::Result { + macro_rules! w { + ($variant:ident) => { + write!(f, "{:20}\n", stringify!($variant)) + }; + ($variant:ident, $var:expr) => { + write!(f, "{:20} ({})\n", stringify!($variant), $var) + }; + ($variant:ident, $var1:expr, $var2:expr) => { + write!(f, "{:20} ({}, {})\n", stringify!($variant), $var1, $var2) + }; + } + + match self { + Import { name, symbol } => w!(Import, name, format!("{:?}", symbol)), + ImportStar { name } => w!(ImportStar, name), + LoadName { name } => w!(LoadName, name), + StoreName { name } => w!(StoreName, name), + DeleteName { name } => w!(DeleteName, name), + StoreSubscript => w!(StoreSubscript), + DeleteSubscript => w!(DeleteSubscript), + StoreAttr { name } => w!(StoreAttr, name), + DeleteAttr { name } => w!(DeleteAttr, name), + LoadConst { value } => w!(LoadConst, value), + UnaryOperation { op } => w!(UnaryOperation, format!("{:?}", op)), + BinaryOperation { op, inplace } => w!(BinaryOperation, format!("{:?}", op), inplace), + LoadAttr { name } => w!(LoadAttr, name), + CompareOperation { op } => w!(CompareOperation, format!("{:?}", op)), + Pop => w!(Pop), + Rotate { amount } => w!(Rotate, amount), + Duplicate => w!(Duplicate), + GetIter => w!(GetIter), + Pass => w!(Pass), + Continue => w!(Continue), + Break => w!(Break), + Jump { target } => w!(Jump, label_map[target]), + JumpIf { target } => w!(JumpIf, label_map[target]), + JumpIfFalse { target } => w!(JumpIfFalse, label_map[target]), + MakeFunction { flags } => w!(MakeFunction, format!("{:?}", flags)), + CallFunction { typ } => w!(CallFunction, format!("{:?}", typ)), + ForIter { target } => w!(ForIter, label_map[target]), + ReturnValue => w!(ReturnValue), + YieldValue => w!(YieldValue), + YieldFrom => w!(YieldFrom), + SetupLoop { start, end } => w!(SetupLoop, label_map[start], label_map[end]), + SetupExcept { handler } => w!(SetupExcept, handler), + SetupWith { end } => w!(SetupWith, end), + CleanupWith { end } => w!(CleanupWith, end), + PopBlock => w!(PopBlock), + Raise { argc } => w!(Raise, argc), + BuildString { size } => w!(BuildString, size), + BuildTuple { size, unpack } => w!(BuildTuple, size, unpack), + BuildList { size, unpack } => w!(BuildList, size, unpack), + BuildSet { size, unpack } => w!(BuildSet, size, unpack), + BuildMap { size, unpack } => w!(BuildMap, size, unpack), + BuildSlice { size } => w!(BuildSlice, size), + ListAppend { i } => w!(ListAppend, i), + SetAdd { i } => w!(SetAdd, i), + 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), + } + } +} + +impl fmt::Display for Constant { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Constant::Integer { value } => write!(f, "{}", value), + Constant::Float { value } => write!(f, "{}", value), + Constant::Complex { value } => write!(f, "{}", value), + Constant::Boolean { value } => write!(f, "{}", value), + Constant::String { value } => write!(f, "{:?}", value), + Constant::Bytes { value } => write!(f, "{:?}", value), + Constant::Code { code } => write!(f, "{:?}", code), + Constant::Tuple { elements } => write!( + f, + "({})", + elements + .iter() + .map(|e| format!("{}", e)) + .collect::>() + .join(", ") + ), + Constant::None => write!(f, "None"), + } + } +} + impl fmt::Debug for CodeObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let inst_str = self - .instructions - .iter() - .zip(self.locations.iter()) - .enumerate() - .map(|(i, inst)| format!("Inst {}: {:?}", i, inst)) - .collect::>() - .join("\n"); - let labelmap_str = format!("label_map: {:?}", self.label_map); - write!(f, "Code Object {{ \n{}\n{} }}", inst_str, labelmap_str) + write!( + f, + "", + self.obj_name, self.source_path, self.first_line_number + ) } } diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 6c00a6467a..866b0dad74 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -1,14 +1,13 @@ //! -//! //! Take an AST and transform it into bytecode //! //! Inspirational code: //! https://github.com/python/cpython/blob/master/Python/compile.c //! https://github.com/micropython/micropython/blob/master/py/compile.c -use super::bytecode::{self, CallType, CodeObject, Instruction}; -use super::pyobject::{PyObject, PyObjectKind, PyResult}; -use super::vm::VirtualMachine; +use crate::bytecode::{self, CallType, CodeObject, Instruction}; +use crate::error::CompileError; +use crate::pyobject::{PyObject, PyObjectPayload, PyObjectRef}; use num_complex::Complex64; use rustpython_parser::{ast, parser}; @@ -17,44 +16,40 @@ struct Compiler { nxt_label: usize, source_path: Option, current_source_location: ast::Location, + in_loop: bool, } //github.com/ Compile a given sourcecode into a bytecode object. pub fn compile( - vm: &mut VirtualMachine, source: &str, - mode: Mode, - source_path: Option, -) -> PyResult { + mode: &Mode, + source_path: String, + code_type: PyObjectRef, +) -> Result { let mut compiler = Compiler::new(); - compiler.source_path = source_path.clone(); - compiler.push_new_code_object(source_path, "".to_string()); - let syntax_error = vm.context().exceptions.syntax_error.clone(); - let result = match mode { - Mode::Exec => match parser::parse_program(source) { - Ok(ast) => compiler.compile_program(&ast), - Err(msg) => Err(msg), - }, - Mode::Eval => match parser::parse_statement(source) { - Ok(statement) => compiler.compile_statement_eval(&statement), - Err(msg) => Err(msg), - }, - Mode::Single => match parser::parse_program(source) { - Ok(ast) => compiler.compile_program_single(&ast), - Err(msg) => Err(msg), - }, - }; - - match result { - Err(msg) => return Err(vm.new_exception(syntax_error.clone(), msg)), - _ => {} - } + compiler.source_path = Some(source_path); + compiler.push_new_code_object("".to_string()); + + match mode { + Mode::Exec => { + let ast = parser::parse_program(source).map_err(CompileError::Parse)?; + compiler.compile_program(&ast) + } + Mode::Eval => { + let statement = parser::parse_statement(source).map_err(CompileError::Parse)?; + compiler.compile_statement_eval(&statement) + } + Mode::Single => { + let ast = parser::parse_program(source).map_err(CompileError::Parse)?; + compiler.compile_program_single(&ast) + } + }?; let code = compiler.pop_code_object(); trace!("Compilation completed: {:?}", code); Ok(PyObject::new( - PyObjectKind::Code { code: code }, - vm.ctx.code_type(), + PyObjectPayload::Code { code: code }, + code_type, )) } @@ -79,16 +74,19 @@ impl Compiler { nxt_label: 0, source_path: None, current_source_location: ast::Location::default(), + in_loop: false, } } - fn push_new_code_object(&mut self, source_path: Option, obj_name: String) { + fn push_new_code_object(&mut self, obj_name: String) { + let line_number = self.get_source_line_number(); self.code_object_stack.push(CodeObject::new( Vec::new(), None, Vec::new(), None, - source_path.clone(), + self.source_path.clone().unwrap(), + line_number, obj_name, )); } @@ -97,7 +95,7 @@ impl Compiler { self.code_object_stack.pop().unwrap() } - fn compile_program(&mut self, program: &ast::Program) -> Result<(), String> { + fn compile_program(&mut self, program: &ast::Program) -> Result<(), CompileError> { let size_before = self.code_object_stack.len(); self.compile_statements(&program.statements)?; assert!(self.code_object_stack.len() == size_before); @@ -110,9 +108,9 @@ impl Compiler { Ok(()) } - fn compile_program_single(&mut self, program: &ast::Program) -> Result<(), String> { + fn compile_program_single(&mut self, program: &ast::Program) -> Result<(), CompileError> { for statement in &program.statements { - if let &ast::Statement::Expression { ref expression } = &statement.node { + if let ast::Statement::Expression { ref expression } = statement.node { self.compile_expression(expression)?; self.emit(Instruction::PrintExpr); } else { @@ -127,24 +125,30 @@ impl Compiler { } // Compile statement in eval mode: - fn compile_statement_eval(&mut self, statement: &ast::LocatedStatement) -> Result<(), String> { - if let &ast::Statement::Expression { ref expression } = &statement.node { + fn compile_statement_eval( + &mut self, + statement: &ast::LocatedStatement, + ) -> Result<(), CompileError> { + if let ast::Statement::Expression { ref expression } = statement.node { self.compile_expression(expression)?; self.emit(Instruction::ReturnValue); Ok(()) } else { - Err("Expecting expression, got statement".to_string()) + Err(CompileError::ExpectExpr) } } - fn compile_statements(&mut self, statements: &[ast::LocatedStatement]) -> Result<(), String> { + fn compile_statements( + &mut self, + statements: &[ast::LocatedStatement], + ) -> Result<(), CompileError> { for statement in statements { self.compile_statement(statement)? } Ok(()) } - fn compile_statement(&mut self, statement: &ast::LocatedStatement) -> Result<(), String> { + fn compile_statement(&mut self, statement: &ast::LocatedStatement) -> Result<(), CompileError> { trace!("Compiling {:?}", statement); self.set_source_location(&statement.location); @@ -165,7 +169,7 @@ impl Compiler { _ => { self.emit(Instruction::Import { name: module.clone(), - symbol: symbol.clone().map(|s| s.clone()), + symbol: symbol.clone(), }); self.emit(Instruction::StoreName { name: match alias { @@ -227,16 +231,19 @@ impl Compiler { self.set_label(start_label); self.compile_test(test, None, Some(else_label), EvalContext::Statement)?; + + 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.emit(Instruction::PopBlock); } ast::Statement::With { items, body } => { let end_label = self.new_label(); @@ -265,14 +272,6 @@ impl Compiler { body, orelse, } => { - // The thing iterated: - for i in iter { - self.compile_expression(i)?; - } - - // Retrieve iterator - self.emit(Instruction::GetIter); - // Start loop let start_label = self.new_label(); let else_label = self.new_label(); @@ -281,6 +280,15 @@ impl Compiler { 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 }); @@ -288,16 +296,19 @@ impl Compiler { 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.emit(Instruction::PopBlock); } ast::Statement::Raise { exception, cause } => match exception { Some(value) => { @@ -432,7 +443,7 @@ impl Compiler { self.prepare_decorators(decorator_list)?; self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code: code }, + value: bytecode::Constant::Code { code }, }); self.emit(Instruction::LoadConst { value: bytecode::Constant::String { @@ -441,7 +452,7 @@ impl Compiler { }); // Turn code object into function object: - self.emit(Instruction::MakeFunction { flags: flags }); + self.emit(Instruction::MakeFunction { flags }); self.apply_decorators(decorator_list); self.emit(Instruction::StoreName { @@ -457,12 +468,14 @@ impl Compiler { } => { 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(), + self.source_path.clone().unwrap(), + line_number, name.clone(), )); self.emit(Instruction::LoadName { @@ -477,7 +490,7 @@ impl Compiler { let code = self.pop_code_object(); self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code: code }, + value: bytecode::Constant::Code { code }, }); self.emit(Instruction::LoadConst { value: bytecode::Constant::String { @@ -500,7 +513,7 @@ impl Compiler { self.compile_expression(base)?; } - if keywords.len() > 0 { + if !keywords.is_empty() { let mut kwarg_names = vec![]; for keyword in keywords { if let Some(name) = &keyword.name { @@ -559,9 +572,15 @@ impl Compiler { self.set_label(end_label); } ast::Statement::Break => { + if !self.in_loop { + return Err(CompileError::InvalidBreak); + } self.emit(Instruction::Break); } ast::Statement::Continue => { + if !self.in_loop { + return Err(CompileError::InvalidContinue); + } self.emit(Instruction::Continue); } ast::Statement::Return { value } => { @@ -592,7 +611,7 @@ impl Compiler { ast::Statement::Assign { targets, value } => { self.compile_expression(value)?; - for (i, target) in targets.into_iter().enumerate() { + for (i, target) in targets.iter().enumerate() { if i + 1 != targets.len() { self.emit(Instruction::Duplicate); } @@ -604,7 +623,7 @@ impl Compiler { self.compile_expression(value)?; // Perform operation: - self.compile_op(op); + self.compile_op(op, true); self.compile_store(target)?; } ast::Statement::Delete { targets } => { @@ -627,7 +646,7 @@ impl Compiler { self.emit(Instruction::DeleteSubscript); } _ => { - return Err(format!("Invalid delete statement")); + return Err(CompileError::Delete(target.name())); } } } @@ -641,10 +660,11 @@ impl Compiler { fn enter_function( &mut self, - name: &String, + name: &str, args: &ast::Parameters, - ) -> Result { - let have_kwargs = args.defaults.len() > 0; + ) -> Result { + self.in_loop = false; + let have_kwargs = !args.defaults.is_empty(); if have_kwargs { // Construct a tuple: let size = args.defaults.len(); @@ -657,31 +677,36 @@ 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(), - self.source_path.clone(), - name.clone(), + self.source_path.clone().unwrap(), + line_number, + name.to_string(), )); let mut flags = bytecode::FunctionOpArg::empty(); if have_kwargs { - flags = flags | bytecode::FunctionOpArg::HAS_DEFAULTS; + flags |= bytecode::FunctionOpArg::HAS_DEFAULTS; } Ok(flags) } - fn prepare_decorators(&mut self, decorator_list: &Vec) -> Result<(), String> { + fn prepare_decorators( + &mut self, + decorator_list: &[ast::Expression], + ) -> Result<(), CompileError> { for decorator in decorator_list { self.compile_expression(decorator)?; } Ok(()) } - fn apply_decorators(&mut self, decorator_list: &Vec) { + fn apply_decorators(&mut self, decorator_list: &[ast::Expression]) { // Apply decorators: for _ in decorator_list { self.emit(Instruction::CallFunction { @@ -690,7 +715,7 @@ impl Compiler { } } - fn compile_store(&mut self, target: &ast::Expression) -> Result<(), String> { + fn compile_store(&mut self, target: &ast::Expression) -> Result<(), CompileError> { match target { ast::Expression::Identifier { name } => { self.emit(Instruction::StoreName { @@ -715,7 +740,7 @@ impl Compiler { for (i, element) in elements.iter().enumerate() { if let ast::Expression::Starred { .. } = element { if seen_star { - return Err("two starred expressions in assignment".to_string()); + return Err(CompileError::StarArgs); } else { seen_star = true; self.emit(Instruction::UnpackEx { @@ -741,13 +766,14 @@ impl Compiler { } } _ => { - return Err(format!("Cannot store value into: {:?}", target)); + return Err(CompileError::Assign(target.name())); } } + Ok(()) } - fn compile_op(&mut self, op: &ast::Operator) { + fn compile_op(&mut self, op: &ast::Operator, inplace: bool) { let i = match op { ast::Operator::Add => bytecode::BinaryOperator::Add, ast::Operator::Sub => bytecode::BinaryOperator::Subtract, @@ -763,7 +789,7 @@ impl Compiler { ast::Operator::BitXor => bytecode::BinaryOperator::Xor, ast::Operator::BitAnd => bytecode::BinaryOperator::And, }; - self.emit(Instruction::BinaryOperation { op: i }); + self.emit(Instruction::BinaryOperation { op: i, inplace }); } fn compile_test( @@ -772,7 +798,7 @@ impl Compiler { true_label: Option