erlang-etf 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +49 -0
- data/Rakefile +6 -0
- data/erlang-etf.gemspec +30 -0
- data/lib/erlang/etf.rb +40 -0
- data/lib/erlang/etf/atom.rb +46 -0
- data/lib/erlang/etf/atom_utf8.rb +44 -0
- data/lib/erlang/etf/bert.rb +74 -0
- data/lib/erlang/etf/binary.rb +44 -0
- data/lib/erlang/etf/bit_binary.rb +47 -0
- data/lib/erlang/etf/export.rb +44 -0
- data/lib/erlang/etf/extensions.rb +157 -0
- data/lib/erlang/etf/extensions/array.rb +29 -0
- data/lib/erlang/etf/extensions/big_decimal.rb +22 -0
- data/lib/erlang/etf/extensions/erlang-export.rb +26 -0
- data/lib/erlang/etf/extensions/erlang-list.rb +31 -0
- data/lib/erlang/etf/extensions/erlang-nil.rb +22 -0
- data/lib/erlang/etf/extensions/erlang-pid.rb +22 -0
- data/lib/erlang/etf/extensions/erlang-string.rb +22 -0
- data/lib/erlang/etf/extensions/erlang-tuple.rb +31 -0
- data/lib/erlang/etf/extensions/false_class.rb +28 -0
- data/lib/erlang/etf/extensions/float.rb +20 -0
- data/lib/erlang/etf/extensions/hash.rb +32 -0
- data/lib/erlang/etf/extensions/integer.rb +48 -0
- data/lib/erlang/etf/extensions/nil_class.rb +29 -0
- data/lib/erlang/etf/extensions/object.rb +24 -0
- data/lib/erlang/etf/extensions/regexp.rb +34 -0
- data/lib/erlang/etf/extensions/string.rb +35 -0
- data/lib/erlang/etf/extensions/symbol.rb +45 -0
- data/lib/erlang/etf/extensions/time.rb +29 -0
- data/lib/erlang/etf/extensions/true_class.rb +28 -0
- data/lib/erlang/etf/float.rb +57 -0
- data/lib/erlang/etf/fun.rb +67 -0
- data/lib/erlang/etf/integer.rb +29 -0
- data/lib/erlang/etf/large_big.rb +53 -0
- data/lib/erlang/etf/large_tuple.rb +55 -0
- data/lib/erlang/etf/list.rb +50 -0
- data/lib/erlang/etf/new_float.rb +33 -0
- data/lib/erlang/etf/new_fun.rb +98 -0
- data/lib/erlang/etf/new_reference.rb +59 -0
- data/lib/erlang/etf/nil.rb +23 -0
- data/lib/erlang/etf/pid.rb +45 -0
- data/lib/erlang/etf/port.rb +34 -0
- data/lib/erlang/etf/reference.rb +41 -0
- data/lib/erlang/etf/small_atom.rb +48 -0
- data/lib/erlang/etf/small_atom_utf8.rb +44 -0
- data/lib/erlang/etf/small_big.rb +59 -0
- data/lib/erlang/etf/small_integer.rb +29 -0
- data/lib/erlang/etf/small_tuple.rb +56 -0
- data/lib/erlang/etf/string.rb +46 -0
- data/lib/erlang/etf/term.rb +101 -0
- data/lib/erlang/etf/terms.rb +105 -0
- data/lib/erlang/etf/version.rb +5 -0
- data/spec/erlang/etf/atom_spec.rb +90 -0
- data/spec/erlang/etf/atom_utf8_spec.rb +90 -0
- data/spec/erlang/etf/binary_spec.rb +90 -0
- data/spec/erlang/etf/bit_binary_spec.rb +99 -0
- data/spec/erlang/etf/export_spec.rb +58 -0
- data/spec/erlang/etf/extensions/array_spec.rb +40 -0
- data/spec/erlang/etf/extensions/big_decimal_spec.rb +26 -0
- data/spec/erlang/etf/extensions/erlang-export_spec.rb +32 -0
- data/spec/erlang/etf/extensions/erlang-list_spec.rb +76 -0
- data/spec/erlang/etf/extensions/erlang-nil_spec.rb +24 -0
- data/spec/erlang/etf/extensions/erlang-pid_spec.rb +33 -0
- data/spec/erlang/etf/extensions/erlang-string_spec.rb +26 -0
- data/spec/erlang/etf/extensions/erlang-tuple_spec.rb +56 -0
- data/spec/erlang/etf/extensions/false_class_spec.rb +29 -0
- data/spec/erlang/etf/extensions/float_spec.rb +24 -0
- data/spec/erlang/etf/extensions/hash_spec.rb +90 -0
- data/spec/erlang/etf/extensions/integer_spec.rb +259 -0
- data/spec/erlang/etf/extensions/nil_class_spec.rb +29 -0
- data/spec/erlang/etf/extensions/object_spec.rb +30 -0
- data/spec/erlang/etf/extensions/regexp_spec.rb +35 -0
- data/spec/erlang/etf/extensions/string_spec.rb +43 -0
- data/spec/erlang/etf/extensions/symbol_spec.rb +64 -0
- data/spec/erlang/etf/extensions/time_spec.rb +32 -0
- data/spec/erlang/etf/extensions/true_class_spec.rb +29 -0
- data/spec/erlang/etf/float_spec.rb +92 -0
- data/spec/erlang/etf/fun_spec.rb +132 -0
- data/spec/erlang/etf/integer_spec.rb +57 -0
- data/spec/erlang/etf/large_big_spec.rb +67 -0
- data/spec/erlang/etf/large_tuple_spec.rb +119 -0
- data/spec/erlang/etf/list_spec.rb +159 -0
- data/spec/erlang/etf/new_float_spec.rb +92 -0
- data/spec/erlang/etf/new_fun_spec.rb +146 -0
- data/spec/erlang/etf/new_reference_spec.rb +60 -0
- data/spec/erlang/etf/nil_spec.rb +50 -0
- data/spec/erlang/etf/pid_spec.rb +61 -0
- data/spec/erlang/etf/port_spec.rb +58 -0
- data/spec/erlang/etf/reference_spec.rb +58 -0
- data/spec/erlang/etf/small_atom_spec.rb +90 -0
- data/spec/erlang/etf/small_atom_utf8_spec.rb +90 -0
- data/spec/erlang/etf/small_big_spec.rb +67 -0
- data/spec/erlang/etf/small_integer_spec.rb +57 -0
- data/spec/erlang/etf/small_tuple_spec.rb +112 -0
- data/spec/erlang/etf/string_spec.rb +92 -0
- data/spec/erlang/etf/term_spec.rb +27 -0
- data/spec/erlang/etf/terms_spec.rb +23 -0
- data/spec/erlang/etf_spec.rb +23 -0
- data/spec/erlang_spec.rb +77 -0
- data/spec/spec_helper.rb +7 -0
- metadata +310 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
#
|
6
|
+
# dictionary {bert, dict, KeysAndValues}
|
7
|
+
#
|
8
|
+
# Dictionaries (hash tables) are expressed via an array of 2-tuples representing the key/value pairs.
|
9
|
+
# The KeysAndValues array is mandatory, such that an empty dict is expressed as {bert, dict, []}.
|
10
|
+
# Keys and values may be any term.
|
11
|
+
# For example, {bert, dict, [{name, <<"Tom">>}, {age, 30}]}.
|
12
|
+
#
|
13
|
+
# See: http://bert-rpc.org/
|
14
|
+
#
|
15
|
+
module Hash
|
16
|
+
|
17
|
+
def __erlang_type__
|
18
|
+
:bert_dict
|
19
|
+
end
|
20
|
+
|
21
|
+
def __erlang_evolve__
|
22
|
+
::Erlang::Tuple[:bert, :dict, map do |(key, value)|
|
23
|
+
::Erlang::Tuple[key, value]
|
24
|
+
end].__erlang_evolve__
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
module Integer
|
6
|
+
|
7
|
+
UINT8_MIN = 0.freeze
|
8
|
+
UINT8_MAX = (+(1 << 8) - 1).freeze
|
9
|
+
|
10
|
+
INT32_MIN = (-(1 << 31) + 1).freeze
|
11
|
+
INT32_MAX = (+(1 << 31) - 1).freeze
|
12
|
+
|
13
|
+
SMALL_BIG_N_MAX = 255.freeze
|
14
|
+
|
15
|
+
def __erlang_type__
|
16
|
+
if self >= UINT8_MIN and self <= UINT8_MAX
|
17
|
+
:small_integer
|
18
|
+
elsif self >= INT32_MIN and self <= INT32_MAX
|
19
|
+
:integer
|
20
|
+
else
|
21
|
+
n = (abs.to_s(2).bytesize / 8.0).ceil
|
22
|
+
if n <= SMALL_BIG_N_MAX
|
23
|
+
:small_big
|
24
|
+
else
|
25
|
+
:large_big
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def __erlang_evolve__
|
31
|
+
case __erlang_type__
|
32
|
+
when :small_integer
|
33
|
+
ETF::SmallInteger.new(self)
|
34
|
+
when :integer
|
35
|
+
ETF::Integer.new(self)
|
36
|
+
when :small_big
|
37
|
+
ETF::SmallBig.new(self)
|
38
|
+
when :large_big
|
39
|
+
ETF::LargeBig.new(self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
#
|
6
|
+
# nil {bert, nil}
|
7
|
+
#
|
8
|
+
# Erlang equates nil with the empty array [] while other languages do not.
|
9
|
+
# Even though NIL appears as a primitive in the serialization specification, BERT only uses it to represent the empty array.
|
10
|
+
# In order to be language agnostic, nil is encoded as a separate complex type to allow for disambiguation.
|
11
|
+
#
|
12
|
+
# See: http://bert-rpc.org/
|
13
|
+
#
|
14
|
+
module NilClass
|
15
|
+
|
16
|
+
def __erlang_type__
|
17
|
+
:bert_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def __erlang_evolve__
|
21
|
+
::Erlang::Tuple[:bert, :nil].__erlang_evolve__
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
module Object
|
6
|
+
|
7
|
+
def __erlang_type__
|
8
|
+
raise NotImplementedError, "#__erlang_type__ undefined for #{inspect} of class #{self.class}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def __erlang_evolve__
|
12
|
+
raise NotImplementedError, "#__erlang_evolve__ undefined for #{inspect} of class #{self.class}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def __erlang_dump__(buffer)
|
16
|
+
__erlang_evolve__.serialize(buffer)
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
#
|
6
|
+
# regex {bert, regex, Source, Options}
|
7
|
+
#
|
8
|
+
# Regular expressions are expressed by their source binary and PCRE options.
|
9
|
+
# Options is a list of atoms representing the PCRE options.
|
10
|
+
# For example, {bert, regex, <<"^c(a*)t$">>, [caseless]} would represent a case insensitive regular epxression that would match "cat".
|
11
|
+
# See re:compile/2 for valid options.
|
12
|
+
#
|
13
|
+
# See: http://bert-rpc.org/
|
14
|
+
#
|
15
|
+
module Regexp
|
16
|
+
|
17
|
+
def __erlang_type__
|
18
|
+
:bert_regex
|
19
|
+
end
|
20
|
+
|
21
|
+
def __erlang_evolve__
|
22
|
+
opts = []
|
23
|
+
opts << :caseless if (options & ::Regexp::IGNORECASE) != 0
|
24
|
+
opts << :extended if (options & ::Regexp::EXTENDED) != 0
|
25
|
+
opts << :multiline if (options & ::Regexp::MULTILINE) != 0
|
26
|
+
::Erlang::Tuple[:bert, :regex, source, opts].__erlang_evolve__
|
27
|
+
end
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
BINARY_ENCODING = Encoding.find("binary")
|
6
|
+
UTF8_ENCODING = Encoding.find("utf-8")
|
7
|
+
|
8
|
+
module String
|
9
|
+
|
10
|
+
def __erlang_type__
|
11
|
+
:binary
|
12
|
+
end
|
13
|
+
|
14
|
+
def __erlang_evolve__
|
15
|
+
ETF::Binary.new(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_utf8_binary
|
19
|
+
encode(UTF8_ENCODING).force_encoding(BINARY_ENCODING)
|
20
|
+
rescue EncodingError
|
21
|
+
data = dup.force_encoding(UTF8_ENCODING)
|
22
|
+
raise unless data.valid_encoding?
|
23
|
+
data.force_encoding(BINARY_ENCODING)
|
24
|
+
end
|
25
|
+
|
26
|
+
def from_utf8_binary
|
27
|
+
force_encoding(UTF8_ENCODING).encode!
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
module Symbol
|
6
|
+
|
7
|
+
def __erlang_type__
|
8
|
+
if to_s.bytesize < 256
|
9
|
+
if to_s.ascii_only?
|
10
|
+
:small_atom
|
11
|
+
else
|
12
|
+
:small_atom_utf8
|
13
|
+
end
|
14
|
+
else
|
15
|
+
if to_s.ascii_only?
|
16
|
+
:atom
|
17
|
+
else
|
18
|
+
:atom_utf8
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def __erlang_evolve__
|
24
|
+
case __erlang_type__
|
25
|
+
when :atom
|
26
|
+
ETF::Atom.new(to_s)
|
27
|
+
when :atom_utf8
|
28
|
+
ETF::AtomUTF8.new(to_s)
|
29
|
+
when :small_atom
|
30
|
+
ETF::SmallAtom.new(to_s)
|
31
|
+
when :small_atom_utf8
|
32
|
+
ETF::SmallAtomUTF8.new(to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_utf8_binary
|
37
|
+
to_s.to_utf8_binary
|
38
|
+
end
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
#
|
6
|
+
# time {bert, time, Megaseconds, Seconds, Microseconds}
|
7
|
+
#
|
8
|
+
# The given time is the number of Megaseconds + Seconds + Microseconds elapsed since 00:00 GMT, January 1, 1970 (zero hour).
|
9
|
+
# For example, 2009-10-11 at 14:12:01 and 446,228 microseconds would be expressed as {bert, time, 1255, 295581, 446228}.
|
10
|
+
# In english, this is 1255 megaseconds (millions of seconds) + 295,581 seconds + 446,228 microseconds (millionths of a second) since zero hour.
|
11
|
+
#
|
12
|
+
# See: http://bert-rpc.org/
|
13
|
+
#
|
14
|
+
module Time
|
15
|
+
|
16
|
+
def __erlang_type__
|
17
|
+
:bert_time
|
18
|
+
end
|
19
|
+
|
20
|
+
def __erlang_evolve__
|
21
|
+
::Erlang::Tuple[:bert, :time, to_i / 1_000_000, to_i % 1_000_000, usec].__erlang_evolve__
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
#
|
6
|
+
# boolean {bert, true} or {bert, false}
|
7
|
+
#
|
8
|
+
# Erlang equates the true and false atoms with booleans while other languages do not have this behavior.
|
9
|
+
# To disambiguate these cases, booleans are expressed as their own complex type.
|
10
|
+
#
|
11
|
+
# See: http://bert-rpc.org/
|
12
|
+
#
|
13
|
+
module TrueClass
|
14
|
+
|
15
|
+
def __erlang_type__
|
16
|
+
:bert_boolean
|
17
|
+
end
|
18
|
+
|
19
|
+
def __erlang_evolve__
|
20
|
+
::Erlang::Tuple[:bert, :true].__erlang_evolve__
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
module Erlang
|
4
|
+
module ETF
|
5
|
+
|
6
|
+
#
|
7
|
+
# 1 | 31
|
8
|
+
# -- | ------------
|
9
|
+
# 99 | Float String
|
10
|
+
#
|
11
|
+
# A float is stored in string format. the format used in sprintf
|
12
|
+
# to format the float is "%.20e" (there are more bytes allocated
|
13
|
+
# than necessary). To unpack the float use sscanf with format
|
14
|
+
# "%lf".
|
15
|
+
#
|
16
|
+
# This term is used in minor version 0 of the external format; it
|
17
|
+
# has been superseded by NEW_FLOAT_EXT .
|
18
|
+
#
|
19
|
+
class Float
|
20
|
+
include Term
|
21
|
+
|
22
|
+
FLOAT_STRING_FORMAT = "%.20e".freeze
|
23
|
+
|
24
|
+
uint8 :tag, always: Terms::FLOAT_EXT
|
25
|
+
|
26
|
+
string :float_string
|
27
|
+
|
28
|
+
undef serialize_float_string
|
29
|
+
def serialize_float_string(buffer)
|
30
|
+
if float_string.is_a?(::BigDecimal)
|
31
|
+
buffer << (FLOAT_STRING_FORMAT % float_string).ljust(31, "\000")
|
32
|
+
else
|
33
|
+
buffer << float_string
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
undef deserialize_float_string
|
38
|
+
def deserialize_float_string(buffer)
|
39
|
+
self.float_string = buffer.read(31)
|
40
|
+
end
|
41
|
+
|
42
|
+
finalize
|
43
|
+
|
44
|
+
def initialize(float_string)
|
45
|
+
@float_string = float_string
|
46
|
+
end
|
47
|
+
|
48
|
+
def __ruby_evolve__
|
49
|
+
if float_string.is_a?(::BigDecimal)
|
50
|
+
float_string
|
51
|
+
else
|
52
|
+
::BigDecimal.new(float_string.gsub("\000", ""))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Erlang
|
2
|
+
module ETF
|
3
|
+
|
4
|
+
#
|
5
|
+
# 1 | 4 | N1 | N2 | N3 | N4 | N5
|
6
|
+
# --- | ------- | --- | ------ | ----- | ---- | -------------
|
7
|
+
# 117 | NumFree | Pid | Module | Index | Uniq | Free vars ...
|
8
|
+
#
|
9
|
+
# Pid
|
10
|
+
# is a process identifier as in PID_EXT. It represents the
|
11
|
+
# process in which the fun was created.
|
12
|
+
# Module
|
13
|
+
# is an encoded as an atom, using ATOM_EXT, SMALL_ATOM_EXT or
|
14
|
+
# ATOM_CACHE_REF. This is the module that the fun is
|
15
|
+
# implemented in.
|
16
|
+
# Index
|
17
|
+
# is an integer encoded using SMALL_INTEGER_EXT or INTEGER_EXT.
|
18
|
+
# It is typically a small index into the module's fun table.
|
19
|
+
# Uniq
|
20
|
+
# is an integer encoded using SMALL_INTEGER_EXT or INTEGER_EXT.
|
21
|
+
# Uniq is the hash value of the parse for the fun.
|
22
|
+
# Free vars
|
23
|
+
# is NumFree number of terms, each one encoded according to its
|
24
|
+
# type.
|
25
|
+
#
|
26
|
+
class Fun
|
27
|
+
include Term
|
28
|
+
|
29
|
+
uint8 :tag, always: Terms::FUN_EXT
|
30
|
+
|
31
|
+
uint16be :num_free, always: -> { free_vars.size }
|
32
|
+
|
33
|
+
term :pid # pid
|
34
|
+
|
35
|
+
term :mod # atom, small_atom
|
36
|
+
|
37
|
+
term :index # small_integer, integer
|
38
|
+
|
39
|
+
term :uniq # small_integer, integer
|
40
|
+
|
41
|
+
term :free_vars, type: :array
|
42
|
+
|
43
|
+
deserialize do |buffer|
|
44
|
+
num_free, = buffer.read(BYTES_16).unpack(UINT16BE_PACK)
|
45
|
+
deserialize_pid(buffer)
|
46
|
+
deserialize_mod(buffer)
|
47
|
+
deserialize_index(buffer)
|
48
|
+
deserialize_uniq(buffer)
|
49
|
+
self.free_vars = []
|
50
|
+
num_free.times do
|
51
|
+
self.free_vars << Terms.deserialize(buffer)
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
finalize
|
57
|
+
|
58
|
+
def initialize(pid, mod, index, uniq, free_vars = [])
|
59
|
+
self.pid = pid
|
60
|
+
self.mod = mod
|
61
|
+
self.index = index
|
62
|
+
self.uniq = uniq
|
63
|
+
self.free_vars = free_vars
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|