diff --git a/Cargo.lock b/Cargo.lock index 33e9ddcfd..0303e06e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -849,6 +849,7 @@ dependencies = [ "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)", "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 28df3f47f..0cb3782b1 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -164,6 +164,13 @@ assert not ''.isdecimal() assert '123'.isdecimal() assert not '\u00B2'.isdecimal() +assert not ''.isidentifier() +assert 'python'.isidentifier() +assert '_'.isidentifier() +assert '유니코드'.isidentifier() +assert not '😂'.isidentifier() +assert not '123'.isidentifier() + # String Formatting assert "{} {}".format(1,2) == "1 2" assert "{0} {1}".format(2,3) == "2 3" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 5af648084..8ac5ebabb 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -24,6 +24,7 @@ rustc_version_runtime = "0.1.*" statrs = "0.10.0" caseless = "0.2.1" unicode-segmentation = "1.2.1" +unicode-xid = "0.1.0" lazy_static = "^1.0.1" lexical = "2.0.0" itertools = "^0.8.0" diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index e5e2afe37..7b2fcbe9a 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1,3 +1,5 @@ +extern crate unicode_xid; + use std::fmt; use std::hash::{Hash, Hasher}; use std::ops::Range; @@ -7,6 +9,7 @@ use std::string::ToString; use num_traits::ToPrimitive; use unicode_casing::CharExt; use unicode_segmentation::UnicodeSegmentation; +use unicode_xid::UnicodeXID; use crate::format::{FormatParseError, FormatPart, FormatString}; use crate::function::{OptionalArg, PyFuncArgs}; @@ -832,20 +835,14 @@ impl PyString { #[pymethod] fn isidentifier(&self, _vm: &VirtualMachine) -> bool { - let value = &self.value; + let mut chars = self.value.chars(); + let is_identifier_start = match chars.next() { + Some('_') => true, + Some(c) => UnicodeXID::is_xid_start(c), + None => false, + }; // a string is not an identifier if it has whitespace or starts with a number - if !value.chars().any(|c| c.is_ascii_whitespace()) - && !value.chars().nth(0).unwrap().is_digit(10) - { - for c in value.chars() { - if c != "_".chars().nth(0).unwrap() && !c.is_digit(10) && !c.is_alphabetic() { - return false; - } - } - true - } else { - false - } + is_identifier_start && chars.all(|c| UnicodeXID::is_xid_continue(c)) } }