Skip to content

Commit c7e59fb

Browse files
authored
Implement iterator (#1)
* Implement iterator * .
1 parent 2978a89 commit c7e59fb

File tree

4 files changed

+133
-113
lines changed

4 files changed

+133
-113
lines changed

iterator.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package json
2+
3+
4+
type iterator struct{
5+
s string
6+
offset int
7+
}
8+
9+
func (iter *iterator) getCurrent() byte {
10+
return iter.s[iter.offset]
11+
}
12+
13+
func (iter *iterator) advance() {
14+
iter.offset++
15+
}
16+
17+
func (iter *iterator) isEnd() bool {
18+
return iter.offset >= len(iter.s)
19+
}

load.go

Lines changed: 57 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -14,106 +14,96 @@ import (
1414

1515
// Load is used load an object from a string
1616
func Load(s string) interface{} {
17-
_, value := load(s, 0)
18-
return value
17+
iter := &iterator{s:s}
18+
return load(iter)
1919
}
2020

21-
func load(s string, current int) (int, interface{}) {
22-
current = consumeWhiteSpace(s, current)
21+
22+
func load(iter *iterator) interface{} {
23+
consumeWhiteSpace(iter)
2324
switch {
24-
case isString(s, current):
25-
return loadString(s, current)
26-
case isSequence(s, current):
27-
return loadSequence(s, current)
28-
case isMapping(s, current):
29-
return loadMapping(s, current)
25+
case iter.getCurrent() == '"':
26+
return loadString(iter)
27+
case iter.getCurrent() == '[':
28+
return loadSequence(iter)
29+
case iter.getCurrent() == '{':
30+
return loadMapping(iter)
3031
default:
31-
end := current + 100
32-
if len(s) < end {
33-
end = len(s)
32+
end := iter.offset + 100
33+
if len(iter.s) < end {
34+
end = len(iter.s)
3435
}
35-
panic(fmt.Sprintf("There is an error around\n\n%s", s[current:end]))
36+
panic(fmt.Sprintf("There is an error around\n\n%s", iter.s[iter.offset:end]))
3637
}
3738
}
3839

3940
// strings
4041

41-
func isString(s string, current int) bool {
42-
return s[current] == '"'
43-
}
44-
45-
func loadString(s string, current int) (int, interface{}) {
46-
// actually should probably raise an error if '"' isn't consumed
47-
start := consume(s, current, '"')
48-
current = start
49-
for current < len(s) && s[current] != '"' {
50-
current++
42+
func loadString(iter *iterator) interface{} {
43+
consume(iter, '"')
44+
start := iter.offset
45+
for !iter.isEnd() && (iter.getCurrent() != '"') {
46+
iter.advance()
5147
}
52-
// and current + 1 since the next not visited character in s is at current + 1
53-
return current + 1, s[start:current]
48+
end := iter.offset
49+
consume(iter, '"')
50+
return iter.s[start:end]
5451
}
5552

5653
// sequences
5754

58-
func isSequence(s string, current int) bool {
59-
return s[current] == '['
60-
}
61-
62-
func loadSequence(s string, current int) (int, []interface{}) {
55+
func loadSequence(iter *iterator) []interface{} {
6356
seq := make([]interface{}, 0)
64-
// actually should probably raise an error if '[' isn't consumed
65-
current = consume(s, current, '[')
57+
consume(iter, '[')
6658
var item interface{}
67-
for (current < len(s)) && (s[current] != ']') {
68-
current, item = load(s, current)
69-
current = consumeWhiteSpace(s, current)
70-
// technically not allowed to have ["key",] but it is currently allowed
71-
current = consume(s, current, ',')
72-
current = consumeWhiteSpace(s, current)
59+
for !iter.isEnd() && (iter.getCurrent() != ']') {
60+
item = load(iter)
7361
seq = append(seq, item)
74-
current = consumeWhiteSpace(s, current)
62+
consumeWhiteSpace(iter)
63+
if iter.getCurrent() == ']'{
64+
break
65+
}
66+
consume(iter, ',')
67+
consumeWhiteSpace(iter)
7568
}
76-
return current + 1, seq
69+
consume(iter, ']')
70+
return seq
7771
}
7872

7973
// mappings
8074

81-
func isMapping(s string, current int) bool {
82-
return s[current] == '{'
83-
}
84-
85-
func loadMapping(s string, current int) (int, map[string]interface{}) {
75+
func loadMapping(iter *iterator) map[string]interface{} {
8676
mapping := make(map[string]interface{}, 0)
87-
// actually should probably raise an error if '{' isn't consumed
88-
current = consume(s, current, '{')
77+
consume(iter, '{')
8978
var key, value interface{}
90-
for (current < len(s)) && (s[current] != '}') {
91-
current, key = load(s, current)
92-
current = consumeWhiteSpace(s, current)
93-
current = consume(s, current, ':')
94-
current = consumeWhiteSpace(s, current)
95-
current, value = load(s, current)
79+
for !iter.isEnd() && (iter.s[iter.offset] != '}') {
80+
key = load(iter)
81+
consumeWhiteSpace(iter)
82+
consume(iter, ':')
83+
consumeWhiteSpace(iter)
84+
value = load(iter)
9685
mapping[key.(string)] = value
97-
// technically not allowed to have {"key":"value",} but it is currently allowed
98-
current = consume(s, current, ',')
99-
current = consumeWhiteSpace(s, current)
86+
if iter.getCurrent() == '}'{
87+
break
88+
}
89+
consume(iter, ',')
90+
consumeWhiteSpace(iter)
10091
}
101-
return current + 1, mapping
92+
consume(iter, '}')
93+
return mapping
10294
}
10395

10496
// utils - this could be in a separate file
10597

106-
// consumeWhiteSpace returns the next index after current such that s[index] is not whitespace
107-
func consumeWhiteSpace(s string, current int) int {
108-
for current < len(s) && unicode.IsSpace(rune(s[current])) {
109-
current++
98+
func consumeWhiteSpace(iter *iterator){
99+
for !iter.isEnd() && unicode.IsSpace(rune(iter.getCurrent())) {
100+
iter.advance()
110101
}
111-
return current
112102
}
113103

114-
func consume(s string, current int, char byte) int {
115-
if s[current] == char {
116-
return current + 1
104+
func consume(iter *iterator, char byte) {
105+
// actually should probably raise an error if char isn't consumed
106+
if iter.getCurrent() == char {
107+
iter.advance()
117108
}
118-
return current
119109
}

load_test.go

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,10 @@ func TestLoad(t *testing.T) {
3636

3737
for _, testcase := range testCases {
3838
output := Load(testcase.input)
39-
assert.Equal(output, testcase.expectedOutput, "These should be equal")
39+
assert.Equal(testcase.expectedOutput, output)
4040
}
4141
}
4242

43-
func TestIsString(t *testing.T) {
44-
testCases := []TestCase{
45-
{`This is a string`, false},
46-
{`"Key"`, true},
47-
}
48-
for _, testcase := range testCases {
49-
if output := isString(testcase.input, 0); output != testcase.expectedOutput {
50-
t.Errorf("Expected isString (%v) to be %t but got %t", testcase.input, testcase.expectedOutput, output)
51-
}
52-
}
53-
}
5443

5544
func TestLoadString(t *testing.T) {
5645
testCases := []TestCase{
@@ -60,8 +49,9 @@ func TestLoadString(t *testing.T) {
6049
{`"Key" `, "Key"},
6150
}
6251
for _, testcase := range testCases {
63-
if _, output := loadString(testcase.input, 0); output != testcase.expectedOutput {
64-
t.Errorf("Expected loadString (%v) to be %v but got %v", testcase.input, testcase.expectedOutput, output)
52+
iter := &iterator{s:testcase.input}
53+
if output := loadString(iter); output != testcase.expectedOutput {
54+
t.Errorf("Expected loadString(%v) to be %v but got %v", iter, testcase.expectedOutput, output)
6555
}
6656
}
6757

@@ -71,23 +61,13 @@ func TestLoadString(t *testing.T) {
7161
{`"Key" `, 5},
7262
}
7363
for _, testcase := range testCases {
74-
if output, _ := loadString(testcase.input, 0); output != testcase.expectedOutput {
75-
t.Errorf("Expected loadString (%v) to be %v but got %v", testcase.input, testcase.expectedOutput, output)
64+
iter := &iterator{s:testcase.input}
65+
if loadString(iter); iter.offset != testcase.expectedOutput {
66+
t.Errorf("Expected loadString(%v) to be %v but got %v", iter, testcase.expectedOutput, iter.offset)
7667
}
7768
}
7869
}
7970

80-
func TestIsSequence(t *testing.T) {
81-
testCases := []TestCase{
82-
{`[]`, true},
83-
{`"Key"`, false},
84-
}
85-
for _, testcase := range testCases {
86-
if output := isSequence(testcase.input, 0); output != testcase.expectedOutput {
87-
t.Errorf("Expected isSequence (%v) to be %t but got %t", testcase.input, testcase.expectedOutput, output)
88-
}
89-
}
90-
}
9171

9272
func TestLoadSequence(t *testing.T) {
9373
assert := assert.New(t)
@@ -100,20 +80,9 @@ func TestLoadSequence(t *testing.T) {
10080
{`["v1", {"v2": "v3"}]`, []interface{}{"v1", map[string]interface{}{"v2": "v3"}}},
10181
}
10282
for _, testcase := range testCases {
103-
_, output := loadSequence(testcase.input, 0)
104-
assert.Equal(output, testcase.expectedOutput, "Expected loadSequence(%v) to be %v but got %v", testcase.input, testcase.expectedOutput, output)
105-
}
106-
}
107-
108-
func TestIsMapping(t *testing.T) {
109-
testCases := []TestCase{
110-
{`[]`, false},
111-
{`{`, true},
112-
}
113-
for _, testcase := range testCases {
114-
if output := isMapping(testcase.input, 0); output != testcase.expectedOutput {
115-
t.Errorf("Expected isSequence (%v) to be %t but got %t", testcase.input, testcase.expectedOutput, output)
116-
}
83+
iter := &iterator{s:testcase.input}
84+
output := loadSequence(iter)
85+
assert.Equal(testcase.expectedOutput, output, "Expected loadSequence(%v) to be %v but got %v", iter, testcase.expectedOutput, output)
11786
}
11887
}
11988

@@ -126,20 +95,22 @@ func TestLoadMapping(t *testing.T) {
12695
{`{"v1": ["v2", "v3"]}`, map[string]interface{}{"v1": []interface{}{"v2", "v3"}}},
12796
}
12897
for _, testcase := range testCases {
129-
_, output := loadMapping(testcase.input, 0)
130-
assert.Equal(output, testcase.expectedOutput, "Expected loadMapping(%v) to be %v but got %v", testcase.input, testcase.expectedOutput, output)
98+
iter := &iterator{s:testcase.input}
99+
output := loadMapping(iter)
100+
assert.Equal(testcase.expectedOutput, output, "Expected loadMapping(%v) to be %v but got %v", iter, testcase.expectedOutput, output)
131101
}
132102
}
133103

134104
func TestConsumeSpaces(t *testing.T) {
105+
assert := assert.New(t)
135106
testCases := []TestCase{
136107
{`"key"`, 0},
137108
{` "key" `, 1},
138109
{` "key"`, 3},
139110
}
140111
for _, testcase := range testCases {
141-
if output := consumeWhiteSpace(testcase.input, 0); output != testcase.expectedOutput {
142-
t.Errorf("Expected consumeWhiteSpace (%v) to be %d but got %d", testcase.input, testcase.expectedOutput, output)
143-
}
112+
iter := &iterator{s:testcase.input}
113+
consumeWhiteSpace(iter)
114+
assert.Equal(testcase.expectedOutput, iter.offset, "Expected consumeWhiteSpace(%v) to be %d but got %d", iter, testcase.expectedOutput, iter.offset)
144115
}
145116
}

log.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## Learnings
2+
3+
I learned that Go's "=" is not good enough for asserting equality of nested objects when writing tests. This was disappointing because it meant I needed to
4+
use a third party library.
5+
I learned that you need to be careful about using ":=" in a loop if it shadows an outer variable. I lost a couple of hours to trying to debug this
6+
7+
P.s I skimmed through the official implementation and it feels like I could do a better job (Is it hard to follow or it is just me?)
8+
Plus lots of state. My implementation probably suffers from using recursion though
9+
10+
11+
### Random thoughts
12+
Not use recursion? - is that possible?
13+
14+
After looking at the code, I realize that instead of passing (s, current) into virtually every function, I could create a scanner type
15+
and have these functions be methods in this type.
16+
```golang
17+
type Scanner struct{
18+
s string
19+
current int
20+
}
21+
22+
func (s *Scanner) consumeWhiteSpace(){
23+
24+
}
25+
26+
func (s *Scanner) isMappingStart(){
27+
28+
}
29+
30+
func (s *scanner) isMappingEnd(){
31+
32+
}
33+
```
34+
Other thing is to introduce the concept of tokens - only needed for strings? Since that's where you can have escaped characters!
35+
36+
So initially, I introduced the concept of scanners but then I realized that it didn't fit completely.
37+
So I discovered the concept that was missing was the concept of iterators. A iterator is simply - struct{s string, offset int}
38+
And this made other stuff make sense since I moved the stuff that didn't make sense in the iterator to standalone functions that instead of (s, current)
39+
made use of the iterator. The other interesting thing is that I violated Command Query Responsibility Segregation (CQRS) and it was the right decision.
40+
Know when to break the rules!

0 commit comments

Comments
 (0)