Skip to content

Commit

Permalink
Minor finishing touches (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
opethe1st committed Aug 27, 2019
1 parent 2be3870 commit 943435f
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 63 deletions.
8 changes: 4 additions & 4 deletions iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ type iterator struct {
offset int
}

func (iter *iterator) getCurrent() byte {
func (iter *iterator) Current() byte {
return iter.s[iter.offset]
}

func (iter *iterator) advance() {
func (iter *iterator) Next() {
iter.offset++
}

func (iter *iterator) isEnd() bool {
return iter.offset >= len(iter.s)
func (iter *iterator) HasNext() bool {
return iter.offset < len(iter.s)
}
130 changes: 76 additions & 54 deletions load.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/*
Package json implements a subset of the json specification defined at https://www.json.org.
Json is a very simple data format that supports serializing primitive types like string and numbers as well as composite ones like
sequences and mappings. You can check https://www.json.org for more details.
This package doesn't implement the entire specification. The only types it supports are string (strings without escaped characters),
sequences (aka arrays) and mapping (aka dictionaries)
Package json implements the json specification.
Check https://www.json.org for more details.
*/
package json

Expand All @@ -23,41 +20,37 @@ func Load(s string) interface{} {
func load(iter *iterator) interface{} {
consumeWhiteSpace(iter)
switch {
case iter.getCurrent() == 'n':
case iter.Current() == 'n':
return loadKeyword(iter, "null", nil)
case iter.getCurrent() == 't':
case iter.Current() == 't':
return loadKeyword(iter, "true", true)
case iter.getCurrent() == 'f':
case iter.Current() == 'f':
return loadKeyword(iter, "false", false)
case isNumber(iter):
return loadNumber(iter)
case iter.getCurrent() == '"':
case iter.Current() == '"':
return loadString(iter)
case iter.getCurrent() == '[':
case iter.Current() == '[':
return loadSequence(iter)
case iter.getCurrent() == '{':
case iter.Current() == '{':
return loadMapping(iter)
default:
end := iter.offset + 100
if len(iter.s) < end {
end = len(iter.s)
}
panic(fmt.Sprintf("There is an error around\n\n%s", iter.s[iter.offset:end]))
panic(errorMessage(iter))
}
}

func loadKeyword(iter *iterator, keyword string, value interface{}) interface{} {
for _, val := range keyword {
if rune(iter.getCurrent()) != val {
if rune(iter.Current()) != val {
panic(fmt.Sprintf("There was an error while reading in %s", keyword))
}
iter.advance()
iter.Next()
}
return value
}

func isNumber(iter *iterator) bool {
switch iter.getCurrent() {
switch iter.Current() {
case '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '0':
return true
}
Expand All @@ -67,59 +60,58 @@ func isNumber(iter *iterator) bool {
func loadNumber(iter *iterator) interface{} {
//negative numbers
sign := 1.0
if iter.getCurrent() == '-' {
if iter.Current() == '-' {
sign = -1.0
iter.advance()
iter.Next()
}
num := 0.0
for !iter.isEnd() && unicode.IsDigit(rune(iter.getCurrent())) {
for iter.HasNext() && unicode.IsDigit(rune(iter.Current())) {
num *= 10
val, _ := strconv.ParseInt(string(iter.getCurrent()), 10, 64)
val, _ := strconv.ParseInt(string(iter.Current()), 10, 64)
num += float64(val)
iter.advance()
iter.Next()
}

// decimal
// some of the code here is a duplicate of what is above, I should consolidate into one function.
consume(iter, '.')
frac := 0.0
power := 0.1
for !iter.isEnd() && unicode.IsDigit(rune(iter.getCurrent())) {
val, _ := strconv.ParseInt(string(iter.getCurrent()), 10, 64)
for iter.HasNext() && unicode.IsDigit(rune(iter.Current())) {
val, _ := strconv.ParseInt(string(iter.Current()), 10, 64)
frac += power * float64(val)
power *= 0.1
iter.advance()
iter.Next()
}

exponent := 0.0
//exponent
if !iter.isEnd() && ((iter.getCurrent() == 'e') || (iter.getCurrent() == 'E')) {
if iter.getCurrent() == 'e' {
if iter.HasNext() && ((iter.Current() == 'e') || (iter.Current() == 'E')) {
if iter.Current() == 'e' {
consume(iter, 'e')
}
if iter.getCurrent() == 'E' {
if iter.Current() == 'E' {
consume(iter, 'E')
}
exponentSign := 1.0
//TODO(ope) this is subtly wrong since it allows +-123234, I will fix later
if !iter.isEnd() && iter.getCurrent() == '+' {
if iter.HasNext() && iter.Current() == '+' {
consume(iter, '+')
}
if !iter.isEnd() && iter.getCurrent() == '-' {
if iter.HasNext() && iter.Current() == '-' {
consume(iter, '-')
exponentSign = -1.0
}
// there needs to be at least one digit after an exponent
exponent = 0.0
for !iter.isEnd() && unicode.IsDigit(rune(iter.getCurrent())) {
for iter.HasNext() && unicode.IsDigit(rune(iter.Current())) {
exponent *= 10
val, _ := strconv.ParseInt(string(iter.getCurrent()), 10, 64)
val, _ := strconv.ParseInt(string(iter.Current()), 10, 64)
exponent += float64(val)
iter.advance()
iter.Next()
}
exponent *= exponentSign
}
fmt.Println(num, frac, exponent)
return (num + frac) * sign * math.Pow(10, exponent)
}

Expand Down Expand Up @@ -160,10 +152,10 @@ func loadString(iter *iterator) string {
'f': 15,
'F': 15,
}
for !iter.isEnd() && (iter.getCurrent() != '"') {
if iter.getCurrent() == '\\' {
iter.advance()
current := iter.getCurrent()
for iter.HasNext() && (iter.Current() != '"') {
if iter.Current() == '\\' {
iter.Next()
current := iter.Current()
switch current {
case '"', '\\', 'b', 'f', 'n', 'r', 't':
s = append(s, mapping[rune(current)])
Expand All @@ -172,20 +164,19 @@ func loadString(iter *iterator) string {
var ans rune
// I should make sure these are valid hex digits btw, but will leave it for error reporting
for i := 0; i < 4; i++ {
iter.advance() // move past the 'u'
fmt.Println(i, ans, string(iter.getCurrent()))
iter.Next() // move past the 'u'
ans = ans * 16
ans += convertToDecimal[rune(iter.getCurrent())]
ans += convertToDecimal[rune(iter.Current())]
}
s = append(s, ans)
default:
s = append(s, rune('\\'))
s = append(s, rune(iter.getCurrent()))
s = append(s, rune(iter.Current()))
}
} else {
s = append(s, rune(iter.getCurrent()))
s = append(s, rune(iter.Current()))
}
iter.advance()
iter.Next()
}
consume(iter, '"')
return string(s)
Expand All @@ -195,11 +186,11 @@ func loadSequence(iter *iterator) []interface{} {
seq := make([]interface{}, 0)
consume(iter, '[')
var item interface{}
for !iter.isEnd() && (iter.getCurrent() != ']') {
for iter.HasNext() && (iter.Current() != ']') {
item = load(iter)
seq = append(seq, item)
consumeWhiteSpace(iter)
if iter.getCurrent() == ']' {
if iter.Current() == ']' {
break
}
consume(iter, ',')
Expand All @@ -213,14 +204,15 @@ func loadMapping(iter *iterator) map[string]interface{} {
mapping := make(map[string]interface{}, 0)
consume(iter, '{')
var key, value interface{}
for !iter.isEnd() && (iter.s[iter.offset] != '}') {
for iter.HasNext() && (iter.Current() != '}') {
key = load(iter)
consumeWhiteSpace(iter)
consume(iter, ':')
consumeWhiteSpace(iter)
value = load(iter)
mapping[key.(string)] = value
if iter.getCurrent() == '}' {
consumeWhiteSpace(iter)
if iter.Current() == '}' {
break
}
consume(iter, ',')
Expand All @@ -233,14 +225,44 @@ func loadMapping(iter *iterator) map[string]interface{} {
// utils - this could be in a separate file

func consumeWhiteSpace(iter *iterator) {
for !iter.isEnd() && unicode.IsSpace(rune(iter.getCurrent())) {
iter.advance()
for iter.HasNext() && unicode.IsSpace(rune(iter.Current())) {
iter.Next()
}
}

func consume(iter *iterator, char byte) {
// actually should probably raise an error if char isn't consumed
if !iter.isEnd() && iter.getCurrent() == char {
iter.advance()
if iter.HasNext() && iter.Current() == char {
iter.Next()
}
// add this else part later - this is part of better error handling
// else {
// panic(fmt.Sprintf("Expected %q but got %q", char, iter.Current())+errorMessage(iter))
// }
}

func errorMessage(iter *iterator) string {
startBefore := iter.offset - 50
if startBefore < 0 {
startBefore = 0
}
endBefore := iter.offset
if endBefore < 0 {
endBefore = 0
}
before := iter.s[startBefore:endBefore]

startAfter := iter.offset + 1
if startAfter > len(iter.s) {
startAfter = len(iter.s)
}
endAfter := iter.offset + 50
if endAfter > len(iter.s) {
endAfter = len(iter.s)
}
after := iter.s[startAfter:endAfter]
// this doesnt work well if the character to underline is a whitespace
return fmt.Sprintf(`There is an error around
%s%s%s
`, before, string([]byte{iter.Current(), 204, 179}), after)
}
3 changes: 1 addition & 2 deletions load_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package json

import (
"github.com/stretchr/testify/assert"
"math"
"testing"

"github.com/stretchr/testify/assert"
)

type TestCase struct {
Expand Down
6 changes: 3 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ I wrote this to practise Go.
What's left?
* Handle whitespace ✅
* Error handling
* Handle boolean and null literals
* Handle number literals
* Handle string escapes
* Handle boolean and null literals
* Handle number literals
* Handle string escapes
* Write the dump function - > (easy? how do we know the type of the object being pointed to?)
* Benchmark against the standard libraries implementation
* Study the standard libraries implementation
Expand Down

0 comments on commit 943435f

Please sign in to comment.