libcall 0.0.1 → 0.0.2
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 +4 -4
- data/README.md +42 -22
- data/lib/libcall/caller.rb +37 -43
- data/lib/libcall/cli.rb +47 -16
- data/lib/libcall/library_finder.rb +73 -78
- data/lib/libcall/parser.rb +48 -58
- data/lib/libcall/platform.rb +43 -0
- data/lib/libcall/type_map.rb +209 -0
- data/lib/libcall/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b45f825f6989bd4673b982435ce1bad8ff1b1785785118ffa66c8bdad84b1d7c
|
|
4
|
+
data.tar.gz: 9a60c1d6d69b2ff7e70a2884b6dfbc8e2b6099c87df40558559fdffffff5c65c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed0cde569e1eea83480b744907d3397f6e7abae68b991993090312edc6ccf74d4cf973afbad2dd3402142eccf390a3f992cf2174ad016ea0009d5d8b91e104bd
|
|
7
|
+
data.tar.gz: 42571c69fd4e1487e17b592ec98afc321ed646e938d9d773a96c4e450f4762c9d49975f7de47a33ba5f747e2e2c1367da00d7a3233ff059a8ab5a70f43a3bb92
|
data/README.md
CHANGED
|
@@ -27,9 +27,9 @@ libcall [OPTIONS] <LIBRARY> <FUNCTION> (TYPE VALUE)...
|
|
|
27
27
|
libcall -lm -r f64 sqrt double 16
|
|
28
28
|
# => 4.0
|
|
29
29
|
|
|
30
|
-
#
|
|
31
|
-
libcall
|
|
32
|
-
# =>
|
|
30
|
+
# libc strlen
|
|
31
|
+
libcall -lc strlen string "hello" -r usize
|
|
32
|
+
# => 5
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
### Argument Syntax
|
|
@@ -63,7 +63,6 @@ libcall -lc getenv string -- -r -r cstr
|
|
|
63
63
|
- `-l LIBRARY` - library name (searches standard paths)
|
|
64
64
|
- `-L PATH` - add library search path
|
|
65
65
|
- `-r TYPE` - return type (void, i32, f64, cstr, ptr)
|
|
66
|
-
- Options may appear before or after the function name.
|
|
67
66
|
- `--dry-run` - validate without executing
|
|
68
67
|
- `--json` - JSON output
|
|
69
68
|
- `--verbose` - detailed info
|
|
@@ -82,10 +81,10 @@ Library search:
|
|
|
82
81
|
libcall --json -lm sqrt double 9.0 -r f64
|
|
83
82
|
|
|
84
83
|
# Dry run
|
|
85
|
-
libcall --dry-run
|
|
84
|
+
libcall --dry-run -lc getpid -r int
|
|
86
85
|
|
|
87
|
-
#
|
|
88
|
-
libcall -
|
|
86
|
+
# Output parameter with libm
|
|
87
|
+
libcall -lm modf double -3.14 out:double -r f64
|
|
89
88
|
|
|
90
89
|
# TYPE/VALUE pairs with -r after function
|
|
91
90
|
libcall -lm fabs double -5.5 -r f64
|
|
@@ -101,18 +100,18 @@ libcall msvcrt.dll getenv string "PATH" -r cstr
|
|
|
101
100
|
|
|
102
101
|
## Type Reference
|
|
103
102
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
| `i8`
|
|
107
|
-
| `u8`
|
|
108
|
-
| `i16`
|
|
109
|
-
| `u16`
|
|
110
|
-
| `i32`
|
|
111
|
-
| `u32`
|
|
112
|
-
| `i64`
|
|
113
|
-
| `u64`
|
|
114
|
-
| `f32`
|
|
115
|
-
| `f64`
|
|
103
|
+
| Short (suffix) | Formal (C) | Note/Range |
|
|
104
|
+
| -------------- | ----------------------------------- | ------------------ |
|
|
105
|
+
| `i8` | `char` (≈ `int8_t`) | -128 to 127 |
|
|
106
|
+
| `u8` | `unsigned char` (≈ `uint8_t`) | 0 to 255 |
|
|
107
|
+
| `i16` | `short` (≈ `int16_t`) | -32768 to 32767 |
|
|
108
|
+
| `u16` | `unsigned short` (≈ `uint16_t`) | 0 to 65535 |
|
|
109
|
+
| `i32` | `int` (≈ `int32_t`) | typical 32-bit int |
|
|
110
|
+
| `u32` | `unsigned int` (≈ `uint32_t`) | unsigned 32-bit |
|
|
111
|
+
| `i64` | `long long` (≈ `int64_t`) | 64-bit |
|
|
112
|
+
| `u64` | `unsigned long long` (≈ `uint64_t`) | 64-bit |
|
|
113
|
+
| `f32` | `float` | single precision |
|
|
114
|
+
| `f64` | `double` | double precision |
|
|
116
115
|
|
|
117
116
|
Also supported:
|
|
118
117
|
|
|
@@ -133,11 +132,32 @@ PKG_CONFIG_PATH=/path/to/pkgconfig libcall -lmypackage func i32 42 -r i32
|
|
|
133
132
|
You can pass output pointers by specifying `out:TYPE`. The pointer is allocated automatically, passed to the function, and printed after the call.
|
|
134
133
|
|
|
135
134
|
```sh
|
|
136
|
-
#
|
|
137
|
-
libcall -
|
|
135
|
+
# double frexp(double x, int* exp)
|
|
136
|
+
libcall -lm frexp double 8.0 out:int -r f64
|
|
138
137
|
|
|
139
138
|
# JSON includes an "outputs" array
|
|
140
|
-
libcall --json -
|
|
139
|
+
libcall --json -lm frexp double 8.0 out:int -r f64
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Arrays
|
|
143
|
+
|
|
144
|
+
- Input arrays: `TYPE[]` takes a comma-separated value list.
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
# zlib (Linux/macOS): uLong crc32(uLong crc, const Bytef* buf, uInt len)
|
|
148
|
+
libcall -lz crc32 uint 0 uchar[] 104,101,108,108,111 uint 5 -r uint
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- Output arrays: `out:TYPE[N]` allocates N elements and prints them after the call.
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
# Linux (libc): ssize_t getrandom(void* buf, size_t buflen, unsigned int flags)
|
|
155
|
+
libcall -lc getrandom out:uchar[16] size_t 16 uint 0 -r long
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
# macOS (libSystem): void arc4random_buf(void* buf, size_t nbytes)
|
|
160
|
+
libcall -lSystem arc4random_buf out:uchar[16] size_t 16 -r void
|
|
141
161
|
```
|
|
142
162
|
|
|
143
163
|
## Warning
|
data/lib/libcall/caller.rb
CHANGED
|
@@ -20,19 +20,36 @@ module Libcall
|
|
|
20
20
|
out_refs = []
|
|
21
21
|
|
|
22
22
|
arg_pairs.each_with_index do |(type_sym, value), idx|
|
|
23
|
-
arg_types <<
|
|
24
|
-
|
|
25
|
-
if type_sym.is_a?(Array)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
arg_types << TypeMap.to_fiddle_type(type_sym)
|
|
24
|
+
|
|
25
|
+
if type_sym.is_a?(Array)
|
|
26
|
+
case type_sym.first
|
|
27
|
+
when :out
|
|
28
|
+
inner = type_sym[1]
|
|
29
|
+
ptr = TypeMap.allocate_output_pointer(inner)
|
|
30
|
+
out_refs << { index: idx, kind: :out, type: inner, ptr: ptr }
|
|
31
|
+
arg_values << ptr.to_i
|
|
32
|
+
when :array
|
|
33
|
+
base = type_sym[1]
|
|
34
|
+
values = Array(value)
|
|
35
|
+
ptr = TypeMap.allocate_array(base, values.length)
|
|
36
|
+
TypeMap.write_array(ptr, base, values)
|
|
37
|
+
arg_values << ptr.to_i
|
|
38
|
+
when :out_array
|
|
39
|
+
base = type_sym[1]
|
|
40
|
+
count = type_sym[2]
|
|
41
|
+
ptr = TypeMap.allocate_array(base, count)
|
|
42
|
+
out_refs << { index: idx, kind: :out_array, base: base, count: count, ptr: ptr }
|
|
43
|
+
arg_values << ptr.to_i
|
|
44
|
+
else
|
|
45
|
+
raise Error, "Unknown array/output form: #{type_sym.inspect}"
|
|
46
|
+
end
|
|
30
47
|
else
|
|
31
48
|
arg_values << value
|
|
32
49
|
end
|
|
33
50
|
end
|
|
34
51
|
|
|
35
|
-
ret_type =
|
|
52
|
+
ret_type = TypeMap.to_fiddle_type(return_type)
|
|
36
53
|
|
|
37
54
|
handle = Fiddle.dlopen(lib_path)
|
|
38
55
|
func_ptr = handle[func_name]
|
|
@@ -74,43 +91,20 @@ module Libcall
|
|
|
74
91
|
end
|
|
75
92
|
end
|
|
76
93
|
|
|
77
|
-
def allocate_output_pointer(type_sym)
|
|
78
|
-
size = case type_sym
|
|
79
|
-
when :char, :uchar then Fiddle::SIZEOF_CHAR
|
|
80
|
-
when :short, :ushort then Fiddle::SIZEOF_SHORT
|
|
81
|
-
when :int, :uint then Fiddle::SIZEOF_INT
|
|
82
|
-
when :long, :ulong then Fiddle::SIZEOF_LONG
|
|
83
|
-
when :long_long, :ulong_long then Fiddle::SIZEOF_LONG_LONG
|
|
84
|
-
when :float then Fiddle::SIZEOF_FLOAT
|
|
85
|
-
when :double then Fiddle::SIZEOF_DOUBLE
|
|
86
|
-
when :voidp then Fiddle::SIZEOF_VOIDP
|
|
87
|
-
else
|
|
88
|
-
raise Error, "Cannot allocate output pointer for type: #{type_sym}"
|
|
89
|
-
end
|
|
90
|
-
Fiddle::Pointer.malloc(size)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
94
|
def read_output_values(out_refs)
|
|
94
95
|
out_refs.map do |ref|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
when :double then ref[:ptr][0, Fiddle::SIZEOF_DOUBLE].unpack1('d')
|
|
108
|
-
when :voidp then format('0x%x', ref[:ptr][0, Fiddle::SIZEOF_VOIDP].unpack1('J'))
|
|
109
|
-
else
|
|
110
|
-
raise Error, "Cannot read output value for type: #{ref[:type]}"
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
{ index: ref[:index], type: ref[:type].to_s, value: value }
|
|
96
|
+
case ref[:kind]
|
|
97
|
+
when :out
|
|
98
|
+
value = TypeMap.read_output_pointer(ref[:ptr], ref[:type])
|
|
99
|
+
{ index: ref[:index], type: ref[:type].to_s, value: value }
|
|
100
|
+
when :out_array
|
|
101
|
+
base = ref[:base]
|
|
102
|
+
count = ref[:count]
|
|
103
|
+
values = TypeMap.read_array(ref[:ptr], base, count)
|
|
104
|
+
{ index: ref[:index], type: "#{base}[#{count}]", value: values }
|
|
105
|
+
else
|
|
106
|
+
raise Error, "Unknown out reference kind: #{ref[:kind]}"
|
|
107
|
+
end
|
|
114
108
|
end
|
|
115
109
|
end
|
|
116
110
|
end
|
data/lib/libcall/cli.rb
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require_relative 'platform'
|
|
4
5
|
|
|
5
6
|
module Libcall
|
|
6
7
|
# Command-line interface for calling C functions from shared libraries
|
|
7
8
|
class CLI
|
|
9
|
+
PLATFORM_EXAMPLES = {
|
|
10
|
+
windows: <<~EXAMPLES.chomp,
|
|
11
|
+
libcall -lmsvcrt puts string "Hello from libcall" -r int
|
|
12
|
+
libcall -lKernel32 GetTickCount -r uint32
|
|
13
|
+
libcall -lmsvcrt getenv string "PATH" -r string
|
|
14
|
+
EXAMPLES
|
|
15
|
+
darwin: <<~EXAMPLES.chomp,
|
|
16
|
+
libcall -lSystem getpid -r int
|
|
17
|
+
libcall -lSystem puts string "Hello from libcall" -r int
|
|
18
|
+
libcall -lSystem getenv string "PATH" -r string
|
|
19
|
+
EXAMPLES
|
|
20
|
+
unix: <<~EXAMPLES.chomp
|
|
21
|
+
libcall -lm sqrt double 16 -r double
|
|
22
|
+
libcall -lc getpid -r int
|
|
23
|
+
libcall -lc getenv string "PATH" -r string
|
|
24
|
+
EXAMPLES
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
8
27
|
def initialize(argv)
|
|
9
28
|
@argv = argv
|
|
10
29
|
@options = {
|
|
@@ -57,19 +76,33 @@ module Libcall
|
|
|
57
76
|
private
|
|
58
77
|
|
|
59
78
|
def parse_options_banner
|
|
79
|
+
examples = if Platform.windows?
|
|
80
|
+
PLATFORM_EXAMPLES[:windows]
|
|
81
|
+
elsif Platform.darwin?
|
|
82
|
+
PLATFORM_EXAMPLES[:darwin]
|
|
83
|
+
else
|
|
84
|
+
PLATFORM_EXAMPLES[:unix]
|
|
85
|
+
end
|
|
86
|
+
|
|
60
87
|
<<~BANNER
|
|
61
88
|
Usage: libcall [OPTIONS] <LIBRARY> <FUNCTION> (TYPE VALUE)...
|
|
62
89
|
|
|
63
90
|
Call C functions in shared libraries from the command line.
|
|
64
91
|
|
|
65
|
-
|
|
92
|
+
Arguments are passed as TYPE VALUE pairs.
|
|
66
93
|
|
|
67
94
|
Examples:
|
|
68
|
-
|
|
69
|
-
libcall -ltest -L ./build add_i32 int 10 int -23 -r int
|
|
70
|
-
libcall --dry-run ./mylib.so test u64 42 -r void
|
|
95
|
+
#{examples.lines.map { |line| line.chomp }.join("\n ")}
|
|
71
96
|
|
|
72
97
|
Options:
|
|
98
|
+
-l, --lib LIBRARY Library name to search for (e.g., -lm for libm)
|
|
99
|
+
-L, --lib-path PATH Add directory to library search path
|
|
100
|
+
-r, --ret TYPE Return type (default: void)
|
|
101
|
+
--dry-run Show what would be executed without calling
|
|
102
|
+
--json Output result in JSON format
|
|
103
|
+
--verbose Show detailed information
|
|
104
|
+
-h, --help Show this help message
|
|
105
|
+
-v, --version Show version information
|
|
73
106
|
BANNER
|
|
74
107
|
end
|
|
75
108
|
|
|
@@ -122,8 +155,8 @@ module Libcall
|
|
|
122
155
|
|
|
123
156
|
type_sym = Parser.parse_type(type_tok)
|
|
124
157
|
|
|
125
|
-
# TYPE that represents an output pointer does not require a value
|
|
126
|
-
if type_sym.is_a?(Array) && type_sym.first
|
|
158
|
+
# TYPE that represents an output pointer/array does not require a value
|
|
159
|
+
if type_sym.is_a?(Array) && %i[out out_array].include?(type_sym.first)
|
|
127
160
|
arg_pairs << [type_sym, nil]
|
|
128
161
|
next
|
|
129
162
|
end
|
|
@@ -256,18 +289,16 @@ module Libcall
|
|
|
256
289
|
end
|
|
257
290
|
|
|
258
291
|
puts JSON.pretty_generate(output, allow_nan: true)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
puts " [#{out[:index]}] #{out[:type]} = #{out[:value]}"
|
|
266
|
-
end
|
|
292
|
+
elsif result.is_a?(Hash) && result.key?(:outputs)
|
|
293
|
+
puts "Result: #{result[:result]}" unless result[:result].nil?
|
|
294
|
+
unless result[:outputs].empty?
|
|
295
|
+
puts 'Output parameters:'
|
|
296
|
+
result[:outputs].each do |out|
|
|
297
|
+
puts " [#{out[:index]}] #{out[:type]} = #{out[:value]}"
|
|
267
298
|
end
|
|
268
|
-
else
|
|
269
|
-
puts result unless result.nil?
|
|
270
299
|
end
|
|
300
|
+
else
|
|
301
|
+
puts result unless result.nil?
|
|
271
302
|
end
|
|
272
303
|
end
|
|
273
304
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'fiddle'
|
|
4
|
+
require_relative 'platform'
|
|
4
5
|
|
|
5
6
|
begin
|
|
6
7
|
require 'pkg-config'
|
|
@@ -19,29 +20,23 @@ module Libcall
|
|
|
19
20
|
# Find library by name (e.g., "m" -> "/lib/x86_64-linux-gnu/libm.so.6")
|
|
20
21
|
def find(lib_name)
|
|
21
22
|
# If it's a path, return as-is
|
|
22
|
-
return File.expand_path(lib_name) if
|
|
23
|
-
return File.expand_path(lib_name) if File.file?(lib_name)
|
|
23
|
+
return File.expand_path(lib_name) if path_like?(lib_name)
|
|
24
24
|
|
|
25
25
|
search_paths = @lib_paths + @default_paths
|
|
26
26
|
|
|
27
27
|
if defined?(PKGConfig)
|
|
28
|
-
pkg_exists =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
pkg_exists = begin
|
|
29
|
+
PKGConfig.public_send(
|
|
30
|
+
PKGConfig.respond_to?(:exist?) ? :exist? : :have_package,
|
|
31
|
+
lib_name
|
|
32
|
+
)
|
|
33
|
+
rescue StandardError
|
|
34
|
+
false
|
|
35
|
+
end
|
|
33
36
|
|
|
34
37
|
if pkg_exists
|
|
35
|
-
lib_dirs =
|
|
36
|
-
|
|
37
|
-
else
|
|
38
|
-
PKGConfig.libs(lib_name).to_s.split.select { |t| t.start_with?('-L') }.map { |t| t[2..] }
|
|
39
|
-
end
|
|
40
|
-
lib_names = if PKGConfig.respond_to?(:libs_only_l)
|
|
41
|
-
PKGConfig.libs_only_l(lib_name).to_s.split.map { |l| l.start_with?('-l') ? l[2..] : l }
|
|
42
|
-
else
|
|
43
|
-
PKGConfig.libs(lib_name).to_s.split.select { |t| t.start_with?('-l') }.map { |t| t[2..] }
|
|
44
|
-
end
|
|
38
|
+
lib_dirs = extract_pkg_config_flags(lib_name, 'L')
|
|
39
|
+
lib_names = extract_pkg_config_flags(lib_name, 'l')
|
|
45
40
|
|
|
46
41
|
search_paths = lib_dirs + search_paths
|
|
47
42
|
|
|
@@ -62,53 +57,65 @@ module Libcall
|
|
|
62
57
|
|
|
63
58
|
private
|
|
64
59
|
|
|
60
|
+
def path_like?(name)
|
|
61
|
+
name.include?('/') || name.include?('\\') || File.file?(name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Extract -L or -l flags from pkg-config output, normalized without the dash prefix
|
|
65
|
+
def extract_pkg_config_flags(lib_name, flag_char)
|
|
66
|
+
base = if flag_char == 'L' && PKGConfig.respond_to?(:libs_only_L)
|
|
67
|
+
PKGConfig.libs_only_L(lib_name)
|
|
68
|
+
elsif flag_char == 'l' && PKGConfig.respond_to?(:libs_only_l)
|
|
69
|
+
PKGConfig.libs_only_l(lib_name)
|
|
70
|
+
else
|
|
71
|
+
PKGConfig.libs(lib_name)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
base.to_s.split
|
|
75
|
+
.select { |t| t.start_with?("-#{flag_char}") }
|
|
76
|
+
.map { |t| t[2..] }
|
|
77
|
+
end
|
|
78
|
+
|
|
65
79
|
def default_library_paths
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# Windows paths
|
|
70
|
-
paths << 'C:/Windows/System32'
|
|
71
|
-
paths << 'C:/Windows/SysWOW64'
|
|
72
|
-
|
|
73
|
-
# MSYS2/MinGW paths
|
|
74
|
-
if ENV['MSYSTEM']
|
|
75
|
-
msys_prefix = ENV['MINGW_PREFIX'] || 'C:/msys64/mingw64'
|
|
76
|
-
paths << "#{msys_prefix}/bin"
|
|
77
|
-
paths << "#{msys_prefix}/lib"
|
|
78
|
-
end
|
|
80
|
+
(Platform.windows? ? windows_library_paths : unix_library_paths)
|
|
81
|
+
.select { |p| Dir.exist?(p) }
|
|
82
|
+
end
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
else
|
|
83
|
-
# Unix-like systems (Linux, macOS)
|
|
84
|
-
# Standard library paths
|
|
85
|
-
paths << '/lib'
|
|
86
|
-
paths << '/usr/lib'
|
|
87
|
-
paths << '/usr/local/lib'
|
|
88
|
-
|
|
89
|
-
# Architecture-specific paths
|
|
90
|
-
if RUBY_PLATFORM =~ /x86_64/
|
|
91
|
-
paths << '/lib/x86_64-linux-gnu'
|
|
92
|
-
paths << '/usr/lib/x86_64-linux-gnu'
|
|
93
|
-
elsif RUBY_PLATFORM =~ /aarch64|arm64/
|
|
94
|
-
paths << '/lib/aarch64-linux-gnu'
|
|
95
|
-
paths << '/usr/lib/aarch64-linux-gnu'
|
|
96
|
-
end
|
|
84
|
+
def windows_library_paths
|
|
85
|
+
paths = %w[C:/Windows/System32 C:/Windows/SysWOW64]
|
|
97
86
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
87
|
+
# MSYS2/MinGW paths
|
|
88
|
+
if ENV['MSYSTEM']
|
|
89
|
+
msys_prefix = ENV['MINGW_PREFIX'] || 'C:/msys64/mingw64'
|
|
90
|
+
paths.concat(["#{msys_prefix}/bin", "#{msys_prefix}/lib"])
|
|
91
|
+
end
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
|
|
93
|
+
# Add PATH directories on Windows
|
|
94
|
+
paths.concat(ENV['PATH'].split(';').map { |p| p.tr('\\', '/') }) if ENV['PATH']
|
|
106
95
|
|
|
107
|
-
|
|
108
|
-
|
|
96
|
+
paths
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def unix_library_paths
|
|
100
|
+
# Standard library paths
|
|
101
|
+
paths = %w[/lib /usr/lib /usr/local/lib]
|
|
102
|
+
|
|
103
|
+
# Architecture-specific paths (Linux)
|
|
104
|
+
case Platform.architecture
|
|
105
|
+
when 'x86_64'
|
|
106
|
+
paths.concat(%w[/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu])
|
|
107
|
+
when 'aarch64'
|
|
108
|
+
paths.concat(%w[/lib/aarch64-linux-gnu /usr/lib/aarch64-linux-gnu])
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
# macOS paths
|
|
112
|
+
paths.concat(%w[/usr/local/lib /opt/homebrew/lib]) if Platform.darwin?
|
|
113
|
+
|
|
114
|
+
# Environment-based paths
|
|
115
|
+
paths.concat(ENV.fetch('LD_LIBRARY_PATH', '').split(':'))
|
|
116
|
+
paths.concat(ENV.fetch('DYLD_LIBRARY_PATH', '').split(':'))
|
|
117
|
+
|
|
118
|
+
paths
|
|
112
119
|
end
|
|
113
120
|
|
|
114
121
|
def resolve_by_name_in_paths(lib_name, search_paths)
|
|
@@ -119,31 +126,19 @@ module Libcall
|
|
|
119
126
|
end
|
|
120
127
|
|
|
121
128
|
# Try with lib prefix and common extensions
|
|
122
|
-
extensions = if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
123
|
-
['', '.dll', '.so', '.a']
|
|
124
|
-
elsif RUBY_PLATFORM =~ /darwin/
|
|
125
|
-
['', '.dylib', '.so', '.a']
|
|
126
|
-
else
|
|
127
|
-
['', '.so', '.a']
|
|
128
|
-
end
|
|
129
|
-
|
|
130
129
|
prefixes = lib_name.start_with?('lib') ? [''] : ['lib', '']
|
|
130
|
+
extensions = Platform.library_extensions
|
|
131
131
|
|
|
132
|
-
prefixes.each do |prefix|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
full_path = File.join(path, name)
|
|
137
|
-
return File.expand_path(full_path) if File.file?(full_path)
|
|
132
|
+
prefixes.product(extensions, search_paths).each do |prefix, ext, path|
|
|
133
|
+
name = "#{prefix}#{lib_name}#{ext}"
|
|
134
|
+
full_path = File.join(path, name)
|
|
135
|
+
return File.expand_path(full_path) if File.file?(full_path)
|
|
138
136
|
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
# Check for versioned libraries (libm.so.6, etc.)
|
|
138
|
+
next if ext.empty?
|
|
141
139
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return File.expand_path(matches.first) unless matches.empty?
|
|
145
|
-
end
|
|
146
|
-
end
|
|
140
|
+
matches = Dir.glob(File.join(path, "#{name}.*")).select { |f| File.file?(f) }
|
|
141
|
+
return File.expand_path(matches.first) unless matches.empty?
|
|
147
142
|
end
|
|
148
143
|
|
|
149
144
|
nil
|
data/lib/libcall/parser.rb
CHANGED
|
@@ -1,51 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'type_map'
|
|
4
|
+
|
|
3
5
|
module Libcall
|
|
4
6
|
# Parse and coerce TYPE VALUE argument pairs for FFI calls
|
|
5
7
|
class Parser
|
|
6
|
-
TYPE_MAP = {
|
|
7
|
-
'i8' => :char,
|
|
8
|
-
'u8' => :uchar,
|
|
9
|
-
'i16' => :short,
|
|
10
|
-
'u16' => :ushort,
|
|
11
|
-
'i32' => :int,
|
|
12
|
-
'u32' => :uint,
|
|
13
|
-
'i64' => :long_long,
|
|
14
|
-
'u64' => :ulong_long,
|
|
15
|
-
'isize' => :long,
|
|
16
|
-
'usize' => :ulong,
|
|
17
|
-
'f32' => :float,
|
|
18
|
-
'f64' => :double,
|
|
19
|
-
'cstr' => :string,
|
|
20
|
-
'ptr' => :voidp,
|
|
21
|
-
'pointer' => :voidp,
|
|
22
|
-
'void' => :void,
|
|
23
|
-
# Common aliases
|
|
24
|
-
'int' => :int,
|
|
25
|
-
'uint' => :uint,
|
|
26
|
-
'long' => :long,
|
|
27
|
-
'ulong' => :ulong,
|
|
28
|
-
'float' => :float,
|
|
29
|
-
'double' => :double,
|
|
30
|
-
'char' => :char,
|
|
31
|
-
'str' => :string,
|
|
32
|
-
'string' => :string
|
|
33
|
-
}.freeze
|
|
34
|
-
|
|
35
|
-
INTEGER_TYPES = %i[int uint long ulong long_long ulong_long char uchar short ushort].freeze
|
|
36
|
-
FLOAT_TYPES = %i[float double].freeze
|
|
37
|
-
|
|
38
8
|
# Pair-only API helpers
|
|
39
9
|
def self.parse_type(type_str)
|
|
10
|
+
# Output array spec: out:TYPE[N]
|
|
11
|
+
if type_str.start_with?('out:') && type_str.match(/^out:(.+)\[(\d+)\]$/)
|
|
12
|
+
base = Regexp.last_match(1)
|
|
13
|
+
count = Regexp.last_match(2).to_i
|
|
14
|
+
base_sym = TypeMap.lookup(base)
|
|
15
|
+
raise Error, "Unknown output array type: #{base}" unless base_sym
|
|
16
|
+
|
|
17
|
+
return [:out_array, base_sym, count]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Input array spec: TYPE[] (value as comma-separated list)
|
|
21
|
+
if type_str.end_with?('[]')
|
|
22
|
+
base = type_str[0..-3]
|
|
23
|
+
base_sym = TypeMap.lookup(base)
|
|
24
|
+
raise Error, "Unknown array base type: #{base}" unless base_sym
|
|
25
|
+
|
|
26
|
+
return [:array, base_sym]
|
|
27
|
+
end
|
|
28
|
+
|
|
40
29
|
# Output pointer spec: out:TYPE (e.g., out:int, out:f64)
|
|
41
30
|
if type_str.start_with?('out:')
|
|
42
31
|
inner = type_str.sub(/^out:/, '')
|
|
43
|
-
inner_sym =
|
|
32
|
+
inner_sym = TypeMap.lookup(inner)
|
|
44
33
|
raise Error, "Unknown type in out: #{inner}" unless inner_sym
|
|
34
|
+
|
|
45
35
|
return [:out, inner_sym]
|
|
46
36
|
end
|
|
47
37
|
|
|
48
|
-
type_sym =
|
|
38
|
+
type_sym = TypeMap.lookup(type_str)
|
|
49
39
|
raise Error, "Unknown type: #{type_str}" unless type_sym
|
|
50
40
|
|
|
51
41
|
type_sym
|
|
@@ -54,17 +44,25 @@ module Libcall
|
|
|
54
44
|
def self.parse_return_type(type_str)
|
|
55
45
|
return :void if type_str.nil? || type_str.empty? || type_str == 'void'
|
|
56
46
|
|
|
57
|
-
type_sym =
|
|
47
|
+
type_sym = TypeMap.lookup(type_str)
|
|
58
48
|
raise Error, "Unknown return type: #{type_str}" unless type_sym
|
|
59
49
|
|
|
60
50
|
type_sym
|
|
61
51
|
end
|
|
62
52
|
|
|
63
53
|
def self.coerce_value(type_sym, token)
|
|
54
|
+
# Input array values: comma-separated
|
|
55
|
+
if type_sym.is_a?(Array) && type_sym.first == :array
|
|
56
|
+
base = type_sym[1]
|
|
57
|
+
return [] if token.nil? || token.empty?
|
|
58
|
+
|
|
59
|
+
return token.split(',').map { |t| coerce_single_value(base, t.strip) }
|
|
60
|
+
end
|
|
61
|
+
|
|
64
62
|
case type_sym
|
|
65
|
-
when *FLOAT_TYPES
|
|
63
|
+
when *TypeMap::FLOAT_TYPES
|
|
66
64
|
Float(token)
|
|
67
|
-
when *INTEGER_TYPES
|
|
65
|
+
when *TypeMap::INTEGER_TYPES
|
|
68
66
|
Integer(token)
|
|
69
67
|
when :voidp
|
|
70
68
|
# Accept common null tokens for pointer types
|
|
@@ -80,32 +78,24 @@ module Libcall
|
|
|
80
78
|
end
|
|
81
79
|
end
|
|
82
80
|
|
|
83
|
-
def self.
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
def self.coerce_single_value(type_sym, token)
|
|
82
|
+
case type_sym
|
|
83
|
+
when *TypeMap::FLOAT_TYPES
|
|
84
|
+
Float(token)
|
|
85
|
+
when *TypeMap::INTEGER_TYPES
|
|
86
|
+
Integer(token)
|
|
87
|
+
when :string
|
|
88
|
+
strip_quotes(token)
|
|
86
89
|
else
|
|
87
|
-
|
|
90
|
+
raise Error, "Unknown element type for coercion: #{type_sym}"
|
|
88
91
|
end
|
|
89
92
|
end
|
|
90
93
|
|
|
91
|
-
def self.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
case type_sym
|
|
96
|
-
when :void then Fiddle::TYPE_VOID
|
|
97
|
-
when :char then Fiddle::TYPE_CHAR
|
|
98
|
-
when :uchar then Fiddle::TYPE_UCHAR
|
|
99
|
-
when :short then Fiddle::TYPE_SHORT
|
|
100
|
-
when :ushort then Fiddle::TYPE_USHORT
|
|
101
|
-
when :int, :uint then Fiddle::TYPE_INT
|
|
102
|
-
when :long, :ulong then Fiddle::TYPE_LONG
|
|
103
|
-
when :long_long, :ulong_long then Fiddle::TYPE_LONG_LONG
|
|
104
|
-
when :float then Fiddle::TYPE_FLOAT
|
|
105
|
-
when :double then Fiddle::TYPE_DOUBLE
|
|
106
|
-
when :voidp, :string then Fiddle::TYPE_VOIDP
|
|
94
|
+
def self.strip_quotes(token)
|
|
95
|
+
if (token.start_with?('"') && token.end_with?('"')) || (token.start_with?("'") && token.end_with?("'"))
|
|
96
|
+
token[1...-1]
|
|
107
97
|
else
|
|
108
|
-
|
|
98
|
+
token
|
|
109
99
|
end
|
|
110
100
|
end
|
|
111
101
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Libcall
|
|
4
|
+
# Platform detection utilities
|
|
5
|
+
module Platform
|
|
6
|
+
# Check if running on Windows
|
|
7
|
+
def self.windows?
|
|
8
|
+
RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Check if running on macOS
|
|
12
|
+
def self.darwin?
|
|
13
|
+
RUBY_PLATFORM =~ /darwin/
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Check if running on Unix-like system (Linux, BSD, etc.)
|
|
17
|
+
def self.unix?
|
|
18
|
+
!windows?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Get platform-specific library extensions
|
|
22
|
+
def self.library_extensions
|
|
23
|
+
if windows?
|
|
24
|
+
['', '.dll', '.so', '.a']
|
|
25
|
+
elsif darwin?
|
|
26
|
+
['', '.dylib', '.so', '.a']
|
|
27
|
+
else
|
|
28
|
+
['', '.so', '.a']
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Get architecture string
|
|
33
|
+
def self.architecture
|
|
34
|
+
if RUBY_PLATFORM =~ /x86_64/
|
|
35
|
+
'x86_64'
|
|
36
|
+
elsif RUBY_PLATFORM =~ /aarch64|arm64/
|
|
37
|
+
'aarch64'
|
|
38
|
+
else
|
|
39
|
+
'unknown'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fiddle'
|
|
4
|
+
|
|
5
|
+
module Libcall
|
|
6
|
+
# Type mapping for FFI calls
|
|
7
|
+
module TypeMap
|
|
8
|
+
# Map from string type names to FFI type symbols
|
|
9
|
+
MAP = {
|
|
10
|
+
# Short type names (Rust-like)
|
|
11
|
+
'i8' => :char,
|
|
12
|
+
'u8' => :uchar,
|
|
13
|
+
'i16' => :short,
|
|
14
|
+
'u16' => :ushort,
|
|
15
|
+
'i32' => :int,
|
|
16
|
+
'u32' => :uint,
|
|
17
|
+
'i64' => :long_long,
|
|
18
|
+
'u64' => :ulong_long,
|
|
19
|
+
'isize' => :long,
|
|
20
|
+
'usize' => :ulong,
|
|
21
|
+
'f32' => :float,
|
|
22
|
+
'f64' => :double,
|
|
23
|
+
# Pointer types
|
|
24
|
+
'cstr' => :string,
|
|
25
|
+
'ptr' => :voidp,
|
|
26
|
+
'pointer' => :voidp,
|
|
27
|
+
'void' => :void,
|
|
28
|
+
# Common C type names
|
|
29
|
+
'char' => :char,
|
|
30
|
+
'short' => :short,
|
|
31
|
+
'ushort' => :ushort,
|
|
32
|
+
'int' => :int,
|
|
33
|
+
'uint' => :uint,
|
|
34
|
+
'long' => :long,
|
|
35
|
+
'ulong' => :ulong,
|
|
36
|
+
'float' => :float,
|
|
37
|
+
'double' => :double,
|
|
38
|
+
# Extended type names (stdint-like)
|
|
39
|
+
'int8' => :char,
|
|
40
|
+
'uint8' => :uchar,
|
|
41
|
+
'int16' => :short,
|
|
42
|
+
'uint16' => :ushort,
|
|
43
|
+
'int32' => :int,
|
|
44
|
+
'uint32' => :uint,
|
|
45
|
+
'int64' => :long_long,
|
|
46
|
+
'uint64' => :ulong_long,
|
|
47
|
+
'float32' => :float,
|
|
48
|
+
'float64' => :double,
|
|
49
|
+
# Size and pointer-sized integers
|
|
50
|
+
'size_t' => :ulong,
|
|
51
|
+
'ssize_t' => :long,
|
|
52
|
+
'intptr' => :long,
|
|
53
|
+
'uintptr' => :ulong,
|
|
54
|
+
'intptr_t' => :long,
|
|
55
|
+
'uintptr_t' => :ulong,
|
|
56
|
+
'ptrdiff_t' => :long,
|
|
57
|
+
# Boolean
|
|
58
|
+
'bool' => :int,
|
|
59
|
+
# String aliases
|
|
60
|
+
'str' => :string,
|
|
61
|
+
'string' => :string
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
# Integer type symbols
|
|
65
|
+
INTEGER_TYPES = %i[
|
|
66
|
+
int uint
|
|
67
|
+
long ulong
|
|
68
|
+
long_long ulong_long
|
|
69
|
+
char uchar
|
|
70
|
+
short ushort
|
|
71
|
+
].freeze
|
|
72
|
+
|
|
73
|
+
# Floating point type symbols
|
|
74
|
+
FLOAT_TYPES = %i[float double].freeze
|
|
75
|
+
|
|
76
|
+
# Look up FFI type symbol from string
|
|
77
|
+
def self.lookup(type_str)
|
|
78
|
+
MAP[type_str]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Check if type symbol is an integer type
|
|
82
|
+
def self.integer_type?(type_sym)
|
|
83
|
+
INTEGER_TYPES.include?(type_sym)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Check if type symbol is a floating point type
|
|
87
|
+
def self.float_type?(type_sym)
|
|
88
|
+
FLOAT_TYPES.include?(type_sym)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Convert type symbol to Fiddle type constant
|
|
92
|
+
def self.to_fiddle_type(type_sym)
|
|
93
|
+
# Array and output parameters are passed as pointers
|
|
94
|
+
if type_sym.is_a?(Array)
|
|
95
|
+
tag = type_sym.first
|
|
96
|
+
return Fiddle::TYPE_VOIDP if %i[out array out_array].include?(tag)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
case type_sym
|
|
100
|
+
when :void then Fiddle::TYPE_VOID
|
|
101
|
+
when :char then Fiddle::TYPE_CHAR
|
|
102
|
+
when :uchar then Fiddle::TYPE_UCHAR
|
|
103
|
+
when :short then Fiddle::TYPE_SHORT
|
|
104
|
+
when :ushort then Fiddle::TYPE_USHORT
|
|
105
|
+
when :int, :uint then Fiddle::TYPE_INT
|
|
106
|
+
when :long, :ulong then Fiddle::TYPE_LONG
|
|
107
|
+
when :long_long, :ulong_long then Fiddle::TYPE_LONG_LONG
|
|
108
|
+
when :float then Fiddle::TYPE_FLOAT
|
|
109
|
+
when :double then Fiddle::TYPE_DOUBLE
|
|
110
|
+
when :voidp, :string then Fiddle::TYPE_VOIDP
|
|
111
|
+
else
|
|
112
|
+
raise Error, "Unknown Fiddle type: #{type_sym}"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Get the size in bytes for a type symbol
|
|
117
|
+
def self.sizeof(type_sym)
|
|
118
|
+
case type_sym
|
|
119
|
+
when :char, :uchar then Fiddle::SIZEOF_CHAR
|
|
120
|
+
when :short, :ushort then Fiddle::SIZEOF_SHORT
|
|
121
|
+
when :int, :uint then Fiddle::SIZEOF_INT
|
|
122
|
+
when :long, :ulong then Fiddle::SIZEOF_LONG
|
|
123
|
+
when :long_long, :ulong_long then Fiddle::SIZEOF_LONG_LONG
|
|
124
|
+
when :float then Fiddle::SIZEOF_FLOAT
|
|
125
|
+
when :double then Fiddle::SIZEOF_DOUBLE
|
|
126
|
+
when :voidp, :string then Fiddle::SIZEOF_VOIDP
|
|
127
|
+
else
|
|
128
|
+
raise Error, "Cannot get size for type: #{type_sym}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Allocate a pointer for output parameter
|
|
133
|
+
def self.allocate_output_pointer(type_sym)
|
|
134
|
+
ptr = Fiddle::Pointer.malloc(sizeof(type_sym))
|
|
135
|
+
# For out:string, we pass char**. Initialize inner pointer to NULL for safety.
|
|
136
|
+
ptr[0, Fiddle::SIZEOF_VOIDP] = [0].pack('J') if type_sym == :string
|
|
137
|
+
ptr
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Read value from output pointer
|
|
141
|
+
def self.read_output_pointer(ptr, type_sym)
|
|
142
|
+
case type_sym
|
|
143
|
+
when :char then ptr[0, Fiddle::SIZEOF_CHAR].unpack1('c')
|
|
144
|
+
when :uchar then ptr[0, Fiddle::SIZEOF_CHAR].unpack1('C')
|
|
145
|
+
when :short then ptr[0, Fiddle::SIZEOF_SHORT].unpack1('s')
|
|
146
|
+
when :ushort then ptr[0, Fiddle::SIZEOF_SHORT].unpack1('S')
|
|
147
|
+
when :int then ptr[0, Fiddle::SIZEOF_INT].unpack1('i')
|
|
148
|
+
when :uint then ptr[0, Fiddle::SIZEOF_INT].unpack1('I')
|
|
149
|
+
when :long then ptr[0, Fiddle::SIZEOF_LONG].unpack1('l!')
|
|
150
|
+
when :ulong then ptr[0, Fiddle::SIZEOF_LONG].unpack1('L!')
|
|
151
|
+
when :long_long then ptr[0, Fiddle::SIZEOF_LONG_LONG].unpack1('q')
|
|
152
|
+
when :ulong_long then ptr[0, Fiddle::SIZEOF_LONG_LONG].unpack1('Q')
|
|
153
|
+
when :float then ptr[0, Fiddle::SIZEOF_FLOAT].unpack1('f')
|
|
154
|
+
when :double then ptr[0, Fiddle::SIZEOF_DOUBLE].unpack1('d')
|
|
155
|
+
when :string
|
|
156
|
+
addr = ptr[0, Fiddle::SIZEOF_VOIDP].unpack1('J')
|
|
157
|
+
return '(null)' if addr.zero?
|
|
158
|
+
|
|
159
|
+
begin
|
|
160
|
+
Fiddle::Pointer.new(addr).to_s
|
|
161
|
+
rescue StandardError
|
|
162
|
+
format('0x%x', addr)
|
|
163
|
+
end
|
|
164
|
+
when :voidp then format('0x%x', ptr[0, Fiddle::SIZEOF_VOIDP].unpack1('J'))
|
|
165
|
+
else
|
|
166
|
+
raise Error, "Cannot read output value for type: #{type_sym}"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Allocate memory for an array of base type and count elements
|
|
171
|
+
def self.allocate_array(base_type, count)
|
|
172
|
+
Fiddle::Pointer.malloc(sizeof(base_type) * count)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def self.write_array(ptr, base_type, values)
|
|
176
|
+
return if values.nil? || values.empty?
|
|
177
|
+
|
|
178
|
+
bytes = sizeof(base_type) * values.length
|
|
179
|
+
ptr[0, bytes] = values.pack(pack_template(base_type) + values.length.to_s)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def self.read_array(ptr, base_type, count)
|
|
183
|
+
return [] if count <= 0
|
|
184
|
+
|
|
185
|
+
bytes = sizeof(base_type) * count
|
|
186
|
+
raw = ptr[0, bytes]
|
|
187
|
+
raw.unpack(pack_template(base_type) + count.to_s)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def self.pack_template(base_type)
|
|
191
|
+
case base_type
|
|
192
|
+
when :char then 'c'
|
|
193
|
+
when :uchar then 'C'
|
|
194
|
+
when :short then 's'
|
|
195
|
+
when :ushort then 'S'
|
|
196
|
+
when :int then 'i'
|
|
197
|
+
when :uint then 'I'
|
|
198
|
+
when :long then 'l!'
|
|
199
|
+
when :ulong then 'L!'
|
|
200
|
+
when :long_long then 'q'
|
|
201
|
+
when :ulong_long then 'Q'
|
|
202
|
+
when :float then 'f'
|
|
203
|
+
when :double then 'd'
|
|
204
|
+
else
|
|
205
|
+
raise Error, "Unsupported array base type: #{base_type}"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
data/lib/libcall/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: libcall
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kojix2
|
|
@@ -51,6 +51,8 @@ files:
|
|
|
51
51
|
- lib/libcall/cli.rb
|
|
52
52
|
- lib/libcall/library_finder.rb
|
|
53
53
|
- lib/libcall/parser.rb
|
|
54
|
+
- lib/libcall/platform.rb
|
|
55
|
+
- lib/libcall/type_map.rb
|
|
54
56
|
- lib/libcall/version.rb
|
|
55
57
|
homepage: https://github.com/kojix2/libcall
|
|
56
58
|
licenses:
|