Skip to content

Commit 6fc336a

Browse files
authored
Merge pull request #76 from tenta-browser/misclib
Miscellaneous additions to builtins
2 parents 4a57242 + ff0f154 commit 6fc336a

File tree

4 files changed

+184
-41
lines changed

4 files changed

+184
-41
lines changed

grumpy-runtime-src/runtime/bytearray.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,52 @@ func byteArrayGT(f *Frame, v, w *Object) (*Object, *BaseException) {
9898
}
9999

100100
func byteArrayInit(f *Frame, o *Object, args Args, _ KWArgs) (*Object, *BaseException) {
101-
if raised := checkFunctionArgs(f, "__init__", args, IntType); raised != nil {
101+
argc := len(args)
102+
if argc == 0 {
103+
return None, nil
104+
}
105+
expectedTypes := []*Type{ObjectType}
106+
if raised := checkFunctionArgs(f, "__init__", args, expectedTypes...); raised != nil {
102107
return nil, raised
103108
}
104109
a := toByteArrayUnsafe(o)
110+
if args[0].isInstance(IntType) {
111+
a.mutex.Lock()
112+
a.value = make([]byte, toIntUnsafe(args[0]).Value())
113+
a.mutex.Unlock()
114+
return None, nil
115+
}
116+
// else it must be an iterable
117+
value := []byte{}
118+
iter, raised := Iter(f, args[0])
119+
if raised != nil {
120+
return nil, raised
121+
}
122+
item, raised := Next(f, iter)
123+
for ; raised == nil; item, raised = Next(f, iter) {
124+
switch {
125+
case item.isInstance(StrType):
126+
sval := toStrUnsafe(item).Value()
127+
if len(sval) != 1 {
128+
return nil, f.RaiseType(ValueErrorType, "string must be of size 1")
129+
}
130+
value = append(value, sval[0])
131+
case item.isInstance(IntType):
132+
ival := toIntUnsafe(item).Value()
133+
if ival < 0 || ival >= 256 {
134+
return nil, f.RaiseType(ValueErrorType, "byte must be in range(0, 256)")
135+
}
136+
value = append(value, byte(ival))
137+
default:
138+
return nil, f.RaiseType(TypeErrorType, "an integer or string of size 1 is required")
139+
}
140+
}
141+
if !raised.isInstance(StopIterationType) {
142+
return nil, raised
143+
}
144+
f.RestoreExc(nil, nil)
105145
a.mutex.Lock()
106-
a.value = make([]byte, toIntUnsafe(args[0]).Value())
146+
a.value = value
107147
a.mutex.Unlock()
108148
return None, nil
109149
}

grumpy-runtime-src/runtime/bytearray_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,18 @@ func TestByteArrayGetItem(t *testing.T) {
6868

6969
func TestByteArrayInit(t *testing.T) {
7070
cases := []invokeTestCase{
71+
{args: wrapArgs(), want: newTestByteArray("").ToObject()},
7172
{args: wrapArgs(3), want: newTestByteArray("\x00\x00\x00").ToObject()},
72-
{args: wrapArgs(newObject(ObjectType)), wantExc: mustCreateException(TypeErrorType, `'__init__' requires a 'int' object but received a "object"`)},
73+
{args: wrapArgs([]int{3, 2, 1}), want: newTestByteArray("\x03\x02\x01").ToObject()},
74+
{args: wrapArgs("abc"), want: newTestByteArray("abc").ToObject()},
75+
{args: wrapArgs([]string{"a", "b", "c"}), want: newTestByteArray("abc").ToObject()},
76+
{args: wrapArgs(newTestXRange(3)), want: newTestByteArray("\x00\x01\x02").ToObject()},
77+
{args: wrapArgs(newTestRange(3)), want: newTestByteArray("\x00\x01\x02").ToObject()},
78+
{args: wrapArgs(newObject(ObjectType)), wantExc: mustCreateException(TypeErrorType, `'object' object is not iterable`)},
79+
{args: wrapArgs([]int{-1}), wantExc: mustCreateException(ValueErrorType, `byte must be in range(0, 256)`)},
80+
{args: wrapArgs([]int{256}), wantExc: mustCreateException(ValueErrorType, `byte must be in range(0, 256)`)},
81+
{args: wrapArgs([]string{"ab"}), wantExc: mustCreateException(ValueErrorType, `string must be of size 1`)},
82+
{args: wrapArgs([]interface{}{5, []interface{}{}}), wantExc: mustCreateException(TypeErrorType, `an integer or string of size 1 is required`)},
7383
}
7484
for _, cas := range cases {
7585
if err := runInvokeTestCase(ByteArrayType.ToObject(), &cas); err != "" {

grumpy-runtime-src/runtime/str.go

Lines changed: 113 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ var (
3232
StrType = newBasisType("str", reflect.TypeOf(Str{}), toStrUnsafe, BaseStringType)
3333
whitespaceSplitRegexp = regexp.MustCompile(`\s+`)
3434
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%])`)
3636
internedStrs = map[string]*Str{}
3737
caseOffset = byte('a' - 'A')
3838

@@ -553,18 +553,6 @@ func strLT(f *Frame, v, w *Object) (*Object, *BaseException) {
553553
return strCompare(v, w, True, False, False), nil
554554
}
555555

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-
568556
func strMul(f *Frame, v, w *Object) (*Object, *BaseException) {
569557
s := toStrUnsafe(v).Value()
570558
n, ok, raised := strRepeatCount(f, len(s), w)
@@ -624,6 +612,52 @@ func strNew(f *Frame, t *Type, args Args, _ KWArgs) (*Object, *BaseException) {
624612
return s.ToObject(), nil
625613
}
626614

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+
627661
// strReplace returns a copy of the string s with the first n non-overlapping
628662
// instances of old replaced by sub. If old is empty, it matches at the
629663
// 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) {
960994
dict["startswith"] = newBuiltinFunction("startswith", strStartsWith).ToObject()
961995
dict["strip"] = newBuiltinFunction("strip", strStrip).ToObject()
962996
dict["swapcase"] = newBuiltinFunction("swapcase", strSwapCase).ToObject()
997+
dict["partition"] = newBuiltinFunction("partition", strPartition).ToObject()
998+
dict["rpartition"] = newBuiltinFunction("rpartition", strRPartition).ToObject()
963999
dict["replace"] = newBuiltinFunction("replace", strReplace).ToObject()
9641000
dict["rstrip"] = newBuiltinFunction("rstrip", strRStrip).ToObject()
9651001
dict["title"] = newBuiltinFunction("title", strTitle).ToObject()
@@ -1003,7 +1039,33 @@ func strCompare(v, w *Object, ltResult, eqResult, gtResult *Int) *Object {
10031039
return gtResult.ToObject()
10041040
}
10051041

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+
)
10071069
var buf bytes.Buffer
10081070
valueIndex := 0
10091071
index := strings.Index(format, "%")
@@ -1014,34 +1076,53 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
10141076
if matches == nil {
10151077
return nil, f.RaiseType(ValueErrorType, "invalid format spec")
10161078
}
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++
10201101
}
10211102
fieldWidth := -1
1022-
if matches[2] == "*" || matches[4] != "" {
1103+
if matches[idxWidth] == "*" || matches[idxPrecision] != "" {
10231104
return nil, f.RaiseType(NotImplementedErrorType, "field width not yet supported")
10241105
}
1025-
if matches[2] != "" {
1106+
if matches[idxWidth] != "" {
10261107
var err error
1027-
fieldWidth, err = strconv.Atoi(matches[2])
1108+
fieldWidth, err = strconv.Atoi(matches[idxWidth])
10281109
if err != nil {
10291110
return nil, f.RaiseType(TypeErrorType, fmt.Sprint(err))
10301111
}
10311112
}
1113+
flags := matches[idxFlags]
10321114
if flags != "" && flags != "0" {
10331115
return nil, f.RaiseType(NotImplementedErrorType, "conversion flags not yet supported")
10341116
}
10351117
var val string
10361118
switch fieldType {
10371119
case "r", "s":
1038-
o := values.elems[valueIndex]
10391120
var s *Str
10401121
var raised *BaseException
10411122
if fieldType == "r" {
1042-
s, raised = Repr(f, o)
1123+
s, raised = Repr(f, value)
10431124
} else {
1044-
s, raised = ToStr(f, o)
1125+
s, raised = ToStr(f, value)
10451126
}
10461127
if raised != nil {
10471128
return nil, raised
@@ -1051,10 +1132,8 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
10511132
val = strLeftPad(val, fieldWidth, " ")
10521133
}
10531134
buf.WriteString(val)
1054-
valueIndex++
10551135
case "f":
1056-
o := values.elems[valueIndex]
1057-
if v, ok := floatCoerce(o); ok {
1136+
if v, ok := floatCoerce(value); ok {
10581137
val := strconv.FormatFloat(v, 'f', 6, 64)
10591138
if fieldWidth > 0 {
10601139
fillchar := " "
@@ -1064,13 +1143,11 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
10641143
val = strLeftPad(val, fieldWidth, fillchar)
10651144
}
10661145
buf.WriteString(val)
1067-
valueIndex++
10681146
} 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()))
10701148
}
10711149
case "d", "x", "X", "o":
1072-
o := values.elems[valueIndex]
1073-
i, raised := ToInt(f, values.elems[valueIndex])
1150+
i, raised := ToInt(f, value)
10741151
if raised != nil {
10751152
return nil, raised
10761153
}
@@ -1080,15 +1157,15 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
10801157
return nil, raised
10811158
}
10821159
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)
10861163
} else {
10871164
val = strconv.FormatInt(int64(toIntUnsafe(i).Value()), 8)
10881165
}
10891166
} 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)
10921169
} else {
10931170
val = strconv.FormatInt(int64(toIntUnsafe(i).Value()), 16)
10941171
}
@@ -1104,7 +1181,6 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
11041181
val = strLeftPad(val, fieldWidth, fillchar)
11051182
}
11061183
buf.WriteString(val)
1107-
valueIndex++
11081184
case "%":
11091185
val = "%"
11101186
if fieldWidth > 0 {
@@ -1115,7 +1191,7 @@ func strInterpolate(f *Frame, format string, values *Tuple) (*Object, *BaseExcep
11151191
format := "conversion type not yet supported: %s"
11161192
return nil, f.RaiseType(NotImplementedErrorType, fmt.Sprintf(format, fieldType))
11171193
}
1118-
format = format[len(matches[0]):]
1194+
format = format[len(matches[idxAll]):]
11191195
index = strings.Index(format, "%")
11201196
}
11211197
if valueIndex < len(values.elems) {

grumpy-runtime-src/runtime/str_test.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func TestStrBinaryOps(t *testing.T) {
8888
{args: wrapArgs(Mod, "%06r", "abc"), want: NewStr(" 'abc'").ToObject()},
8989
{args: wrapArgs(Mod, "%s %s", true), wantExc: mustCreateException(TypeErrorType, "not enough arguments for format string")},
9090
{args: wrapArgs(Mod, "%Z", None), wantExc: mustCreateException(ValueErrorType, "invalid format spec")},
91-
{args: wrapArgs(Mod, "%s", NewDict()), wantExc: mustCreateException(NotImplementedErrorType, "mappings not yet supported")},
91+
{args: wrapArgs(Mod, "%s", NewDict()), want: NewStr("{}").ToObject()},
9292
{args: wrapArgs(Mod, "% d", 23), wantExc: mustCreateException(NotImplementedErrorType, "conversion flags not yet supported")},
9393
{args: wrapArgs(Mod, "%.3f", 102.1), wantExc: mustCreateException(NotImplementedErrorType, "field width not yet supported")},
9494
{args: wrapArgs(Mod, "%x", 0x1f), want: NewStr("1f").ToObject()},
@@ -106,6 +106,11 @@ func TestStrBinaryOps(t *testing.T) {
106106
{args: wrapArgs(Mod, "%04o", newTestTuple(123)), want: NewStr("0173").ToObject()},
107107
{args: wrapArgs(Mod, "%o", newTestTuple("123")), wantExc: mustCreateException(TypeErrorType, "an integer is required")},
108108
{args: wrapArgs(Mod, "%o", None), wantExc: mustCreateException(TypeErrorType, "an integer is required")},
109+
{args: wrapArgs(Mod, "%(foo)s", "bar"), wantExc: mustCreateException(TypeErrorType, "format requires a mapping")},
110+
{args: wrapArgs(Mod, "%(foo)s %(bar)d", newTestDict("foo", "baz", "bar", 123)), want: NewStr("baz 123").ToObject()},
111+
{args: wrapArgs(Mod, "%(foo)s", newTestDict()), wantExc: mustCreateException(KeyErrorType, "'foo'")},
112+
{args: wrapArgs(Mod, "%(foo)s %s", newTestDict("foo", "bar")), wantExc: mustCreateException(TypeErrorType, "not enough arguments for format string")},
113+
{args: wrapArgs(Mod, "%s %(foo)s", newTestDict("foo", "bar")), want: NewStr("{'foo': 'bar'} bar").ToObject()},
109114
{args: wrapArgs(Mul, "", 10), want: NewStr("").ToObject()},
110115
{args: wrapArgs(Mul, "foo", -2), want: NewStr("").ToObject()},
111116
{args: wrapArgs(Mul, "foobar", 0), want: NewStr("").ToObject()},
@@ -589,6 +594,18 @@ func TestStrMethods(t *testing.T) {
589594
{"strip", wrapArgs("foo", "bar", "baz"), nil, mustCreateException(TypeErrorType, "'strip' of 'str' requires 2 arguments")},
590595
{"strip", wrapArgs("\xfboo", NewUnicode("o")), nil, mustCreateException(UnicodeDecodeErrorType, "'utf8' codec can't decode byte 0xfb in position 0")},
591596
{"strip", wrapArgs("foo", NewUnicode("o")), NewUnicode("f").ToObject(), nil},
597+
{"partition", wrapArgs("foo", ""), nil, mustCreateException(ValueErrorType, "empty separator")},
598+
{"partition", wrapArgs("foo", ":"), newTestTuple("foo", "", "").ToObject(), nil},
599+
{"partition", wrapArgs(":foo", ":"), newTestTuple("", ":", "foo").ToObject(), nil},
600+
{"partition", wrapArgs("foo:", ":"), newTestTuple("foo", ":", "").ToObject(), nil},
601+
{"partition", wrapArgs("foo:bar", ":"), newTestTuple("foo", ":", "bar").ToObject(), nil},
602+
{"partition", wrapArgs("foo:bar:zor", ":"), newTestTuple("foo", ":", "bar:zor").ToObject(), nil},
603+
{"rpartition", wrapArgs("foo", ""), nil, mustCreateException(ValueErrorType, "empty separator")},
604+
{"rpartition", wrapArgs("foo", ":"), newTestTuple("", "", "foo").ToObject(), nil},
605+
{"rpartition", wrapArgs(":foo", ":"), newTestTuple("", ":", "foo").ToObject(), nil},
606+
{"rpartition", wrapArgs("foo:", ":"), newTestTuple("foo", ":", "").ToObject(), nil},
607+
{"rpartition", wrapArgs("foo:bar", ":"), newTestTuple("foo", ":", "bar").ToObject(), nil},
608+
{"rpartition", wrapArgs("foo:bar:zor", ":"), newTestTuple("foo:bar", ":", "zor").ToObject(), nil},
592609
{"replace", wrapArgs("one!two!three!", "!", "@", 1), NewStr("one@two!three!").ToObject(), nil},
593610
{"replace", wrapArgs("one!two!three!", "!", ""), NewStr("onetwothree").ToObject(), nil},
594611
{"replace", wrapArgs("one!two!three!", "!", "@", 2), NewStr("one@two@three!").ToObject(), nil},

0 commit comments

Comments
 (0)