@@ -2,8 +2,9 @@ use crate::function::PyFuncArgs;
2
2
use crate :: obj:: objint:: PyInt ;
3
3
use crate :: obj:: objstr:: PyString ;
4
4
use crate :: obj:: { objstr, objtype} ;
5
- use crate :: pyobject:: { ItemProtocol , PyObjectRef , PyResult , TypeProtocol } ;
5
+ use crate :: pyobject:: { IntoPyObject , ItemProtocol , PyObjectRef , PyResult , TypeProtocol } ;
6
6
use crate :: vm:: VirtualMachine ;
7
+ use itertools:: { Itertools , PeekingNext } ;
7
8
use num_bigint:: { BigInt , Sign } ;
8
9
use num_traits:: cast:: ToPrimitive ;
9
10
use num_traits:: Signed ;
@@ -557,6 +558,9 @@ pub(crate) enum FormatParseError {
557
558
MissingStartBracket ,
558
559
UnescapedStartBracketInLiteral ,
559
560
InvalidFormatSpecifier ,
561
+ EmptyAttribute ,
562
+ MissingRightBracket ,
563
+ InvalidCharacterAfterRightBracket ,
560
564
}
561
565
562
566
impl FromStr for FormatSpec {
@@ -566,25 +570,116 @@ impl FromStr for FormatSpec {
566
570
}
567
571
}
568
572
573
+ #[ derive( Debug , PartialEq ) ]
574
+ pub ( crate ) enum FieldNamePart {
575
+ Attribute ( String ) ,
576
+ Index ( usize ) ,
577
+ StringIndex ( String ) ,
578
+ }
579
+
580
+ impl FieldNamePart {
581
+ fn parse_part (
582
+ chars : & mut impl PeekingNext < Item = char > ,
583
+ ) -> Result < FieldNamePart , FormatParseError > {
584
+ let ch = chars. next ( ) . unwrap ( ) ;
585
+ if ch == '.' {
586
+ let mut attribute = String :: new ( ) ;
587
+ for ch in chars. peeking_take_while ( |ch| * ch != '.' && * ch != '[' ) {
588
+ attribute. push ( ch) ;
589
+ }
590
+ if attribute. is_empty ( ) {
591
+ Err ( FormatParseError :: EmptyAttribute )
592
+ } else {
593
+ Ok ( FieldNamePart :: Attribute ( attribute) )
594
+ }
595
+ } else if ch == '[' {
596
+ let mut index = String :: new ( ) ;
597
+ for ch in chars {
598
+ if ch == ']' {
599
+ return if index. is_empty ( ) {
600
+ Err ( FormatParseError :: EmptyAttribute )
601
+ } else if let Ok ( index) = index. parse :: < usize > ( ) {
602
+ Ok ( FieldNamePart :: Index ( index) )
603
+ } else {
604
+ Ok ( FieldNamePart :: StringIndex ( index) )
605
+ } ;
606
+ }
607
+ index. push ( ch) ;
608
+ }
609
+ Err ( FormatParseError :: MissingRightBracket )
610
+ } else {
611
+ Err ( FormatParseError :: InvalidCharacterAfterRightBracket )
612
+ }
613
+ }
614
+ }
615
+
616
+ #[ derive( Debug , PartialEq ) ]
617
+ pub ( crate ) enum FieldType {
618
+ AutoSpec ,
619
+ IndexSpec ( usize ) ,
620
+ KeywordSpec ( String ) ,
621
+ }
622
+
623
+ #[ derive( Debug , PartialEq ) ]
624
+ pub ( crate ) struct FieldName {
625
+ field_type : FieldType ,
626
+ parts : Vec < FieldNamePart > ,
627
+ }
628
+
629
+ impl FieldName {
630
+ fn parse ( text : & str ) -> Result < FieldName , FormatParseError > {
631
+ let mut chars = text. chars ( ) . peekable ( ) ;
632
+ let mut first = String :: new ( ) ;
633
+ for ch in chars. peeking_take_while ( |ch| * ch != '.' && * ch != '[' ) {
634
+ first. push ( ch) ;
635
+ }
636
+
637
+ let field_type = if first. is_empty ( ) {
638
+ FieldType :: AutoSpec
639
+ } else if let Ok ( index) = first. parse :: < usize > ( ) {
640
+ FieldType :: IndexSpec ( index)
641
+ } else {
642
+ FieldType :: KeywordSpec ( first)
643
+ } ;
644
+
645
+ let mut parts = Vec :: new ( ) ;
646
+ while chars. peek ( ) . is_some ( ) {
647
+ parts. push ( FieldNamePart :: parse_part ( & mut chars) ?)
648
+ }
649
+
650
+ Ok ( FieldName { field_type, parts } )
651
+ }
652
+ }
653
+
569
654
#[ derive( Debug , PartialEq ) ]
570
655
enum FormatPart {
571
- AutoSpec ( String ) ,
572
- IndexSpec ( usize , String ) ,
573
- KeywordSpec ( String , String ) ,
656
+ Field ( FieldName , String ) ,
574
657
Literal ( String ) ,
575
658
}
576
659
577
660
impl FormatPart {
578
661
fn is_auto ( & self ) -> bool {
579
662
match self {
580
- FormatPart :: AutoSpec ( _) => true ,
663
+ FormatPart :: Field (
664
+ FieldName {
665
+ field_type : FieldType :: AutoSpec ,
666
+ ..
667
+ } ,
668
+ _,
669
+ ) => true ,
581
670
_ => false ,
582
671
}
583
672
}
584
673
585
674
fn is_index ( & self ) -> bool {
586
675
match self {
587
- FormatPart :: IndexSpec ( _, _) => true ,
676
+ FormatPart :: Field (
677
+ FieldName {
678
+ field_type : FieldType :: IndexSpec ( _) ,
679
+ ..
680
+ } ,
681
+ _,
682
+ ) => true ,
588
683
_ => false ,
589
684
}
590
685
}
@@ -655,16 +750,7 @@ impl FormatString {
655
750
String :: new ( )
656
751
} ;
657
752
let format_spec = preconversor_spec + & format_spec;
658
-
659
- if arg_part. is_empty ( ) {
660
- return Ok ( FormatPart :: AutoSpec ( format_spec) ) ;
661
- }
662
-
663
- if let Ok ( index) = arg_part. parse :: < usize > ( ) {
664
- Ok ( FormatPart :: IndexSpec ( index, format_spec) )
665
- } else {
666
- Ok ( FormatPart :: KeywordSpec ( arg_part. to_owned ( ) , format_spec) )
667
- }
753
+ Ok ( FormatPart :: Field ( FieldName :: parse ( arg_part) ?, format_spec) )
668
754
}
669
755
670
756
fn parse_spec < ' a > (
@@ -724,46 +810,34 @@ impl FormatString {
724
810
}
725
811
}
726
812
727
- pub ( crate ) fn format ( & self , arguments : & PyFuncArgs , vm : & VirtualMachine ) -> PyResult {
813
+ fn format_internal (
814
+ & self ,
815
+ vm : & VirtualMachine ,
816
+ mut field_func : impl FnMut ( & FieldType ) -> PyResult ,
817
+ ) -> PyResult {
728
818
let mut final_string = String :: new ( ) ;
729
- if self . format_parts . iter ( ) . any ( FormatPart :: is_auto)
730
- && self . format_parts . iter ( ) . any ( FormatPart :: is_index)
731
- {
732
- return Err ( vm. new_value_error (
733
- "cannot switch from automatic field numbering to manual field specification"
734
- . to_owned ( ) ,
735
- ) ) ;
736
- }
737
- let mut auto_argument_index: usize = 1 ;
738
819
for part in & self . format_parts {
739
820
let result_string: String = match part {
740
- FormatPart :: AutoSpec ( format_spec) => {
741
- let result = match arguments. args . get ( auto_argument_index) {
742
- Some ( argument) => call_object_format ( vm, argument. clone ( ) , & format_spec) ?,
743
- None => {
744
- return Err ( vm. new_index_error ( "tuple index out of range" . to_owned ( ) ) ) ;
745
- }
746
- } ;
747
- auto_argument_index += 1 ;
748
- objstr:: clone_value ( & result)
749
- }
750
- FormatPart :: IndexSpec ( index, format_spec) => {
751
- let result = match arguments. args . get ( * index + 1 ) {
752
- Some ( argument) => call_object_format ( vm, argument. clone ( ) , & format_spec) ?,
753
- None => {
754
- return Err ( vm. new_index_error ( "tuple index out of range" . to_owned ( ) ) ) ;
755
- }
756
- } ;
757
- objstr:: clone_value ( & result)
758
- }
759
- FormatPart :: KeywordSpec ( keyword, format_spec) => {
760
- let result = match arguments. get_optional_kwarg ( & keyword) {
761
- Some ( argument) => call_object_format ( vm, argument. clone ( ) , & format_spec) ?,
762
- None => {
763
- return Err ( vm. new_key_error ( vm. new_str ( keyword. to_owned ( ) ) ) ) ;
821
+ FormatPart :: Field ( FieldName { field_type, parts } , format_spec) => {
822
+ let mut argument = field_func ( field_type) ?;
823
+
824
+ for name_part in parts {
825
+ match name_part {
826
+ FieldNamePart :: Attribute ( attribute) => {
827
+ argument = vm. get_attribute ( argument, attribute. as_str ( ) ) ?;
828
+ }
829
+ FieldNamePart :: Index ( index) => {
830
+ // TODO Implement DictKey for usize so we can pass index directly
831
+ argument = argument. get_item ( & index. into_pyobject ( vm) ?, vm) ?;
832
+ }
833
+ FieldNamePart :: StringIndex ( index) => {
834
+ argument = argument. get_item ( index, vm) ?;
835
+ }
764
836
}
765
- } ;
766
- objstr:: clone_value ( & result)
837
+ }
838
+
839
+ let value = call_object_format ( vm, argument, & format_spec) ?;
840
+ objstr:: clone_value ( & value)
767
841
}
768
842
FormatPart :: Literal ( literal) => literal. clone ( ) ,
769
843
} ;
@@ -772,25 +846,44 @@ impl FormatString {
772
846
Ok ( vm. ctx . new_str ( final_string) )
773
847
}
774
848
775
- pub ( crate ) fn format_map ( & self , dict : & PyObjectRef , vm : & VirtualMachine ) -> PyResult {
776
- let mut final_string = String :: new ( ) ;
777
- for part in & self . format_parts {
778
- let result_string: String = match part {
779
- FormatPart :: AutoSpec ( _) | FormatPart :: IndexSpec ( _, _) => {
780
- return Err (
781
- vm. new_value_error ( "Format string contains positional fields" . to_owned ( ) )
782
- ) ;
783
- }
784
- FormatPart :: KeywordSpec ( keyword, format_spec) => {
785
- let argument = dict. get_item ( keyword, & vm) ?;
786
- let result = call_object_format ( vm, argument. clone ( ) , & format_spec) ?;
787
- objstr:: clone_value ( & result)
788
- }
789
- FormatPart :: Literal ( literal) => literal. clone ( ) ,
790
- } ;
791
- final_string. push_str ( & result_string) ;
849
+ pub ( crate ) fn format ( & self , arguments : & PyFuncArgs , vm : & VirtualMachine ) -> PyResult {
850
+ if self . format_parts . iter ( ) . any ( FormatPart :: is_auto)
851
+ && self . format_parts . iter ( ) . any ( FormatPart :: is_index)
852
+ {
853
+ return Err ( vm. new_value_error (
854
+ "cannot switch from automatic field numbering to manual field specification"
855
+ . to_owned ( ) ,
856
+ ) ) ;
792
857
}
793
- Ok ( vm. ctx . new_str ( final_string) )
858
+
859
+ let mut auto_argument_index: usize = 1 ;
860
+ self . format_internal ( vm, |field_type| match field_type {
861
+ FieldType :: AutoSpec => {
862
+ auto_argument_index += 1 ;
863
+ arguments
864
+ . args
865
+ . get ( auto_argument_index - 1 )
866
+ . cloned ( )
867
+ . ok_or_else ( || vm. new_index_error ( "tuple index out of range" . to_owned ( ) ) )
868
+ }
869
+ FieldType :: IndexSpec ( index) => arguments
870
+ . args
871
+ . get ( * index + 1 )
872
+ . cloned ( )
873
+ . ok_or_else ( || vm. new_index_error ( "tuple index out of range" . to_owned ( ) ) ) ,
874
+ FieldType :: KeywordSpec ( keyword) => arguments
875
+ . get_optional_kwarg ( & keyword)
876
+ . ok_or_else ( || vm. new_key_error ( vm. new_str ( keyword. to_owned ( ) ) ) ) ,
877
+ } )
878
+ }
879
+
880
+ pub ( crate ) fn format_map ( & self , dict : & PyObjectRef , vm : & VirtualMachine ) -> PyResult {
881
+ self . format_internal ( vm, |field_type| match field_type {
882
+ FieldType :: AutoSpec | FieldType :: IndexSpec ( _) => {
883
+ Err ( vm. new_value_error ( "Format string contains positional fields" . to_owned ( ) ) )
884
+ }
885
+ FieldType :: KeywordSpec ( keyword) => dict. get_item ( keyword, & vm) ,
886
+ } )
794
887
}
795
888
}
796
889
@@ -968,9 +1061,21 @@ mod tests {
968
1061
let expected = Ok ( FormatString {
969
1062
format_parts : vec ! [
970
1063
FormatPart :: Literal ( "abcd" . to_owned( ) ) ,
971
- FormatPart :: IndexSpec ( 1 , String :: new( ) ) ,
1064
+ FormatPart :: Field (
1065
+ FieldName {
1066
+ field_type: FieldType :: IndexSpec ( 1 ) ,
1067
+ parts: Vec :: new( ) ,
1068
+ } ,
1069
+ String :: new( ) ,
1070
+ ) ,
972
1071
FormatPart :: Literal ( ":" . to_owned( ) ) ,
973
- FormatPart :: KeywordSpec ( "key" . to_owned( ) , String :: new( ) ) ,
1072
+ FormatPart :: Field (
1073
+ FieldName {
1074
+ field_type: FieldType :: KeywordSpec ( "key" . to_owned( ) ) ,
1075
+ parts: Vec :: new( ) ,
1076
+ } ,
1077
+ String :: new( ) ,
1078
+ ) ,
974
1079
] ,
975
1080
} ) ;
976
1081
@@ -993,7 +1098,13 @@ mod tests {
993
1098
let expected = Ok ( FormatString {
994
1099
format_parts : vec ! [
995
1100
FormatPart :: Literal ( "{" . to_owned( ) ) ,
996
- FormatPart :: KeywordSpec ( "key" . to_owned( ) , String :: new( ) ) ,
1101
+ FormatPart :: Field (
1102
+ FieldName {
1103
+ field_type: FieldType :: KeywordSpec ( "key" . to_owned( ) ) ,
1104
+ parts: Vec :: new( ) ,
1105
+ } ,
1106
+ String :: new( ) ,
1107
+ ) ,
997
1108
FormatPart :: Literal ( "}ddfe" . to_owned( ) ) ,
998
1109
] ,
999
1110
} ) ;
@@ -1014,4 +1125,56 @@ mod tests {
1014
1125
assert_eq ! ( parse_format_spec( "o!" ) , Err ( "Invalid format specifier" ) ) ;
1015
1126
assert_eq ! ( parse_format_spec( "d " ) , Err ( "Invalid format specifier" ) ) ;
1016
1127
}
1128
+
1129
+ #[ test]
1130
+ fn test_parse_field_name ( ) {
1131
+ assert_eq ! (
1132
+ FieldName :: parse( "" ) ,
1133
+ Ok ( FieldName {
1134
+ field_type: FieldType :: AutoSpec ,
1135
+ parts: Vec :: new( ) ,
1136
+ } )
1137
+ ) ;
1138
+ assert_eq ! (
1139
+ FieldName :: parse( "0" ) ,
1140
+ Ok ( FieldName {
1141
+ field_type: FieldType :: IndexSpec ( 0 ) ,
1142
+ parts: Vec :: new( ) ,
1143
+ } )
1144
+ ) ;
1145
+ assert_eq ! (
1146
+ FieldName :: parse( "key" ) ,
1147
+ Ok ( FieldName {
1148
+ field_type: FieldType :: KeywordSpec ( "key" . to_owned( ) ) ,
1149
+ parts: Vec :: new( ) ,
1150
+ } )
1151
+ ) ;
1152
+ assert_eq ! (
1153
+ FieldName :: parse( "key.attr[0][string]" ) ,
1154
+ Ok ( FieldName {
1155
+ field_type: FieldType :: KeywordSpec ( "key" . to_owned( ) ) ,
1156
+ parts: vec![
1157
+ FieldNamePart :: Attribute ( "attr" . to_owned( ) ) ,
1158
+ FieldNamePart :: Index ( 0 ) ,
1159
+ FieldNamePart :: StringIndex ( "string" . to_owned( ) )
1160
+ ] ,
1161
+ } )
1162
+ ) ;
1163
+ assert_eq ! (
1164
+ FieldName :: parse( "key.." ) ,
1165
+ Err ( FormatParseError :: EmptyAttribute )
1166
+ ) ;
1167
+ assert_eq ! (
1168
+ FieldName :: parse( "key[]" ) ,
1169
+ Err ( FormatParseError :: EmptyAttribute )
1170
+ ) ;
1171
+ assert_eq ! (
1172
+ FieldName :: parse( "key[" ) ,
1173
+ Err ( FormatParseError :: MissingRightBracket )
1174
+ ) ;
1175
+ assert_eq ! (
1176
+ FieldName :: parse( "key[0]after" ) ,
1177
+ Err ( FormatParseError :: InvalidCharacterAfterRightBracket )
1178
+ ) ;
1179
+ }
1017
1180
}
0 commit comments