Skip to content

Commit

Permalink
initial commit (outputs valid/invalid file)
Browse files Browse the repository at this point in the history
  • Loading branch information
melsonic committed Jun 19, 2024
0 parents commit 4a49527
Show file tree
Hide file tree
Showing 24 changed files with 485 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
*jp
*algo.md


# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test

# Vendor directory (for dependencies)
vendor/

# Logs
logs/

# IDE/editor specific files
.vscode/
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
build:
go build -o jp main.go

test:
go build -o jp main.go && ./test.sh

run:
go run main.go demo.json
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
### JSON PARSER (from the coding challenges)

- A JSON parser comprises mainly two parts:
- **Lexical Analysis**: In this step, the input is converted into an array of tokens.
- **Syntactic Analysis**: In this step, the tokens are parsed to check whether they align with the rules of the specified language.

### Application Details

- Currently, the software outputs whether the given JSON file is valid or not.
- To test the application yourself, you can put your JSON content inside the `demo.json` file and run `make run`. It will tell you whether the content is valid JSON or not.
- Additionally, there are plenty of test cases inside the `tests` directory. You can run them individually using the command `go run main.go relative-filepath`.
- I have also prepared a bash script for running all the test cases inside the `tests` directory. You can run it using either the `./tests.sh` command, but first make sure you run `make build`. You can also run `make test` which is just an addition of these two commands.

### Working of the Application

- In the first step, the input file content is broken into an array of tokens.
- In the next step, the tokens are cleaned up by trimming the whitespaces around them.
- The tokens are then passed into a parser function that parses the tokens to see if they align with the specified language rules.
70 changes: 70 additions & 0 deletions demo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
[
{
"id": "0001",
"type": "donut",
"name": "Cake",
"ppu": 0.55,
"batters":
{
"batter":
[
{ "id": "1001", "type": "Regular" },
{ "id": "1002", "type": "Chocolate" },
{ "id": "1003", "type": "Blueberry" },
{ "id": "1004", "type": "Devil's Food" }
]
},
"topping":
[
{ "id": "5001", "type": "None" },
{ "id": "5002", "type": "Glazed" },
{ "id": "5005", "type": "Sugar" },
{ "id": "5007", "type": "Powdered Sugar" },
{ "id": "5006", "type": "Chocolate with Sprinkles" },
{ "id": "5003", "type": "Chocolate" },
{ "id": "5004", "type": "Maple" }
]
},
{
"id": "0002",
"type": "donut",
"name": "Raised",
"ppu": 0.55,
"batters":
{
"batter":
[
{ "id": "1001", "type": "Regular" }
]
},
"topping":
[
{ "id": "5001", "type": "None" },
{ "id": "5002", "type": "Glazed" },
{ "id": "5005", "type": "Sugar" },
{ "id": "5003", "type": "Chocolate" },
{ "id": "5004", "type": "Maple" }
]
},
{
"id": "0003",
"type": "donut",
"name": "Old Fashioned",
"ppu": 0.55,
"batters":
{
"batter":
[
{ "id": "1001", "type": "Regular" },
{ "id": "1002", "type": "Chocolate" }
]
},
"topping":
[
{ "id": "5001", "type": "None" },
{ "id": "5002", "type": "Glazed" },
{ "id": "5003", "type": "Chocolate" },
{ "id": "5004", "type": "Maple" }
]
}
]
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/melsonic/json-parser

go 1.21.6

require github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
27 changes: 27 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

// _
// (_)___ ___ _ __ _ __ __ _ _ __ ___ ___ _ __
// | / __|/ _ \| '_ \ _____| '_ \ / _` | '__/ __|/ _ \ '__|
// | \__ \ (_) | | | |_____| |_) | (_| | | \__ \ __/ |
// _/ |___/\___/|_| |_| | .__/ \__,_|_| |___/\___|_|
// |__/ |_|
//

import (
"log"
"os"

"github.com/melsonic/json-parser/util"
)

func main() {
var result bool = false
jsonFileName := os.Args[1]
content, err := os.ReadFile(jsonFileName)
if err != nil {
log.Fatal(err)
}
result = util.Validate(content)
util.PrintResult(result)
}
19 changes: 19 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

for file in tests/*
do
if [ -d $file ]
then
for rfile in $file/*
do
echo "$rfile"
./jp $rfile
echo ""
done
else
echo "$file"
./jp $file
echo ""
fi
done

Empty file added tests/step1/invalid.json
Empty file.
1 change: 1 addition & 0 deletions tests/step1/valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions tests/step2/invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"key": "value",}
4 changes: 4 additions & 0 deletions tests/step2/invalid2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"key": "value",
key2: "value"
}
1 change: 1 addition & 0 deletions tests/step2/valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"key": "value"}
4 changes: 4 additions & 0 deletions tests/step2/valid2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"key": "value",
"key2": "value"
}
7 changes: 7 additions & 0 deletions tests/step3/invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"key1": true,
"key2": False,
"key3": null,
"key4": "value",
"key5": 101
}
7 changes: 7 additions & 0 deletions tests/step3/valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"key1": true,
"key2": false,
"key3": null,
"key4": "value",
"key5": 101
}
8 changes: 8 additions & 0 deletions tests/step4/invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"key": "value",
"key-n": 101,
"key-o": {
"inner key": "inner value"
},
"key-l": ['list value']
}
6 changes: 6 additions & 0 deletions tests/step4/valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"key": "value",
"key-n": 101,
"key-o": {},
"key-l": []
}
8 changes: 8 additions & 0 deletions tests/step4/valid2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"key": "value",
"key-n": 101,
"key-o": {
"inner key": "inner value"
},
"key-l": ["list value"]
}
32 changes: 32 additions & 0 deletions util/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package util

var (
EmptyLineByte byte = byte('\n')
WhiteSpaceByte byte = byte(' ')
ColonByte byte = byte(':')
CommaByte byte = byte(',')
LeftCurlyBraceByte byte = byte('{')
RightCurlyBraceByte byte = byte('}')
LeftSquareBracketByte byte = byte('[')
RightSquareBracketByte byte = byte(']')
DoubleQuoteByte byte = byte('"')
BackSlashByte byte = byte('\\')
ForwardSlashByte byte = byte('/')
)

var (
LeftCurlyBrace string = "{"
RightCurlyBrace string = "}"
LeftSquareBrace string = "["
RightSquareBrace string = "]"
Comma string = ","
Colon string = ":"
DoubleQuote string = "\""
)

var BackSlashRune rune = rune('\\')

var (
CheckSlice = []byte{LeftCurlyBraceByte, LeftSquareBracketByte, ColonByte, CommaByte, RightSquareBracketByte, RightCurlyBraceByte}
AllowedCharsAfterEscapeChar = []rune{'"', '\\', '/', 'b', 'f', 'n', 'r', 't'}
)
41 changes: 41 additions & 0 deletions util/lexer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package util

func Lexer(content []byte) []string {
var isInsideString bool = false
var prevByte byte
var currBytes []byte
var lexResult []string
for _, byt := range content {
if byt != DoubleQuoteByte || prevByte == BackSlashByte {
if isInsideString {
currBytes = append(currBytes, byt)
} else {
var present bool = false
for _, cs := range CheckSlice {
if cs == byt {
present = true
break
}
}
if present {
if len(currBytes) > 0 {
lexResult = append(lexResult, string(currBytes))
currBytes = nil
}
lexResult = append(lexResult, string(byt))
} else {
currBytes = append(currBytes, byt)
}
}
} else {
currBytes = append(currBytes, byt)
if isInsideString {
lexResult = append(lexResult, string(currBytes))
currBytes = nil
}
isInsideString = !isInsideString
}
prevByte = byt
}
return lexResult
}
17 changes: 17 additions & 0 deletions util/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package util

func Parser(lexToken []string) ([]string, bool) {
var result bool = false
token := lexToken[0]
if token == LeftCurlyBrace {
lexToken = lexToken[1:]
lexToken, result = IsValidObject(lexToken)
} else if token == LeftSquareBrace {
lexToken = lexToken[1:]
lexToken, result = IsValidArray(lexToken)
} else {
result = IsValidString(token) || IsValidNumber(token) || IsValidBoolean(token) || IsValidNull(token)
lexToken = lexToken[1:]
}
return lexToken, result
}
36 changes: 36 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package util

import (
"fmt"
"strings"
)

// entry function for validator
func Validate(content []byte) bool {
var result bool = false
var lexToken []string = Lexer(content)
lexToken = CleanUp(lexToken)
_, result = Parser(lexToken)
return result
}

// clean up the spaces around the braces
func CleanUp(lexToken []string) []string {
lenLexToken := len(lexToken)
var trimmedLexToken []string
for i := 0; i < lenLexToken; i++ {
tempStr := strings.TrimSpace(lexToken[i])
if tempStr != "" {
trimmedLexToken = append(trimmedLexToken, tempStr)
}
}
return trimmedLexToken
}

func PrintResult(result bool) {
if result {
fmt.Printf("The file is a valid\n")
} else {
fmt.Printf("The file is invalid\n")
}
}
Loading

0 comments on commit 4a49527

Please sign in to comment.