32
32
StrType = newBasisType ("str" , reflect .TypeOf (Str {}), toStrUnsafe , BaseStringType )
33
33
whitespaceSplitRegexp = regexp .MustCompile (`\s+` )
34
34
strASCIISpaces = []byte (" \t \n \v \f \r " )
35
- strInterpolationRegexp = regexp .MustCompile (`^%([ #0 +-]?)(( \*|[0-9]+)?)(( \.(\*|[0-9]+))?) [hlL]?([diouxXeEfFgGcrs%])` )
35
+ strInterpolationRegexp = regexp .MustCompile (`^%(\(([^)]+)\))?([ #0 +-]?)(\*|[0-9]+)?( \.(\*|[0-9]+))?[hlL]?([diouxXeEfFgGcrs%])` )
36
36
internedStrs = map [string ]* Str {}
37
37
caseOffset = byte ('a' - 'A' )
38
38
@@ -553,18 +553,6 @@ func strLT(f *Frame, v, w *Object) (*Object, *BaseException) {
553
553
return strCompare (v , w , True , False , False ), nil
554
554
}
555
555
556
- func strMod (f * Frame , v , w * Object ) (* Object , * BaseException ) {
557
- s := toStrUnsafe (v ).Value ()
558
- switch {
559
- case w .isInstance (DictType ):
560
- return nil , f .RaiseType (NotImplementedErrorType , "mappings not yet supported" )
561
- case w .isInstance (TupleType ):
562
- return strInterpolate (f , s , toTupleUnsafe (w ))
563
- default :
564
- return strInterpolate (f , s , NewTuple1 (w ))
565
- }
566
- }
567
-
568
556
func strMul (f * Frame , v , w * Object ) (* Object , * BaseException ) {
569
557
s := toStrUnsafe (v ).Value ()
570
558
n , ok , raised := strRepeatCount (f , len (s ), w )
@@ -624,6 +612,52 @@ func strNew(f *Frame, t *Type, args Args, _ KWArgs) (*Object, *BaseException) {
624
612
return s .ToObject (), nil
625
613
}
626
614
615
+ // strPartition splits the string at the first occurrence of sep, and
616
+ // return a 3-tuple containing the part before the separator, the separator
617
+ // itself, and the part after the separator. If the separator is not found,
618
+ // return a 3-tuple containing the string itself, followed by two empty strings.
619
+ func strPartition (f * Frame , args Args , _ KWArgs ) (* Object , * BaseException ) {
620
+ if raised := checkMethodArgs (f , "partition" , args , StrType , StrType ); raised != nil {
621
+ return nil , raised
622
+ }
623
+ sep := toStrUnsafe (args [1 ]).Value ()
624
+ if sep == "" {
625
+ return nil , f .RaiseType (ValueErrorType , "empty separator" )
626
+ }
627
+ s := toStrUnsafe (args [0 ]).Value ()
628
+ pos := strings .Index (s , sep )
629
+ if pos < 0 {
630
+ emptyStr := NewStr ("" ).ToObject ()
631
+ return NewTuple (args [0 ], emptyStr , emptyStr ).ToObject (), nil
632
+ }
633
+ start := NewStr (s [0 :pos ]).ToObject ()
634
+ end := NewStr (s [pos + len (sep ):]).ToObject ()
635
+ return NewTuple (start , args [1 ], end ).ToObject (), nil
636
+ }
637
+
638
+ // strPartition splits the string at the last occurrence of sep, and
639
+ // return a 3-tuple containing the part before the separator, the separator
640
+ // itself, and the part after the separator. If the separator is not found,
641
+ // return a 3-tuple containing two empty strings, followed by the string itself.
642
+ func strRPartition (f * Frame , args Args , _ KWArgs ) (* Object , * BaseException ) {
643
+ if raised := checkMethodArgs (f , "rpartition" , args , StrType , StrType ); raised != nil {
644
+ return nil , raised
645
+ }
646
+ sep := toStrUnsafe (args [1 ]).Value ()
647
+ if sep == "" {
648
+ return nil , f .RaiseType (ValueErrorType , "empty separator" )
649
+ }
650
+ s := toStrUnsafe (args [0 ]).Value ()
651
+ pos := strings .LastIndex (s , sep )
652
+ if pos < 0 {
653
+ emptyStr := NewStr ("" ).ToObject ()
654
+ return NewTuple (emptyStr , emptyStr , args [0 ]).ToObject (), nil
655
+ }
656
+ start := NewStr (s [0 :pos ]).ToObject ()
657
+ end := NewStr (s [pos + len (sep ):]).ToObject ()
658
+ return NewTuple (start , args [1 ], end ).ToObject (), nil
659
+ }
660
+
627
661
// strReplace returns a copy of the string s with the first n non-overlapping
628
662
// instances of old replaced by sub. If old is empty, it matches at the
629
663
// beginning of the string. If n < 0, there is no limit on the number of
@@ -960,6 +994,8 @@ func initStrType(dict map[string]*Object) {
960
994
dict ["startswith" ] = newBuiltinFunction ("startswith" , strStartsWith ).ToObject ()
961
995
dict ["strip" ] = newBuiltinFunction ("strip" , strStrip ).ToObject ()
962
996
dict ["swapcase" ] = newBuiltinFunction ("swapcase" , strSwapCase ).ToObject ()
997
+ dict ["partition" ] = newBuiltinFunction ("partition" , strPartition ).ToObject ()
998
+ dict ["rpartition" ] = newBuiltinFunction ("rpartition" , strRPartition ).ToObject ()
963
999
dict ["replace" ] = newBuiltinFunction ("replace" , strReplace ).ToObject ()
964
1000
dict ["rstrip" ] = newBuiltinFunction ("rstrip" , strRStrip ).ToObject ()
965
1001
dict ["title" ] = newBuiltinFunction ("title" , strTitle ).ToObject ()
@@ -1003,7 +1039,33 @@ func strCompare(v, w *Object, ltResult, eqResult, gtResult *Int) *Object {
1003
1039
return gtResult .ToObject ()
1004
1040
}
1005
1041
1006
- func strInterpolate (f * Frame , format string , values * Tuple ) (* Object , * BaseException ) {
1042
+ func strMod (f * Frame , v , args * Object ) (* Object , * BaseException ) {
1043
+ format := toStrUnsafe (v ).Value ()
1044
+ // If the format string contains mappings, args must be a dict,
1045
+ // otherwise it must be treated as a tuple of values, so if it's not a tuple already
1046
+ // it must be transformed into a single element tuple.
1047
+ var values * Tuple
1048
+ var mappings * Dict
1049
+ if args .isInstance (TupleType ) {
1050
+ values = toTupleUnsafe (args )
1051
+ } else {
1052
+ values = NewTuple1 (args )
1053
+ if args .isInstance (DictType ) {
1054
+ mappings = toDictUnsafe (args )
1055
+ }
1056
+ }
1057
+ const (
1058
+ idxAll = iota
1059
+ // Todo: mapping keys can contain balanced parentheses, these should be matched
1060
+ // manually before using the regexp
1061
+ _
1062
+ idxMappingKey
1063
+ idxFlags
1064
+ idxWidth
1065
+ idxPrecision
1066
+ _
1067
+ idxType
1068
+ )
1007
1069
var buf bytes.Buffer
1008
1070
valueIndex := 0
1009
1071
index := strings .Index (format , "%" )
@@ -1014,34 +1076,53 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1014
1076
if matches == nil {
1015
1077
return nil , f .RaiseType (ValueErrorType , "invalid format spec" )
1016
1078
}
1017
- flags , fieldType := matches [1 ], matches [7 ]
1018
- if fieldType != "%" && valueIndex >= len (values .elems ) {
1019
- return nil , f .RaiseType (TypeErrorType , "not enough arguments for format string" )
1079
+ mappingKey , fieldType := matches [idxMappingKey ], matches [idxType ]
1080
+ var value * Object
1081
+ if mappingKey != "" {
1082
+ // Nb: mappings are checked even in case of "%%"
1083
+ if mappings == nil {
1084
+ return nil , f .RaiseType (TypeErrorType , "format requires a mapping" )
1085
+ }
1086
+ var raised * BaseException
1087
+ value , raised = mappings .GetItemString (f , mappingKey )
1088
+ if raised != nil {
1089
+ return nil , raised
1090
+ }
1091
+ if value == nil {
1092
+ return nil , f .RaiseType (KeyErrorType , fmt .Sprintf ("'%s'" , mappingKey ))
1093
+ }
1094
+ valueIndex = 1
1095
+ } else if fieldType != "%" {
1096
+ if valueIndex >= len (values .elems ) {
1097
+ return nil , f .RaiseType (TypeErrorType , "not enough arguments for format string" )
1098
+ }
1099
+ value = values .elems [valueIndex ]
1100
+ valueIndex ++
1020
1101
}
1021
1102
fieldWidth := - 1
1022
- if matches [2 ] == "*" || matches [4 ] != "" {
1103
+ if matches [idxWidth ] == "*" || matches [idxPrecision ] != "" {
1023
1104
return nil , f .RaiseType (NotImplementedErrorType , "field width not yet supported" )
1024
1105
}
1025
- if matches [2 ] != "" {
1106
+ if matches [idxWidth ] != "" {
1026
1107
var err error
1027
- fieldWidth , err = strconv .Atoi (matches [2 ])
1108
+ fieldWidth , err = strconv .Atoi (matches [idxWidth ])
1028
1109
if err != nil {
1029
1110
return nil , f .RaiseType (TypeErrorType , fmt .Sprint (err ))
1030
1111
}
1031
1112
}
1113
+ flags := matches [idxFlags ]
1032
1114
if flags != "" && flags != "0" {
1033
1115
return nil , f .RaiseType (NotImplementedErrorType , "conversion flags not yet supported" )
1034
1116
}
1035
1117
var val string
1036
1118
switch fieldType {
1037
1119
case "r" , "s" :
1038
- o := values .elems [valueIndex ]
1039
1120
var s * Str
1040
1121
var raised * BaseException
1041
1122
if fieldType == "r" {
1042
- s , raised = Repr (f , o )
1123
+ s , raised = Repr (f , value )
1043
1124
} else {
1044
- s , raised = ToStr (f , o )
1125
+ s , raised = ToStr (f , value )
1045
1126
}
1046
1127
if raised != nil {
1047
1128
return nil , raised
@@ -1051,10 +1132,8 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1051
1132
val = strLeftPad (val , fieldWidth , " " )
1052
1133
}
1053
1134
buf .WriteString (val )
1054
- valueIndex ++
1055
1135
case "f" :
1056
- o := values .elems [valueIndex ]
1057
- if v , ok := floatCoerce (o ); ok {
1136
+ if v , ok := floatCoerce (value ); ok {
1058
1137
val := strconv .FormatFloat (v , 'f' , 6 , 64 )
1059
1138
if fieldWidth > 0 {
1060
1139
fillchar := " "
@@ -1064,13 +1143,11 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1064
1143
val = strLeftPad (val , fieldWidth , fillchar )
1065
1144
}
1066
1145
buf .WriteString (val )
1067
- valueIndex ++
1068
1146
} else {
1069
- return nil , f .RaiseType (TypeErrorType , fmt .Sprintf ("float argument required, not %s" , o .typ .Name ()))
1147
+ return nil , f .RaiseType (TypeErrorType , fmt .Sprintf ("float argument required, not %s" , value .typ .Name ()))
1070
1148
}
1071
1149
case "d" , "x" , "X" , "o" :
1072
- o := values .elems [valueIndex ]
1073
- i , raised := ToInt (f , values .elems [valueIndex ])
1150
+ i , raised := ToInt (f , value )
1074
1151
if raised != nil {
1075
1152
return nil , raised
1076
1153
}
@@ -1080,15 +1157,15 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1080
1157
return nil , raised
1081
1158
}
1082
1159
val = s .Value ()
1083
- } else if matches [7 ] == "o" {
1084
- if o .isInstance (LongType ) {
1085
- val = toLongUnsafe (o ).Value ().Text (8 )
1160
+ } else if matches [idxType ] == "o" {
1161
+ if value .isInstance (LongType ) {
1162
+ val = toLongUnsafe (value ).Value ().Text (8 )
1086
1163
} else {
1087
1164
val = strconv .FormatInt (int64 (toIntUnsafe (i ).Value ()), 8 )
1088
1165
}
1089
1166
} else {
1090
- if o .isInstance (LongType ) {
1091
- val = toLongUnsafe (o ).Value ().Text (16 )
1167
+ if value .isInstance (LongType ) {
1168
+ val = toLongUnsafe (value ).Value ().Text (16 )
1092
1169
} else {
1093
1170
val = strconv .FormatInt (int64 (toIntUnsafe (i ).Value ()), 16 )
1094
1171
}
@@ -1104,7 +1181,6 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1104
1181
val = strLeftPad (val , fieldWidth , fillchar )
1105
1182
}
1106
1183
buf .WriteString (val )
1107
- valueIndex ++
1108
1184
case "%" :
1109
1185
val = "%"
1110
1186
if fieldWidth > 0 {
@@ -1115,7 +1191,7 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1115
1191
format := "conversion type not yet supported: %s"
1116
1192
return nil , f .RaiseType (NotImplementedErrorType , fmt .Sprintf (format , fieldType ))
1117
1193
}
1118
- format = format [len (matches [0 ]):]
1194
+ format = format [len (matches [idxAll ]):]
1119
1195
index = strings .Index (format , "%" )
1120
1196
}
1121
1197
if valueIndex < len (values .elems ) {
0 commit comments