-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathwriter_buffer.py
138 lines (116 loc) · 5.45 KB
/
writer_buffer.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
136
137
# Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at:
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the
# License.
"""Buffer for binary Ion writers."""
class _Node:
def __init__(self, value=None):
self.value = value
self.parent = None
self.children = None
def add_child(self, node):
if self.children is None:
self.children = []
node.parent = self
self.children.append(node)
def add_leaf(self, node):
if not self.children:
self.add_child(node)
return
leftmost_child = self.children[0]
while leftmost_child.children:
leftmost_child = leftmost_child.children[0]
leftmost_child.add_child(node)
class BufferTree:
"""A tree of buffers that can be depth-first traversed to produce a
correctly-ordered Ion stream.
Containers are treated as subtrees. Calling ``start_container`` creates
an empty node which represents the start of a new subtree, while calling
``end_container`` steps out of that subtree, allowing for additional
nodes to be added as siblings of that subtree's start node.
It is important that there is exactly one call to start_container per call
to ``end_container``. Nodes representing the start of a container subtree
are kept in a stack; upon calling ``end_container``, the subtree represented
by the node that was created by the most recent call to start_container will
be stepped out. Other than that, no container semantics are asserted by this class.
Scalar values are treated as child nodes of the current container subtree
(which may be at the top-level if the current container subtree's root node
is the root node of the BufferTree). Calling add_scalar_value will add a new
node as a child of the current container subtree. A container subtree's
children are ordered.
If None values are passed in to end_container or add_scalar_value,
nodes will be added to the tree, in the same way as described above, but no value
will be yielded for that node upon drain.
"""
def __init__(self):
self.__root = None
self.__container_lengths = None # Stack of pending container lengths.
self.__container_nodes = None # Stack of pending container nodes.
self.__container_node = None # Node representing the currently active container.
self.current_container_length = None # Length of the currently active container.
self.__reset()
def __reset(self):
self.__root = _Node()
self.__container_lengths = []
self.__container_nodes = []
self.__container_node = self.__root
self.current_container_length = 0
def __depth_traverse(self, node):
if node.children:
for child in node.children:
for val in self.__depth_traverse(child):
yield val
yield node.value
def start_container(self):
"""Add a node to the tree that represents the start of a container.
Until end_container is called, any nodes added through add_scalar_value
or start_container will be children of this new node.
"""
self.__container_lengths.append(self.current_container_length)
self.current_container_length = 0
new_container_node = _Node()
self.__container_node.add_child(new_container_node)
self.__container_nodes.append(self.__container_node)
self.__container_node = new_container_node
def end_container(self, header_buf):
"""Add a node containing the container's header to the current subtree.
This node will be added as the leftmost leaf of the subtree that was
started by the matching call to start_container.
Args:
header_buf (bytearray): bytearray containing the container header.
"""
if not self.__container_nodes:
raise ValueError("Attempted to end container with none active.")
# Header needs to be the first node visited on this subtree.
self.__container_node.add_leaf(_Node(header_buf))
self.__container_node = self.__container_nodes.pop()
parent_container_length = self.__container_lengths.pop()
self.current_container_length = \
parent_container_length + self.current_container_length + len(header_buf)
def add_scalar_value(self, value_buf):
"""Add a node to the tree containing a scalar value.
Args:
value_buf (bytearray): bytearray containing the scalar value.
"""
self.__container_node.add_child(_Node(value_buf))
self.current_container_length += len(value_buf)
def drain(self):
"""Walk the BufferTree and reset it when finished.
Yields:
any: The current node's value.
"""
if self.__container_nodes:
raise ValueError("Attempted to drain without ending all containers.")
for buf in self.__depth_traverse(self.__root):
if buf is not None:
yield buf
self.__reset()