@@ -14,12 +14,14 @@ use unicode_xid::UnicodeXID;
14
14
use crate :: format:: { FormatParseError , FormatPart , FormatString } ;
15
15
use crate :: function:: { OptionalArg , PyFuncArgs } ;
16
16
use crate :: pyobject:: {
17
- IdProtocol , IntoPyObject , PyClassImpl , PyContext , PyIterable , PyObjectRef , PyRef , PyResult ,
18
- PyValue , TryFromObject , TryIntoRef , TypeProtocol ,
17
+ IdProtocol , IntoPyObject , ItemProtocol , PyClassImpl , PyContext , PyIterable , PyObjectRef , PyRef ,
18
+ PyResult , PyValue , TryFromObject , TryIntoRef , TypeProtocol ,
19
19
} ;
20
20
use crate :: vm:: VirtualMachine ;
21
21
22
- use super :: objint;
22
+ use super :: objdict:: PyDict ;
23
+ use super :: objint:: { self , PyInt } ;
24
+ use super :: objnone:: PyNone ;
23
25
use super :: objsequence:: PySliceableSequence ;
24
26
use super :: objslice:: PySlice ;
25
27
use super :: objtype:: { self , PyClassRef } ;
@@ -844,6 +846,99 @@ impl PyString {
844
846
// a string is not an identifier if it has whitespace or starts with a number
845
847
is_identifier_start && chars. all ( |c| UnicodeXID :: is_xid_continue ( c) )
846
848
}
849
+
850
+ // https://docs.python.org/3/library/stdtypes.html#str.translate
851
+ #[ pymethod]
852
+ fn translate ( & self , table : PyObjectRef , vm : & VirtualMachine ) -> PyResult < String > {
853
+ let mut translated = String :: new ( ) ;
854
+ // It throws a type error if it is not subscribtable
855
+ vm. get_method ( table. clone ( ) , "__getitem__" ) ?;
856
+ for c in self . value . chars ( ) {
857
+ match table. get_item ( c as u32 , vm) {
858
+ Ok ( value) => {
859
+ if let Some ( text) = value. payload :: < PyString > ( ) {
860
+ translated. extend ( text. value . chars ( ) ) ;
861
+ } else if let Some ( _) = value. payload :: < PyNone > ( ) {
862
+ // Do Nothing
863
+ } else if let Some ( bigint) = value. payload :: < PyInt > ( ) {
864
+ match bigint. as_bigint ( ) . to_u32 ( ) . and_then ( std:: char:: from_u32) {
865
+ Some ( ch) => translated. push ( ch as char ) ,
866
+ None => {
867
+ return Err ( vm. new_value_error ( format ! (
868
+ "character mapping must be in range(0x110000)"
869
+ ) ) ) ;
870
+ }
871
+ }
872
+ } else {
873
+ return Err ( vm. new_type_error (
874
+ "character mapping must return integer, None or str" . to_owned ( ) ,
875
+ ) ) ;
876
+ }
877
+ }
878
+ _ => translated. push ( c) ,
879
+ }
880
+ }
881
+ Ok ( translated)
882
+ }
883
+
884
+ #[ pymethod]
885
+ fn maketrans (
886
+ dict_or_str : PyObjectRef ,
887
+ to_str : OptionalArg < PyStringRef > ,
888
+ none_str : OptionalArg < PyStringRef > ,
889
+ vm : & VirtualMachine ,
890
+ ) -> PyResult {
891
+ let new_dict = vm. context ( ) . new_dict ( ) ;
892
+ if let OptionalArg :: Present ( to_str) = to_str {
893
+ match dict_or_str. downcast :: < PyString > ( ) {
894
+ Ok ( from_str) => {
895
+ if to_str. len ( vm) == from_str. len ( vm) {
896
+ for ( c1, c2) in from_str. value . chars ( ) . zip ( to_str. value . chars ( ) ) {
897
+ new_dict. set_item ( c1 as u32 , vm. new_int ( c2 as u32 ) , vm) ?;
898
+ }
899
+ if let OptionalArg :: Present ( none_str) = none_str {
900
+ for c in none_str. value . chars ( ) {
901
+ new_dict. set_item ( c as u32 , vm. get_none ( ) , vm) ?;
902
+ }
903
+ }
904
+ new_dict. into_pyobject ( vm)
905
+ } else {
906
+ Err ( vm. new_value_error (
907
+ "the first two maketrans arguments must have equal length" . to_owned ( ) ,
908
+ ) )
909
+ }
910
+ }
911
+ _ => Err ( vm. new_type_error (
912
+ "first maketrans argument must be a string if there is a second argument"
913
+ . to_owned ( ) ,
914
+ ) ) ,
915
+ }
916
+ } else {
917
+ // dict_str must be a dict
918
+ match dict_or_str. downcast :: < PyDict > ( ) {
919
+ Ok ( dict) => {
920
+ for ( key, val) in dict {
921
+ if let Some ( num) = key. payload :: < PyInt > ( ) {
922
+ new_dict. set_item ( num. as_bigint ( ) . to_i32 ( ) , val, vm) ?;
923
+ } else if let Some ( string) = key. payload :: < PyString > ( ) {
924
+ if string. len ( vm) == 1 {
925
+ let num_value = string. value . chars ( ) . next ( ) . unwrap ( ) as u32 ;
926
+ new_dict. set_item ( num_value, val, vm) ?;
927
+ } else {
928
+ return Err ( vm. new_value_error (
929
+ "string keys in translate table must be of length 1" . to_owned ( ) ,
930
+ ) ) ;
931
+ }
932
+ }
933
+ }
934
+ new_dict. into_pyobject ( vm)
935
+ }
936
+ _ => Err ( vm. new_value_error (
937
+ "if you give only one argument to maketrans it must be a dict" . to_owned ( ) ,
938
+ ) ) ,
939
+ }
940
+ }
941
+ }
847
942
}
848
943
849
944
impl PyValue for PyString {
@@ -1119,4 +1214,30 @@ mod tests {
1119
1214
assert ! ( !PyString :: from( s) . istitle( & vm) ) ;
1120
1215
}
1121
1216
}
1217
+
1218
+ #[ test]
1219
+ fn str_maketrans_and_translate ( ) {
1220
+ let vm = VirtualMachine :: new ( ) ;
1221
+
1222
+ let table = vm. context ( ) . new_dict ( ) ;
1223
+ table
1224
+ . set_item ( "a" , vm. new_str ( "🎅" . to_owned ( ) ) , & vm)
1225
+ . unwrap ( ) ;
1226
+ table. set_item ( "b" , vm. get_none ( ) , & vm) . unwrap ( ) ;
1227
+ table
1228
+ . set_item ( "c" , vm. new_str ( "xda" . to_owned ( ) ) , & vm)
1229
+ . unwrap ( ) ;
1230
+ let translated = PyString :: maketrans (
1231
+ table. into_object ( ) ,
1232
+ OptionalArg :: Missing ,
1233
+ OptionalArg :: Missing ,
1234
+ & vm,
1235
+ )
1236
+ . unwrap ( ) ;
1237
+ let text = PyString :: from ( "abc" ) ;
1238
+ let translated = text. translate ( translated, & vm) . unwrap ( ) ;
1239
+ assert_eq ! ( translated, "🎅xda" . to_owned( ) ) ;
1240
+ let translated = text. translate ( vm. new_int ( 3 ) , & vm) ;
1241
+ assert_eq ! ( translated. unwrap_err( ) . class( ) . name, "TypeError" . to_owned( ) ) ;
1242
+ }
1122
1243
}
0 commit comments