Skip to content

Commit

Permalink
main cli component
Browse files Browse the repository at this point in the history
  • Loading branch information
NickSolante committed Nov 23, 2024
1 parent de73c5e commit efde074
Show file tree
Hide file tree
Showing 6 changed files with 370 additions and 22 deletions.
23 changes: 11 additions & 12 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,23 @@ name: Go

on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
branches: ["main"]

jobs:

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.23"

- name: Build
run: go build -v ./...
- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
- name: Test
run: go test -v ./...
2 changes: 1 addition & 1 deletion lib/lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Lexer struct {
ch byte
}

func New(input string) *Lexer {
func NewLexer(input string) *Lexer {
l := &Lexer{input: input}
l.readChar()
return l
Expand Down
12 changes: 5 additions & 7 deletions lib/lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestLexer(t *testing.T) {
{token.CBRACKET, "}"},
}

l := New(input)
l := NewLexer(input)

for i, tt := range tests {
tok := l.NextToken()
Expand All @@ -32,18 +32,17 @@ func TestLexer(t *testing.T) {
}
})

t.Run("invalid json", func(t *testing.T) {
input := ""
t.Run("string hello", func(t *testing.T) {
input := `"train"`

tests := []struct {
expectedType token.TokenType
expectedLiteral string
}{
{token.OBRACKET, "{"},
{token.CBRACKET, "}"},
{token.STRING, "train"},
}

l := New(input)
l := NewLexer(input)

for i, tt := range tests {
tok := l.NextToken()
Expand All @@ -56,6 +55,5 @@ func TestLexer(t *testing.T) {
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", i, tt.expectedLiteral, tok.Literal)
}
}

})
}
143 changes: 143 additions & 0 deletions lib/parser/parser.go
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
}
169 changes: 169 additions & 0 deletions lib/parser/parser_test.go
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)
}
})
}
}
Loading

0 comments on commit efde074

Please sign in to comment.