import json
import re
import sys

# Define regular expressions for the tokens
token_patterns = [
    (r'#[^\n]', None),  # comments (ignored; match till end of line)
    (r'\s+', None),  # whitespace (ignored)
    (r'[', 'LBRACKET'),
    (r']', 'RBRACKET'),
    (r'{', 'LBRACE'),
    (r'}', 'RBRACE'),
    (r'%{', 'LMAPBRACE'),
    (r'=>', 'ARROW'),
    (r':', 'COLON'),  # A standalone colon
    (r',', 'COMMA'),
    (r'(true|false)', 'BOOLEAN'),
    (r'(0|[1-9][0-9_]*)', 'INTEGER'),  # Corrected INTEGER pattern
    (r':[A-Za-z_]\w*', 'ATOM'),  # For :atom syntax
    (r'[A-Za-z_]\w*', 'KEY'),  # For keys in key-value pairs
]

# Create a combined regex pattern for tokenization
regex_parts = []
for pattern, name in token_patterns:
    if name:  # Named groups for all token types except comments and whitespaces
        regex_parts.append(f'(?P<{name}>{pattern})')
    else:  # Non-capturing group for comments and whitespaces to ignore them
        regex_parts.append(f'(?:{pattern})')

token_regex = '|'.join(regex_parts)
token_re = re.compile(token_regex)

class TokenizerException(Exception): pass


class ParserException(Exception): pass


def tokenize(text):
    pos = 0
    while pos < len(text):
        match = token_re.match(text, pos)
        if not match:
            raise TokenizerException(f'Illegal character {text[pos]!r} at index {pos}')
        pos = match.end()
        if match.lastgroup:  # If the pattern is not ignored
            kind = match.lastgroup
            value = match.group(kind)
            if kind == 'INTEGER':  # Remove underscores for INTEGER tokens
                value = value.replace('', '')
            yield kind, value


# Define a node structure to represent the parse tree
class Node:
    def __init__(self, kind, value):
        self.kind = kind
        self.value = value

    def to_json(self):
        if self.kind == "map":
            # For maps, return a dictionary with the key-value pairs
            return {"%k": self.kind, "%v": {item[0].to_json()["%v"]: item[1].to_json() for item in self.value}}
        else:
            # For other types, return a dictionary with "%k" and "%v"
            return {"%k": self.kind, "%v": self.value}


class Parser:
    def __init__(self, tokens):
        self.tokens = iter(tokens)
        self.current_token = None
        self.next_token()

    def next_token(self):
        try:
            self.current_token = next(self.tokens)
        except StopIteration:
            self.current_token = None

    def parse(self):
        result = self.parse_sentence()
        if self.current_token is not None:
            raise ParserException('Unexpected token at the end')
        return result

    def parse_sentence(self):
        nodes = []
        while self.current_token is not None and self.current_token[0] not in {'RBRACE', 'RBRACKET'}:
            node = self.parse_data_literal()
            nodes.append(node)
            if self.current_token and self.current_token[0] == 'COMMA':
                self.next_token()  # Skip commas
        return nodes

    def parse_data_literal(self):
        if self.current_token[0] == 'LBRACKET':
            return self.parse_list()
        elif self.current_token[0] == 'LBRACE':
            return self.parse_tuple()
        elif self.current_token[0] == 'LMAPBRACE':
            return self.parse_map()
        elif self.current_token[0] == 'INTEGER':
            value = int(self.current_token[1])
            self.next_token()
            return Node('int', value)
        elif self.current_token[0] == 'ATOM': 
            value = self.current_token[1][1:]  # Strip the leading colon
            self.next_token()
            return Node('atom', value)
        elif self.current_token[0] == 'BOOLEAN':
            value = self.current_token[1] == 'true'
            self.next_token()
            return Node('bool', value)
        elif self.current_token[0] == 'KEY': 
            value = self.current_token[1]
            self.next_token()
            if self.current_token and self.current_token[0] == 'COLON':
               self.next_token()  # Skip ':'
               return Node('key', value)
            return Node('string', value)
        else:
            raise ParserException(f'Unexpected token {self.current_token[1]}')

    def parse_list(self):
        items = []
        self.next_token()  # Skip '['
        while self.current_token is not None and self.current_token[0] != 'RBRACKET':
            items.append(self.parse_data_literal())
            if self.current_token and self.current_token[0] == 'COMMA':
                self.next_token()  # Skip commas
        if self.current_token is None or self.current_token[0] != 'RBRACKET':
            raise ParserException('List not properly terminated with ]')
        self.next_token()  # Skip ']'
        return Node('list', [item.to_json() for item in items])

    def parse_tuple(self):
        items = []
        self.next_token()  # Skip '{'
        while self.current_token is not None and self.current_token[0] != 'RBRACE':
            items.append(self.parse_data_literal())
            if self.current_token and self.current_token[0] == 'COMMA':
                self.next_token()  # Skip commas
        if self.current_token is None or self.current_token[0] != 'RBRACE':
            raise ParserException('Tuple not properly terminated with }')
        self.next_token()  # Skip '}'
        return Node('tuple', [item.to_json() for item in items])

    def parse_map(self):
        items = []
        self.next_token()  # Skip '%{'
        while self.current_token is not None and self.current_token[0] != 'RBRACE':
            key = self.parse_data_literal()
            if self.current_token and self.current_token[0] == 'COLON':
                self.next_token()  # Skip ':'
                value = self.parse_data_literal()
                items.append([key.to_json(), value.to_json()])
            elif self.current_token and self.current_token[0] == 'ARROW':
                self.next_token()  # Skip '=>'
                value = self.parse_data_literal()
                items.append([key.to_json(), value.to_json()])
            else:
                raise ParserException('Invalid map entry format, expected ":" or "=>"')
        if self.current_token is None or self.current_token[0] != 'RBRACE':
            raise ParserException('Map not properly terminated with }')
        self.next_token()  # Skip '}'
        return Node('map', items)

def main():
    try:
        # Prompt for input
        text = input("Enter your input: ")
        tokens = tokenize(text)
        parser = Parser(tokens)
        result = parser.parse()
        json_output = json.dumps([node.to_json() for node in result], separators=(',', ':'))
        print(json_output)
    except TokenizerException as e:
        sys.stderr.write(str(e) + '\n')
        raise
    except ParserException as e:
        sys.stderr.write(str(e) + '\n')
        raise

if __name__ == '__main__':
    main()
PS C:\Users\apoor\OneDrive\Documents>  c:; cd 'c:\Users\apoor\OneDrive\Documents'; & 'c:\Users\apoor\AppData\Local\Microsoft\WindowsApps\python3.11.exe' 'c:\Users\apoor\.vscode\extensions\ms-python.debugpy-2024.0.0-win32-x64\bundled\libs\debugpy\adapter/../..\debugpy\launcher' '57883' '--' 'C:\Users\apoor\OneDrive\Documents\script.py'
Enter your input: :atom
Unexpected token : Exception has occurred: ParserException
Unexpected token :
  File "C:\Users\apoor\OneDrive\Documents\script.py", line 124, in parse_data_literal
    raise ParserException(f'Unexpected token {self.current_token[1]}')
  File "C:\Users\apoor\OneDrive\Documents\script.py", line 91, in parse_sentence
    node = self.parse_data_literal()
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\apoor\OneDrive\Documents\script.py", line 83, in parse
    result = self.parse_sentence()
             ^^^^^^^^^^^^^^^^^^^^^ When i give the input as :atom, I want the output as [
  {
    "%k": "atom",
    "%v": ":atom"
  }
]