#!/bin/env -S python from typing import TypeVar, Any from os.path import dirname import sys sys.path.append(f"{dirname(__file__)}/../src") from pyalibert import regex, Parser, just, Declare from okipy.lib import Suite # utils whitespace = just(" ").or_(just("\n")).repeat() T = TypeVar("T") def lexeme(p: Parser[T]) -> Parser[T]: return p << whitespace suite = Suite("cases") @suite.test() def case_json(ctx): # Punctuation lbrace = lexeme(just("{")) rbrace = lexeme(just("}")) lbrack = lexeme(just("[")) rbrack = lexeme(just("]")) colon = lexeme(just(":")) comma = lexeme(just(",")) # Primitives true = lexeme(just("true")).set(True) false = lexeme(just("false")).set(False) null = lexeme(just("null")).set(None) number = lexeme(regex(r"-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?")).map(float) just_part = regex(r'[^"\\]+') just_esc = just("\\") >> ( just("\\") | just("/") | just('"') | just("b").set("\b") | just("f").set("\f") | just("n").set("\n") | just("r").set("\r") | just("t").set("\t") | regex(r"u[0-9a-fA-F]{4}").map(lambda s: chr(int(s[1:], 16)))) quoted = lexeme(just('"') >> (just_part | just_esc).repeat().map(lambda l: "".join(l)) << just('"')) # Data structures json = Declare[Any]() object_pair = (quoted << colon) & json.parser() json_object = lbrace >> object_pair.sep_by(comma).map(dict) << rbrace array = lbrack >> json.parser().sep_by(comma) << rbrack # Everything json.define(quoted | number | json_object | array | true | false | null) json = json.parser() # asserts assert json.parse("1") == 1 assert json.parse('"1"') == "1" assert json.parse('["1", 1]') == ["1", 1] assert json.parse('{"1": 1}') == {"1": 1} assert json.parse(r"""{ "int": 1, "just": "hello", "a list": [1, 2, 3], "escapes": "\n \u24D2", "nested": {"x": "y"}, "other": [true, false, null] }""") == { "int": 1, "just": "hello", "a list": [1, 2, 3], "escapes": "\n ⓒ", "nested": {"x": "y"}, "other": [True, False, None], } if __name__ == "__main__": suite.run(sys.argv[1:])