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 )
@@ -1051,7 +1039,33 @@ func strCompare(v, w *Object, ltResult, eqResult, gtResult *Int) *Object {
1051
1039
return gtResult .ToObject ()
1052
1040
}
1053
1041
1054
- 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
+ )
1055
1069
var buf bytes.Buffer
1056
1070
valueIndex := 0
1057
1071
index := strings .Index (format , "%" )
@@ -1062,34 +1076,53 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1062
1076
if matches == nil {
1063
1077
return nil , f .RaiseType (ValueErrorType , "invalid format spec" )
1064
1078
}
1065
- flags , fieldType := matches [1 ], matches [7 ]
1066
- if fieldType != "%" && valueIndex >= len (values .elems ) {
1067
- 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 ++
1068
1101
}
1069
1102
fieldWidth := - 1
1070
- if matches [2 ] == "*" || matches [4 ] != "" {
1103
+ if matches [idxWidth ] == "*" || matches [idxPrecision ] != "" {
1071
1104
return nil , f .RaiseType (NotImplementedErrorType , "field width not yet supported" )
1072
1105
}
1073
- if matches [2 ] != "" {
1106
+ if matches [idxWidth ] != "" {
1074
1107
var err error
1075
- fieldWidth , err = strconv .Atoi (matches [2 ])
1108
+ fieldWidth , err = strconv .Atoi (matches [idxWidth ])
1076
1109
if err != nil {
1077
1110
return nil , f .RaiseType (TypeErrorType , fmt .Sprint (err ))
1078
1111
}
1079
1112
}
1113
+ flags := matches [idxFlags ]
1080
1114
if flags != "" && flags != "0" {
1081
1115
return nil , f .RaiseType (NotImplementedErrorType , "conversion flags not yet supported" )
1082
1116
}
1083
1117
var val string
1084
1118
switch fieldType {
1085
1119
case "r" , "s" :
1086
- o := values .elems [valueIndex ]
1087
1120
var s * Str
1088
1121
var raised * BaseException
1089
1122
if fieldType == "r" {
1090
- s , raised = Repr (f , o )
1123
+ s , raised = Repr (f , value )
1091
1124
} else {
1092
- s , raised = ToStr (f , o )
1125
+ s , raised = ToStr (f , value )
1093
1126
}
1094
1127
if raised != nil {
1095
1128
return nil , raised
@@ -1099,10 +1132,8 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1099
1132
val = strLeftPad (val , fieldWidth , " " )
1100
1133
}
1101
1134
buf .WriteString (val )
1102
- valueIndex ++
1103
1135
case "f" :
1104
- o := values .elems [valueIndex ]
1105
- if v , ok := floatCoerce (o ); ok {
1136
+ if v , ok := floatCoerce (value ); ok {
1106
1137
val := strconv .FormatFloat (v , 'f' , 6 , 64 )
1107
1138
if fieldWidth > 0 {
1108
1139
fillchar := " "
@@ -1112,13 +1143,11 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1112
1143
val = strLeftPad (val , fieldWidth , fillchar )
1113
1144
}
1114
1145
buf .WriteString (val )
1115
- valueIndex ++
1116
1146
} else {
1117
- 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 ()))
1118
1148
}
1119
1149
case "d" , "x" , "X" , "o" :
1120
- o := values .elems [valueIndex ]
1121
- i , raised := ToInt (f , values .elems [valueIndex ])
1150
+ i , raised := ToInt (f , value )
1122
1151
if raised != nil {
1123
1152
return nil , raised
1124
1153
}
@@ -1128,15 +1157,15 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1128
1157
return nil , raised
1129
1158
}
1130
1159
val = s .Value ()
1131
- } else if matches [7 ] == "o" {
1132
- if o .isInstance (LongType ) {
1133
- val = toLongUnsafe (o ).Value ().Text (8 )
1160
+ } else if matches [idxType ] == "o" {
1161
+ if value .isInstance (LongType ) {
1162
+ val = toLongUnsafe (value ).Value ().Text (8 )
1134
1163
} else {
1135
1164
val = strconv .FormatInt (int64 (toIntUnsafe (i ).Value ()), 8 )
1136
1165
}
1137
1166
} else {
1138
- if o .isInstance (LongType ) {
1139
- val = toLongUnsafe (o ).Value ().Text (16 )
1167
+ if value .isInstance (LongType ) {
1168
+ val = toLongUnsafe (value ).Value ().Text (16 )
1140
1169
} else {
1141
1170
val = strconv .FormatInt (int64 (toIntUnsafe (i ).Value ()), 16 )
1142
1171
}
@@ -1152,7 +1181,6 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1152
1181
val = strLeftPad (val , fieldWidth , fillchar )
1153
1182
}
1154
1183
buf .WriteString (val )
1155
- valueIndex ++
1156
1184
case "%" :
1157
1185
val = "%"
1158
1186
if fieldWidth > 0 {
@@ -1163,7 +1191,7 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
1163
1191
format := "conversion type not yet supported: %s"
1164
1192
return nil , f .RaiseType (NotImplementedErrorType , fmt .Sprintf (format , fieldType ))
1165
1193
}
1166
- format = format [len (matches [0 ]):]
1194
+ format = format [len (matches [idxAll ]):]
1167
1195
index = strings .Index (format , "%" )
1168
1196
}
1169
1197
if valueIndex < len (values .elems ) {
0 commit comments