Skip to content

Commit d831b11

Browse files
committed
py: string: make __getitem__ and __contains__ work and unit tests
1 parent d95eafb commit d831b11

File tree

8 files changed

+968
-45
lines changed

8 files changed

+968
-45
lines changed

notes.txt

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ Limitations
1212
* \N{...} escapes not implemented
1313
* Interactive interpreter does single lines only
1414
* compile(..., "single") not working
15-
* string[x] wrong for unicode strings (easy fix)
1615
* lots of builtins still to implement
1716

1817
Todo

py/int.go

+5
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ func convertToInt(other Object) (Int, bool) {
200200
} else {
201201
return Int(0), true
202202
}
203+
// case Float:
204+
// ib := Int(b)
205+
// if Float(ib) == b {
206+
// return ib, true
207+
// }
203208
}
204209
return 0, false
205210
}

py/internal.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func IndexIntCheck(a Object, max int) (int, error) {
103103
i += max
104104
}
105105
if i < 0 || i >= max {
106-
return 0, ExceptionNewf(IndexError, "list index out of range")
106+
return 0, ExceptionNewf(IndexError, "index out of range")
107107
}
108108
return i, nil
109109
}

py/sequence.go

+12
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ func Send(self, value Object) (Object, error) {
113113

114114
// SequenceContains returns True if obj is in seq
115115
func SequenceContains(seq, obj Object) (found bool, err error) {
116+
if I, ok := seq.(I__contains__); ok {
117+
result, err := I.M__contains__(obj)
118+
if err != nil {
119+
return false, err
120+
}
121+
return result == True, nil
122+
} else if result, ok, err := TypeCall1(seq, "__contains__", obj); ok {
123+
if err != nil {
124+
return false, err
125+
}
126+
return result == True, nil
127+
}
116128
var loopErr error
117129
err = Iterate(seq, func(item Object) bool {
118130
var eq Object

py/string.go

+77-11
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
package py
1010

1111
import (
12+
"bytes"
1213
"fmt"
14+
"strings"
1315
"unicode/utf8"
1416
)
1517

@@ -42,8 +44,13 @@ func (s String) M__bool__() (Object, error) {
4244
return NewBool(len(s) > 0), nil
4345
}
4446

47+
// len returns length of the string in unicode characters
48+
func (s String) len() int {
49+
return utf8.RuneCountInString(string(s))
50+
}
51+
4552
func (s String) M__len__() (Object, error) {
46-
return Int(utf8.RuneCountInString(string(s))), nil
53+
return Int(s.len()), nil
4754
}
4855

4956
func (a String) M__add__(other Object) (Object, error) {
@@ -66,11 +73,14 @@ func (a String) M__iadd__(other Object) (Object, error) {
6673

6774
func (a String) M__mul__(other Object) (Object, error) {
6875
if b, ok := convertToInt(other); ok {
69-
newString := String("")
76+
if b < 0 {
77+
b = 0
78+
}
79+
var out bytes.Buffer
7080
for i := 0; i < int(b); i++ {
71-
newString += a
81+
out.WriteString(string(a))
7282
}
73-
return newString, nil
83+
return String(out.String()), nil
7484
}
7585
return NotImplemented, nil
7686
}
@@ -164,28 +174,83 @@ func (a String) M__imod__(other Object) (Object, error) {
164174
return a.M__mod__(other)
165175
}
166176

177+
// Returns position in string of n-th character
178+
//
179+
// returns end of string if not found
180+
func (s String) pos(n int) int {
181+
characterNumber := 0
182+
for i, _ := range s {
183+
if characterNumber == n {
184+
return i
185+
}
186+
characterNumber++
187+
}
188+
return len(s)
189+
}
190+
191+
// slice returns the slice of this string using character positions
192+
//
193+
// length should be the length of the string in unicode characters
194+
func (s String) slice(start, stop, length int) String {
195+
if start >= stop {
196+
return String("")
197+
}
198+
if length == len(s) {
199+
return s[start:stop] // ascii only
200+
}
201+
if start <= 0 && stop >= length {
202+
return s
203+
}
204+
startI := s.pos(start)
205+
stopI := s[startI:].pos(stop-start) + startI
206+
return s[startI:stopI]
207+
}
208+
167209
func (s String) M__getitem__(key Object) (Object, error) {
168-
// FIXME doesn't take into account unicode yet - ASCII only!!!
210+
length := s.len()
211+
asciiOnly := length == len(s)
169212
if slice, ok := key.(*Slice); ok {
170-
start, stop, step, slicelength, err := slice.GetIndices(len(s))
213+
start, stop, step, slicelength, err := slice.GetIndices(length)
171214
if err != nil {
172215
return nil, err
173216
}
174217
if step == 1 {
175218
// Return a subslice since strings are immutable
176-
return s[start:stop], nil
219+
return s.slice(start, stop, length), nil
220+
}
221+
if asciiOnly {
222+
newString := make([]byte, slicelength)
223+
for i, j := start, 0; j < slicelength; i, j = i+step, j+1 {
224+
newString[j] = s[i]
225+
}
226+
return String(newString), nil
177227
}
178-
newString := make([]byte, slicelength)
228+
// Unpack the string into a []rune to do this for speed
229+
runeString := []rune(string(s))
230+
newString := make([]rune, slicelength)
179231
for i, j := start, 0; j < slicelength; i, j = i+step, j+1 {
180-
newString[j] = s[i]
232+
newString[j] = runeString[i]
181233
}
182234
return String(newString), nil
183235
}
184-
i, err := IndexIntCheck(key, len(s))
236+
i, err := IndexIntCheck(key, length)
185237
if err != nil {
186238
return nil, err
187239
}
188-
return s[i : i+1], nil
240+
if asciiOnly {
241+
return s[i : i+1], nil
242+
}
243+
s = s[s.pos(i):]
244+
_, runeSize := utf8.DecodeRuneInString(string(s))
245+
return s[:runeSize], nil
246+
}
247+
248+
func (s String) M__contains__(item Object) (Object, error) {
249+
needle, ok := item.(String)
250+
if !ok {
251+
return nil, ExceptionNewf(TypeError, "'in <string>' requires string as left operand, not %s", item.Type().Name)
252+
}
253+
return NewBool(strings.Contains(string(s), string(needle))), nil
189254
}
190255

191256
// Check stringerface is satisfied
@@ -197,3 +262,4 @@ var _ I__imod__ = String("")
197262
var _ I__len__ = String("")
198263
var _ I__bool__ = String("")
199264
var _ I__getitem__ = String("")
265+
var _ I__contains__ = String("")

py/tests/int.py

+24-32
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
1+
from libtest import assertRaises
2+
13
tenE5 = 10**5
24
tenE30 = 10**30
35
minInt = -9223372036854775807 - 1
46
negativeMinInt = 9223372036854775808
57
minIntMinus1 = -9223372036854775809
68
maxInt = 9223372036854775807
79
maxIntPlus1 = 9223372036854775808
8-
def assertRaises(expecting, s, base=None):
9-
try:
10-
if base is None:
11-
int(s)
12-
else:
13-
int(s, base=base)
14-
except expecting as e:
15-
pass
16-
else:
17-
assert False, "%s not raised" % (expecting,)
1810

1911
doc="test overflow"
2012
assert int("1000000000000000000") == 10**18
@@ -100,38 +92,38 @@ def assertRaises(expecting, s, base=None):
10092
assert int("F", 16) == 15
10193
assert int("0xF", 16) == 15
10294
assert int("0XF", 0) == 15
103-
assertRaises(ValueError, "0xF", 10)
95+
assertRaises(ValueError, int, "0xF", 10)
10496

10597
assert int("77", 8) == 63
10698
assert int("0o77", 0) == 63
10799
assert int("0O77", 8) == 63
108-
assertRaises(ValueError, "0o77", 10)
100+
assertRaises(ValueError, int, "0o77", 10)
109101

110102
assert int("11", 2) == 3
111103
assert int("0b11", 0) == 3
112104
assert int("0B11", 2) == 3
113-
assertRaises(ValueError, "0b11", 10)
105+
assertRaises(ValueError, int, "0b11", 10)
114106

115107
doc="errors"
116-
assertRaises(ValueError, "07", 0)
117-
assertRaises(ValueError, "", 0)
118-
assertRaises(ValueError, " ", 0)
119-
assertRaises(ValueError, "+", 0)
120-
assertRaises(ValueError, "-", 0)
121-
assertRaises(ValueError, "0x", 0)
122-
assertRaises(ValueError, "+ 1", 0)
123-
assertRaises(ValueError, "- 1", 0)
124-
assertRaises(ValueError, "a", 0)
125-
assertRaises(ValueError, "a", 10)
126-
assertRaises(ValueError, "£", 0)
127-
assertRaises(ValueError, "100000000000000000000000000000000000000000000000000000a", 0)
128-
assertRaises(ValueError, "10", base=1)
129-
assertRaises(ValueError, "10", base=-1)
130-
assertRaises((OverflowError, ValueError), "10", base=100000000000000000000000000000000000000000000)
131-
assertRaises(TypeError, 1.5, 10)
132-
assertRaises(TypeError, 1.5, 0)
133-
assertRaises(TypeError, ...)
134-
assertRaises(TypeError, ..., 10)
108+
assertRaises(ValueError, int, "07", 0)
109+
assertRaises(ValueError, int, "", 0)
110+
assertRaises(ValueError, int, " ", 0)
111+
assertRaises(ValueError, int, "+", 0)
112+
assertRaises(ValueError, int, "-", 0)
113+
assertRaises(ValueError, int, "0x", 0)
114+
assertRaises(ValueError, int, "+ 1", 0)
115+
assertRaises(ValueError, int, "- 1", 0)
116+
assertRaises(ValueError, int, "a", 0)
117+
assertRaises(ValueError, int, "a", 10)
118+
assertRaises(ValueError, int, "£", 0)
119+
assertRaises(ValueError, int, "100000000000000000000000000000000000000000000000000000a", 0)
120+
assertRaises(ValueError, int, "10", base=1)
121+
assertRaises(ValueError, int, "10", base=-1)
122+
assertRaises((OverflowError, ValueError), int, "10", base=100000000000000000000000000000000000000000000)
123+
assertRaises(TypeError, int, 1.5, 10)
124+
assertRaises(TypeError, int, 1.5, 0)
125+
assertRaises(TypeError, int, ...)
126+
assertRaises(TypeError, int, ..., 10)
135127

136128
doc="conversions"
137129
i = int(1E30)

py/tests/libtest.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""
2+
Simple test harness
3+
"""
4+
5+
def assertRaises(expecting, fn, *args, **kwargs):
6+
"""Check the exception was raised - don't check the text"""
7+
try:
8+
fn(*args, **kwargs)
9+
except expecting as e:
10+
pass
11+
else:
12+
assert False, "%s not raised" % (expecting,)
13+
14+
def assertRaisesText(expecting, text, fn, *args, **kwargs):
15+
"""Check the exception with text in is raised"""
16+
try:
17+
fn(*args, **kwargs)
18+
except expecting as e:
19+
assert text in e.args[0], "'%s' not found in '%s'" % (text, e.args[0])
20+
else:
21+
assert False, "%s not raised" % (expecting,)

0 commit comments

Comments
 (0)