Fix several bugs to get Python scanner/parser basically working, processes ../tests...
[pilex.git] / element.py
1 # Copyright (C) 2018 Nick Downing <nick@ndcode.org>
2 # SPDX-License-Identifier: GPL-2.0-only
3 #
4 # This program is free software; you can redistribute it and/or modify it under
5 # the terms of the GNU General Public License as published by the Free Software
6 # Foundation; version 2.
7 #
8 # This program is distributed in the hope that it will be useful, but WITHOUT
9 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
11 # details.
12 #
13 # You should have received a copy of the GNU General Public License along with
14 # this program; if not, write to the Free Software Foundation, Inc., 51
15 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
16
17 import xml.etree.ElementTree
18
19 class Element(xml.etree.ElementTree._Element_Py):
20   def __init__(self, tag = 'Element', attrib = {}, text = '', children = []):
21     xml.etree.ElementTree._Element_Py.__init__(self, tag, attrib)
22     self.ref = -1
23     self.seen = False
24     set_text(self, 0, text)
25     self[:] = children
26   def serialize(self, ref_list):
27     for i in self:
28       # parented, enforce that child can only be parented at most once
29       # (although there can be unlimited numbers of numeric refs to it)
30       assert not i.seen
31       i.seen = True
32       if i.ref == -1:
33         i.serialize(ref_list)
34   def deserialize(self, ref_list):
35     for i in self:
36       i.deserialize(ref_list)
37   def copy(self, factory = None):
38     result = (Element if factory is None else factory)(self.tag, self.attrib)
39     result.text = self.text
40     result.tail = self.tail
41     result[:] = [i.copy() for i in self]
42     return result
43   def repr_serialize(self, params):
44     if len(self):
45       params.append(
46         'children = [{0:s}]'.format(
47           ', '.join([repr(i) for i in self])
48         )
49       )
50   def __repr__(self):
51     params = []
52     self.repr_serialize(params)
53     return 'element.Element({0:s})'.format(', '.join(params))
54
55 bool_to_str = ['false', 'true']
56 def serialize_bool(value):
57   return bool_to_str[int(value)]
58
59 str_to_bool = {'false': False, 'true': True}
60 def deserialize_bool(text):
61   return str_to_bool[text]
62
63 def serialize_int(value):
64   return str(value)
65
66 def deserialize_int(text):
67   return int(text)
68
69 def serialize_ref(value, ref_list):
70   if value is None:
71     ref = -1
72   else:
73     ref = value.ref
74     if ref == -1:
75       ref = len(ref_list)
76       ref_list.append(value)
77       value.ref = ref
78       value.set('ref', str(ref))
79       # this doesn't set the seen flag, so it will be parented by the
80       # root, unless it is already parented or gets parented later on
81       if not value.seen:
82         value.serialize(ref_list)
83   return str(ref)
84
85 def deserialize_ref(text, ref_list):
86   ref = int(text)
87   return None if ref < 0 else ref_list[ref]
88
89 def serialize_str(value):
90   return value
91
92 def deserialize_str(text):
93   return text
94
95 def serialize(value, fout, encoding = 'unicode'):
96   ref_list = []
97   serialize_ref(value, ref_list)
98   parents = [i for i in ref_list if not i.seen]
99   root = Element('root', children = parents)
100   for i in range(len(root)):
101     set_text(root, i, '\n  ')
102   set_text(root, len(root), '\n')
103   root.tail = '\n'
104   xml.etree.ElementTree.ElementTree(root).write(fout, encoding)
105   for i in root:
106     i.tail = None
107   for i in ref_list:
108     i.ref = -1
109     del i.attrib['ref']
110   i = 0
111   while i < len(parents):
112     for j in parents[i]:
113       j.seen = False
114       parents.append(j)
115     i += 1
116
117 def deserialize(fin, factory = Element, encoding = 'unicode'):
118   root = xml.etree.ElementTree.parse(
119     fin,
120     xml.etree.ElementTree.XMLParser(
121       target = xml.etree.ElementTree.TreeBuilder(factory),
122       encoding = encoding
123     )
124   ).getroot()
125   assert root.tag == 'root'
126   for i in root:
127     i.tail = None
128   i = 0
129   parents = root[:]
130   ref_list = []
131   while i < len(parents):
132     j = parents[i]
133     if 'ref' in j.attrib:
134       ref = int(j.attrib['ref'])
135       del j.attrib['ref']
136       if len(ref_list) < ref + 1:
137         ref_list.extend([None] * (ref + 1 - len(ref_list)))
138       ref_list[ref] = j
139     parents.extend(j[:])
140     i += 1
141   for i in root:
142     i.deserialize(ref_list)
143   return ref_list[0]
144
145 # compatibility scheme to access arbitrary xml.etree.ElementTree.Element-like
146 # objects (not just Element defined above) using a more consistent interface:
147 def get_text(root, i):
148   if i < 0:
149     i += len(root) + 1
150   text = root.text if i == 0 else root[i - 1].tail
151   return '' if text is None else text
152
153 def set_text(root, i, text):
154   if i < 0:
155     i += len(root) + 1
156   if len(text) == 0:
157     text = None
158   if i == 0:
159     root.text = text
160   else:
161     root[i - 1].tail = text
162
163 def to_text(root):
164   return ''.join(
165     [
166       j
167       for i in range(len(root))
168       for j in [get_text(root, i), to_text(root[i])]
169     ] +
170     [get_text(root, len(root))]
171   )
172
173 def concatenate(children, factory = Element, *args, **kwargs):
174   root = factory(*args, **kwargs)
175   for child in children:
176     i = len(root)
177     set_text(root, i, get_text(root, i) + get_text(child, 0))
178     root[i:] = child[:]
179   return root