-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparser.py
135 lines (112 loc) · 5.5 KB
/
parser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from typing import Type, Any
import xml.etree.ElementTree as ET
from .blocks import Block, Field, StaticField, VariableField
from .exceptions import ProgramParseException
from .program import Program
class Parser:
factories: dict[str, Type[Block]]
def __init__(self, factories: dict[str, Type[Block]]) -> None:
self.factories = factories
def parse_program(self, xml_input: str) -> Program:
parser = ParserInstance(self.factories)
root_block, variables = parser.parse_program(xml_input)
return Program(root_block, variables, xml_input)
class ParserInstance:
"""Instance for one parsing, keeps internally track of parsed variables.
Should not be reused multiple times.
"""
factories: dict[str, Type[Block]]
variables: dict[str, list[VariableField]]
def __init__(self, factories: dict[str, Type[Block]]) -> None:
self.factories = factories
self.variables = {}
def parse_program(self, xml_input: str) -> tuple[Block, dict[str, type]]:
root_block: Block | None = None
for el in ET.XML(xml_input):
tag = el.tag.split("}")[-1]
if tag == "variables":
self.parse_variables(el)
elif tag == "block":
if root_block is not None:
raise ProgramParseException("Multiple blocks, don't know where the program starts")
root_block = self.parse_block(f"block[{el.attrib['type']}]", el)
else:
raise ProgramParseException(f"Unknown element {tag}")
if root_block is None:
raise ProgramParseException("No block to execute")
# Check variable types
variables: dict[str, type] = {}
for variable, instances in self.variables.items():
for instance in instances:
if instance.var_type is Any:
# raise ProgramParseException(f"Instance of variable {variable} has not determined type")
continue
if variable in variables and variables[variable] != instance.var_type:
raise ProgramParseException(f"Variable {variable} has type conflict ({variables[variable]} and {instance.var_type})")
variables[variable] = instance.var_type
return root_block, variables
def parse_variables(self, xml_variables: ET.Element):
self.variables = {}
for variable in xml_variables:
if variable.text is None:
raise ProgramParseException("Empty variable definition")
var = variable.text.strip()
if var in self.variables:
raise ProgramParseException(f"Duplicate variable {var}")
self.variables[var] = []
def parse_block(self, path: str, block: ET.Element) -> Block:
type = block.attrib['type']
if type not in self.factories:
raise ProgramParseException(f"Unknown block {type}")
factory = self.factories[type]
# Get mutation, fields, values, statements and next
mutation: dict[str, str] = {}
fields: dict[str, Field] = {}
values: dict[str, Block] = {}
statements: dict[str, Block] = {}
next: Block | None = None
for el in block:
tag = el.tag.split("}")[-1]
if tag == "mutation":
mutation = el.attrib
elif tag == "field":
name = el.attrib['name']
el_path = f"{path}.field[{name}]"
if el.text is None:
raise ProgramParseException(f"{el_path}: Empty tag {name}")
value = el.text.strip()
field: Field
if name == "VAR":
if value not in self.variables:
raise ProgramParseException(f"{el_path}: Variable {value} not specified in <variables>")
field = VariableField(value)
self.variables[value].append(field)
else:
field = StaticField(name, value)
fields[name] = field
elif tag in ("value", "statement", "next"):
name = el.attrib.get('name', '')
el_path = name and f"{path}.{tag}[{name}]" or f"{path}.{tag}"
if len(el) == 2 and el[0].tag.split("}")[-1] == "shadow":
el.remove(el[1])
if len(el) != 1:
raise ProgramParseException(f"{el_path}: Element <{tag} name=\"{name}\"> needs exactly 1 child")
child = el[0]
child_tag = child.tag.split("}")[-1]
if child_tag not in ("block", "shadow"):
raise ProgramParseException(f"{el_path}: Expected <block> inside <{tag}> but <{child_tag}> found")
child_type = child.attrib['type']
el_path = f"{el_path}.{child_tag}[{child_type}]"
if tag == "value":
values[name] = self.parse_block(el_path, child)
elif tag == "statement":
statements[name] = self.parse_block(el_path, child)
else:
if next is not None:
raise ProgramParseException("There cannot be multiple <next> in one block")
next = self.parse_block(el_path, child)
try:
return factory(mutation=mutation, fields=fields, values=values,
statements=statements, next=next)
except ProgramParseException as e:
raise ProgramParseException(f"{path}: {e}")