├── tests
├── __init__.py
└── erlang_tests.py
├── examples
├── test.exs
└── port.py
├── README.markdown
├── .travis.yml
├── README.rst
├── LICENSE
├── setup.py
└── erlang.py
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/test.exs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env elixir
2 | # This is free and unencumbered software released into the public domain.
3 |
4 | port = Port.open({:spawn_executable, "/usr/bin/env"},
5 | [:binary, {:packet, 4}, {:args, ["python", "-u", "port.py"]}, :nouse_stdio])
6 |
7 | defmodule Python do
8 | def call(port, request) do
9 | Port.command(port, :erlang.term_to_binary(request))
10 | receive do
11 | {^port, {:data, response}} -> :erlang.binary_to_term(response)
12 | end
13 | end
14 | end
15 |
16 | IO.inspect Python.call(port, {:ping})
17 |
18 | Port.close(port)
19 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Erlang External Term Format for Python
2 | ======================================
3 |
4 | [](https://app.travis-ci.com/okeuday/erlang_py)
5 |
6 | Provides all encoding and decoding for the Erlang External Term Format
7 | (as defined at [https://erlang.org/doc/apps/erts/erl_ext_dist.html](https://erlang.org/doc/apps/erts/erl_ext_dist.html))
8 | in a single Python module.
9 |
10 | Available as a [Python package at `https://pypi.python.org/pypi/erlang_py/`](https://pypi.python.org/pypi/erlang_py/).
11 |
12 | Tests
13 | -----
14 |
15 | python setup.py test
16 |
17 | Author
18 | ------
19 |
20 | Michael Truog (mjtruog at protonmail dot com)
21 |
22 | License
23 | -------
24 |
25 | MIT License
26 |
27 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | - "3.4"
5 | - "3.5"
6 | - "3.6"
7 | - "3.7"
8 | - "3.8"
9 | - "3.9"
10 | - "pypy"
11 | # - "pypy3"
12 | before_install:
13 | - |
14 | if [ $TRAVIS_PYTHON_VERSION == "3.7" ]; then
15 | pip install setuptools==60.8.2
16 | fi
17 | script: python setup.py test
18 | branches:
19 | only:
20 | - master
21 | notifications:
22 | email:
23 | recipients:
24 | - mjtruog@gmail.com
25 | irc:
26 | channels:
27 | - "irc.oftc.net#cloudi"
28 | template:
29 | - "%{repository_slug} (%{branch} - %{commit}) %{author}: %{commit_message}"
30 | - "View Changes %{compare_url}"
31 | - "Build #%{build_number}: %{message} (%{build_url})"
32 | on_success: change
33 | on_failure: always
34 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Erlang External Term Format for Python
2 | ======================================
3 |
4 | |Build Status|
5 |
6 | Provides all encoding and decoding for the Erlang External Term Format
7 | (as defined at https://erlang.org/doc/apps/erts/erl_ext_dist.html) in a
8 | single Python module.
9 |
10 | Available as a `Python package at
11 | ``https://pypi.python.org/pypi/erlang_py/`` `__.
12 |
13 | Tests
14 | -----
15 |
16 | ::
17 |
18 | python setup.py test
19 |
20 | Author
21 | ------
22 |
23 | Michael Truog (mjtruog at protonmail dot com)
24 |
25 | License
26 | -------
27 |
28 | MIT License
29 |
30 | .. |Build Status| image:: https://app.travis-ci.com/okeuday/erlang_py.svg?branch=master
31 | :target: https://app.travis-ci.com/okeuday/erlang_py
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2011-2023 Michael Truog
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the "Software"),
7 | to deal in the Software without restriction, including without limitation
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 | and/or sell copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/port.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # This is free and unencumbered software released into the public domain.
3 |
4 | import erlang, os, struct
5 |
6 | def send(term, stream):
7 | """Write an Erlang term to an output stream."""
8 | payload = erlang.term_to_binary(term)
9 | header = struct.pack('!I', len(payload))
10 | stream.write(header)
11 | stream.write(payload)
12 | stream.flush()
13 |
14 | def recv(stream):
15 | """Read an Erlang term from an input stream."""
16 | header = stream.read(4)
17 | if len(header) != 4:
18 | return None # EOF
19 | (length,) = struct.unpack('!I', header)
20 | payload = stream.read(length)
21 | if len(payload) != length:
22 | return None
23 | term = erlang.binary_to_term(payload)
24 | return term
25 |
26 | def recv_loop(stream):
27 | """Yield Erlang terms from an input stream."""
28 | message = recv(stream)
29 | while message:
30 | yield message
31 | message = recv(stream)
32 |
33 | if __name__ == '__main__':
34 | input, output = os.fdopen(3, 'rb'), os.fdopen(4, 'wb')
35 | for message in recv_loop(input):
36 | send(message, output) # echo the message back
37 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #-*-Mode:python;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | # ex: set ft=python fenc=utf-8 sts=4 ts=4 sw=4 et:
3 |
4 | try:
5 | from setuptools import setup, Command
6 | except ImportError:
7 | from distutils.core import setup, Command
8 |
9 | class PyTest(Command):
10 | user_options = []
11 | def initialize_options(self):
12 | pass
13 | def finalize_options(self):
14 | pass
15 | def run(self):
16 | import tests.erlang_tests
17 | import unittest
18 | suite = unittest.TestSuite()
19 | suite.addTests(tests.erlang_tests.get_suite())
20 | unittest.TextTestRunner().run(suite)
21 |
22 | long_description = open('README.rst', 'r').read()
23 | setup(
24 | name='erlang_py',
25 | py_modules=['erlang'],
26 | cmdclass={'test': PyTest},
27 | license='MIT',
28 | classifiers=[
29 | 'Development Status :: 5 - Production/Stable',
30 | 'Intended Audience :: Developers',
31 | 'License :: OSI Approved :: MIT License',
32 | 'Operating System :: OS Independent',
33 | 'Programming Language :: Python',
34 | 'Programming Language :: Python :: 3',
35 | 'Programming Language :: Erlang',
36 | 'Topic :: Software Development :: Libraries :: Python Modules',
37 | 'Topic :: System :: Distributed Computing',
38 | ],
39 | version='2.0.7',
40 | description='Erlang External Term Format for Python',
41 | long_description=long_description,
42 | long_description_content_type='text/x-rst',
43 | author='Michael Truog',
44 | author_email='mjtruog@protonmail.com',
45 | url='https://github.com/okeuday/erlang_py',
46 | )
47 |
--------------------------------------------------------------------------------
/tests/erlang_tests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #-*-Mode:python;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
3 | # ex: set ft=python fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
4 | #
5 | # MIT License
6 | #
7 | # Copyright (c) 2014-2023 Michael Truog
8 | # Copyright (c) 2009-2013 Dmitry Vasiliev
9 | #
10 | # Permission is hereby granted, free of charge, to any person obtaining a
11 | # copy of this software and associated documentation files (the "Software"),
12 | # to deal in the Software without restriction, including without limitation
13 | # the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 | # and/or sell copies of the Software, and to permit persons to whom the
15 | # Software is furnished to do so, subject to the following conditions:
16 | #
17 | # The above copyright notice and this permission notice shall be included in
18 | # all copies or substantial portions of the Software.
19 | #
20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 | # DEALINGS IN THE SOFTWARE.
27 | #
28 | """
29 | Erlang External Term Format Encoding/Decoding Tests
30 | """
31 |
32 | import sys
33 | import os
34 | sys.path.insert(0,
35 | os.path.sep.join(
36 | os.path.dirname(
37 | os.path.abspath(__file__)
38 | ).split(os.path.sep)[:-1]
39 | )
40 | )
41 | import unittest
42 | import erlang
43 | from collections import OrderedDict
44 | try:
45 | import coverage
46 | except ImportError:
47 | coverage = None
48 |
49 | # many of the test cases were adapted
50 | # from erlport (https://github.com/hdima/erlport)
51 | # to make the tests more exhaustive
52 |
53 | # pylint: disable=missing-docstring
54 |
55 | class AtomTestCase(unittest.TestCase):
56 | def test_atom(self):
57 | atom1 = erlang.OtpErlangAtom('test')
58 | self.assertEqual(erlang.OtpErlangAtom, type(atom1))
59 | self.assertEqual(erlang.OtpErlangAtom('test'), atom1)
60 | self.assertEqual("OtpErlangAtom('test')", repr(atom1))
61 | self.assertTrue(isinstance(atom1, erlang.OtpErlangAtom))
62 | atom2 = erlang.OtpErlangAtom('test2')
63 | atom1_new = erlang.OtpErlangAtom('test')
64 | self.assertFalse(atom1 is atom2)
65 | self.assertNotEqual(hash(atom1), hash(atom2))
66 | self.assertFalse(atom1 is atom1_new)
67 | self.assertEqual(hash(atom1), hash(atom1_new))
68 | self.assertEqual('X' * 255, erlang.OtpErlangAtom('X' * 255).value)
69 | self.assertEqual('X' * 256, erlang.OtpErlangAtom('X' * 256).value)
70 | def test_invalid_atom(self):
71 | self.assertRaises(erlang.OutputException,
72 | erlang.OtpErlangAtom.binary,
73 | erlang.OtpErlangAtom([1, 2]))
74 |
75 | class ListTestCase(unittest.TestCase):
76 | def test_list(self):
77 | lst = erlang.OtpErlangList([116, 101, 115, 116])
78 | self.assertTrue(isinstance(lst, erlang.OtpErlangList))
79 | self.assertEqual(erlang.OtpErlangList([116, 101, 115, 116]), lst)
80 | self.assertEqual([116, 101, 115, 116], lst.value)
81 | self.assertEqual('OtpErlangList([116, 101, 115, 116],improper=False)',
82 | repr(lst))
83 |
84 | class ImproperListTestCase(unittest.TestCase):
85 | def test_improper_list(self):
86 | lst = erlang.OtpErlangList([1, 2, 3, 4], improper=True)
87 | self.assertTrue(isinstance(lst, erlang.OtpErlangList))
88 | self.assertEqual([1, 2, 3, 4], lst.value)
89 | self.assertEqual(4, lst.value[-1])
90 | self.assertEqual('OtpErlangList([1, 2, 3, 4],improper=True)',
91 | repr(lst))
92 | def test_comparison(self):
93 | lst = erlang.OtpErlangList([1, 2, 3, 4], improper=True)
94 | self.assertEqual(lst, lst)
95 | self.assertEqual(lst,
96 | erlang.OtpErlangList([1, 2, 3, 4], improper=True))
97 | self.assertNotEqual(lst,
98 | erlang.OtpErlangList([1, 2, 3, 5], improper=True))
99 | self.assertNotEqual(lst,
100 | erlang.OtpErlangList([1, 2, 3], improper=True))
101 | def test_errors(self):
102 | self.assertRaises(erlang.OutputException,
103 | erlang.OtpErlangList.binary,
104 | erlang.OtpErlangList("invalid", improper=True))
105 |
106 | class DecodeTestCase(unittest.TestCase):
107 | # pylint: disable=invalid-name
108 | def test_binary_to_term(self):
109 | self.assertRaises(erlang.ParseException,
110 | erlang.binary_to_term, b'')
111 | self.assertRaises(erlang.ParseException,
112 | erlang.binary_to_term, b'\0')
113 | self.assertRaises(erlang.ParseException,
114 | erlang.binary_to_term, b'\x83')
115 | self.assertRaises(erlang.ParseException,
116 | erlang.binary_to_term, b'\x83z')
117 | def test_binary_to_term_atom(self):
118 | self.assertRaises(erlang.ParseException,
119 | erlang.binary_to_term, b'\x83d')
120 | self.assertRaises(erlang.ParseException,
121 | erlang.binary_to_term, b'\x83d\0')
122 | self.assertRaises(erlang.ParseException,
123 | erlang.binary_to_term, b'\x83d\0\1')
124 | self.assertEqual(erlang.OtpErlangAtom(b''),
125 | erlang.binary_to_term(b'\x83d\0\0'))
126 | self.assertEqual(erlang.OtpErlangAtom(b''),
127 | erlang.binary_to_term(b'\x83s\0'))
128 | self.assertEqual(erlang.OtpErlangAtom(b'test'),
129 | erlang.binary_to_term(b'\x83d\0\4test'))
130 | self.assertEqual(erlang.OtpErlangAtom(b'test'),
131 | erlang.binary_to_term(b'\x83s\4test'))
132 | self.assertEqual(erlang.OtpErlangAtom(u'name'),
133 | erlang.binary_to_term(b'\x83w\x04name'))
134 | def test_binary_to_term_predefined_atoms(self):
135 | self.assertEqual(True, erlang.binary_to_term(b'\x83s\4true'))
136 | self.assertEqual(False, erlang.binary_to_term(b'\x83s\5false'))
137 | self.assertEqual(False, erlang.binary_to_term(b'\x83w\5false'))
138 | self.assertEqual(None, erlang.binary_to_term(b'\x83s\11undefined'))
139 | self.assertEqual(None, erlang.binary_to_term(b'\x83w\11undefined'))
140 | self.assertEqual(None, erlang.binary_to_term(b'\x83d\0\11undefined'))
141 | self.assertEqual(None, erlang.binary_to_term(b'\x83v\0\11undefined'))
142 | def test_binary_to_term_empty_list(self):
143 | self.assertEqual([], erlang.binary_to_term(b'\x83j'))
144 | def test_binary_to_term_string_list(self):
145 | self.assertRaises(erlang.ParseException,
146 | erlang.binary_to_term, b'\x83k')
147 | self.assertRaises(erlang.ParseException,
148 | erlang.binary_to_term, b'\x83k\0')
149 | self.assertRaises(erlang.ParseException,
150 | erlang.binary_to_term, b'\x83k\0\1')
151 | self.assertEqual(b'', erlang.binary_to_term(b'\x83k\0\0'))
152 | self.assertEqual(b'test', erlang.binary_to_term(b'\x83k\0\4test'))
153 | def test_binary_to_term_list(self):
154 | self.assertRaises(erlang.ParseException,
155 | erlang.binary_to_term, b'\x83l')
156 | self.assertRaises(erlang.ParseException,
157 | erlang.binary_to_term, b'\x83l\0')
158 | self.assertRaises(erlang.ParseException,
159 | erlang.binary_to_term, b'\x83l\0\0')
160 | self.assertRaises(erlang.ParseException,
161 | erlang.binary_to_term, b'\x83l\0\0\0')
162 | self.assertRaises(erlang.ParseException,
163 | erlang.binary_to_term, b'\x83l\0\0\0\0')
164 | self.assertEqual([], erlang.binary_to_term(b'\x83l\0\0\0\0j'))
165 | self.assertEqual([[], []], erlang.binary_to_term(b'\x83l\0\0\0\2jjj'))
166 | def test_binary_to_term_improper_list(self):
167 | self.assertRaises(erlang.ParseException,
168 | erlang.binary_to_term, b'\x83l\0\0\0\0k')
169 | lst = erlang.binary_to_term(b'\x83l\0\0\0\1jd\0\4tail')
170 | self.assertEqual(isinstance(lst, erlang.OtpErlangList), True)
171 | self.assertEqual([[], erlang.OtpErlangAtom(b'tail')], lst.value)
172 | self.assertEqual(True, lst.improper)
173 | def test_binary_to_term_map(self):
174 | # represents #{ {at,[hello]} => ok}
175 | binary = b"\x83t\x00\x00\x00\x01h\x02d\x00\x02atl\x00\x00\x00\x01d\x00\x05hellojd\x00\x02ok"
176 | map_with_list = erlang.binary_to_term(binary)
177 | self.assertEqual(isinstance(map_with_list, dict), True)
178 | def test_binary_to_term_small_tuple(self):
179 | self.assertRaises(erlang.ParseException,
180 | erlang.binary_to_term, b'\x83h')
181 | self.assertRaises(erlang.ParseException,
182 | erlang.binary_to_term, b'\x83h\1')
183 | self.assertEqual((), erlang.binary_to_term(b'\x83h\0'))
184 | self.assertEqual(([], []), erlang.binary_to_term(b'\x83h\2jj'))
185 | def test_binary_to_term_large_tuple(self):
186 | self.assertRaises(erlang.ParseException,
187 | erlang.binary_to_term, b'\x83i')
188 | self.assertRaises(erlang.ParseException,
189 | erlang.binary_to_term, b'\x83i\0')
190 | self.assertRaises(erlang.ParseException,
191 | erlang.binary_to_term, b'\x83i\0\0')
192 | self.assertRaises(erlang.ParseException,
193 | erlang.binary_to_term, b'\x83i\0\0\0')
194 | self.assertRaises(erlang.ParseException,
195 | erlang.binary_to_term, b'\x83i\0\0\0\1')
196 | self.assertEqual((), erlang.binary_to_term(b'\x83i\0\0\0\0'))
197 | self.assertEqual(([], []), erlang.binary_to_term(b'\x83i\0\0\0\2jj'))
198 | def test_binary_to_term_small_integer(self):
199 | self.assertRaises(erlang.ParseException,
200 | erlang.binary_to_term, b'\x83a')
201 | self.assertEqual(0, erlang.binary_to_term(b'\x83a\0'))
202 | self.assertEqual(255, erlang.binary_to_term(b'\x83a\xff'))
203 | def test_binary_to_term_integer(self):
204 | self.assertRaises(erlang.ParseException,
205 | erlang.binary_to_term, b'\x83b')
206 | self.assertRaises(erlang.ParseException,
207 | erlang.binary_to_term, b'\x83b\0')
208 | self.assertRaises(erlang.ParseException,
209 | erlang.binary_to_term, b'\x83b\0\0')
210 | self.assertRaises(erlang.ParseException,
211 | erlang.binary_to_term, b'\x83b\0\0\0')
212 | self.assertEqual(0, erlang.binary_to_term(b'\x83b\0\0\0\0'))
213 | self.assertEqual(2147483647,
214 | erlang.binary_to_term(b'\x83b\x7f\xff\xff\xff'))
215 | self.assertEqual(-2147483648,
216 | erlang.binary_to_term(b'\x83b\x80\0\0\0'))
217 | self.assertEqual(-1, erlang.binary_to_term(b'\x83b\xff\xff\xff\xff'))
218 | def test_binary_to_term_binary(self):
219 | self.assertRaises(erlang.ParseException,
220 | erlang.binary_to_term, b'\x83m')
221 | self.assertRaises(erlang.ParseException,
222 | erlang.binary_to_term, b'\x83m\0')
223 | self.assertRaises(erlang.ParseException,
224 | erlang.binary_to_term, b'\x83m\0\0')
225 | self.assertRaises(erlang.ParseException,
226 | erlang.binary_to_term, b'\x83m\0\0\0')
227 | self.assertRaises(erlang.ParseException,
228 | erlang.binary_to_term, b'\x83m\0\0\0\1')
229 | self.assertEqual(erlang.OtpErlangBinary(b''),
230 | erlang.binary_to_term(b'\x83m\0\0\0\0'))
231 | self.assertEqual(erlang.OtpErlangBinary(b'data'),
232 | erlang.binary_to_term(b'\x83m\0\0\0\4data'))
233 | def test_binary_to_term_float(self):
234 | self.assertRaises(erlang.ParseException,
235 | erlang.binary_to_term, b'\x83F')
236 | self.assertRaises(erlang.ParseException,
237 | erlang.binary_to_term, b'\x83F\0')
238 | self.assertRaises(erlang.ParseException,
239 | erlang.binary_to_term, b'\x83F\0\0')
240 | self.assertRaises(erlang.ParseException,
241 | erlang.binary_to_term, b'\x83F\0\0\0')
242 | self.assertRaises(erlang.ParseException,
243 | erlang.binary_to_term, b'\x83F\0\0\0\0')
244 | self.assertRaises(erlang.ParseException,
245 | erlang.binary_to_term, b'\x83F\0\0\0\0\0')
246 | self.assertRaises(erlang.ParseException,
247 | erlang.binary_to_term, b'\x83F\0\0\0\0\0\0')
248 | self.assertRaises(erlang.ParseException,
249 | erlang.binary_to_term, b'\x83F\0\0\0\0\0\0\0')
250 | self.assertEqual(0.0, erlang.binary_to_term(b'\x83F\0\0\0\0\0\0\0\0'))
251 | self.assertEqual(1.5, erlang.binary_to_term(b'\x83F?\xf8\0\0\0\0\0\0'))
252 | def test_binary_to_term_small_big_integer(self):
253 | self.assertRaises(erlang.ParseException,
254 | erlang.binary_to_term, b'\x83n')
255 | self.assertRaises(erlang.ParseException,
256 | erlang.binary_to_term, b'\x83n\0')
257 | self.assertRaises(erlang.ParseException,
258 | erlang.binary_to_term, b'\x83n\1\0')
259 | self.assertEqual(0, erlang.binary_to_term(b'\x83n\0\0'))
260 | self.assertEqual(6618611909121,
261 | erlang.binary_to_term(b'\x83n\6\0\1\2\3\4\5\6'))
262 | self.assertEqual(-6618611909121,
263 | erlang.binary_to_term(b'\x83n\6\1\1\2\3\4\5\6'))
264 | def test_binary_to_term_big_integer(self):
265 | self.assertRaises(erlang.ParseException,
266 | erlang.binary_to_term, b'\x83o')
267 | self.assertRaises(erlang.ParseException,
268 | erlang.binary_to_term, b'\x83o\0')
269 | self.assertRaises(erlang.ParseException,
270 | erlang.binary_to_term, b'\x83o\0\0')
271 | self.assertRaises(erlang.ParseException,
272 | erlang.binary_to_term, b'\x83o\0\0\0')
273 | self.assertRaises(erlang.ParseException,
274 | erlang.binary_to_term, b'\x83o\0\0\0\0')
275 | self.assertRaises(erlang.ParseException,
276 | erlang.binary_to_term, b'\x83o\0\0\0\1\0')
277 | self.assertEqual(0, erlang.binary_to_term(b'\x83o\0\0\0\0\0'))
278 | self.assertEqual(6618611909121,
279 | erlang.binary_to_term(b'\x83o\0\0\0\6\0\1\2\3\4\5\6'))
280 | self.assertEqual(-6618611909121,
281 | erlang.binary_to_term(b'\x83o\0\0\0\6\1\1\2\3\4\5\6'))
282 | def test_binary_to_term_map(self):
283 | self.assertRaises(erlang.ParseException,
284 | erlang.binary_to_term, b'\x83t')
285 | self.assertRaises(erlang.ParseException,
286 | erlang.binary_to_term, b'\x83t\x00')
287 | self.assertRaises(erlang.ParseException,
288 | erlang.binary_to_term, b'\x83t\x00\x00')
289 | self.assertRaises(erlang.ParseException,
290 | erlang.binary_to_term, b'\x83t\x00\x00\x00')
291 | self.assertRaises(erlang.ParseException,
292 | erlang.binary_to_term, b'\x83t\x00\x00\x00\x01')
293 | self.assertEqual({}, erlang.binary_to_term(b'\x83t\x00\x00\x00\x00'))
294 | map1 = {
295 | erlang.OtpErlangAtom(b'a'): 1,
296 | }
297 | map1_binary = b'\x83t\x00\x00\x00\x01s\x01aa\x01'
298 | self.assertEqual(map1, erlang.binary_to_term(map1_binary))
299 | map2 = {
300 | erlang.OtpErlangBinary(b'\xA8', 6):
301 | erlang.OtpErlangBinary(b'everything'),
302 | None:
303 | erlang.OtpErlangBinary(b'nothing'),
304 | }
305 | map2_binary = (
306 | b'\x83\x74\x00\x00\x00\x02\x77\x09\x75\x6E\x64\x65\x66\x69'
307 | b'\x6E\x65\x64\x6D\x00\x00\x00\x07\x6E\x6F\x74\x68\x69\x6E'
308 | b'\x67\x4D\x00\x00\x00\x01\x06\xA8\x6D\x00\x00\x00\x0A\x65'
309 | b'\x76\x65\x72\x79\x74\x68\x69\x6E\x67'
310 | )
311 | self.assertEqual(map2, erlang.binary_to_term(map2_binary))
312 | def test_binary_to_term_pid(self):
313 | pid_old_binary = (
314 | b'\x83\x67\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F'
315 | b'\x68\x6F\x73\x74\x00\x00\x00\x4E\x00\x00\x00\x00\x00'
316 | )
317 | pid_old = erlang.binary_to_term(pid_old_binary)
318 | self.assertTrue(isinstance(pid_old, erlang.OtpErlangPid))
319 | self.assertEqual(erlang.term_to_binary(pid_old),
320 | b'\x83gs\rnonode@nohost\x00\x00\x00N'
321 | b'\x00\x00\x00\x00\x00')
322 | pid_new_binary = (
323 | b'\x83\x58\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68'
324 | b'\x6F\x73\x74\x00\x00\x00\x4E\x00\x00\x00\x00\x00\x00\x00\x00'
325 | )
326 | pid_new = erlang.binary_to_term(pid_new_binary)
327 | self.assertTrue(isinstance(pid_new, erlang.OtpErlangPid))
328 | self.assertEqual(erlang.term_to_binary(pid_new),
329 | b'\x83Xs\rnonode@nohost\x00\x00\x00N'
330 | b'\x00\x00\x00\x00\x00\x00\x00\x00')
331 | def test_binary_to_term_port(self):
332 | port_old_binary = (
333 | b'\x83\x66\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68'
334 | b'\x6F\x73\x74\x00\x00\x00\x06\x00'
335 | )
336 | port_old = erlang.binary_to_term(port_old_binary)
337 | self.assertTrue(isinstance(port_old, erlang.OtpErlangPort))
338 | self.assertEqual(erlang.term_to_binary(port_old),
339 | b'\x83fs\rnonode@nohost\x00\x00\x00\x06\x00')
340 | port_new_binary = (
341 | b'\x83\x59\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68'
342 | b'\x6F\x73\x74\x00\x00\x00\x06\x00\x00\x00\x00'
343 | )
344 | port_new = erlang.binary_to_term(port_new_binary)
345 | self.assertTrue(isinstance(port_new, erlang.OtpErlangPort))
346 | self.assertEqual(erlang.term_to_binary(port_new),
347 | b'\x83Ys\rnonode@nohost\x00\x00\x00\x06'
348 | b'\x00\x00\x00\x00')
349 | port_v4_binary = (
350 | b'\x83\x78\x77\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F'
351 | b'\x73\x74\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00'
352 | )
353 | port_v4 = erlang.binary_to_term(port_v4_binary)
354 | self.assertTrue(isinstance(port_v4, erlang.OtpErlangPort))
355 | self.assertEqual(erlang.term_to_binary(port_v4), port_v4_binary)
356 | def test_binary_to_term_ref(self):
357 | ref_new_binary = (
358 | b'\x83\x72\x00\x03\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E'
359 | b'\x6F\x68\x6F\x73\x74\x00\x00\x03\xE8\x4E\xE7\x68\x00\x02\xA4'
360 | b'\xC8\x53\x40'
361 | )
362 | ref_new = erlang.binary_to_term(ref_new_binary)
363 | self.assertTrue(isinstance(ref_new, erlang.OtpErlangReference))
364 | self.assertEqual(erlang.term_to_binary(ref_new),
365 | b'\x83r\x00\x03s\rnonode@nohost\x00\x00\x03\xe8'
366 | b'N\xe7h\x00\x02\xa4\xc8S@')
367 | ref_newer_binary = (
368 | b'\x83\x5A\x00\x03\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E'
369 | b'\x6F\x68\x6F\x73\x74\x00\x00\x00\x00\x00\x01\xAC\x03\xC7\x00'
370 | b'\x00\x04\xBB\xB2\xCA\xEE'
371 | )
372 | ref_newer = erlang.binary_to_term(ref_newer_binary)
373 | self.assertTrue(isinstance(ref_newer, erlang.OtpErlangReference))
374 | self.assertEqual(erlang.term_to_binary(ref_newer),
375 | b'\x83Z\x00\x03s\rnonode@nohost\x00\x00\x00\x00\x00'
376 | b'\x01\xac\x03\xc7\x00\x00\x04\xbb\xb2\xca\xee')
377 | def test_binary_to_term_compressed_term(self):
378 | self.assertRaises(erlang.ParseException,
379 | erlang.binary_to_term, b'\x83P')
380 | self.assertRaises(erlang.ParseException,
381 | erlang.binary_to_term, b'\x83P\0')
382 | self.assertRaises(erlang.ParseException,
383 | erlang.binary_to_term, b'\x83P\0\0')
384 | self.assertRaises(erlang.ParseException,
385 | erlang.binary_to_term, b'\x83P\0\0\0')
386 | self.assertRaises(erlang.ParseException,
387 | erlang.binary_to_term, b'\x83P\0\0\0\0')
388 | self.assertRaises(
389 | erlang.ParseException, erlang.binary_to_term,
390 | b'\x83P\0\0\0\x16\x78\xda\xcb\x66\x10\x49\xc1\2\0\x5d\x60\x08\x50'
391 | )
392 | self.assertEqual(
393 | b'd' * 20,
394 | erlang.binary_to_term(
395 | b'\x83P\0\0\0\x17\x78\xda\xcb\x66'
396 | b'\x10\x49\xc1\2\0\x5d\x60\x08\x50'
397 | )
398 | )
399 |
400 | class EncodeTestCase(unittest.TestCase):
401 | # pylint: disable=invalid-name
402 | def test_term_to_binary_tuple(self):
403 | self.assertEqual(b'\x83h\0', erlang.term_to_binary(()))
404 | self.assertEqual(b'\x83h\2h\0h\0', erlang.term_to_binary(((), ())))
405 | self.assertEqual(b'\x83h\xff' + b'h\0' * 255,
406 | erlang.term_to_binary(tuple([()] * 255)))
407 | self.assertEqual(b'\x83i\0\0\1\0' + b'h\0' * 256,
408 | erlang.term_to_binary(tuple([()] * 256)))
409 | def test_term_to_binary_empty_list(self):
410 | self.assertEqual(b'\x83j', erlang.term_to_binary([]))
411 | def test_term_to_binary_string_list(self):
412 | self.assertEqual(b'\x83j', erlang.term_to_binary(''))
413 | self.assertEqual(b'\x83k\0\1\0', erlang.term_to_binary('\0'))
414 | value = (
415 | b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r'
416 | b'\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a'
417 | b'\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>'
418 | b'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopq'
419 | b'rstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88'
420 | b'\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95'
421 | b'\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2'
422 | b'\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf'
423 | b'\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc'
424 | b'\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9'
425 | b'\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6'
426 | b'\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3'
427 | b'\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0'
428 | b'\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'
429 | )
430 | self.assertEqual(b'\x83k\1\0' + value, erlang.term_to_binary(value))
431 | def test_term_to_binary_list_basic(self):
432 | self.assertEqual(b'\x83\x6A', erlang.term_to_binary([]))
433 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x6A\x6A',
434 | erlang.term_to_binary(['']))
435 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x61\x01\x6A',
436 | erlang.term_to_binary([1]))
437 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x61\xFF\x6A',
438 | erlang.term_to_binary([255]))
439 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\x00\x00\x01\x00\x6A',
440 | erlang.term_to_binary([256]))
441 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\x7F\xFF\xFF\xFF\x6A',
442 | erlang.term_to_binary([2147483647]))
443 | self.assertEqual(
444 | b'\x83\x6C\x00\x00\x00\x01\x6E\x04\x00\x00\x00\x00\x80\x6A',
445 | erlang.term_to_binary([2147483648])
446 | )
447 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x61\x00\x6A',
448 | erlang.term_to_binary([0]))
449 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\xFF\xFF\xFF\xFF\x6A',
450 | erlang.term_to_binary([-1]))
451 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\xFF\xFF\xFF\x00\x6A',
452 | erlang.term_to_binary([-256]))
453 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\xFF\xFF\xFE\xFF\x6A',
454 | erlang.term_to_binary([-257]))
455 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\x80\x00\x00\x00\x6A',
456 | erlang.term_to_binary([-2147483648]))
457 | self.assertEqual(
458 | b'\x83\x6C\x00\x00\x00\x01\x6E\x04\x01\x01\x00\x00\x80\x6A',
459 | erlang.term_to_binary([-2147483649])
460 | )
461 | self.assertEqual(
462 | b'\x83\x6C\x00\x00\x00\x01\x6B\x00\x04\x74\x65\x73\x74\x6A',
463 | erlang.term_to_binary(['test'])
464 | )
465 | self.assertEqual(
466 | b'\x83\x6C\x00\x00\x00\x02\x62\x00\x00\x01\x75\x62\x00\x00'
467 | b'\x01\xC7\x6A',
468 | erlang.term_to_binary([373, 455])
469 | )
470 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x6A\x6A',
471 | erlang.term_to_binary([[]]))
472 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x02\x6A\x6A\x6A',
473 | erlang.term_to_binary([[], []]))
474 | self.assertEqual(
475 | b'\x83\x6C\x00\x00\x00\x03\x6C\x00\x00\x00\x02\x6B\x00\x04\x74\x68'
476 | b'\x69\x73\x6B\x00\x02\x69\x73\x6A\x6C\x00\x00\x00\x01\x6C\x00\x00'
477 | b'\x00\x01\x6B\x00\x01\x61\x6A\x6A\x6B\x00\x04\x74\x65\x73\x74\x6A',
478 | erlang.term_to_binary([['this', 'is'], [['a']], 'test'])
479 | )
480 | def test_term_to_binary_list(self):
481 | self.assertEqual(b'\x83l\0\0\0\1jj', erlang.term_to_binary([[]]))
482 | self.assertEqual(b'\x83l\0\0\0\5jjjjjj',
483 | erlang.term_to_binary([[], [], [], [], []]))
484 | self.assertEqual(
485 | b'\x83l\0\0\0\5jjjjjj',
486 | erlang.term_to_binary(erlang.OtpErlangList([
487 | erlang.OtpErlangList([]),
488 | erlang.OtpErlangList([]),
489 | erlang.OtpErlangList([]),
490 | erlang.OtpErlangList([]),
491 | erlang.OtpErlangList([])
492 | ]))
493 | )
494 | def test_term_to_binary_improper_list(self):
495 | self.assertEqual(
496 | b'\x83l\0\0\0\1h\0h\0',
497 | erlang.term_to_binary(
498 | erlang.OtpErlangList([(), ()], improper=True)
499 | )
500 | )
501 | self.assertEqual(
502 | b'\x83l\0\0\0\1a\0a\1',
503 | erlang.term_to_binary(
504 | erlang.OtpErlangList([0, 1], improper=True)
505 | )
506 | )
507 | def test_term_to_binary_unicode(self):
508 | self.assertEqual(b'\x83j', erlang.term_to_binary(''))
509 | self.assertEqual(b'\x83k\0\4test', erlang.term_to_binary('test'))
510 | self.assertEqual(b'\x83k\0\3\x00\xc3\xbf',
511 | erlang.term_to_binary(b'\x00\xc3\xbf'))
512 | self.assertEqual(b'\x83k\0\2\xc4\x80',
513 | erlang.term_to_binary(b'\xc4\x80'))
514 | self.assertEqual(
515 | b'\x83k\0\x08\xd1\x82\xd0\xb5\xd1\x81\xd1\x82',
516 | erlang.term_to_binary(b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82')
517 | )
518 | # becomes a list of small integers
519 | self.assertEqual(
520 | b'\x83l\x00\x02\x00\x00' + (b'a\xd0a\x90' * 65536) + b'j',
521 | erlang.term_to_binary(b'\xd0\x90' * 65536)
522 | )
523 | def test_term_to_binary_atom(self):
524 | self.assertEqual(b'\x83s\0',
525 | erlang.term_to_binary(erlang.OtpErlangAtom(b'')))
526 | self.assertEqual(
527 | b'\x83s\4test',
528 | erlang.term_to_binary(erlang.OtpErlangAtom(b'test'))
529 | )
530 | def test_term_to_binary_string_basic(self):
531 | self.assertEqual(b'\x83\x6A', erlang.term_to_binary(''))
532 | self.assertEqual(b'\x83\x6B\x00\x04\x74\x65\x73\x74',
533 | erlang.term_to_binary('test'))
534 | self.assertEqual(
535 | b'\x83\x6B\x00\x09\x74\x77\x6F\x20\x77\x6F\x72\x64\x73',
536 | erlang.term_to_binary('two words')
537 | )
538 | self.assertEqual(
539 | b'\x83\x6B\x00\x16\x74\x65\x73\x74\x69\x6E\x67\x20\x6D\x75\x6C\x74'
540 | b'\x69\x70\x6C\x65\x20\x77\x6F\x72\x64\x73',
541 | erlang.term_to_binary('testing multiple words')
542 | )
543 | self.assertEqual(b'\x83\x6B\x00\x01\x20',
544 | erlang.term_to_binary(' '))
545 | self.assertEqual(b'\x83\x6B\x00\x02\x20\x20',
546 | erlang.term_to_binary(' '))
547 | self.assertEqual(b'\x83\x6B\x00\x01\x31',
548 | erlang.term_to_binary('1'))
549 | self.assertEqual(b'\x83\x6B\x00\x02\x33\x37',
550 | erlang.term_to_binary('37'))
551 | self.assertEqual(b'\x83\x6B\x00\x07\x6F\x6E\x65\x20\x3D\x20\x31',
552 | erlang.term_to_binary('one = 1'))
553 | self.assertEqual(
554 | b'\x83\x6B\x00\x20\x21\x40\x23\x24\x25\x5E\x26\x2A\x28\x29\x5F\x2B'
555 | b'\x2D\x3D\x5B\x5D\x7B\x7D\x5C\x7C\x3B\x27\x3A\x22\x2C\x2E\x2F\x3C'
556 | b'\x3E\x3F\x7E\x60',
557 | erlang.term_to_binary('!@#$%^&*()_+-=[]{}\\|;\':",./<>?~`')
558 | )
559 | self.assertEqual(
560 | b'\x83\x6B\x00\x09\x22\x08\x0C\x0A\x0D\x09\x0B\x53\x12',
561 | erlang.term_to_binary('\"\b\f\n\r\t\v\123\x12')
562 | )
563 | def test_term_to_binary_string(self):
564 | self.assertEqual(b'\x83j', erlang.term_to_binary(''))
565 | self.assertEqual(b'\x83k\0\4test', erlang.term_to_binary('test'))
566 | def test_term_to_binary_predefined_atom(self):
567 | self.assertEqual(b'\x83w\4true', erlang.term_to_binary(True))
568 | self.assertEqual(b'\x83w\5false', erlang.term_to_binary(False))
569 | self.assertEqual(b'\x83w\11undefined', erlang.term_to_binary(None))
570 | def test_term_to_binary_short_integer(self):
571 | self.assertEqual(b'\x83a\0', erlang.term_to_binary(0))
572 | self.assertEqual(b'\x83a\xff', erlang.term_to_binary(255))
573 | def test_term_to_binary_integer(self):
574 | self.assertEqual(b'\x83b\xff\xff\xff\xff', erlang.term_to_binary(-1))
575 | self.assertEqual(b'\x83b\x80\0\0\0',
576 | erlang.term_to_binary(-2147483648))
577 | self.assertEqual(b'\x83b\0\0\1\0', erlang.term_to_binary(256))
578 | self.assertEqual(b'\x83b\x7f\xff\xff\xff',
579 | erlang.term_to_binary(2147483647))
580 | def test_term_to_binary_long_integer(self):
581 | self.assertEqual(b'\x83n\4\0\0\0\0\x80',
582 | erlang.term_to_binary(2147483648))
583 | self.assertEqual(b'\x83n\4\1\1\0\0\x80',
584 | erlang.term_to_binary(-2147483649))
585 | self.assertEqual(b'\x83o\0\0\1\0\0' + b'\0' * 255 + b'\1',
586 | erlang.term_to_binary(2 ** 2040))
587 | self.assertEqual(b'\x83o\0\0\1\0\1' + b'\0' * 255 + b'\1',
588 | erlang.term_to_binary(-2 ** 2040))
589 | def test_term_to_binary_float(self):
590 | self.assertEqual(b'\x83F\0\0\0\0\0\0\0\0', erlang.term_to_binary(0.0))
591 | self.assertEqual(b'\x83F?\xe0\0\0\0\0\0\0', erlang.term_to_binary(0.5))
592 | self.assertEqual(b'\x83F\xbf\xe0\0\0\0\0\0\0',
593 | erlang.term_to_binary(-0.5))
594 | self.assertEqual(b'\x83F@\t!\xfbM\x12\xd8J',
595 | erlang.term_to_binary(3.1415926))
596 | self.assertEqual(b'\x83F\xc0\t!\xfbM\x12\xd8J',
597 | erlang.term_to_binary(-3.1415926))
598 | def test_term_to_binary_map(self):
599 | self.assertEqual(b'\x83t\x00\x00\x00\x00', erlang.term_to_binary({}))
600 | map1 = {
601 | erlang.OtpErlangAtom(b'a'): 1,
602 | }
603 | map1_binary = b'\x83t\x00\x00\x00\x01s\x01aa\x01'
604 | self.assertEqual(map1_binary, erlang.term_to_binary(map1))
605 | map2 = OrderedDict([
606 | (erlang.OtpErlangAtom(u'undefined'),
607 | erlang.OtpErlangBinary(b'nothing')),
608 | (erlang.OtpErlangBinary(b'\xA8', 6),
609 | erlang.OtpErlangBinary(b'everything')),
610 | ])
611 | map2_binary = (
612 | b'\x83\x74\x00\x00\x00\x02\x77\x09\x75\x6E\x64\x65\x66\x69'
613 | b'\x6E\x65\x64\x6D\x00\x00\x00\x07\x6E\x6F\x74\x68\x69\x6E'
614 | b'\x67\x4D\x00\x00\x00\x01\x06\xA8\x6D\x00\x00\x00\x0A\x65'
615 | b'\x76\x65\x72\x79\x74\x68\x69\x6E\x67'
616 | )
617 | self.assertEqual(map2_binary, erlang.term_to_binary(map2))
618 | def test_term_to_binary_compressed_term(self):
619 | self.assertEqual(b'\x83P\x00\x00\x00\x15'
620 | b'x\x9c\xcba``\xe0\xcfB\x03\x00B@\x07\x1c',
621 | erlang.term_to_binary([[]] * 15, compressed=True))
622 | self.assertEqual(b'\x83P\x00\x00\x00\x15'
623 | b'x\x9c\xcba``\xe0\xcfB\x03\x00B@\x07\x1c',
624 | erlang.term_to_binary([[]] * 15, compressed=6))
625 | self.assertEqual(b'\x83P\x00\x00\x00\x15'
626 | b'x\xda\xcba``\xe0\xcfB\x03\x00B@\x07\x1c',
627 | erlang.term_to_binary([[]] * 15, compressed=9))
628 | self.assertEqual(b'\x83P\x00\x00\x00\x15'
629 | b'x\x01\x01\x15\x00\xea\xffl\x00\x00\x00'
630 | b'\x0fjjjjjjjjjjjjjjjjB@\x07\x1c',
631 | erlang.term_to_binary([[]] * 15, compressed=0))
632 | self.assertEqual(b'\x83P\x00\x00\x00\x15'
633 | b'x\x01\xcba``\xe0\xcfB\x03\x00B@\x07\x1c',
634 | erlang.term_to_binary([[]] * 15, 1))
635 | self.assertEqual(b'\x83P\0\0\0\x17\x78\xda\xcb\x66'
636 | b'\x10\x49\xc1\2\0\x5d\x60\x08\x50',
637 | erlang.term_to_binary('d' * 20, compressed=9))
638 |
639 | def get_suite():
640 | load = unittest.TestLoader().loadTestsFromTestCase
641 | suite = unittest.TestSuite()
642 | suite.addTests(load(AtomTestCase))
643 | suite.addTests(load(ListTestCase))
644 | suite.addTests(load(ImproperListTestCase))
645 | suite.addTests(load(DecodeTestCase))
646 | suite.addTests(load(EncodeTestCase))
647 | return suite
648 |
649 | def main():
650 | if coverage is None:
651 | unittest.main()
652 | else:
653 | cov = coverage.coverage()
654 | cov.start()
655 | unittest.main()
656 | cov.stop()
657 | cov.save()
658 | modules = [erlang.__file__, __file__]
659 | cov.report(morfs=modules, show_missing=False)
660 | cov.html_report(morfs=modules, directory='.cover')
661 |
662 | if __name__ == '__main__':
663 | main()
664 |
--------------------------------------------------------------------------------
/erlang.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #-*-Mode:python;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
3 | # ex: set ft=python fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
4 | #
5 | # MIT License
6 | #
7 | # Copyright (c) 2011-2023 Michael Truog
8 | #
9 | # Permission is hereby granted, free of charge, to any person obtaining a
10 | # copy of this software and associated documentation files (the "Software"),
11 | # to deal in the Software without restriction, including without limitation
12 | # the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 | # and/or sell copies of the Software, and to permit persons to whom the
14 | # Software is furnished to do so, subject to the following conditions:
15 | #
16 | # The above copyright notice and this permission notice shall be included in
17 | # all copies or substantial portions of the Software.
18 | #
19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | # DEALINGS IN THE SOFTWARE.
26 | #
27 | """
28 | Erlang External Term Format Encoding/Decoding
29 | """
30 | # pylint: disable=too-many-lines
31 |
32 | import sys
33 | import struct
34 | import zlib
35 | import copy
36 |
37 | if sys.version_info[0] >= 3:
38 | TypeLong = int # pylint: disable=invalid-name
39 | TypeUnicode = str # pylint: disable=invalid-name
40 | def b_chr(integer):
41 | """
42 | bytes chr function
43 | """
44 | return bytes([integer])
45 | def b_ord(character):
46 | """
47 | bytes ord function
48 | """
49 | return character
50 | else:
51 | TypeLong = long # pylint: disable=invalid-name
52 | TypeUnicode = unicode # pylint: disable=invalid-name
53 | def b_chr(integer):
54 | """
55 | bytes chr function
56 | """
57 | return chr(integer)
58 | def b_ord(character):
59 | """
60 | bytes ord function
61 | """
62 | return ord(character)
63 |
64 | __all__ = ['OtpErlangAtom',
65 | 'OtpErlangBinary',
66 | 'OtpErlangList',
67 | 'OtpErlangPid',
68 | 'OtpErlangPort',
69 | 'OtpErlangReference',
70 | 'OtpErlangFunction',
71 | 'binary_to_term',
72 | 'term_to_binary',
73 | 'set_undefined',
74 | 'InputException',
75 | 'OutputException',
76 | 'ParseException']
77 |
78 | _UNDEFINED = b'undefined' # Change with set_undefined
79 |
80 | # tag values here http://www.erlang.org/doc/apps/erts/erl_ext_dist.html
81 | _TAG_VERSION = 131
82 | _TAG_COMPRESSED_ZLIB = 80
83 | _TAG_NEW_FLOAT_EXT = 70
84 | _TAG_BIT_BINARY_EXT = 77
85 | _TAG_ATOM_CACHE_REF = 78
86 | _TAG_NEW_PID_EXT = 88
87 | _TAG_NEW_PORT_EXT = 89
88 | _TAG_NEWER_REFERENCE_EXT = 90
89 | _TAG_SMALL_INTEGER_EXT = 97
90 | _TAG_INTEGER_EXT = 98
91 | _TAG_FLOAT_EXT = 99
92 | _TAG_ATOM_EXT = 100
93 | _TAG_REFERENCE_EXT = 101
94 | _TAG_PORT_EXT = 102
95 | _TAG_PID_EXT = 103
96 | _TAG_SMALL_TUPLE_EXT = 104
97 | _TAG_LARGE_TUPLE_EXT = 105
98 | _TAG_NIL_EXT = 106
99 | _TAG_STRING_EXT = 107
100 | _TAG_LIST_EXT = 108
101 | _TAG_BINARY_EXT = 109
102 | _TAG_SMALL_BIG_EXT = 110
103 | _TAG_LARGE_BIG_EXT = 111
104 | _TAG_NEW_FUN_EXT = 112
105 | _TAG_EXPORT_EXT = 113
106 | _TAG_NEW_REFERENCE_EXT = 114
107 | _TAG_SMALL_ATOM_EXT = 115
108 | _TAG_MAP_EXT = 116
109 | _TAG_FUN_EXT = 117
110 | _TAG_ATOM_UTF8_EXT = 118
111 | _TAG_SMALL_ATOM_UTF8_EXT = 119
112 | _TAG_V4_PORT_EXT = 120
113 | _TAG_LOCAL_EXT = 121
114 |
115 | # Erlang term classes listed alphabetically
116 |
117 | class OtpErlangAtom(object):
118 | """
119 | OtpErlangAtom
120 | """
121 | # pylint: disable=useless-object-inheritance
122 | # pylint: disable=too-few-public-methods
123 | def __init__(self, value):
124 | self.value = value
125 | def binary(self):
126 | """
127 | return encoded representation
128 | """
129 | if isinstance(self.value, int):
130 | return b_chr(_TAG_ATOM_CACHE_REF) + b_chr(self.value)
131 | if isinstance(self.value, TypeUnicode):
132 | value_encoded = self.value.encode('utf-8')
133 | length = len(value_encoded)
134 | if length <= 255:
135 | return (
136 | b_chr(_TAG_SMALL_ATOM_UTF8_EXT) +
137 | b_chr(length) + value_encoded
138 | )
139 | if length <= 65535:
140 | return (
141 | b_chr(_TAG_ATOM_UTF8_EXT) +
142 | struct.pack(b'>H', length) + value_encoded
143 | )
144 | raise OutputException('uint16 overflow')
145 | if isinstance(self.value, bytes):
146 | # deprecated
147 | # (not used in Erlang/OTP 26, i.e., minor_version 2)
148 | length = len(self.value)
149 | if length <= 255:
150 | return b_chr(_TAG_SMALL_ATOM_EXT) + b_chr(length) + self.value
151 | if length <= 65535:
152 | return (
153 | b_chr(_TAG_ATOM_EXT) +
154 | struct.pack(b'>H', length) + self.value
155 | )
156 | raise OutputException('uint16 overflow')
157 | raise OutputException('unknown atom type')
158 | def __repr__(self):
159 | return '%s(%s)' % (self.__class__.__name__, repr(self.value))
160 | def __hash__(self):
161 | return hash(self.binary())
162 | def __eq__(self, other):
163 | return self.binary() == other.binary()
164 |
165 | class OtpErlangBinary(object):
166 | """
167 | OtpErlangBinary
168 | """
169 | # pylint: disable=useless-object-inheritance
170 | # pylint: disable=too-few-public-methods
171 | def __init__(self, value, bits=8):
172 | self.value = value
173 | self.bits = bits # bits in last byte
174 | def binary(self):
175 | """
176 | return encoded representation
177 | """
178 | if isinstance(self.value, bytes):
179 | length = len(self.value)
180 | if length > 4294967295:
181 | raise OutputException('uint32 overflow')
182 | if self.bits != 8:
183 | return (
184 | b_chr(_TAG_BIT_BINARY_EXT) +
185 | struct.pack(b'>I', length) +
186 | b_chr(self.bits) + self.value
187 | )
188 | return (
189 | b_chr(_TAG_BINARY_EXT) +
190 | struct.pack(b'>I', length) +
191 | self.value
192 | )
193 | raise OutputException('unknown binary type')
194 | def __repr__(self):
195 | return '%s(%s,bits=%s)' % (
196 | self.__class__.__name__, repr(self.value), repr(self.bits)
197 | )
198 | def __hash__(self):
199 | return hash(self.binary())
200 | def __eq__(self, other):
201 | return self.binary() == other.binary()
202 |
203 | class OtpErlangList(object):
204 | """
205 | OtpErlangList
206 | """
207 | # pylint: disable=useless-object-inheritance
208 | # pylint: disable=too-few-public-methods
209 | def __init__(self, value, improper=False):
210 | self.value = value
211 | self.improper = improper # no empty list tail?
212 | def binary(self):
213 | """
214 | return encoded representation
215 | """
216 | if isinstance(self.value, list):
217 | length = len(self.value)
218 | if length == 0:
219 | return b_chr(_TAG_NIL_EXT)
220 | if length > 4294967295:
221 | raise OutputException('uint32 overflow')
222 | if self.improper:
223 | return (
224 | b_chr(_TAG_LIST_EXT) +
225 | struct.pack(b'>I', length - 1) +
226 | b''.join([_term_to_binary(element)
227 | for element in self.value])
228 | )
229 | return (
230 | b_chr(_TAG_LIST_EXT) +
231 | struct.pack(b'>I', length) +
232 | b''.join([_term_to_binary(element)
233 | for element in self.value]) +
234 | b_chr(_TAG_NIL_EXT)
235 | )
236 | raise OutputException('unknown list type')
237 | def __repr__(self):
238 | return '%s(%s,improper=%s)' % (
239 | self.__class__.__name__, repr(self.value), repr(self.improper)
240 | )
241 | def __hash__(self):
242 | return hash(self.binary())
243 | def __eq__(self, other):
244 | return self.binary() == other.binary()
245 |
246 | class OtpErlangPid(object):
247 | """
248 | OtpErlangPid
249 | """
250 | # pylint: disable=useless-object-inheritance
251 | # pylint: disable=too-few-public-methods
252 | def __init__(self, node, id_value, serial, creation):
253 | # pylint: disable=invalid-name
254 | self.node = node
255 | self.id = id_value
256 | self.serial = serial
257 | self.creation = creation
258 | def binary(self):
259 | """
260 | return encoded representation
261 | """
262 | creation_size = len(self.creation)
263 | if creation_size == 1:
264 | return (
265 | b_chr(_TAG_PID_EXT) +
266 | self.node.binary() + self.id + self.serial + self.creation
267 | )
268 | if creation_size == 4:
269 | return (
270 | b_chr(_TAG_NEW_PID_EXT) +
271 | self.node.binary() + self.id + self.serial + self.creation
272 | )
273 | raise OutputException('unknown pid type')
274 | def __repr__(self):
275 | return '%s(%s,%s,%s,%s)' % (
276 | self.__class__.__name__,
277 | repr(self.node), repr(self.id), repr(self.serial),
278 | repr(self.creation)
279 | )
280 | def __hash__(self):
281 | return hash(self.binary())
282 | def __eq__(self, other):
283 | return self.binary() == other.binary()
284 |
285 | class OtpErlangPort(object):
286 | """
287 | OtpErlangPort
288 | """
289 | # pylint: disable=useless-object-inheritance
290 | # pylint: disable=too-few-public-methods
291 | def __init__(self, node, id_value, creation):
292 | # pylint: disable=invalid-name
293 | self.node = node
294 | self.id = id_value
295 | self.creation = creation
296 | def binary(self):
297 | """
298 | return encoded representation
299 | """
300 | id_size = len(self.id)
301 | if id_size == 8:
302 | return (
303 | b_chr(_TAG_V4_PORT_EXT) +
304 | self.node.binary() + self.id + self.creation
305 | )
306 | creation_size = len(self.creation)
307 | if creation_size == 4:
308 | return (
309 | b_chr(_TAG_NEW_PORT_EXT) +
310 | self.node.binary() + self.id + self.creation
311 | )
312 | if creation_size == 1:
313 | return (
314 | b_chr(_TAG_PORT_EXT) +
315 | self.node.binary() + self.id + self.creation
316 | )
317 | raise OutputException('unknown port type')
318 | def __repr__(self):
319 | return '%s(%s,%s,%s)' % (
320 | self.__class__.__name__,
321 | repr(self.node), repr(self.id), repr(self.creation)
322 | )
323 | def __hash__(self):
324 | return hash(self.binary())
325 | def __eq__(self, other):
326 | return self.binary() == other.binary()
327 |
328 | class OtpErlangReference(object):
329 | """
330 | OtpErlangReference
331 | """
332 | # pylint: disable=useless-object-inheritance
333 | # pylint: disable=too-few-public-methods
334 | def __init__(self, node, id_value, creation):
335 | # pylint: disable=invalid-name
336 | self.node = node
337 | self.id = id_value
338 | self.creation = creation
339 | def binary(self):
340 | """
341 | return encoded representation
342 | """
343 | length = int(len(self.id) / 4)
344 | if length == 0:
345 | return (
346 | b_chr(_TAG_REFERENCE_EXT) +
347 | self.node.binary() + self.id + self.creation
348 | )
349 | if length <= 65535:
350 | creation_size = len(self.creation)
351 | if creation_size == 1:
352 | return (
353 | b_chr(_TAG_NEW_REFERENCE_EXT) +
354 | struct.pack(b'>H', length) +
355 | self.node.binary() + self.creation + self.id
356 | )
357 | if creation_size == 4:
358 | return (
359 | b_chr(_TAG_NEWER_REFERENCE_EXT) +
360 | struct.pack(b'>H', length) +
361 | self.node.binary() + self.creation + self.id
362 | )
363 | raise OutputException('unknown reference type')
364 | raise OutputException('uint16 overflow')
365 | def __repr__(self):
366 | return '%s(%s,%s,%s)' % (
367 | self.__class__.__name__,
368 | repr(self.node), repr(self.id), repr(self.creation)
369 | )
370 | def __hash__(self):
371 | return hash(self.binary())
372 | def __eq__(self, other):
373 | return self.binary() == other.binary()
374 |
375 | class OtpErlangFunction(object):
376 | """
377 | OtpErlangFunction
378 | """
379 | # pylint: disable=useless-object-inheritance
380 | # pylint: disable=too-few-public-methods
381 | def __init__(self, tag, value):
382 | self.tag = tag
383 | self.value = value
384 | def binary(self):
385 | """
386 | return encoded representation
387 | """
388 | return b_chr(self.tag) + self.value
389 | def __repr__(self):
390 | return '%s(%s,%s)' % (
391 | self.__class__.__name__,
392 | repr(self.tag), repr(self.value)
393 | )
394 | def __hash__(self):
395 | return hash(self.binary())
396 | def __eq__(self, other):
397 | return self.binary() == other.binary()
398 |
399 | # dependency to support Erlang maps as map keys in python
400 |
401 | class frozendict(dict):
402 | """
403 | frozendict is under the PSF (Python Software Foundation) License
404 | (from http://code.activestate.com/recipes/414283-frozen-dictionaries/)
405 | """
406 | # pylint: disable=invalid-name
407 | def _blocked_attribute(self):
408 | # pylint: disable=no-self-use
409 | raise AttributeError('A frozendict cannot be modified.')
410 | _blocked_attribute = property(_blocked_attribute)
411 | __delitem__ = __setitem__ = clear = _blocked_attribute
412 | pop = popitem = setdefault = update = _blocked_attribute
413 | def __new__(cls, *args, **kw):
414 | # pylint: disable=unused-argument
415 | # pylint: disable=too-many-nested-blocks
416 | new = dict.__new__(cls)
417 | args_ = []
418 | for arg in args:
419 | if isinstance(arg, dict):
420 | arg = copy.copy(arg)
421 | for k, v in arg.items():
422 | if isinstance(v, dict):
423 | arg[k] = frozendict(v)
424 | elif isinstance(v, list):
425 | v_ = list()
426 | for elm in v:
427 | if isinstance(elm, dict):
428 | v_.append(frozendict(elm))
429 | else:
430 | v_.append(elm)
431 | arg[k] = tuple(v_)
432 | args_.append(arg)
433 | else:
434 | args_.append(arg)
435 | dict.__init__(new, *args_, **kw)
436 | return new
437 | def __init__(self, *args, **kw):
438 | # pylint: disable=unused-argument
439 | # pylint: disable=super-init-not-called
440 | self.__cached_hash = None
441 | def __hash__(self):
442 | if self.__cached_hash is None:
443 | self.__cached_hash = hash(frozenset(self.items()))
444 | return self.__cached_hash
445 | def __repr__(self):
446 | return "frozendict(%s)" % dict.__repr__(self)
447 |
448 | # core functionality
449 |
450 | def binary_to_term(data):
451 | """
452 | Decode Erlang terms within binary data into Python types
453 | """
454 | if not isinstance(data, bytes):
455 | raise ParseException('not bytes input')
456 | size = len(data)
457 | if size <= 1:
458 | raise ParseException('null input')
459 | if b_ord(data[0]) != _TAG_VERSION:
460 | raise ParseException('invalid version')
461 | try:
462 | i, term = _binary_to_term(1, data)
463 | if i != size:
464 | raise ParseException('unparsed data')
465 | return term
466 | except struct.error:
467 | raise ParseException('missing data')
468 | except IndexError:
469 | raise ParseException('missing data')
470 |
471 | def term_to_binary(term, compressed=False):
472 | """
473 | Encode Python types into Erlang terms in binary data
474 | """
475 | data_uncompressed = _term_to_binary(term)
476 | if compressed is False:
477 | return b_chr(_TAG_VERSION) + data_uncompressed
478 | if compressed is True:
479 | compressed = 6
480 | if compressed < 0 or compressed > 9:
481 | raise InputException('compressed in [0..9]')
482 | data_compressed = zlib.compress(data_uncompressed, compressed)
483 | size_uncompressed = len(data_uncompressed)
484 | if size_uncompressed > 4294967295:
485 | raise OutputException('uint32 overflow')
486 | return (
487 | b_chr(_TAG_VERSION) + b_chr(_TAG_COMPRESSED_ZLIB) +
488 | struct.pack(b'>I', size_uncompressed) + data_compressed
489 | )
490 |
491 | # binary_to_term implementation functions
492 |
493 | def _binary_to_term(i, data):
494 | # pylint: disable=too-many-locals
495 | # pylint: disable=too-many-return-statements
496 | # pylint: disable=too-many-branches
497 | # pylint: disable=too-many-statements
498 | tag = b_ord(data[i])
499 | i += 1
500 | if tag == _TAG_NEW_FLOAT_EXT:
501 | return (i + 8, struct.unpack(b'>d', data[i:i + 8])[0])
502 | if tag == _TAG_BIT_BINARY_EXT:
503 | j = struct.unpack(b'>I', data[i:i + 4])[0]
504 | i += 4
505 | bits = b_ord(data[i])
506 | i += 1
507 | return (i + j, OtpErlangBinary(data[i:i + j], bits))
508 | if tag == _TAG_ATOM_CACHE_REF:
509 | return (i + 1, OtpErlangAtom(b_ord(data[i])))
510 | if tag == _TAG_SMALL_INTEGER_EXT:
511 | return (i + 1, b_ord(data[i]))
512 | if tag == _TAG_INTEGER_EXT:
513 | return (i + 4, struct.unpack(b'>i', data[i:i + 4])[0])
514 | if tag == _TAG_FLOAT_EXT:
515 | value = float(data[i:i + 31].partition(b_chr(0))[0])
516 | return (i + 31, value)
517 | if tag in (_TAG_V4_PORT_EXT, _TAG_NEW_PORT_EXT,
518 | _TAG_REFERENCE_EXT, _TAG_PORT_EXT):
519 | i, node = _binary_to_atom(i, data)
520 | if tag == _TAG_V4_PORT_EXT:
521 | id_value = data[i:i + 8]
522 | i += 8
523 | else:
524 | id_value = data[i:i + 4]
525 | i += 4
526 | if tag in (_TAG_V4_PORT_EXT, _TAG_NEW_PORT_EXT):
527 | creation = data[i:i + 4]
528 | i += 4
529 | else:
530 | creation = data[i:i + 1]
531 | i += 1
532 | if tag == _TAG_REFERENCE_EXT:
533 | return (i, OtpErlangReference(node, id_value, creation))
534 | # tag == _TAG_V4_PORT_EXT or tag == _TAG_NEW_PORT_EXT or
535 | # tag == _TAG_PORT_EXT
536 | return (i, OtpErlangPort(node, id_value, creation))
537 | if tag in (_TAG_NEW_PID_EXT, _TAG_PID_EXT):
538 | i, node = _binary_to_atom(i, data)
539 | id_value = data[i:i + 4]
540 | i += 4
541 | serial = data[i:i + 4]
542 | i += 4
543 | if tag == _TAG_NEW_PID_EXT:
544 | creation = data[i:i + 4]
545 | i += 4
546 | elif tag == _TAG_PID_EXT:
547 | creation = data[i:i + 1]
548 | i += 1
549 | return (i, OtpErlangPid(node, id_value, serial, creation))
550 | if tag in (_TAG_SMALL_TUPLE_EXT, _TAG_LARGE_TUPLE_EXT):
551 | if tag == _TAG_SMALL_TUPLE_EXT:
552 | length = b_ord(data[i])
553 | i += 1
554 | elif tag == _TAG_LARGE_TUPLE_EXT:
555 | length = struct.unpack(b'>I', data[i:i + 4])[0]
556 | i += 4
557 | i, tuple_value = _binary_to_term_sequence(i, length, data)
558 | return (i, tuple(tuple_value))
559 | if tag == _TAG_NIL_EXT:
560 | return (i, [])
561 | if tag == _TAG_STRING_EXT:
562 | j = struct.unpack(b'>H', data[i:i + 2])[0]
563 | i += 2
564 | return (i + j, data[i:i + j])
565 | if tag == _TAG_LIST_EXT:
566 | length = struct.unpack(b'>I', data[i:i + 4])[0]
567 | i += 4
568 | i, list_value = _binary_to_term_sequence(i, length, data)
569 | i, tail = _binary_to_term(i, data)
570 | if not isinstance(tail, list) or tail != []:
571 | list_value.append(tail)
572 | list_value = OtpErlangList(list_value, improper=True)
573 | return (i, list_value)
574 | if tag == _TAG_BINARY_EXT:
575 | j = struct.unpack(b'>I', data[i:i + 4])[0]
576 | i += 4
577 | return (i + j, OtpErlangBinary(data[i:i + j], 8))
578 | if tag in (_TAG_SMALL_BIG_EXT, _TAG_LARGE_BIG_EXT):
579 | if tag == _TAG_SMALL_BIG_EXT:
580 | j = b_ord(data[i])
581 | i += 1
582 | elif tag == _TAG_LARGE_BIG_EXT:
583 | j = struct.unpack(b'>I', data[i:i + 4])[0]
584 | i += 4
585 | sign = b_ord(data[i])
586 | bignum = 0
587 | for bignum_index in range(j):
588 | digit = b_ord(data[i + j - bignum_index])
589 | bignum = bignum * 256 + int(digit)
590 | if sign == 1:
591 | bignum *= -1
592 | i += 1
593 | return (i + j, bignum)
594 | if tag == _TAG_NEW_FUN_EXT:
595 | length = struct.unpack(b'>I', data[i:i + 4])[0]
596 | return (i + length, OtpErlangFunction(tag, data[i:i + length]))
597 | if tag == _TAG_EXPORT_EXT:
598 | old_i = i
599 | i, _ = _binary_to_atom(i, data)
600 | i, _ = _binary_to_atom(i, data)
601 | if b_ord(data[i]) != _TAG_SMALL_INTEGER_EXT:
602 | raise ParseException('invalid small integer tag')
603 | i += 1
604 | _ = b_ord(data[i])
605 | i += 1
606 | return (i, OtpErlangFunction(tag, data[old_i:i]))
607 | if tag in (_TAG_NEWER_REFERENCE_EXT, _TAG_NEW_REFERENCE_EXT):
608 | j = struct.unpack(b'>H', data[i:i + 2])[0] * 4
609 | i += 2
610 | i, node = _binary_to_atom(i, data)
611 | if tag == _TAG_NEWER_REFERENCE_EXT:
612 | creation = data[i:i + 4]
613 | i += 4
614 | elif tag == _TAG_NEW_REFERENCE_EXT:
615 | creation = data[i:i + 1]
616 | i += 1
617 | return (i + j, OtpErlangReference(node, data[i: i + j], creation))
618 | if tag == _TAG_MAP_EXT:
619 | length = struct.unpack(b'>I', data[i:i + 4])[0]
620 | i += 4
621 | pairs = {}
622 | def to_immutable(value):
623 | if isinstance(value, dict):
624 | return frozendict(key)
625 | if isinstance(value, list):
626 | return OtpErlangList(value)
627 | if isinstance(value, tuple):
628 | return tuple(to_immutable(v) for v in value)
629 | return value
630 |
631 | for _ in range(length):
632 | i, key = _binary_to_term(i, data)
633 | i, value = _binary_to_term(i, data)
634 | pairs[to_immutable(key)] = value
635 | return (i, pairs)
636 | if tag == _TAG_FUN_EXT:
637 | old_i = i
638 | numfree = struct.unpack(b'>I', data[i:i + 4])[0]
639 | i += 4
640 | i, _ = _binary_to_pid(i, data)
641 | i, _ = _binary_to_atom(i, data)
642 | i, _ = _binary_to_integer(i, data)
643 | i, _ = _binary_to_integer(i, data)
644 | i, _ = _binary_to_term_sequence(i, numfree, data)
645 | return (i, OtpErlangFunction(tag, data[old_i:i]))
646 | if tag in (_TAG_ATOM_UTF8_EXT, _TAG_ATOM_EXT):
647 | j = struct.unpack(b'>H', data[i:i + 2])[0]
648 | i += 2
649 | atom_name = data[i:i + j]
650 | i = i + j
651 | if atom_name == b'true':
652 | return (i, True)
653 | if atom_name == b'false':
654 | return (i, False)
655 | if atom_name == _UNDEFINED:
656 | return (i, None)
657 | if tag == _TAG_ATOM_UTF8_EXT:
658 | atom_name = TypeUnicode(
659 | atom_name, encoding='utf-8', errors='strict'
660 | )
661 | return (i, OtpErlangAtom(atom_name))
662 | if tag in (_TAG_SMALL_ATOM_UTF8_EXT, _TAG_SMALL_ATOM_EXT):
663 | j = b_ord(data[i])
664 | i += 1
665 | atom_name = data[i:i + j]
666 | i = i + j
667 | if atom_name == b'true':
668 | return (i, True)
669 | if atom_name == b'false':
670 | return (i, False)
671 | if atom_name == _UNDEFINED:
672 | return (i, None)
673 | if tag == _TAG_SMALL_ATOM_UTF8_EXT:
674 | atom_name = TypeUnicode(
675 | atom_name, encoding='utf-8', errors='strict'
676 | )
677 | return (i, OtpErlangAtom(atom_name))
678 | if tag == _TAG_COMPRESSED_ZLIB:
679 | size_uncompressed = struct.unpack(b'>I', data[i:i + 4])[0]
680 | if size_uncompressed == 0:
681 | raise ParseException('compressed data null')
682 | i += 4
683 | data_compressed = data[i:]
684 | j = len(data_compressed)
685 | data_uncompressed = zlib.decompress(data_compressed)
686 | if size_uncompressed != len(data_uncompressed):
687 | raise ParseException('compression corrupt')
688 | (i_new, term) = _binary_to_term(0, data_uncompressed)
689 | if i_new != size_uncompressed:
690 | raise ParseException('unparsed data')
691 | return (i + j, term)
692 | if tag == _TAG_LOCAL_EXT:
693 | raise ParseException('LOCAL_EXT is opaque')
694 | raise ParseException('invalid tag')
695 |
696 | def _binary_to_term_sequence(i, length, data):
697 | sequence = []
698 | for _ in range(length):
699 | i, element = _binary_to_term(i, data)
700 | sequence.append(element)
701 | return (i, sequence)
702 |
703 | # (binary_to_term Erlang term primitive type functions)
704 |
705 | def _binary_to_integer(i, data):
706 | tag = b_ord(data[i])
707 | i += 1
708 | if tag == _TAG_SMALL_INTEGER_EXT:
709 | return (i + 1, b_ord(data[i]))
710 | if tag == _TAG_INTEGER_EXT:
711 | return (i + 4, struct.unpack(b'>i', data[i:i + 4])[0])
712 | raise ParseException('invalid integer tag')
713 |
714 | def _binary_to_pid(i, data):
715 | tag = b_ord(data[i])
716 | i += 1
717 | if tag == _TAG_NEW_PID_EXT:
718 | i, node = _binary_to_atom(i, data)
719 | id_value = data[i:i + 4]
720 | i += 4
721 | serial = data[i:i + 4]
722 | i += 4
723 | creation = data[i:i + 4]
724 | i += 4
725 | return (i, OtpErlangPid(node, id_value, serial, creation))
726 | if tag == _TAG_PID_EXT:
727 | i, node = _binary_to_atom(i, data)
728 | id_value = data[i:i + 4]
729 | i += 4
730 | serial = data[i:i + 4]
731 | i += 4
732 | creation = data[i:i + 1]
733 | i += 1
734 | return (i, OtpErlangPid(node, id_value, serial, creation))
735 | raise ParseException('invalid pid tag')
736 |
737 | def _binary_to_atom(i, data):
738 | tag = b_ord(data[i])
739 | i += 1
740 | if tag == _TAG_ATOM_EXT:
741 | j = struct.unpack(b'>H', data[i:i + 2])[0]
742 | i += 2
743 | return (i + j, OtpErlangAtom(data[i:i + j]))
744 | if tag == _TAG_ATOM_CACHE_REF:
745 | return (i + 1, OtpErlangAtom(b_ord(data[i])))
746 | if tag == _TAG_SMALL_ATOM_EXT:
747 | j = b_ord(data[i])
748 | i += 1
749 | return (i + j, OtpErlangAtom(data[i:i + j]))
750 | if tag == _TAG_ATOM_UTF8_EXT:
751 | j = struct.unpack(b'>H', data[i:i + 2])[0]
752 | i += 2
753 | atom_name = TypeUnicode(
754 | data[i:i + j], encoding='utf-8', errors='strict'
755 | )
756 | return (i + j, OtpErlangAtom(atom_name))
757 | if tag == _TAG_SMALL_ATOM_UTF8_EXT:
758 | j = b_ord(data[i])
759 | i += 1
760 | atom_name = TypeUnicode(
761 | data[i:i + j], encoding='utf-8', errors='strict'
762 | )
763 | return (i + j, OtpErlangAtom(atom_name))
764 | raise ParseException('invalid atom tag')
765 |
766 | # term_to_binary implementation functions
767 |
768 | def _term_to_binary(term):
769 | # pylint: disable=too-many-return-statements
770 | # pylint: disable=too-many-branches
771 | if isinstance(term, bytes):
772 | return _string_to_binary(term)
773 | if isinstance(term, TypeUnicode):
774 | return _string_to_binary(
775 | term.encode(encoding='utf-8', errors='strict')
776 | )
777 | if isinstance(term, list):
778 | return OtpErlangList(term).binary()
779 | if isinstance(term, tuple):
780 | return _tuple_to_binary(term)
781 | if isinstance(term, bool):
782 | if term:
783 | atom_name = b'true'
784 | else:
785 | atom_name = b'false'
786 | return OtpErlangAtom(TypeUnicode(atom_name, encoding='utf-8')).binary()
787 | if isinstance(term, (int, TypeLong)):
788 | return _long_to_binary(term)
789 | if isinstance(term, float):
790 | return _float_to_binary(term)
791 | if isinstance(term, dict):
792 | return _dict_to_binary(term)
793 | if term is None:
794 | return OtpErlangAtom(TypeUnicode(_UNDEFINED, encoding='utf-8')).binary()
795 | if isinstance(term, OtpErlangAtom):
796 | return term.binary()
797 | if isinstance(term, OtpErlangList):
798 | return term.binary()
799 | if isinstance(term, OtpErlangBinary):
800 | return term.binary()
801 | if isinstance(term, OtpErlangFunction):
802 | return term.binary()
803 | if isinstance(term, OtpErlangReference):
804 | return term.binary()
805 | if isinstance(term, OtpErlangPort):
806 | return term.binary()
807 | if isinstance(term, OtpErlangPid):
808 | return term.binary()
809 | raise OutputException('unknown python type')
810 |
811 | # (term_to_binary Erlang term composite type functions)
812 |
813 | def _string_to_binary(term):
814 | length = len(term)
815 | if length == 0:
816 | return b_chr(_TAG_NIL_EXT)
817 | if length <= 65535:
818 | return b_chr(_TAG_STRING_EXT) + struct.pack(b'>H', length) + term
819 | if length <= 4294967295:
820 | return (
821 | b_chr(_TAG_LIST_EXT) + struct.pack(b'>I', length) +
822 | b''.join([b_chr(_TAG_SMALL_INTEGER_EXT) + b_chr(b_ord(c))
823 | for c in term]) +
824 | b_chr(_TAG_NIL_EXT)
825 | )
826 | raise OutputException('uint32 overflow')
827 |
828 | def _tuple_to_binary(term):
829 | length = len(term)
830 | if length <= 255:
831 | return (
832 | b_chr(_TAG_SMALL_TUPLE_EXT) + b_chr(length) +
833 | b''.join([_term_to_binary(element) for element in term])
834 | )
835 | if length <= 4294967295:
836 | return (
837 | b_chr(_TAG_LARGE_TUPLE_EXT) + struct.pack(b'>I', length) +
838 | b''.join([_term_to_binary(element) for element in term])
839 | )
840 | raise OutputException('uint32 overflow')
841 |
842 | def _dict_to_binary(term):
843 | length = len(term)
844 | if length <= 4294967295:
845 | return (
846 | b_chr(_TAG_MAP_EXT) + struct.pack(b'>I', length) +
847 | b''.join([_term_to_binary(key) + _term_to_binary(value)
848 | for key, value in term.items()])
849 | )
850 | raise OutputException('uint32 overflow')
851 |
852 | # (term_to_binary Erlang term primitive type functions)
853 |
854 | def _integer_to_binary(term):
855 | if 0 <= term <= 255:
856 | return b_chr(_TAG_SMALL_INTEGER_EXT) + b_chr(term)
857 | return b_chr(_TAG_INTEGER_EXT) + struct.pack(b'>i', term)
858 |
859 | def _long_to_binary(term):
860 | if -2147483648 <= term <= 2147483647:
861 | return _integer_to_binary(term)
862 | return _bignum_to_binary(term)
863 |
864 | def _bignum_to_binary(term):
865 | bignum = abs(term)
866 | if term < 0:
867 | sign = b_chr(1)
868 | else:
869 | sign = b_chr(0)
870 | value = []
871 | while bignum > 0:
872 | value.append(b_chr(bignum & 255))
873 | bignum >>= 8
874 | length = len(value)
875 | if length <= 255:
876 | return (
877 | b_chr(_TAG_SMALL_BIG_EXT) +
878 | b_chr(length) + sign + b''.join(value)
879 | )
880 | if length <= 4294967295:
881 | return (
882 | b_chr(_TAG_LARGE_BIG_EXT) +
883 | struct.pack(b'>I', length) + sign + b''.join(value)
884 | )
885 | raise OutputException('uint32 overflow')
886 |
887 | def _float_to_binary(term):
888 | return b_chr(_TAG_NEW_FLOAT_EXT) + struct.pack(b'>d', term)
889 |
890 | def set_undefined(value):
891 | """
892 | Set the 'undefined' atom that is decoded as None
893 | (Elixir use may want to use 'nil' instead of 'undefined')
894 | """
895 | # pylint: disable=global-statement
896 | assert isinstance(value, bytes)
897 | global _UNDEFINED
898 | _UNDEFINED = value
899 |
900 | # Exception classes listed alphabetically
901 |
902 | class InputException(ValueError):
903 | """
904 | InputError describes problems with function input parameters
905 | """
906 | def __init__(self, s):
907 | ValueError.__init__(self)
908 | self.__s = str(s)
909 | def __str__(self):
910 | return self.__s
911 |
912 | class OutputException(TypeError):
913 | """
914 | OutputError describes problems with creating function output data
915 | """
916 | def __init__(self, s):
917 | TypeError.__init__(self)
918 | self.__s = str(s)
919 | def __str__(self):
920 | return self.__s
921 |
922 | class ParseException(SyntaxError):
923 | """
924 | ParseError provides specific parsing failure information
925 | """
926 | def __init__(self, s):
927 | SyntaxError.__init__(self)
928 | self.__s = str(s)
929 | def __str__(self):
930 | return self.__s
931 |
932 | def consult(string_in):
933 | """
934 | provide file:consult/1 functionality with python types
935 | """
936 | # pylint: disable=eval-used
937 | # pylint: disable=too-many-branches
938 | # pylint: disable=too-many-statements
939 |
940 | # manually parse textual erlang data to avoid external dependencies
941 | list_out = []
942 | tuple_binary = False # binaries become tuples of integers
943 | quoted_string = False # strings become python string
944 | atom_string = False # atoms become python string
945 | number = False
946 | whitespace = frozenset(('\n', '\t', ' '))
947 | i = 0
948 | while i < len(string_in):
949 | character = string_in[i]
950 | if character == ',':
951 | if atom_string:
952 | list_out.append('"')
953 | atom_string = False
954 | list_out.append(',')
955 | number = string_in[i + 1].isdigit()
956 | elif character == '{':
957 | list_out.append('(')
958 | number = string_in[i + 1].isdigit()
959 | elif character == '}':
960 | if atom_string:
961 | list_out.append('"')
962 | atom_string = False
963 | list_out.append(')')
964 | number = False
965 | elif character == '[':
966 | list_out.append('[')
967 | number = string_in[i + 1].isdigit()
968 | elif character == ']':
969 | if atom_string:
970 | list_out.append('"')
971 | atom_string = False
972 | list_out.append(']')
973 | number = False
974 | elif character == '<' and string_in[i + 1] == '<':
975 | list_out.append('(')
976 | tuple_binary = True
977 | i += 1
978 | elif character == '>' and string_in[i + 1] == '>':
979 | list_out.append(')')
980 | tuple_binary = False
981 | i += 1
982 | elif not quoted_string and not atom_string and character in whitespace:
983 | number = string_in[i + 1].isdigit()
984 | elif tuple_binary or number:
985 | list_out.append(character)
986 | elif character == '"':
987 | if quoted_string:
988 | quoted_string = False
989 | else:
990 | quoted_string = True
991 | list_out.append('"')
992 | elif character == "'":
993 | if atom_string:
994 | atom_string = False
995 | else:
996 | atom_string = True
997 | list_out.append('"')
998 | elif not quoted_string and not atom_string:
999 | atom_string = True
1000 | list_out.append('"')
1001 | list_out.append(character)
1002 | else:
1003 | list_out.append(character)
1004 | i += 1
1005 | return eval(''.join(list_out))
1006 |
--------------------------------------------------------------------------------