import json
import re
import sys

token_patterns = [
(r’#[^\n]‘, None),
(r’\s+‘, None),
(r’[‘, ‘LBRACKET’),
(r’]‘, ‘RBRACKET’),
(r’{‘, ‘LBRACE’),
(r’}‘, ‘RBRACE’),
(r’%{‘, ‘LMAPBRACE’),
(r’=>‘, ‘ARROW’),
(r’,‘, ‘COMMA’),
(r’(true|false)‘, ‘BOOLEAN’),
(r’(0|[1-9][0-9_])‘, ‘INTEGER’),
(r’:[A-Za-z_]\w*‘, ‘ATOM’),
(r’[A-Za-z_]\w*‘, ‘KEY’),
(r’:‘, ‘COLON’),
]

regex_parts = [f’(?P<{name}>{pattern})’ if name else f’(?:{pattern})’ for pattern, name in token_patterns]
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:
yield match.lastgroup, match.group(match.lastgroup)


class Node:
def init(self, kind, value):
self.kind = kind
self.value = value

def to_json(self):
if self.kind == “map”:
# Corrected conditional to account for empty maps
if not self.value: # self.value is empty
return {“%k”: self.kind, “%v”: {}} # Return an empty dictionary instead of an empty list
return {“%k”: self.kind, “%v”: {k.to_json()[“%v”]: v.to_json() for k, v in self.value}}
elif self.kind == “list”:
return {“%k”: self.kind, “%v”: [item.to_json() for item in self.value]}
else:
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:
node = self.parse_data_literal()
nodes.append(node)
if self.current_token and self.current_token[0] in {‘COMMA’, ‘COLON’, ‘ARROW’}:
self.next_token()
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]
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()
return Node(‘key’, value)
return Node(‘string’, value)
else:
raise ParserException(f’Unexpected token {self.current_token[1]}’)

def parse_list(self):
self.next_token()
items = []
while self.current_token and self.current_token[0] != ‘RBRACKET’:
item = self.parse_data_literal()
items.append(item)
if self.current_token and self.current_token[0] == ‘COMMA’:
self.next_token()
if not self.current_token or self.current_token[0] != ‘RBRACKET’:
raise ParserException(‘List not properly terminated with ]’)
self.next_token()
return Node(‘list’, items)

def parse_tuple(self):
items = []
self.next_token()
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()
if self.current_token is None or self.current_token[0] != ‘RBRACE’:
raise ParserException(‘Tuple not properly terminated with }’)
self.next_token()
return Node(‘tuple’, [item.to_json() for item in items])

def parse_map(self):
self.next_token() # Skip the LMAPBRACE
map_items = {}
while self.current_token and self.current_token[0] != ‘RBRACE’:
key = self.parse_data_literal() # Parses the key, which might be an atom, integer, list, tuple etc.

if self.current_token and self.current_token[0] not in {‘ARROW’, ‘COLON’}:
raise ParserException(f’Expected “=>” or “:”, found {self.current_token}‘)
self.next_token() # Skip the ARROW or COLON

value = self.parse_data_literal() # Parses the value associated with the key

# Serialize the key Node to a JSON string to use as a dict key
json_key = json.dumps(key.to_json(), separators=(’,‘, ‘:’))
# Remove outer dictionary structure from JSON string to make it hashable for Python dictionary
json_key = json.loads(json_key)

# Note: We need to convert the JSON object back to a hashable type for the dict
# For simplicity, we are using a tuple which is hashable and can handle nested structures
if isinstance(json_key, list):
json_key = tuple(json_key)
elif isinstance(json_key, dict):
# Convert dict keys to tuples (since lists are not hashable)
json_key = tuple((tuple(k), v) if isinstance(k, list) else (k, v) for k, v in json_key.items())

map_items[json_key] = value.to_json()

if self.current_token and self.current_token[0] == ‘COMMA’:
self.next_token() # Skip the COMMA

if not self.current_token or self.current_token[0] != ‘RBRACE’:
raise ParserException(‘Map not properly terminated with “}”’)
self.next_token() # Skip the RBRACE
return Node(‘map’, map_items)


def main():
try:
text = sys.stdin.read()

tokens = list(tokenize(text))

if not tokens:
print(json.dumps([]))
else:
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’)
except ParserException as e:
sys.stderr.write(str(e) + ‘\n’)


if name == ‘main’:
main()
Standard Error
Traceback (most recent call last):
File “/autograder/submission/prj1-sol/elixir-data.py”, line 198, in
main()
File “/autograder/submission/prj1-sol/elixir-data.py”, line 187, in main
result = parser.parse()
File “/autograder/submission/prj1-sol/elixir-data.py”, line 73, in parse
result = self.parse_sentence()
File “/autograder/submission/prj1-sol/elixir-data.py”, line 81, in parse_sentence
node = self.parse_data_literal()
File “/autograder/submission/prj1-sol/elixir-data.py”, line 93, in parse_data_literal
return self.parse_map()
File “/autograder/submission/prj1-sol/elixir-data.py”, line 166, in parse_map
map_items[json_key] = value.to_json()
TypeError: unhashable type: ‘list’

diff compound-data
— /autograder/source/test-files/50-compound-data.out 2024-01-31 14:31:45.000000000 +0000
+++ tmp/50-compound-data.test 2024-02-19 00:47:51.758892492 +0000
@@ -1,203 +0,0 @@
-[