Initial commit

This commit is contained in:
2022-06-09 19:34:12 +02:00
parent 74e388dda7
commit 8b265077e4
10 changed files with 587 additions and 1 deletions

1
json_py/__init__.py Normal file
View File

@@ -0,0 +1 @@
from json_py.json_py import *

19
json_py/json_py.py Normal file
View 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
View 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
View 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:]