-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
de73c5e
commit efde074
Showing
6 changed files
with
370 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package parser | ||
|
||
import ( | ||
"fmt" | ||
l "go-json-parser/lib/lexer" | ||
"go-json-parser/lib/token" | ||
"strconv" | ||
) | ||
|
||
type Parser struct { | ||
lexer *l.Lexer | ||
} | ||
|
||
func NewParser(l *l.Lexer) *Parser { | ||
return &Parser{lexer: l} | ||
} | ||
|
||
func (p *Parser) Parse() (interface{}, error) { | ||
var output interface{} | ||
var err error | ||
|
||
tok := p.lexer.NextToken() | ||
|
||
output, err = p.ParseToken(tok) | ||
|
||
tok = p.lexer.NextToken() | ||
|
||
if tok.Type != token.EOF { | ||
err = fmt.Errorf("expected end of input but found %s", tok.Literal) | ||
} | ||
return output, err | ||
} | ||
|
||
func (p *Parser) ParseToken(tok token.Token) (interface{}, error) { | ||
var value interface{} | ||
var err error | ||
|
||
switch tok.Type { | ||
case token.TRUE: | ||
value = true | ||
case token.FALSE: | ||
value = false | ||
case token.NULL: | ||
value = nil | ||
case token.STRING: | ||
value = tok.Literal | ||
case token.NUMBER: | ||
value, err = strconv.ParseFloat(tok.Literal, 64) | ||
if err != nil { | ||
return value, err | ||
} | ||
case token.OSQRBRACKET: | ||
value, err = p.ParseArray(make([]interface{}, 0)) | ||
case token.OBRACKET: | ||
value, err = p.ParseObject(make(map[string]interface{})) | ||
case token.EOF: | ||
err = fmt.Errorf("unexpected end of input") | ||
default: | ||
err = fmt.Errorf("unknown token %s", tok.Literal) | ||
} | ||
|
||
return value, err | ||
} | ||
|
||
func (p *Parser) ParseObject(obj map[string]interface{}) (interface{}, error) { | ||
var err error | ||
tok := p.lexer.NextToken() | ||
|
||
if tok.Type == token.CBRACKET { | ||
return obj, err | ||
} | ||
|
||
for { | ||
// We expect that the first value is a string | ||
if tok.Type != token.STRING { | ||
return obj, fmt.Errorf("expected key but found %s", tok.Literal) | ||
} | ||
|
||
key := tok.Literal | ||
|
||
tok = p.lexer.NextToken() | ||
// the 2nd is a colon | ||
if tok.Type != token.COLON { | ||
return obj, fmt.Errorf("expected name separate but found %s", tok.Literal) | ||
} | ||
|
||
// 3rd is a value that will be decided for again | ||
tok = p.lexer.NextToken() | ||
value, err := p.ParseToken(tok) | ||
if err != nil { | ||
return obj, err | ||
} | ||
|
||
obj[key] = value | ||
|
||
tok = p.lexer.NextToken() | ||
// we need to make sure that if there is no error there is a comma after it else we break the loop | ||
if tok.Type != token.COMMA { | ||
break | ||
} | ||
|
||
tok = p.lexer.NextToken() | ||
} | ||
|
||
if tok.Type != token.CBRACKET { | ||
err = fmt.Errorf(`expected but } found %s`, tok.Literal) | ||
} | ||
|
||
return obj, err | ||
} | ||
|
||
func (p *Parser) ParseArray(arr []interface{}) (interface{}, error) { | ||
var err error | ||
|
||
tok := p.lexer.NextToken() | ||
|
||
if tok.Type == token.CSQRBRACKET { | ||
return arr, err | ||
} | ||
|
||
for { | ||
value, err := p.ParseToken(tok) | ||
if err != nil { | ||
return arr, err | ||
} | ||
|
||
arr = append(arr, value) | ||
|
||
tok = p.lexer.NextToken() | ||
|
||
if tok.Type != token.COMMA { | ||
break | ||
} | ||
|
||
tok = p.lexer.NextToken() | ||
} | ||
|
||
if tok.Type != token.CSQRBRACKET { | ||
err = fmt.Errorf("expected end of input but found %s", tok.Literal) | ||
} | ||
|
||
return arr, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package parser | ||
|
||
import ( | ||
"go-json-parser/lib/lexer" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestParser(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
input string | ||
expectedValue interface{} | ||
expectError bool | ||
}{ | ||
{ | ||
name: "Empty Input", | ||
input: "", | ||
expectedValue: nil, | ||
expectError: true, | ||
}, | ||
{ | ||
name: "String", | ||
input: `"hello"`, | ||
expectedValue: "hello", | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Number", | ||
input: `42`, | ||
expectedValue: float64(42), | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Invalid Number", | ||
input: `42..5`, | ||
expectedValue: float64(0), | ||
expectError: true, | ||
}, | ||
{ | ||
name: "Boolean", | ||
input: `true`, | ||
expectedValue: true, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Empty Object", | ||
input: "{}", | ||
expectedValue: make(map[string]interface{}), | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Invalid Object (Unbalanced Brace)", | ||
input: "{", | ||
expectedValue: make(map[string]interface{}), | ||
expectError: true, | ||
}, | ||
{ | ||
name: "Invalid Object (Unbalanced Extra Brace)", | ||
input: "{}}", | ||
expectedValue: make(map[string]interface{}), | ||
expectError: true, | ||
}, | ||
{ | ||
name: "Simple Object with String Value", | ||
input: `{"key":"value"}`, | ||
expectedValue: map[string]interface{}{"key": "value"}, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Invalid Simple Object with String Value", | ||
input: `{"key':"value"}`, | ||
expectedValue: make(map[string]interface{}), | ||
expectError: true, | ||
}, | ||
{ | ||
name: "Simple Object with Numeric, Boolean and Null Values", | ||
input: `{ "keyA": "value", "keyB": 42.5, "keyC": true, "keyD": null }`, | ||
expectedValue: map[string]interface{}{"keyA": "value", "keyB": float64(42.5), "keyC": true, "keyD": nil}, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Simple Object Containing Nested Objects", | ||
input: `{"key": {"key2": "value"}, "key3": []}`, | ||
expectedValue: map[string]interface{}{"key": map[string]interface{}{"key2": "value"}, "key3": []interface{}{}}, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Empty Array", | ||
input: "[]", | ||
expectedValue: make([]interface{}, 0), | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Invalid Array (Unbalanced Brace)", | ||
input: "[", | ||
expectedValue: make([]interface{}, 0), | ||
expectError: true, | ||
}, | ||
{ | ||
name: "Invalid Array (Unbalanced Extra Brace)", | ||
input: "[]]", | ||
expectedValue: make([]interface{}, 0), | ||
expectError: true, | ||
}, | ||
{ | ||
name: "Simple Array with String Values", | ||
input: `[ "a", "b", "c" ]`, | ||
expectedValue: []interface{}{"a", "b", "c"}, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Simple Array with Numeric, Boolean, and Null Values", | ||
input: `[ true, false, null, -42.5, "a" ]`, | ||
expectedValue: []interface{}{true, false, nil, float64(-42.5), "a"}, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Array with Object Values", | ||
input: `[ { "key": "a" }, { "key": "b"}, {"key": "c"} ]`, | ||
expectedValue: []interface{}{ | ||
map[string]interface{}{"key": "a"}, | ||
map[string]interface{}{"key": "b"}, | ||
map[string]interface{}{"key": "c"}, | ||
}, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Array with Nested Arrays", | ||
input: `[ ["a"], ["b"], ["c"] ]`, | ||
expectedValue: []interface{}{ | ||
[]interface{}{"a"}, | ||
[]interface{}{"b"}, | ||
[]interface{}{"c"}, | ||
}, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "Complex Object", | ||
input: `{"key": {"key2": "value", "key3": { "key4": [-84.25] } }, "key5": [{"key6": false, "key7": null }] }`, | ||
expectedValue: map[string]interface{}{ | ||
"key": map[string]interface{}{ | ||
"key2": "value", | ||
"key3": map[string]interface{}{ | ||
"key4": []interface{}{float64(-84.25)}, | ||
}, | ||
}, | ||
"key5": []interface{}{ | ||
map[string]interface{}{"key6": false, "key7": nil}, | ||
}, | ||
}, | ||
expectError: false, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
l := lexer.NewLexer(tt.input) | ||
p := NewParser(l) | ||
actualValue, actualError := p.Parse() | ||
if actualError != nil && !tt.expectError { | ||
t.Fatalf("Expected no error but received: %v", actualError) | ||
} | ||
if !reflect.DeepEqual(actualValue, tt.expectedValue) { | ||
t.Fatalf("Expected %T but received %T", tt.expectedValue, actualValue) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.