Skip to content

Instantly share code, notes, and snippets.

@mnixry
Created May 24, 2025 17:45
Show Gist options
  • Save mnixry/ad0aa1e9d4eb9b5cf0adbef4b3d58af0 to your computer and use it in GitHub Desktop.
Save mnixry/ad0aa1e9d4eb9b5cf0adbef4b3d58af0 to your computer and use it in GitHub Desktop.
A loose ZON (Zig Object Notation) parser in pure Python.
from typing import final
import parsy as p
whitespace = p.regex(r"\s*")
comment = p.regex(r"//.*")
def lexeme(parser: p.Parser):
return parser << (whitespace | comment)
UNDEFINED = object()
@final
class ZonParser:
dot = lexeme(p.string("."))
left_curly = lexeme(p.string("{"))
right_curly = lexeme(p.string("}"))
comma = lexeme(p.string(","))
equal = lexeme(p.string("="))
double_quote = lexeme(p.string('"'))
identifier = lexeme(p.regex(r"[a-zA-Z_][a-zA-Z0-9_]*"))
string_part = p.regex(r'[^"\\]+')
string_escape = p.string("\\") >> (
p.string("\\")
| p.string('"')
| p.string("n").result("\n")
| p.string("r").result("\r")
| p.string("t").result("\t")
| p.regex(r"u\{[0-9a-fA-F]+\}").map(lambda s: chr(int(s[2:-1], 16)))
| p.regex(r"x[0-9a-fA-F]{2}").map(lambda s: chr(int(s[1:], 16)))
)
string = lexeme(double_quote >> (string_escape | string_part) << double_quote)
integer_decimal = p.regex(r"0|[1-9][0-9]*").map(lambda s: int(s))
integer_hexadecimal = p.regex(r"0x[0-9a-fA-F]+").map(lambda s: int(s, 16))
integer_octal = p.regex(r"0o[0-7]+").map(lambda s: int(s, 8))
integer_binary = p.regex(r"0b[01]+").map(lambda s: int(s, 2))
integer = integer_decimal | integer_hexadecimal | integer_octal | integer_binary
float_decimal = p.regex(r"[0-9]+\.[0-9]*|\.[0-9]+").map(lambda s: float(s))
float_scientific = p.regex(r"(?:[0-9]+\.[0-9]*|\.[0-9]+)[eE][+-]?[0-9]+").map(
lambda s: float(s)
)
float_scientific_hexadecimal = p.regex(
r"(?:[0-9]+\.[0-9]*|\.[0-9]+)[pP][+-]?[0-9]+"
).map(lambda s: float.fromhex(s))
float = float_decimal | float_scientific | float_scientific_hexadecimal
number = lexeme(integer | float)
true = p.string("true").result(True) # noqa: FBT003
false = p.string("false").result(False) # noqa: FBT003
boolean = lexeme(true | false)
null = p.string("null").result(None)
undefined = p.string("undefined").result(UNDEFINED)
nullish = lexeme(null | undefined)
zon_value = p.forward_declaration()
key = (p.string("@") >> string) | identifier
pair = p.seq(dot >> key << equal, zon_value).map(tuple)
array = (
(dot + left_curly)
>> zon_value.sep_by(comma)
<< (comma.optional() << right_curly)
)
struct = (
(dot + left_curly)
>> pair.sep_by(comma).map(dict)
<< (comma.optional() << right_curly)
)
zon_value.become(array | struct | boolean | nullish | number | string)
zon_doc = whitespace >> zon_value
@classmethod
def parse(cls, s: str):
return cls.zon_doc.parse(s)
def loads(s: str):
return ZonParser.parse(s)
__all__ = ["UNDEFINED", "ZonParser", "loads"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment