Initial commit
This commit is contained in:
1
json_py/__init__.py
Normal file
1
json_py/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from json_py.json_py import *
|
||||
19
json_py/json_py.py
Normal file
19
json_py/json_py.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from __future__ import annotations
|
||||
import typing
|
||||
import json_py.lexer as lexer
|
||||
import json_py.parser as parser
|
||||
|
||||
|
||||
def to_json(obj: typing.Any) -> str:
|
||||
raise NotImplementedError("Not implemented yet")
|
||||
|
||||
|
||||
def from_json(string: str) -> typing.Any:
|
||||
tokens = lexer.lex(string)
|
||||
return parser.parse(tokens)[0]
|
||||
|
||||
|
||||
__all__ = (
|
||||
"to_json",
|
||||
"from_json",
|
||||
)
|
||||
97
json_py/lexer.py
Normal file
97
json_py/lexer.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
import typing
|
||||
|
||||
|
||||
TypeLexer = typing.Tuple[typing.Optional[typing.Any], str]
|
||||
|
||||
|
||||
def lex_string(string: str) -> TypeLexer:
|
||||
if not string.startswith('"'):
|
||||
return None, string
|
||||
|
||||
string = string[1:]
|
||||
for i in range(len(string)):
|
||||
if string[i] == '"':
|
||||
return string[:i], string[i + 1 :]
|
||||
|
||||
return None, string
|
||||
|
||||
|
||||
def lex_number(string: str) -> TypeLexer:
|
||||
if not string[0].isdigit():
|
||||
return None, string
|
||||
|
||||
has_decimal = False
|
||||
|
||||
for i in range(len(string)):
|
||||
if string[i] == ".":
|
||||
if has_decimal:
|
||||
raise ValueError("Invalid number")
|
||||
|
||||
has_decimal = True
|
||||
continue
|
||||
|
||||
if not string[i].isdigit():
|
||||
if has_decimal:
|
||||
return float(string[:i]), string[i:]
|
||||
return int(string[:i]), string[i:]
|
||||
|
||||
if has_decimal:
|
||||
return float(string), ""
|
||||
return int(string), ""
|
||||
|
||||
|
||||
def lex_bool(string: str) -> TypeLexer:
|
||||
if string[0].lower() not in "tf":
|
||||
return None, string
|
||||
|
||||
if string[:4].lower() == "true":
|
||||
return True, string[4:]
|
||||
elif string[:5].lower() == "false":
|
||||
return False, string[5:]
|
||||
|
||||
return None, string
|
||||
|
||||
|
||||
def lex_null(string: str) -> TypeLexer:
|
||||
if string[:4].lower() == "null":
|
||||
return True, string[4:]
|
||||
|
||||
return None, string
|
||||
|
||||
|
||||
TokenList = typing.List[typing.Any]
|
||||
|
||||
|
||||
def lex(string: str) -> TokenList:
|
||||
tokens: TokenList = []
|
||||
while len(string) > 0:
|
||||
json_string, string = lex_string(string)
|
||||
if json_string is not None:
|
||||
tokens.append(json_string)
|
||||
continue
|
||||
|
||||
json_number, string = lex_number(string)
|
||||
if json_number is not None:
|
||||
tokens.append(json_number)
|
||||
continue
|
||||
|
||||
json_bool, string = lex_bool(string)
|
||||
if json_bool is not None:
|
||||
tokens.append(json_bool)
|
||||
continue
|
||||
|
||||
json_null, string = lex_null(string)
|
||||
if json_null is not None:
|
||||
tokens.append(None)
|
||||
continue
|
||||
|
||||
if string[0] in " ":
|
||||
string = string[1:]
|
||||
elif string[0] in ":{},[]":
|
||||
tokens.append(string[0])
|
||||
string = string[1:]
|
||||
else:
|
||||
raise Exception("Unexpected character: {}".format(string[0]))
|
||||
|
||||
return tokens
|
||||
73
json_py/parser.py
Normal file
73
json_py/parser.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
import typing
|
||||
import json_py.lexer as lexer
|
||||
|
||||
ParserResult = typing.Tuple[typing.Any, lexer.TokenList]
|
||||
|
||||
|
||||
def parse_array(tokens: lexer.TokenList) -> ParserResult:
|
||||
json_array: typing.List[typing.Any] = []
|
||||
|
||||
if tokens[0] == "]":
|
||||
return json_array, tokens[1:]
|
||||
|
||||
expect_comma = False
|
||||
for i in range(len(tokens)):
|
||||
t = tokens[i]
|
||||
if t == "]":
|
||||
if not expect_comma:
|
||||
raise ValueError("Expected one more item")
|
||||
|
||||
return json_array, tokens[i + 1 :]
|
||||
elif t == ",":
|
||||
if not expect_comma:
|
||||
raise ValueError("Unexpected comma")
|
||||
|
||||
expect_comma = False
|
||||
else:
|
||||
if expect_comma:
|
||||
raise ValueError("Expected comma but got item")
|
||||
|
||||
json_array.append(t)
|
||||
expect_comma = True
|
||||
|
||||
raise ValueError("List not closed")
|
||||
|
||||
|
||||
def parse_object(tokens: lexer.TokenList) -> ParserResult:
|
||||
json_object: typing.Any = {}
|
||||
|
||||
if tokens[0] == "}":
|
||||
return json_object, tokens[1:]
|
||||
|
||||
is_syntax: typing.Callable[[str], bool] = lambda x: str(x) in ":"
|
||||
while True:
|
||||
json_key = tokens[0]
|
||||
|
||||
if is_syntax(json_key):
|
||||
raise Exception(f"Expected value before '{json_key}'")
|
||||
|
||||
colon = tokens[1]
|
||||
if colon != ":":
|
||||
raise Exception(f"Expected ':' but got '{colon}'")
|
||||
|
||||
json_value, tokens = parse(tokens[2:])
|
||||
json_object[json_key] = json_value
|
||||
|
||||
next_token = tokens[0]
|
||||
if next_token == ",":
|
||||
tokens = tokens[1:]
|
||||
elif next_token == "}":
|
||||
return json_object, tokens[1:]
|
||||
else:
|
||||
raise Exception(f"Expected ',' or '{'}'}' but got '{next_token}'")
|
||||
|
||||
|
||||
def parse(tokens: lexer.TokenList) -> typing.Any:
|
||||
t = tokens[0]
|
||||
if t == "[":
|
||||
return parse_array(tokens[1:])
|
||||
elif t == "{":
|
||||
return parse_object(tokens[1:])
|
||||
else:
|
||||
return t, tokens[1:]
|
||||
Reference in New Issue
Block a user