crystalruby 0.2.3 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/Dockerfile +23 -2
  4. data/README.md +395 -198
  5. data/Rakefile +4 -3
  6. data/crystalruby.gemspec +2 -2
  7. data/examples/adder/adder.rb +1 -1
  8. data/exe/crystalruby +1 -0
  9. data/lib/crystalruby/adapter.rb +143 -73
  10. data/lib/crystalruby/arc_mutex.rb +47 -0
  11. data/lib/crystalruby/compilation.rb +32 -3
  12. data/lib/crystalruby/config.rb +41 -37
  13. data/lib/crystalruby/function.rb +216 -73
  14. data/lib/crystalruby/library.rb +157 -51
  15. data/lib/crystalruby/reactor.rb +63 -44
  16. data/lib/crystalruby/source_reader.rb +92 -0
  17. data/lib/crystalruby/template.rb +16 -5
  18. data/lib/crystalruby/templates/function.cr +11 -10
  19. data/lib/crystalruby/templates/index.cr +53 -66
  20. data/lib/crystalruby/templates/inline_chunk.cr +1 -1
  21. data/lib/crystalruby/templates/ruby_interface.cr +34 -0
  22. data/lib/crystalruby/templates/top_level_function.cr +62 -0
  23. data/lib/crystalruby/templates/top_level_ruby_interface.cr +33 -0
  24. data/lib/crystalruby/typebuilder.rb +11 -55
  25. data/lib/crystalruby/typemaps.rb +92 -67
  26. data/lib/crystalruby/types/concerns/allocator.rb +80 -0
  27. data/lib/crystalruby/types/fixed_width/named_tuple.cr +80 -0
  28. data/lib/crystalruby/types/fixed_width/named_tuple.rb +86 -0
  29. data/lib/crystalruby/types/fixed_width/proc.cr +45 -0
  30. data/lib/crystalruby/types/fixed_width/proc.rb +79 -0
  31. data/lib/crystalruby/types/fixed_width/tagged_union.cr +53 -0
  32. data/lib/crystalruby/types/fixed_width/tagged_union.rb +113 -0
  33. data/lib/crystalruby/types/fixed_width/tuple.cr +82 -0
  34. data/lib/crystalruby/types/fixed_width/tuple.rb +92 -0
  35. data/lib/crystalruby/types/fixed_width.cr +138 -0
  36. data/lib/crystalruby/types/fixed_width.rb +205 -0
  37. data/lib/crystalruby/types/primitive.cr +21 -0
  38. data/lib/crystalruby/types/primitive.rb +117 -0
  39. data/lib/crystalruby/types/primitive_types/bool.cr +34 -0
  40. data/lib/crystalruby/types/primitive_types/bool.rb +11 -0
  41. data/lib/crystalruby/types/primitive_types/nil.cr +35 -0
  42. data/lib/crystalruby/types/primitive_types/nil.rb +16 -0
  43. data/lib/crystalruby/types/primitive_types/numbers.cr +37 -0
  44. data/lib/crystalruby/types/primitive_types/numbers.rb +28 -0
  45. data/lib/crystalruby/types/primitive_types/symbol.cr +55 -0
  46. data/lib/crystalruby/types/primitive_types/symbol.rb +35 -0
  47. data/lib/crystalruby/types/primitive_types/time.cr +35 -0
  48. data/lib/crystalruby/types/primitive_types/time.rb +25 -0
  49. data/lib/crystalruby/types/type.cr +64 -0
  50. data/lib/crystalruby/types/type.rb +249 -30
  51. data/lib/crystalruby/types/variable_width/array.cr +74 -0
  52. data/lib/crystalruby/types/variable_width/array.rb +88 -0
  53. data/lib/crystalruby/types/variable_width/hash.cr +146 -0
  54. data/lib/crystalruby/types/variable_width/hash.rb +117 -0
  55. data/lib/crystalruby/types/variable_width/string.cr +36 -0
  56. data/lib/crystalruby/types/variable_width/string.rb +18 -0
  57. data/lib/crystalruby/types/variable_width.cr +23 -0
  58. data/lib/crystalruby/types/variable_width.rb +46 -0
  59. data/lib/crystalruby/types.rb +32 -13
  60. data/lib/crystalruby/version.rb +2 -2
  61. data/lib/crystalruby.rb +13 -6
  62. metadata +42 -22
  63. data/lib/crystalruby/types/array.rb +0 -15
  64. data/lib/crystalruby/types/bool.rb +0 -3
  65. data/lib/crystalruby/types/hash.rb +0 -17
  66. data/lib/crystalruby/types/named_tuple.rb +0 -28
  67. data/lib/crystalruby/types/nil.rb +0 -3
  68. data/lib/crystalruby/types/numbers.rb +0 -5
  69. data/lib/crystalruby/types/string.rb +0 -3
  70. data/lib/crystalruby/types/symbol.rb +0 -3
  71. data/lib/crystalruby/types/time.rb +0 -8
  72. data/lib/crystalruby/types/tuple.rb +0 -17
  73. data/lib/crystalruby/types/type_serializer/json.rb +0 -41
  74. data/lib/crystalruby/types/type_serializer.rb +0 -37
  75. data/lib/crystalruby/types/typedef.rb +0 -57
  76. data/lib/crystalruby/types/union_type.rb +0 -43
  77. data/lib/module.rb +0 -3
@@ -1,48 +1,40 @@
1
1
  module CrystalRuby
2
-
3
2
  ARGV1 = "crystalruby"
4
- CALLBACK_MUX = Mutex.new
5
3
 
6
- alias ErrorCallback = (Pointer(UInt8), Pointer(UInt8), UInt32 -> Void)
4
+ alias ErrorCallback = (Pointer(::UInt8), Pointer(::UInt8), Pointer(::UInt8), ::UInt32 -> Void)
5
+
6
+ class_property libname : String = "crystalruby"
7
+ class_property callbacks : Channel(Proc(Nil)) = Channel(Proc(Nil)).new
8
+ class_property rc_mux : Pointer(Void) = Pointer(Void).null
9
+ class_property task_counter : Atomic(Int32) = Atomic(Int32).new(0)
7
10
 
8
11
  # Initializing Crystal Ruby invokes init on the Crystal garbage collector.
9
12
  # We need to be sure to only do this once.
10
- @@initialized = false
11
-
12
- @@libname = "crystalruby"
13
- # Our Ruby <-> Crystal Reactor uses Fibers, with callbacks to allow
14
- # multiple concurrent Crystal operations to be queued
15
- @@callbacks = [] of Proc(Nil)
16
-
17
- # We only continue to yield to the Crystal scheduler from Ruby
18
- # while there are outstanding tasks.
19
- @@task_counter : Atomic(Int32) = Atomic.new(0)
13
+ class_property initialized : Bool = false
20
14
 
21
15
  # We can override the error callback to catch errors in Crystal,
22
16
  # and explicitly expose them to Ruby.
23
- @@error_callback : ErrorCallback = ->(t : UInt8* , s : UInt8*, tid : UInt32){ puts "Error: #{t}:#{s}" }
17
+ @@error_callback : ErrorCallback?
24
18
 
25
19
  # This is the entry point for instantiating CrystalRuby
26
20
  # We:
27
21
  # 1. Initialize the Crystal garbage collector
28
22
  # 2. Set the error callback
29
23
  # 3. Call the Crystal main function
30
- def self.init(libname : Pointer(UInt8), error_callback : ErrorCallback)
31
- return if @@initialized
32
- @@initialized = true
24
+ def self.init(libname : Pointer(::UInt8), @@error_callback : ErrorCallback, @@rc_mux : Pointer(Void))
25
+ return if self.initialized
26
+ self.initialized = true
33
27
  argv_ptr = ARGV1.to_unsafe
34
28
  Crystal.main_user_code(0, pointerof(argv_ptr))
35
- @@error_callback = error_callback
36
- @@libname = String.new(libname)
29
+ self.libname = String.new(libname)
30
+ LibGC.set_finalize_on_demand(1)
37
31
  end
38
32
 
39
33
  # Explicit error handling (triggers exception within Ruby on the same thread)
40
- def self.report_error(error_type : String, str : String, thread_id : UInt32, )
41
- @@error_callback.call(error_type.to_unsafe, str.to_unsafe, thread_id)
42
- end
43
-
44
- def self.error_callback : ErrorCallback
45
- @@error_callback
34
+ def self.report_error(error_type : String, message : String, backtrace : String, thread_id : UInt32)
35
+ if error_reporter = @@error_callback
36
+ error_reporter.call(error_type.to_unsafe, message.to_unsafe, backtrace.to_unsafe, thread_id)
37
+ end
46
38
  end
47
39
 
48
40
  # New async task started
@@ -57,51 +49,40 @@ module CrystalRuby
57
49
 
58
50
  # Get number of outstanding tasks
59
51
  def self.get_task_counter : Int32
60
- @@task_counter.get()
52
+ @@task_counter.get
61
53
  end
62
54
 
63
55
  # Queue a callback for an async task
64
56
  def self.queue_callback(callback : Proc(Nil))
65
- CALLBACK_MUX.synchronize do
66
- @@callbacks << callback
67
- end
68
- end
69
-
70
- # Get number of queued callbacks
71
- def self.count_callbacks : Int32
72
- @@callbacks.size
57
+ self.callbacks.send(callback)
73
58
  end
74
59
 
75
- def self.libname : String
76
- @@libname
77
- end
78
-
79
- # Flush all callbacks
80
- def self.flush_callbacks : Int32
81
- CALLBACK_MUX.synchronize do
82
- count = @@callbacks.size
83
- @@callbacks.each do |callback|
84
- result = callback.call()
85
- end
86
- @@callbacks.clear
87
- end
88
- get_task_counter
60
+ def self.synchronize
61
+ LibC.pthread_mutex_lock(self.rc_mux)
62
+ yield
63
+ LibC.pthread_mutex_unlock(self.rc_mux)
89
64
  end
90
65
  end
91
66
 
92
67
  # Initialize CrystalRuby
93
- fun init(libname : Pointer(UInt8), cb : CrystalRuby::ErrorCallback): Void
94
- CrystalRuby.init(libname, cb)
68
+ fun init(libname : Pointer(::UInt8), cb : CrystalRuby::ErrorCallback, rc_mux : Pointer(Void)) : Void
69
+ CrystalRuby.init(libname, cb, rc_mux)
95
70
  end
96
71
 
97
72
  fun stop : Void
98
- LibGC.deinit()
73
+ LibGC.deinit
99
74
  end
100
75
 
101
76
  @[Link("gc")]
102
77
  lib LibGC
103
78
  $stackbottom = GC_stackbottom : Void*
104
79
  fun deinit = GC_deinit
80
+ fun set_finalize_on_demand = GC_set_finalize_on_demand(Int32)
81
+ fun invoke_finalizers = GC_invoke_finalizers : Int
82
+ end
83
+
84
+ lib LibC
85
+ fun calloc = calloc(Int32, Int32) : Void*
105
86
  end
106
87
 
107
88
  module GC
@@ -112,6 +93,11 @@ module GC
112
93
  def self.set_stackbottom(stack_bottom : Void*)
113
94
  LibGC.stackbottom = stack_bottom
114
95
  end
96
+
97
+ def self.collect
98
+ LibGC.collect
99
+ LibGC.invoke_finalizers
100
+ end
115
101
  end
116
102
 
117
103
  # Yield to the Crystal scheduler from Ruby
@@ -119,24 +105,25 @@ end
119
105
  # Otherwise, we yield to the Crystal scheduler and let Ruby know
120
106
  # how many outstanding tasks still remain (it will stop yielding to Crystal
121
107
  # once this figure reaches 0).
122
- fun yield() : Int32
123
- if CrystalRuby.count_callbacks == 0
124
- Fiber.yield
125
-
126
- # TODO: We should apply backpressure here to prevent busy waiting if the number of outstanding tasks is not decreasing.
127
- # Use a simple exponential backoff strategy, to increase the time between each yield up to a maximum of 1 second.
128
-
129
- CrystalRuby.get_task_counter
130
- else
131
- CrystalRuby.flush_callbacks()
108
+ fun yield : Int32
109
+ Fiber.yield
110
+ loop do
111
+ select
112
+ when callback = CrystalRuby.callbacks.receive
113
+ callback.call
114
+ else
115
+ break
116
+ end
132
117
  end
118
+ CrystalRuby.get_task_counter
133
119
  end
134
120
 
121
+ class Array(T)
122
+ def initialize(size : Int32, @buffer : Pointer(T))
123
+ @size = size.to_i32
124
+ @capacity = @size
125
+ end
126
+ end
135
127
 
136
- # This is where we define all our Crystal modules and types
137
- # derived from their Ruby counterparts.
138
- %{type_modules}
139
-
140
- # Require all generated crystal files
141
128
  require "json"
142
129
  %{requires}
@@ -1,6 +1,6 @@
1
1
  # This is the template used for writing inline chunks of Crystal code without direct
2
2
  # Ruby integration
3
3
 
4
- module %{module_name}
4
+ %{mod_or_class} %{module_name} %{superclass}
5
5
  %{body}
6
6
  end
@@ -0,0 +1,34 @@
1
+ # This is the template used for all CrystalRuby functions
2
+ # Calls to this method *from ruby* are first transformed through the lib function.
3
+ # Crystal code can simply call this method directly, enabling generated crystal code
4
+ # to call other generated crystal code without overhead.
5
+
6
+ %{module_or_class} %{module_name} %{superclass}
7
+ def %{fn_scope}%{fn_name}(%{fn_args}) : %{fn_ret_type}
8
+ %{convert_lib_args}
9
+ cb = %{module_name}.%{callback_name}
10
+ unless cb.nil?
11
+ callback_done_channel = Channel(Nil).new
12
+ return_value = nil
13
+ if Fiber.current == Thread.current.main_fiber
14
+ return_value = cb.call(%{lib_fn_arg_names})
15
+ return %{convert_return_type}
16
+ else
17
+ CrystalRuby.queue_callback(->{
18
+ return_value = cb.call(%{lib_fn_arg_names})
19
+ callback_done_channel.send(nil)
20
+ })
21
+ end
22
+ callback_done_channel.receive
23
+ return %{convert_return_type}
24
+ end
25
+ raise "No callback registered for %{fn_name}"
26
+
27
+ end
28
+
29
+ class_property %{callback_name} : Proc(%{lib_fn_types} %{lib_fn_ret_type})?
30
+ end
31
+
32
+ fun register_%{callback_name}(callback : Proc(%{lib_fn_types} %{lib_fn_ret_type})) : Void
33
+ %{module_name}.%{callback_name} = callback
34
+ end
@@ -0,0 +1,62 @@
1
+ # This is the template used for all CrystalRuby functions
2
+ # Calls to this method *from ruby* are first transformed through the lib function.
3
+ # Crystal code can simply call this method directly, enabling generated crystal code
4
+ # to call other generated crystal code without overhead.
5
+
6
+ def %{fn_name}(%{fn_args}) : %{fn_ret_type}
7
+ %{fn_body}
8
+ end
9
+
10
+ # This function is the entry point for the CrystalRuby code, exposed through FFI.
11
+ # We apply some basic error handling here, and convert the arguments and return values
12
+ # to ensure that we are using Crystal native types.
13
+ fun %{lib_fn_name}(%{lib_fn_args}): %{lib_fn_ret_type}
14
+ begin
15
+ %{convert_lib_args}
16
+ begin
17
+ return_value = %{fn_name}(%{arg_names})%{block_converter}
18
+ return %{convert_return_type}
19
+ rescue ex
20
+ CrystalRuby.report_error("RuntimeError", ex.message.to_s, ex.backtrace.to_json, 0)
21
+ end
22
+ rescue ex
23
+ CrystalRuby.report_error("ArgumentError", ex.message.to_s, ex.backtrace.to_json, 0)
24
+ end
25
+ return %{error_value}
26
+ end
27
+
28
+
29
+ # This function is the async entry point for the CrystalRuby code, exposed through FFI.
30
+ # We apply some basic error handling here, and convert the arguments and return values
31
+ # to ensure that we are using Crystal native types.
32
+ fun %{lib_fn_name}_async(%{lib_fn_args} thread_id: UInt32, callback : %{callback_type}): Void
33
+ begin
34
+ %{convert_lib_args}
35
+ CrystalRuby.increment_task_counter
36
+ spawn do
37
+ begin
38
+ return_value = %{fn_name}(%{arg_names})%{block_converter}
39
+ CrystalRuby.queue_callback(->{
40
+ converted = %{convert_return_type}
41
+ %{callback_call}
42
+ CrystalRuby.decrement_task_counter
43
+ })
44
+ rescue ex
45
+ exception = ex.message.to_s
46
+ backtrace = ex.backtrace.to_json
47
+ CrystalRuby.queue_callback(->{
48
+ CrystalRuby.report_error("RuntimeError", exception, backtrace, thread_id)
49
+ CrystalRuby.decrement_task_counter
50
+ })
51
+ end
52
+ end
53
+ rescue ex
54
+
55
+ exception = ex.message.to_s
56
+ backtrace = ex.backtrace.to_json
57
+ CrystalRuby.queue_callback(->{
58
+ CrystalRuby.report_error("RuntimeError", ex.message.to_s, backtrace, thread_id)
59
+ CrystalRuby.decrement_task_counter
60
+ })
61
+ end
62
+ end
@@ -0,0 +1,33 @@
1
+ # This is the template used for all CrystalRuby functions
2
+ # Calls to this method *from ruby* are first transformed through the lib function.
3
+ # Crystal code can simply call this method directly, enabling generated crystal code
4
+ # to call other generated crystal code without overhead.
5
+
6
+ module TopLevelCallbacks
7
+ class_property %{fn_name}_callback : Proc(%{lib_fn_types} %{lib_fn_ret_type})?
8
+ end
9
+
10
+ def %{fn_scope}%{fn_name}(%{fn_args}) : %{fn_ret_type}
11
+ %{convert_lib_args}
12
+ cb = TopLevelCallbacks.%{fn_name}_callback
13
+ unless cb.nil?
14
+ callback_done_channel = Channel(Nil).new
15
+ return_value = nil
16
+ if Fiber.current == Thread.current.main_fiber
17
+ return_value = cb.call(%{lib_fn_arg_names})
18
+ return %{convert_return_type}
19
+ else
20
+ CrystalRuby.queue_callback(->{
21
+ return_value = cb.call(%{lib_fn_arg_names})
22
+ callback_done_channel.send(nil)
23
+ })
24
+ end
25
+ callback_done_channel.receive
26
+ return %{convert_return_type}
27
+ end
28
+ raise "No callback registered for %{fn_name}"
29
+ end
30
+
31
+ fun register_%{fn_name}_callback(callback : Proc(%{lib_fn_types} %{lib_fn_ret_type})) : Void
32
+ TopLevelCallbacks.%{fn_name}_callback = callback
33
+ end
@@ -4,66 +4,22 @@ module CrystalRuby
4
4
  module TypeBuilder
5
5
  module_function
6
6
 
7
- def with_injected_type_dsl(context, &block)
8
- with_constants(context) do
9
- with_methods(context, &block)
7
+ def build_from_source(src, context: )
8
+ source_type = Types.with_binding_fallback(context) do |binding|
9
+ eval(src.is_a?(String) ? src : SourceReader.extract_source_from_proc(src), binding)
10
10
  end
11
- end
12
11
 
13
- def with_methods(context)
14
- restores = []
15
- %i[Array Hash NamedTuple Tuple].each do |method_name|
16
- old_method = begin
17
- context.instance_method(method_name)
18
- rescue StandardError
19
- nil
20
- end
21
- restores << [context, method_name, old_method]
22
- context.singleton_class.undef_method(method_name) if old_method
23
- context.define_singleton_method(method_name) do |*args|
24
- Types.send(method_name, *args)
25
- end
26
- end
27
- yield
28
- ensure
29
- restores.each do |context, method_name, old_method|
30
- context.singleton_class.undef_method(method_name)
31
- context.define_singleton_method(method_name, old_method) if old_method
32
- end
33
- end
12
+ return source_type if source_type.is_a?(Types::Root::Symbol)
34
13
 
35
- def with_constants(context)
36
- previous_const_pairs = CrystalRuby::Types.constants.map do |type|
37
- [type, begin
38
- context.const_get(type)
39
- rescue StandardError
40
- nil
41
- end]
42
- end
43
- CrystalRuby::Types.constants.each do |type|
44
- begin
45
- context.send(:remove_const, type)
46
- rescue StandardError
47
- nil
48
- end
49
- context.const_set(type, CrystalRuby::Types.const_get(type))
14
+ unless source_type.kind_of?(Class) && source_type < Types::Type
15
+ raise "Invalid type #{source_type.inspect}"
50
16
  end
51
- yield
52
- ensure
53
- previous_const_pairs.each do |const_name, const_value|
54
- begin
55
- context.send(:remove_const, const_name)
56
- rescue StandardError
57
- nil
58
- end
59
- context.const_set(const_name, const_value)
60
- end
61
- end
62
17
 
63
- def build
64
- result = yield
65
- Types::Type.validate!(result)
66
- Types::Typedef(result)
18
+ return source_type unless source_type.anonymous?
19
+
20
+ source_type.tap do |new_type|
21
+ Types::Type.validate!(new_type)
22
+ end
67
23
  end
68
24
  end
69
25
  end
@@ -3,55 +3,60 @@
3
3
  module CrystalRuby
4
4
  module Typemaps
5
5
  CRYSTAL_TYPE_MAP = {
6
- char: "Int8", # In Crystal, :char is typically represented as Int8
7
- uchar: "UInt8", # Unsigned char
8
- int8: "Int8", # Same as :char
9
- uint8: "UInt8", # Same as :uchar
10
- short: "Int16", # Short integer
11
- ushort: "UInt16", # Unsigned short integer
12
- int16: "Int16", # Same as :short
13
- uint16: "UInt16", # Same as :ushort
14
- int: "Int32", # Integer, Crystal defaults to 32 bits
15
- uint: "UInt32", # Unsigned integer
16
- int32: "Int32", # 32-bit integer
17
- uint32: "UInt32", # 32-bit unsigned integer
18
- long: "Int32 | Int64", # Long integer, size depends on the platform (32 or 64 bits)
6
+ char: "Int8", # In Crystal, :char is typically represented as Int8
7
+ uchar: "UInt8", # Unsigned char
8
+ int8: "Int8", # Same as :char
9
+ uint8: "UInt8", # Same as :uchar
10
+ short: "Int16", # Short integer
11
+ ushort: "UInt16", # Unsigned short integer
12
+ int16: "Int16", # Same as :short
13
+ uint16: "UInt16", # Same as :ushort
14
+ int: "Int32", # Integer, Crystal defaults to 32 bits
15
+ uint: "UInt32", # Unsigned integer
16
+ int32: "Int32", # 32-bit integer
17
+ uint32: "UInt32", # 32-bit unsigned integer
18
+ long: "Int32 | Int64", # Long integer, size depends on the platform (32 or 64 bits)
19
19
  ulong: "UInt32 | UInt64", # Unsigned long integer, size depends on the platform
20
- int64: "Int64", # 64-bit integer
21
- uint64: "UInt64", # 64-bit unsigned integer
22
- long_long: "Int64", # Same as :int64
23
- ulong_long: "UInt64", # Same as :uint64
24
- float: "Float32", # Floating point number (single precision)
25
- double: "Float64", # Double precision floating point number
26
- bool: "Bool", # Boolean type
27
- void: "Void", # Void type
28
- string: "String" # String type
20
+ int64: "Int64", # 64-bit integer
21
+ uint64: "UInt64", # 64-bit unsigned integer
22
+ long_long: "Int64", # Same as :int64
23
+ ulong_long: "UInt64", # Same as :uint64
24
+ float: "Float32", # Floating point number (single precision)
25
+ double: "Float64", # Double precision floating point number
26
+ bool: "Bool", # Boolean type
27
+ void: "Void", # Void type
28
+ string: "String", # String type
29
+ pointer: "Pointer(Void)" # Pointer type
30
+
29
31
  }
30
32
 
33
+ FFI_TYPE_MAP = CRYSTAL_TYPE_MAP.invert
34
+
31
35
  ERROR_VALUE = {
32
- char: "0", # In Crystal, :char is typically represented as Int8
33
- uchar: "0", # Unsigned char
34
- int8: "0", # Same as :char
35
- uint8: "0", # Same as :uchar
36
- short: "0", # Short integer
37
- ushort: "0", # Unsigned short integer
38
- int16: "0", # Same as :short
39
- uint16: "0", # Same as :ushort
40
- int: "0", # Integer, Crystal defaults to 32 bits
41
- uint: "0", # Unsigned integer
42
- int32: "0", # 32-bit integer
43
- uint32: "0", # 32-bit unsigned integer
44
- long: "0", # Long integer, size depends on the platform (32 or 64 bits)
45
- ulong: "0", # Unsigned long integer, size depends on the platform
46
- int64: "0", # 64-bit integer
47
- uint64: "0", # 64-bit unsigned integer
48
- long_long: "0", # Same as :int64
49
- ulong_long: "0", # Same as :uint64
50
- float: "0.0", # Floating point number (single precision)
51
- double: "0.0", # Double precision floating point number
36
+ char: "0i8", # In Crystal, :char is typically represented as Int8
37
+ uchar: "0u8", # Unsigned char
38
+ int8: "0i8", # Same as :char
39
+ uint8: "0u8", # Same as :uchar
40
+ short: "0i16", # Short integer
41
+ ushort: "0u16", # Unsigned short integer
42
+ int16: "0i16", # Same as :short
43
+ uint16: "0u16", # Same as :ushort
44
+ int: "0i32", # Integer, Crystal defaults to 32 bits
45
+ uint: "0u32", # Unsigned integer
46
+ int32: "0i32", # 32-bit integer
47
+ uint32: "0u32", # 32-bit unsigned integer
48
+ long: "0i64", # Long integer, size depends on the platform (32 or 64 bits)
49
+ ulong: "0u64", # Unsigned long integer, size depends on the platform
50
+ int64: "0_i64", # 64-bit integer
51
+ uint64: "0_u64", # 64-bit unsigned integer
52
+ long_long: "0_i64", # Same as :int64
53
+ ulong_long: "0_u64", # Same as :uint64
54
+ float: "0.0f32", # Floating point number (single precision)
55
+ double: "0.0f64", # Double precision floating point number
52
56
  bool: "false", # Boolean type
53
57
  void: "Void", # Void type
54
- string: '"".to_unsafe' # String type
58
+ string: '"".to_unsafe', # String type
59
+ pointer: "Pointer(Void).null" # Pointer type
55
60
  }
56
61
 
57
62
  C_TYPE_MAP = CRYSTAL_TYPE_MAP.merge(
@@ -68,32 +73,40 @@ module CrystalRuby
68
73
  void: {
69
74
  to: "nil"
70
75
  }
71
- }.tap do |hash|
72
- hash.define_singleton_method(:convert) do |type, dir, expr|
73
- if hash.key?(type)
74
- conversion_string = hash[type][dir]
75
- conversion_string =~ /%/ ? conversion_string % expr : conversion_string
76
- else
77
- expr
78
- end
76
+ }.tap do |hash|
77
+ hash.define_singleton_method(:convert) do |type, dir, expr|
78
+ if hash.key?(type)
79
+ conversion_string = hash[type][dir]
80
+ conversion_string =~ /%/ ? conversion_string % expr : conversion_string
81
+ else
82
+ expr
79
83
  end
80
84
  end
85
+ end
81
86
 
82
87
  def build_type_map(crystalruby_type)
88
+ crystalruby_type = CRType(&crystalruby_type) if crystalruby_type.is_a?(Proc)
83
89
  {
84
90
  ffi_type: ffi_type(crystalruby_type),
85
91
  ffi_ret_type: ffi_type(crystalruby_type),
86
92
  crystal_type: crystal_type(crystalruby_type),
93
+ crystalruby_type: crystalruby_type,
87
94
  lib_type: lib_type(crystalruby_type),
88
95
  error_value: error_value(crystalruby_type),
89
- arg_mapper: if crystalruby_type.is_a?(Types::TypeSerializer)
96
+ arg_mapper: if crystalruby_type.is_a?(Class) && crystalruby_type < Types::Type
90
97
  lambda { |arg|
91
- crystalruby_type.prepare_argument(arg)
98
+ arg = crystalruby_type.new(arg.memory) if arg.is_a?(Types::Type) && !arg.is_a?(crystalruby_type)
99
+ arg = crystalruby_type.new(arg) unless arg.is_a?(Types::Type)
100
+ arg
92
101
  }
93
102
  end,
94
- retval_mapper: if crystalruby_type.is_a?(Types::TypeSerializer)
103
+ retval_mapper: if crystalruby_type.is_a?(Class) && crystalruby_type < Types::Type
95
104
  lambda { |arg|
96
- crystalruby_type.prepare_retval(arg)
105
+ if arg.is_a?(Types::Type) && !arg.is_a?(crystalruby_type)
106
+ arg = crystalruby_type.new(arg.memory)
107
+ end
108
+ arg = crystalruby_type.new(arg) unless arg.is_a?(Types::Type)
109
+ crystalruby_type.anonymous? ? arg.native : arg
97
110
  }
98
111
  end,
99
112
  convert_crystal_to_lib_type: ->(expr) { convert_crystal_to_lib_type(expr, crystalruby_type) },
@@ -104,13 +117,20 @@ module CrystalRuby
104
117
  def ffi_type(type)
105
118
  case type
106
119
  when Symbol then type
107
- when Types::TypeSerializer then type.ffi_type
120
+ when Class
121
+ if type < Types::FixedWidth
122
+ :pointer
123
+ elsif type < Types::Primitive
124
+ type.ffi_type
125
+ end
108
126
  end
109
127
  end
110
128
 
111
129
  def lib_type(type)
112
- if type.is_a?(Types::TypeSerializer)
113
- type.lib_type
130
+ if type.is_a?(Class) && type < Types::FixedWidth
131
+ "Pointer(::UInt8)"
132
+ elsif type.is_a?(Class) && type < Types::Type
133
+ C_TYPE_MAP.fetch(type.ffi_type)
114
134
  else
115
135
  C_TYPE_MAP.fetch(type)
116
136
  end
@@ -119,8 +139,10 @@ module CrystalRuby
119
139
  end
120
140
 
121
141
  def error_value(type)
122
- if type.is_a?(Types::TypeSerializer)
123
- type.error_value
142
+ if type.is_a?(Class) && type < Types::FixedWidth
143
+ "Pointer(::UInt8).null"
144
+ elsif type.is_a?(Class) && type < Types::Type
145
+ ERROR_VALUE.fetch(type.ffi_type)
124
146
  else
125
147
  ERROR_VALUE.fetch(type)
126
148
  end
@@ -129,8 +151,8 @@ module CrystalRuby
129
151
  end
130
152
 
131
153
  def crystal_type(type)
132
- if type.is_a?(Types::TypeSerializer)
133
- type.crystal_type
154
+ if type.is_a?(Class) && type < Types::Type
155
+ type.anonymous? ? type.native_type_expr : type.inspect
134
156
  else
135
157
  CRYSTAL_TYPE_MAP.fetch(type)
136
158
  end
@@ -139,16 +161,19 @@ module CrystalRuby
139
161
  end
140
162
 
141
163
  def convert_lib_to_crystal_type(expr, type)
142
- if type.is_a?(Types::TypeSerializer)
143
- type.lib_to_crystal_type_expr(expr)
164
+ if type.is_a?(Class) && type < Types::Type
165
+ expr = "#{expr}.not_nil!" unless type.nil?
166
+ type.pointer_to_crystal_type_conversion(expr)
167
+ elsif type == :void
168
+ "nil"
144
169
  else
145
- C_TYPE_CONVERSIONS.convert(type, :from, expr)
170
+ "#{C_TYPE_CONVERSIONS.convert(type, :from, expr)}.not_nil!"
146
171
  end
147
172
  end
148
173
 
149
174
  def convert_crystal_to_lib_type(expr, type)
150
- if type.is_a?(Types::TypeSerializer)
151
- type.crystal_to_lib_type_expr(expr)
175
+ if type.is_a?(Class) && type < Types::Type
176
+ type.crystal_type_to_pointer_type_conversion(expr)
152
177
  else
153
178
  C_TYPE_CONVERSIONS.convert(type, :to, expr)
154
179
  end