opal-webassembly 0.1.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 +14 -0
- data/.rspec +3 -0
- data/.rspec-opal +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +25 -0
- data/LICENSE.adoc +31 -0
- data/LICENSE.txt +21 -0
- data/README.adoc +45 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/opal-repl-wasm +4 -0
- data/bin/setup +8 -0
- data/examples/experiment/Makefile +2 -0
- data/examples/experiment/experiment-wasm.c +74 -0
- data/examples/experiment/experiment-wasm.wasm +0 -0
- data/examples/experiment/experiment.rb +25 -0
- data/examples/experiment/experiment.sh +2 -0
- data/examples/onigmo/onigmo-wasm.wasm +0 -0
- data/examples/onigmo/onigmo.rb +4 -0
- data/examples/onigmo/onigmo.sh +2 -0
- data/examples/simple_ffi/Rakefile +14 -0
- data/examples/simple_ffi/simple-wasm.wasm +0 -0
- data/examples/simple_ffi/simple-wasm.wat +8 -0
- data/examples/simple_ffi/simple.rb +14 -0
- data/exe/opal-repl-wasm +9 -0
- data/lib/opal/webassembly.rb +9 -0
- data/lib/opal/webassembly/processor.rb +49 -0
- data/lib/opal/webassembly/version.rb +5 -0
- data/opal-webassembly.gemspec +31 -0
- data/opal/ffi.rb +22 -0
- data/opal/ffi/library.rb +67 -0
- data/opal/ffi/pointer.rb +289 -0
- data/opal/ffi/struct.rb +125 -0
- data/opal/ffi/types.rb +226 -0
- data/opal/webassembly.rb +68 -0
- data/opal/webassembly/global.rb +15 -0
- data/opal/webassembly/instance.rb +44 -0
- data/opal/webassembly/memory.rb +18 -0
- data/opal/webassembly/module.rb +10 -0
- data/opal/webassembly/table.rb +35 -0
- data/spec-opal/ffi_spec.rb +139 -0
- data/spec-opal/spec_helper.rb +18 -0
- data/spec-opal/webassembly_spec.rb +12 -0
- metadata +104 -0
data/opal/ffi/types.rb
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
module FFI
|
2
|
+
TypeDefs = {}
|
3
|
+
|
4
|
+
# TODO: clean me up
|
5
|
+
class Type
|
6
|
+
def self.alignment; size; end
|
7
|
+
|
8
|
+
class Numeric < self
|
9
|
+
def self.signed; false; end
|
10
|
+
def self.pack(x)
|
11
|
+
[x].pack(packformat).b
|
12
|
+
end
|
13
|
+
def self.unpack(x)
|
14
|
+
x.unpack(packformat.b).first
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Integer < Numeric
|
19
|
+
def self.from_native(x); x.to_i; end
|
20
|
+
def self.to_native(x); x.to_i; end
|
21
|
+
|
22
|
+
def self.packformat
|
23
|
+
case self.size
|
24
|
+
when 1
|
25
|
+
signed ? "c" : "C"
|
26
|
+
when 2
|
27
|
+
signed ? "s<" : "S<"
|
28
|
+
when 4
|
29
|
+
signed ? "l<" : "L<"
|
30
|
+
when 8
|
31
|
+
signed ? "q<" : "Q<"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Float < Numeric
|
37
|
+
def self.from_native(x); x.to_f; end
|
38
|
+
def self.to_native(x); x.to_f; end
|
39
|
+
|
40
|
+
# Unfortunately, that is not supported by Opal. TODO: Upstream fix
|
41
|
+
def self.packformat
|
42
|
+
case self.size
|
43
|
+
when 2
|
44
|
+
"e"
|
45
|
+
when 4
|
46
|
+
"E"
|
47
|
+
when 8
|
48
|
+
raise TypeError, "We can't pack/unpack a long double unfortunately."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class VOID < self
|
54
|
+
def self.size; 1; end
|
55
|
+
def self.from_native(x); nil; end
|
56
|
+
def self.to_native(x); raise TypeError, "Can't convert VOID to a native value"; end
|
57
|
+
def self.pack(x); ""; end
|
58
|
+
def self.unpack(x); nil; end
|
59
|
+
end
|
60
|
+
|
61
|
+
class BOOL < self
|
62
|
+
def self.size; 1; end
|
63
|
+
def self.from_native(x); !!x; end
|
64
|
+
def self.to_native(x); x ? 1 : 0; end
|
65
|
+
def self.pack(x); x ? "\1" : "\0"; end
|
66
|
+
def self.unpack(x); x != "\0"; end
|
67
|
+
end
|
68
|
+
|
69
|
+
class POINTER < Integer
|
70
|
+
def self.size; 4; end
|
71
|
+
|
72
|
+
def self.from_native_mem(x, memory)
|
73
|
+
Pointer.new([memory, x])
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.to_native(x)
|
77
|
+
if !x
|
78
|
+
0
|
79
|
+
elsif x.respond_to? :value # WebAssembly::Global?
|
80
|
+
x.value
|
81
|
+
elsif x.respond_to? :address # Struct? Pointer?
|
82
|
+
x.address
|
83
|
+
elsif x.respond_to? :to_int # Integer?
|
84
|
+
x.to_int
|
85
|
+
elsif x.respond_to? :to_str # String? It leaks though.
|
86
|
+
FFI::Pointer.from_string(x.to_str).address
|
87
|
+
else
|
88
|
+
raise ArgumentError, "Wrong argument #{x} can't be coerced to a pointer."
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# TODO: Implement DataConverter
|
94
|
+
class WrappedStruct < self
|
95
|
+
def alignment; FFI::Type::POINTER.alignment; end
|
96
|
+
def size; FFI::Type::POINTER.size; end
|
97
|
+
def pack x; FFI::Type::POINTER.pack(x); end
|
98
|
+
def unpack x; FFI::Type::POINTER.unpack(x); end
|
99
|
+
|
100
|
+
def initialize x
|
101
|
+
@struct = x
|
102
|
+
end
|
103
|
+
|
104
|
+
def from_native_mem(x, memory)
|
105
|
+
@struct.new(FFI::Pointer.new([memory, x], @struct))
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_native(x)
|
109
|
+
x.pointer.address
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class STRING < self
|
114
|
+
def self.size; 4; end
|
115
|
+
|
116
|
+
def self.from_native_mem(x, memory)
|
117
|
+
FFI::Pointer.new([memory, x]).read_string
|
118
|
+
end
|
119
|
+
|
120
|
+
# WARNING: it leaks!
|
121
|
+
def self.to_native x
|
122
|
+
FFI::Pointer.from_string(x).address
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.pack x; x.to_s; end
|
126
|
+
def self.unpack x; x.to_s; end
|
127
|
+
end
|
128
|
+
|
129
|
+
class CHAR < Integer; def self.size; 1; end; def self.signed; true; end; end
|
130
|
+
class UCHAR < Integer; def self.size; 1; end; end
|
131
|
+
class SHORT < Integer; def self.size; 2; end; def self.signed; true; end; end
|
132
|
+
class USHORT < Integer; def self.size; 2; end; end
|
133
|
+
class INT < Integer; def self.size; 4; end; def self.signed; true; end; end
|
134
|
+
class UINT < Integer; def self.size; 4; end; end
|
135
|
+
class LONG < INT; end
|
136
|
+
class ULONG < INT; end
|
137
|
+
class LONG_LONG < Integer; def self.size; 8; end; def self.signed; true; end; end
|
138
|
+
class ULONG_LONG < Integer; def self.size; 8; end; end
|
139
|
+
class FLOAT < Float; def self.size; 4; end; end
|
140
|
+
class DOUBLE < Float; def self.size; 8; end; end
|
141
|
+
class LONG_DOUBLE < Float; def self.size; 16; end; end
|
142
|
+
|
143
|
+
class INT8 < CHAR; end
|
144
|
+
class UINT8 < CHAR; end
|
145
|
+
class INT16 < SHORT; end
|
146
|
+
class UINT16 < USHORT; end
|
147
|
+
class INT32 < INT; end
|
148
|
+
class UINT32 < UINT; end
|
149
|
+
class INT64 < LONG_LONG; end
|
150
|
+
class UINT64 < ULONG_LONG; end
|
151
|
+
|
152
|
+
def self.[] name, purpose = nil
|
153
|
+
FFI.find_type name, purpose: purpose
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.typedef old, new
|
158
|
+
TypeDefs[new] = find_type(old)
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.add_typedef(old, new); typedef old, new; end
|
162
|
+
|
163
|
+
def self.find_type name, type_map = nil, purpose: nil
|
164
|
+
if name.is_a?(FFI::Type) || (name.is_a?(Class) && name <= FFI::Type)
|
165
|
+
name
|
166
|
+
elsif type_map && type_map.has_key?(name)
|
167
|
+
type_map[name]
|
168
|
+
elsif TypeDefs.has_key?(name)
|
169
|
+
TypeDefs[name]
|
170
|
+
elsif name.is_a?(Class) && name <= FFI::Struct
|
171
|
+
if purpose == nil
|
172
|
+
Type::WrappedStruct.new(name)
|
173
|
+
elsif purpose == :struct
|
174
|
+
name
|
175
|
+
else
|
176
|
+
raise ArgumentError, "Wrong purpose provided. Can't resolve type '#{name}'."
|
177
|
+
end
|
178
|
+
else
|
179
|
+
raise TypeError, "unable to resolve type '#{name}'"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.type_size name
|
184
|
+
find_type(name).size
|
185
|
+
end
|
186
|
+
|
187
|
+
typedef Type::VOID, nil
|
188
|
+
|
189
|
+
typedef Type::VOID, :void
|
190
|
+
typedef Type::BOOL, :bool
|
191
|
+
typedef Type::CHAR, :char
|
192
|
+
typedef Type::UCHAR, :uchar
|
193
|
+
typedef Type::SHORT, :short
|
194
|
+
typedef Type::USHORT, :ushort
|
195
|
+
typedef Type::INT, :int
|
196
|
+
typedef Type::UINT, :uint
|
197
|
+
typedef Type::LONG, :long
|
198
|
+
typedef Type::ULONG, :ulong
|
199
|
+
typedef Type::LONG_LONG, :long_long
|
200
|
+
typedef Type::ULONG_LONG, :ulong_long
|
201
|
+
typedef Type::FLOAT, :float
|
202
|
+
typedef Type::DOUBLE, :double
|
203
|
+
typedef Type::LONG_DOUBLE, :long_double
|
204
|
+
|
205
|
+
typedef Type::INT8, :int8
|
206
|
+
typedef Type::UINT8, :uint8
|
207
|
+
typedef Type::INT16, :int16
|
208
|
+
typedef Type::UINT16, :uint16
|
209
|
+
typedef Type::INT32, :int32
|
210
|
+
typedef Type::UINT32, :uint32
|
211
|
+
typedef Type::INT64, :int64
|
212
|
+
typedef Type::UINT64, :uint64
|
213
|
+
|
214
|
+
typedef Type::POINTER, :pointer
|
215
|
+
typedef Type::STRING, :string
|
216
|
+
|
217
|
+
typedef :uint8, :byte
|
218
|
+
typedef :long, :size_t
|
219
|
+
|
220
|
+
typedef :pointer, :buffer_in
|
221
|
+
typedef :pointer, :buffer_out
|
222
|
+
typedef :pointer, :buffer_inout
|
223
|
+
|
224
|
+
# varargs and strptr are not currently supported
|
225
|
+
# same with enums.
|
226
|
+
end
|
data/opal/webassembly.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'native'
|
2
|
+
|
3
|
+
require 'webassembly/instance'
|
4
|
+
require 'webassembly/module'
|
5
|
+
require 'webassembly/table'
|
6
|
+
require 'webassembly/memory'
|
7
|
+
require 'webassembly/global'
|
8
|
+
|
9
|
+
module WebAssembly
|
10
|
+
@libs = {}
|
11
|
+
|
12
|
+
def self.libs
|
13
|
+
@libs
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.load_from_buffer(buffer, name)
|
17
|
+
mod = Module.new(buffer)
|
18
|
+
@libs[name] = Instance.new(mod)
|
19
|
+
end
|
20
|
+
|
21
|
+
%x{
|
22
|
+
// Taken from https://github.com/niklasvh/base64-arraybuffer/blob/master/lib/base64-arraybuffer.js
|
23
|
+
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
24
|
+
|
25
|
+
// Use a lookup table to find the index.
|
26
|
+
var lookup = new Uint8Array(256);
|
27
|
+
for (var i = 0; i < chars.length; i++) {
|
28
|
+
lookup[chars.charCodeAt(i)] = i;
|
29
|
+
}
|
30
|
+
|
31
|
+
function base64_to_arraybuffer(base64) {
|
32
|
+
var bufferLength = base64.length * 0.75,
|
33
|
+
len = base64.length, i, p = 0,
|
34
|
+
encoded1, encoded2, encoded3, encoded4;
|
35
|
+
|
36
|
+
if (base64[base64.length - 1] === "=") {
|
37
|
+
bufferLength--;
|
38
|
+
if (base64[base64.length - 2] === "=") {
|
39
|
+
bufferLength--;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
var arraybuffer = new ArrayBuffer(bufferLength),
|
44
|
+
bytes = new Uint8Array(arraybuffer);
|
45
|
+
|
46
|
+
for (i = 0; i < len; i+=4) {
|
47
|
+
encoded1 = lookup[base64.charCodeAt(i)];
|
48
|
+
encoded2 = lookup[base64.charCodeAt(i+1)];
|
49
|
+
encoded3 = lookup[base64.charCodeAt(i+2)];
|
50
|
+
encoded4 = lookup[base64.charCodeAt(i+3)];
|
51
|
+
|
52
|
+
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
|
53
|
+
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
|
54
|
+
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
|
55
|
+
}
|
56
|
+
|
57
|
+
return arraybuffer;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
def self.load_from_base64(base64, name)
|
62
|
+
self.load_from_buffer(`base64_to_arraybuffer(#{base64})`, name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.[](name)
|
66
|
+
self.libs[name]
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module WebAssembly
|
2
|
+
class Global < `WebAssembly.Global`
|
3
|
+
def self.new(value, type="i32", mutable=true)
|
4
|
+
`new WebAssembly.Global({value: #{type}, mutable: #{mutable}}, #{value})`
|
5
|
+
end
|
6
|
+
|
7
|
+
def value
|
8
|
+
self.JS[:value]
|
9
|
+
end
|
10
|
+
|
11
|
+
def value= new_val
|
12
|
+
self.JS[:value] = new_val
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'native'
|
2
|
+
|
3
|
+
module WebAssembly
|
4
|
+
class Instance < `WebAssembly.Instance`
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def self.new(mod, imports={})
|
8
|
+
`new WebAssembly.Instance(#{mod}, #{imports.to_n})`
|
9
|
+
end
|
10
|
+
|
11
|
+
def exports
|
12
|
+
@exports_cache ||= {}.tap do |hash|
|
13
|
+
%x{
|
14
|
+
var hasOwnProperty = Object.prototype.hasOwnProperty.bind(#{self}.exports);
|
15
|
+
for (var key in #{self}.exports) {
|
16
|
+
if (hasOwnProperty(key)) {
|
17
|
+
#{hash[`key`] = `#{self}.exports[key]`}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing meth, *args
|
25
|
+
exports[meth].call(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def [] export
|
29
|
+
exports[export]
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_h
|
33
|
+
exports
|
34
|
+
end
|
35
|
+
|
36
|
+
def memory
|
37
|
+
exports[:memory]
|
38
|
+
end
|
39
|
+
|
40
|
+
def each(&block)
|
41
|
+
exports.each(&block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'buffer'
|
2
|
+
|
3
|
+
module WebAssembly
|
4
|
+
class Memory < `WebAssembly.Memory`
|
5
|
+
# 64kB pages
|
6
|
+
def self.new(initial=1, maximum=100, shared=false)
|
7
|
+
`new WebAssembly.Memory({initial: #{initial}, maximum: #{maximum}, shared: #{shared}})`
|
8
|
+
end
|
9
|
+
|
10
|
+
def grow(by)
|
11
|
+
self.JS.grow(by)
|
12
|
+
end
|
13
|
+
|
14
|
+
def buffer
|
15
|
+
Buffer.new(self.JS[:buffer])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module WebAssembly
|
2
|
+
class Table < `WebAssembly.Table`
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def self.new(initial=1, maximum=100)
|
6
|
+
`new WebAssembly.Table({initial: #{initial}, maximum: #{maximum}, element: "anyfunc"})`
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_a
|
10
|
+
Array(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
self.to_a.each(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(i)
|
18
|
+
self.JS.get(i)
|
19
|
+
end
|
20
|
+
alias [] get
|
21
|
+
|
22
|
+
def set(i, val)
|
23
|
+
self.JS.set(i, val)
|
24
|
+
end
|
25
|
+
alias []= set
|
26
|
+
|
27
|
+
def grow(by)
|
28
|
+
self.JS.grow(by)
|
29
|
+
end
|
30
|
+
|
31
|
+
def length
|
32
|
+
self.JS[:length]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'experiment'
|
3
|
+
|
4
|
+
RSpec.describe FFI do
|
5
|
+
it "has correct assumptions about types (wasm32)" do
|
6
|
+
expect(Experiment.longsize).to eq FFI::Type[:long].size
|
7
|
+
expect(Experiment.longlongsize).to eq FFI::Type[:long_long].size
|
8
|
+
expect(Experiment.ptrsize).to eq FFI::Type[:pointer].size
|
9
|
+
expect(Experiment.floatsize).to eq FFI::Type[:float].size
|
10
|
+
expect(Experiment.doublesize).to eq FFI::Type[:double].size
|
11
|
+
expect(Experiment.longdoublesize).to eq FFI::Type[:long_double].size
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has correct assumptions about struct alignment" do
|
15
|
+
expect(Experiment.structalignld).to eq FFI::Type[:long_double].alignment
|
16
|
+
expect(Experiment.structalignll).to eq FFI::Type[:long_long].alignment
|
17
|
+
expect(Experiment.structaligni).to eq FFI::Type[:int].alignment
|
18
|
+
expect(Experiment.structalignp).to eq FFI::Type[:pointer].alignment
|
19
|
+
end
|
20
|
+
|
21
|
+
it "passes pointers correctly" do
|
22
|
+
ptr = Experiment.retpointer(FFI::Pointer.new([nil, 0x1133dd]))
|
23
|
+
expect(ptr.address).to eq(0x1133dd)
|
24
|
+
expect(ptr.memory).to eq(Experiment.library.memory)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "passes floats correctly" do
|
28
|
+
expect(Experiment.retfloat(0.11)).to be_within(0.00001).of(-0.22)
|
29
|
+
expect(Experiment.retdouble(0.33)).to be_within(0.00000001).of(-0.66)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "passes strings correctly" do
|
33
|
+
require 'corelib/array/pack'
|
34
|
+
Experiment.context do
|
35
|
+
expect(Experiment.retstring("hello world!".b)).to be("lmo!world!")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "packs and unpacks values correctly" do
|
40
|
+
require 'corelib/array/pack'
|
41
|
+
require 'corelib/string/unpack'
|
42
|
+
for type in %i[uint8 int8 uint16 int16 int32 uint32 pointer]
|
43
|
+
type = FFI::Type[type]
|
44
|
+
expect(type.unpack(type.pack(123))).to eq(123)
|
45
|
+
end
|
46
|
+
type = FFI::Type[:string]
|
47
|
+
expect(type.unpack(type.pack(123))).to eq("123")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "supports structs" do
|
51
|
+
require 'corelib/array/pack'
|
52
|
+
require 'corelib/string/unpack'
|
53
|
+
|
54
|
+
class MyStr < FFI::Struct
|
55
|
+
layout :a, :char,
|
56
|
+
:b, :int,
|
57
|
+
:c, [:int, 20]
|
58
|
+
end
|
59
|
+
|
60
|
+
Experiment.context do
|
61
|
+
expect(MyStr.members[:a].offset).to eq(0)
|
62
|
+
expect(MyStr.members[:b].offset).to eq(4)
|
63
|
+
expect(MyStr.members[:c].offset).to eq(8)
|
64
|
+
|
65
|
+
str = MyStr.new
|
66
|
+
|
67
|
+
expect(str[:c]).to be_a(FFI::Pointer)
|
68
|
+
expect(str[:a]).to be_a(Integer)
|
69
|
+
|
70
|
+
str[:a] = 10
|
71
|
+
str[:c][4] = 2000000
|
72
|
+
|
73
|
+
expect(str[:a]).to eq(10)
|
74
|
+
expect(str[:c][4]).to eq(2000000)
|
75
|
+
|
76
|
+
expect(MyStr.size).to eq(22 * 4)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "supports nested pointer structs" do
|
81
|
+
require 'corelib/array/pack'
|
82
|
+
require 'corelib/string/unpack'
|
83
|
+
|
84
|
+
class Struct2 < FFI::Struct
|
85
|
+
layout :age, :int,
|
86
|
+
:a, :int,
|
87
|
+
:b, :int,
|
88
|
+
:c, :int
|
89
|
+
end
|
90
|
+
|
91
|
+
class Struct1 < FFI::Struct
|
92
|
+
layout :id, :int,
|
93
|
+
:data, Struct2.ptr
|
94
|
+
end
|
95
|
+
|
96
|
+
Experiment.context do
|
97
|
+
expect(Struct2.size).to eq(16)
|
98
|
+
expect(Struct1.size).to eq(8)
|
99
|
+
|
100
|
+
struct = Struct1.new
|
101
|
+
struct2 = Struct2.new
|
102
|
+
struct[:data] = struct2
|
103
|
+
struct[:data][:age] = 27
|
104
|
+
|
105
|
+
expect(struct).to be_a(Struct1)
|
106
|
+
expect(struct[:data]).to be_a(Struct2)
|
107
|
+
expect(struct[:data][:age]).to eq(27)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it "supports nested structs" do
|
112
|
+
require 'corelib/array/pack'
|
113
|
+
require 'corelib/string/unpack'
|
114
|
+
|
115
|
+
class Struct4 < FFI::Struct
|
116
|
+
layout :age, :int,
|
117
|
+
:a, :int,
|
118
|
+
:b, :int,
|
119
|
+
:c, :int
|
120
|
+
end
|
121
|
+
|
122
|
+
class Struct3 < FFI::Struct
|
123
|
+
layout :id, :int,
|
124
|
+
:data, Struct4
|
125
|
+
end
|
126
|
+
|
127
|
+
Experiment.context do
|
128
|
+
expect(Struct4.size).to eq(16)
|
129
|
+
expect(Struct3.size).to eq(20)
|
130
|
+
|
131
|
+
struct = Struct3.new
|
132
|
+
struct[:data][:age] = 27
|
133
|
+
|
134
|
+
expect(struct).to be_a(Struct3)
|
135
|
+
expect(struct[:data]).to be_a(Struct4)
|
136
|
+
expect(struct[:data][:age]).to eq(27)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|