#!/usr/bin/env python3
# Fandango standard library

import string

stdlib = """# Standard library for Fandango
# This file is generated by src/fandango/language/stdlib.py
# Do not edit this file directly.

# We define each element as <_element> and then alias it to <element>.
# This is to allow users to override the aliased element.
# For example, if a user wants to override the definition of <digit>,
# they can do so by redefining <digit>, leaving <_digit> unchanged.

# We define each element using all alternatives to avoid biasing the grammar.
"""


def make_def(symbol: str, chars: str, force_binary=False) -> str:
    expansions = []
    for c in chars:
        if c in string.printable and not force_binary:
            expansions.append(repr(c))
        else:
            # Declare these as binary to force latin-1 encoding
            expansions.append(f"b'\\x{ord(c):02x}'")
    return make_rule(symbol, expansions)


def make_rule(symbol: str, expansions: list[str]) -> str:
    return (
        f"<_{symbol}> ::= "
        + " | ".join(expansions)
        + "\n"
        + f"<{symbol}> ::= <_{symbol}>\n\n"
    )


def make_header(title: str) -> str:
    return f"\n# {title}\n"


def make_comment(comment: str) -> str:
    return f"# {comment}\n"


stdlib += make_header("Any character")
any_char = make_rule("char", ["r'(.|\\n)'"])
stdlib += any_char


stdlib += make_header("Printable characters")
printable = make_def("printable", string.printable)
printable += make_def("whitespace", string.whitespace)

# printable += make_def("digit", string.digits)
printable += make_rule("digit", ["r'[0-9]'"])

# printable += make_def("hexdigit", string.hexdigits)
printable += make_rule("hexdigit", ["r'[0-9a-fA-F]'"])

# printable += make_def("octdigit", string.octdigits)
printable += make_rule("octdigit", ["r'[0-7]'"])

# printable += make_def("ascii_letter", string.ascii_letters)
printable += make_rule("ascii_letter", ["r'[a-zA-Z]'"])

# printable += make_def("ascii_lowercase_letter", string.ascii_lowercase)
printable += make_rule("ascii_lowercase_letter", ["r'[a-z]'"])

# printable += make_def("ascii_uppercase_letter", string.ascii_uppercase)
printable += make_rule("ascii_uppercase_letter", ["r'[A-Z]'"])

printable += make_def("punctuation", string.punctuation)

# printable += make_def("alphanum", string.ascii_letters + string.digits)
printable += make_rule("alphanum", ["r'[a-zA-Z0-9]'"])

# printable += make_def("alphanum_", string.ascii_letters + string.digits + "_")
printable += make_rule("alphanum", ["r'[a-zA-Z0-9_]'"])

stdlib += printable

stdlib += make_header("ASCII characters")
ascii_char = make_def(
    "ascii_char", "".join(chr(c) for c in range(0, 128)), force_binary=False
)
stdlib += ascii_char

stdlib += make_header("ASCII control characters")
ASCII_CONTROL = [
    "NUL",
    "SOH",
    "STX",
    "ETX",
    "EOT",
    "ENQ",
    "ACK",
    "BEL",
    "BS",
    "HT",
    "LF",
    "VT",
    "FF",
    "CR",
    "SO",
    "SI",
    "DLE",
    "DC1",
    "DC2",
    "DC3",
    "DC4",
    "NAK",
    "SYN",
    "ETB",
    "CAN",
    "EM",
    "SUB",
    "ESC",
    "FS",
    "GS",
    "RS",
    "US",
    "SP",
]
ascii_control = ""
for i in range(len(ASCII_CONTROL)):
    ascii_control += make_def(ASCII_CONTROL[i], chr(i), force_binary=True)
ascii_control += make_def("DEL", chr(127), force_binary=True)
ascii_control += make_rule(
    "ascii_control", (f"<{symbol}>" for symbol in ASCII_CONTROL + ["DEL"])
)
stdlib += ascii_control

stdlib += make_header("Bits")
bits = make_rule("bit", ["0", "1"])
stdlib += bits

stdlib += make_header("Bytes")
bytes = make_def("byte", "".join(chr(c) for c in range(0, 256)), force_binary=True)
stdlib += bytes


stdlib += make_header("UTF-8 characters, read and processed as bytes")


def make_utf8_rule(symbol: str, chars: list[int], suffix: str = "") -> str:
    return make_rule(
        symbol, ["(" + " | ".join(f"b'\\x{c:02x}'" for c in chars) + ")" + suffix]
    )


utf8 = make_def(
    "utf8_char1", "".join(chr(c) for c in range(0, 128)), force_binary=False
)
utf8 += make_def(
    "utf8_continuation_byte",
    "".join(chr(c) for c in range(0x80, 0xC0)),
    force_binary=True,
)
utf8 += make_utf8_rule("utf8_char2", range(0xC2, 0xE0), " <utf8_continuation_byte>")
utf8 += make_utf8_rule("utf8_char3", range(0xE0, 0xF0), " <utf8_continuation_byte>{2}")
utf8 += make_utf8_rule("utf8_char4", range(0xF0, 0xF6), " <utf8_continuation_byte>{3}")
utf8 += make_rule(
    "utf8_char", ["<utf8_char1>", "<utf8_char2>", "<utf8_char3>", "<utf8_char4>"]
)  # UTF-8 character
stdlib += utf8


stdlib += make_header("Numbers")
stdlib += make_comment(
    "This only specifies the length; interpretation is up to the user"
)
numbers = make_rule("int8", ["<byte>"])
numbers += make_rule("int16", ["<byte><byte>"])
numbers += make_rule("int32", ["<byte>{4}"])
numbers += make_rule("int64", ["<byte>{8}"])
numbers += make_rule("float32", ["<byte>{4}"])
numbers += make_rule("float64", ["<byte>{8}"])
stdlib += numbers

# These should go into a separate file
# We should also have specs for email, urls, network addresses, etc.
# stdlib += make_header("UUIDs")
# stdlib += make_rule("uuid", ["<hexdigit>{8} '-' <hexdigit>{4} '-' <hexdigit>{4}'-' <hexdigit>{4} '-' <hexdigit>{12}"])

stdlib += make_header("Fandango dancer")
stdlib += make_comment("We use this to test UTF-8 compatibility")
dancer = make_rule("fandango_dancer", ["'💃'"])
stdlib += dancer

if __name__ == "__main__":
    print(stdlib)  # Output the standard library
