-
Notifications
You must be signed in to change notification settings - Fork 55
/
list.rb
124 lines (110 loc) · 2.8 KB
/
list.rb
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
require 'bindata'
# An example of a recursively defined data format.
#
# This example format describes atoms and lists.
# It is recursive because lists can contain other lists.
#
# Atoms - contain a single integer
# Lists - contain a mixture of atoms and lists
#
# The binary representation is:
#
# Atoms - A single byte 'a' followed by an int32 containing the value.
# Lists - A single byte 'l' followed by an int32 denoting the number of
# items in the list. This is followed by all the items in the list.
#
# All integers are big endian.
#
#
# A first attempt at a declaration would be:
#
# class Atom < BinData::Record
# string :tag, length: 1, assert: 'a'
# int32be :val
# end
#
# class List < BinData::Record
# string :tag, length: 1, assert: 'l'
# int32be :num, value: -> { vals.length }
# array :vals, initial_length: :num do
# choice selection: ??? do
# atom
# list
# end
# end
# end
#
# Notice how we get stuck on attempting to write a declaration for
# the contents of the list. We can't determine if the list item is
# an atom or list because we haven't read it yet. It appears that
# we can't proceed.
#
# The cause of the problem is that the tag identifying the type is
# coupled with that type.
#
# The solution is to decouple the tag from the type. We introduce a
# new type 'Term' that is a thin container around the tag plus the
# type (atom or list).
#
# The declaration then becomes:
#
# class Term < BinData::Record; end # forward declaration
#
# class Atom < BinData::Int32be
# end
#
# class List < BinData::Record
# int32be :num, value: -> { vals.length }
# array :vals, type: :term, initial_length: :num
# end
#
# class Term < BinData::Record
# string :tag, length: 1
# choice :term, selection: :tag do
# atom 'a'
# list 'l'
# end
# end
class Term < BinData::Record; end # Forward declaration
class Atom < BinData::Int32be
def decode
snapshot
end
def self.encode(val)
Atom.new(val)
end
end
class List < BinData::Record
int32be :num, value: -> { vals.length }
array :vals, initial_length: :num, type: :term
def decode
vals.collect(&:decode)
end
def self.encode(val)
List.new(vals: val.collect { |v| Term.encode(v) })
end
end
class Term < BinData::Record
string :tag, length: 1
choice :term, selection: :tag do
atom 'a'
list 'l'
end
def decode
term.decode
end
def self.encode(val)
if Integer === val
Term.new(tag: 'a', term: Atom.encode(val))
else
Term.new(tag: 'l', term: List.encode(val))
end
end
end
puts "A single Atom"
p Term.encode(4)
p Term.encode(4).decode
puts
puts "A nested List"
p Term.encode([1, [2, 3], 4])
p Term.encode([1, [2, 3], 4]).decode