crystalruby 0.1.6 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c552102391f468b4974cb4ac36c4876001743fdec5fa8f2a69577255de4c52d
4
- data.tar.gz: f7c104b10cf70eb03ab064117be946d4268c97063ba9302a99613e9c473ded97
3
+ metadata.gz: 95d9eecc4d47a8d9f83417779c6174eea95ebeea593e5127a89b6cfbc7884664
4
+ data.tar.gz: b4f241f826e4f2b6c704bda0fd01c201e79a9bc26027bd4702717fd371687650
5
5
  SHA512:
6
- metadata.gz: 0c01cefa9a6f820cd59aed62e02846bd90cd53a742c203b8de0023908c53e77ef2a558adf26023008dcd921e9c9cbc0466a63598361c18ec7cc6bee29a57ce2c
7
- data.tar.gz: a9fe76d0b8a93dab4afd94d696d9955a81e75384fd1d4af8a0af5249c30fd51920d46de4728caab630862c889e799f1207f9915176fe861c6624e7a13d994e5a
6
+ metadata.gz: d98f1e4a671679cfb8d9da45298954e01def202802bd5a64db9d0d9cba08de0776971d031648642f402fe0310d741dc6b2d163253884ad25082298226c1be1a0
7
+ data.tar.gz: 141658885f763ca3088a7058131e7227434920d6213e755e21f85d6bb4d68a062a2a3163127f4e9f0db03a8c9a27a0549837714b38789647028075cc3b77660c
data/README.md CHANGED
@@ -25,7 +25,7 @@ require 'crystalruby'
25
25
  module MyTestModule
26
26
  # The below method will be replaced by a compiled Crystal version
27
27
  # linked using FFI.
28
- crystalize [:int, :int] => :int
28
+ crystalize [a: :int, b: :int] => :int
29
29
  def add(a, b)
30
30
  a + b
31
31
  end
@@ -143,7 +143,7 @@ require 'bundler/inline'
143
143
 
144
144
  gemfile do
145
145
  source 'https://rubygems.org'
146
- gem 'crystalruby', path: '../crystalruby'
146
+ gem 'crystalruby'
147
147
  end
148
148
 
149
149
  require 'crystalruby'
@@ -151,7 +151,7 @@ require 'crystalruby'
151
151
  module Adder
152
152
  crystalize [a: :int, b: :int] => :int
153
153
  def add(a, b)
154
- a + b * 3
154
+ a + b
155
155
  end
156
156
  end
157
157
 
@@ -349,7 +349,6 @@ It should support escape hatches to allow it to coexist with code that performs
349
349
 
350
350
  The library is currently in its infancy. Planned additions are:
351
351
 
352
- - Replace existing checksum process, with one that combines results of inline and external crystal to more accurately detect when recompilation is necessary.
353
352
  - Simple mixin/concern that utilises `FFI::Struct` for bi-directional passing of Ruby objects and Crystal objects (by value).
354
353
  - Install command to generate a sample build script, and supports build command (which simply verifies then invokes this script)
355
354
  - Call Ruby from Crystal using FFI callbacks (implement `.expose_to_crystal`)
@@ -1,9 +1,18 @@
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
+
1
6
  module %{module_name}
2
7
  def self.%{fn_name}(%{fn_args}) : %{fn_ret_type}
3
8
  %{fn_body}
4
9
  end
5
10
  end
6
11
 
12
+ # This function is the entry point for the CrystalRuby code, exposed through FFI.
13
+ # We apply some basic error handling here, and convert the arguments and return values
14
+ # to ensure that we are using Crystal native types.
15
+
7
16
  fun %{lib_fn_name}(%{lib_fn_args}): %{lib_fn_ret_type}
8
17
  begin
9
18
  %{convert_lib_args}
@@ -1,38 +1,46 @@
1
1
  FAKE_ARG = "crystal"
2
- alias Callback = (Pointer(UInt8), Pointer(UInt8) -> Void)
2
+
3
+ alias ErrorCallback = (Pointer(UInt8), Pointer(UInt8) -> Void)
3
4
 
4
5
  module CrystalRuby
6
+ # Initializing Crystal Ruby invokes init on the Crystal garbage collector.
7
+ # We need to be sure to only do this once.
5
8
  @@initialized = false
6
- def self.init
7
- if @@initialized
8
- return
9
- end
10
- @@initialized = true
9
+
10
+ # We won't natively handle Crystal Exceptions in Ruby
11
+ # Instead, we'll catch them in Crystal, and explicitly expose them to Ruby via
12
+ # the error_callback.
13
+ @@error_callback
14
+
15
+ # This is the entry point for instantiating CrystalRuby
16
+ # We:
17
+ # 1. Initialize the Crystal garbage collector
18
+ # 2. Set the error callback
19
+ # 3. Call the Crystal main function
20
+ def self.init(error_callback : ErrorCallback)
21
+ return if @@initialized
11
22
  GC.init
23
+ @@initialized = true
24
+ @@error_callback = error_callback
12
25
  ptr = FAKE_ARG.to_unsafe
13
26
  LibCrystalMain.__crystal_main(1, pointerof(ptr))
14
27
  end
15
28
 
16
- def self.attach_rb_error_handler(cb : Callback)
17
- @@rb_error_handler = cb
18
- end
19
-
29
+ # Explicit error handling (triggers exception within Ruby on the same thread)
20
30
  def self.report_error(error_type : String, str : String)
21
- handler = @@rb_error_handler
22
- if handler
31
+ if handler = @@error_callback
23
32
  handler.call(error_type.to_unsafe, str.to_unsafe)
24
33
  end
25
34
  end
26
35
  end
27
36
 
28
-
29
- fun init(): Void
30
- CrystalRuby.init
31
- end
32
-
33
- fun attach_rb_error_handler(cb : Callback) : Void
34
- CrystalRuby.attach_rb_error_handler(cb)
37
+ fun init(cb : ErrorCallback): Void
38
+ CrystalRuby.init(cb)
35
39
  end
36
40
 
41
+ # This is where we define all our Crystal modules and types
42
+ # derived from their Ruby counterparts.
37
43
  %{type_modules}
44
+
45
+ # Require all generated crystal files
38
46
  %{requires}
@@ -1,63 +1,63 @@
1
1
  module CrystalRuby
2
2
  module Typemaps
3
3
  CRYSTAL_TYPE_MAP = {
4
- :char => "Int8", # In Crystal, :char is typically represented as Int8
5
- :uchar => "UInt8", # Unsigned char
6
- :int8 => "Int8", # Same as :char
7
- :uint8 => "UInt8", # Same as :uchar
8
- :short => "Int16", # Short integer
9
- :ushort => "UInt16", # Unsigned short integer
10
- :int16 => "Int16", # Same as :short
11
- :uint16 => "UInt16", # Same as :ushort
12
- :int => "Int32", # Integer, Crystal defaults to 32 bits
13
- :uint => "UInt32", # Unsigned integer
14
- :int32 => "Int32", # 32-bit integer
15
- :uint32 => "UInt32", # 32-bit unsigned integer
16
- :long => "Int32 | Int64", # Long integer, size depends on the platform (32 or 64 bits)
17
- :ulong => "UInt32 | UInt64", # Unsigned long integer, size depends on the platform
18
- :int64 => "Int64", # 64-bit integer
19
- :uint64 => "UInt64", # 64-bit unsigned integer
20
- :long_long => "Int64", # Same as :int64
21
- :ulong_long => "UInt64", # Same as :uint64
22
- :float => "Float32", # Floating point number (single precision)
23
- :double => "Float64", # Double precision floating point number
24
- :bool => "Bool", # Boolean type
25
- :void => "Void", # Void type
26
- :string => "String" # String type
4
+ char: "Int8", # In Crystal, :char is typically represented as Int8
5
+ uchar: "UInt8", # Unsigned char
6
+ int8: "Int8", # Same as :char
7
+ uint8: "UInt8", # Same as :uchar
8
+ short: "Int16", # Short integer
9
+ ushort: "UInt16", # Unsigned short integer
10
+ int16: "Int16", # Same as :short
11
+ uint16: "UInt16", # Same as :ushort
12
+ int: "Int32", # Integer, Crystal defaults to 32 bits
13
+ uint: "UInt32", # Unsigned integer
14
+ int32: "Int32", # 32-bit integer
15
+ uint32: "UInt32", # 32-bit unsigned integer
16
+ long: "Int32 | Int64", # Long integer, size depends on the platform (32 or 64 bits)
17
+ ulong: "UInt32 | UInt64", # Unsigned long integer, size depends on the platform
18
+ int64: "Int64", # 64-bit integer
19
+ uint64: "UInt64", # 64-bit unsigned integer
20
+ long_long: "Int64", # Same as :int64
21
+ ulong_long: "UInt64", # Same as :uint64
22
+ float: "Float32", # Floating point number (single precision)
23
+ double: "Float64", # Double precision floating point number
24
+ bool: "Bool", # Boolean type
25
+ void: "Void", # Void type
26
+ string: "String" # String type
27
27
  }
28
28
 
29
29
  ERROR_VALUE = {
30
- :char => "0", # In Crystal, :char is typically represented as Int8
31
- :uchar => "0", # Unsigned char
32
- :int8 => "0", # Same as :char
33
- :uint8 => "0", # Same as :uchar
34
- :short => "0", # Short integer
35
- :ushort => "0", # Unsigned short integer
36
- :int16 => "0", # Same as :short
37
- :uint16 => "0", # Same as :ushort
38
- :int => "0", # Integer, Crystal defaults to 32 bits
39
- :uint => "0", # Unsigned integer
40
- :int32 => "0", # 32-bit integer
41
- :uint32 => "0", # 32-bit unsigned integer
42
- :long => "0", # Long integer, size depends on the platform (32 or 64 bits)
43
- :ulong => "0", # Unsigned long integer, size depends on the platform
44
- :int64 => "0", # 64-bit integer
45
- :uint64 => "0", # 64-bit unsigned integer
46
- :long_long => "0", # Same as :int64
47
- :ulong_long => "0", # Same as :uint64
48
- :float => "0.0", # Floating point number (single precision)
49
- :double => "0.0", # Double precision floating point number
50
- :bool => "false", # Boolean type
51
- :void => "Void", # Void type
52
- :string => '""' # String type
30
+ char: "0", # In Crystal, :char is typically represented as Int8
31
+ uchar: "0", # Unsigned char
32
+ int8: "0", # Same as :char
33
+ uint8: "0", # Same as :uchar
34
+ short: "0", # Short integer
35
+ ushort: "0", # Unsigned short integer
36
+ int16: "0", # Same as :short
37
+ uint16: "0", # Same as :ushort
38
+ int: "0", # Integer, Crystal defaults to 32 bits
39
+ uint: "0", # Unsigned integer
40
+ int32: "0", # 32-bit integer
41
+ uint32: "0", # 32-bit unsigned integer
42
+ long: "0", # Long integer, size depends on the platform (32 or 64 bits)
43
+ ulong: "0", # Unsigned long integer, size depends on the platform
44
+ int64: "0", # 64-bit integer
45
+ uint64: "0", # 64-bit unsigned integer
46
+ long_long: "0", # Same as :int64
47
+ ulong_long: "0", # Same as :uint64
48
+ float: "0.0", # Floating point number (single precision)
49
+ double: "0.0", # Double precision floating point number
50
+ bool: "false", # Boolean type
51
+ void: "Void", # Void type
52
+ string: '"".to_unsafe' # String type
53
53
  }
54
54
 
55
55
  C_TYPE_MAP = CRYSTAL_TYPE_MAP.merge({
56
- :string => "UInt8*"
57
- })
56
+ string: "UInt8*"
57
+ })
58
58
 
59
59
  C_TYPE_CONVERSIONS = {
60
- :string => {
60
+ string: {
61
61
  from: "String.new(%s)",
62
62
  to: "%s.to_unsafe"
63
63
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crystalruby
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.8"
5
5
  end
data/lib/crystalruby.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "ffi"
2
4
  require "digest"
3
5
  require "fileutils"
@@ -10,6 +12,7 @@ require_relative "crystalruby/typebuilder"
10
12
  require_relative "crystalruby/template"
11
13
 
12
14
  module CrystalRuby
15
+ CR_SRC_FILES_PATTERN = "./**/*.cr"
13
16
  # Define a method to set the @crystalize proc if it doesn't already exist
14
17
  def crystalize(type = :src, **options, &block)
15
18
  (args,), returns = options.first
@@ -29,14 +32,6 @@ module CrystalRuby
29
32
  crtype(&block).serialize_as(:json)
30
33
  end
31
34
 
32
- def with_temporary_constant(constant_name, new_value)
33
- old_value = const_get(constant_name)
34
- const_set(constant_name, new_value)
35
- yield
36
- ensure
37
- const_set(constant_name, old_value)
38
- end
39
-
40
35
  def method_added(method_name)
41
36
  if @crystalize_next
42
37
  attach_crystalized_method(method_name)
@@ -64,8 +59,7 @@ module CrystalRuby
64
59
  CrystalRuby.write_function(self, name: function[:name], body: function[:body]) do
65
60
  extend FFI::Library
66
61
  ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
67
- attach_function "#{method_name}", fname, function[:ffi_types], function[:return_ffi_type]
68
- attach_function "init!", "init", [], :void
62
+ attach_function method_name, fname, function[:ffi_types], function[:return_ffi_type]
69
63
  if block
70
64
  [singleton_class, self].each do |receiver|
71
65
  receiver.prepend(Module.new do
@@ -73,8 +67,6 @@ module CrystalRuby
73
67
  end)
74
68
  end
75
69
  end
76
-
77
- init!
78
70
  end
79
71
 
80
72
  [singleton_class, self].each do |receiver|
@@ -219,6 +211,9 @@ module CrystalRuby
219
211
  "require \"./#{config.crystal_codegen_dir}/index\"\n"
220
212
  )
221
213
  end
214
+
215
+ attach_crystal_ruby_lib! if compiled?
216
+
222
217
  return if File.exist?("#{config.crystal_src_dir}/shard.yml")
223
218
 
224
219
  IO.write("#{config.crystal_src_dir}/shard.yml", <<~CRYSTAL)
@@ -227,11 +222,25 @@ module CrystalRuby
227
222
  CRYSTAL
228
223
  end
229
224
 
225
+ def attach_crystal_ruby_lib!
226
+ extend FFI::Library
227
+ ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
228
+ attach_function "init!", :init, [:pointer], :void
229
+ const_set(:ErrorCallback, FFI::Function.new(:void, %i[string string]) do |error_type, message|
230
+ error_type = error_type.to_sym
231
+ is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
232
+ error_type = is_exception_type ? Object.const_get(error_type) : RuntimeError
233
+ raise error_type.new(message)
234
+ end)
235
+ init!(ErrorCallback)
236
+ end
237
+
230
238
  def self.instantiated?
231
239
  @instantiated
232
240
  end
233
241
 
234
242
  def self.compiled?
243
+ @compiled = get_current_crystal_lib_digest == get_cr_src_files_digest unless defined?(@compiled)
235
244
  @compiled
236
245
  end
237
246
 
@@ -293,21 +302,14 @@ module CrystalRuby
293
302
  end
294
303
 
295
304
  unless result = system(cmd)
296
- File.delete("#{config.crystal_codegen_dir}/index.cr") if File.exist?("#{config.crystal_codegen_dir}/index.cr")
305
+ File.delete(digest_file_name) if File.exist?(digest_file_name)
297
306
  raise "Error compiling crystal code"
298
307
  end
299
- @compiled = true
300
308
  end
301
- extend FFI::Library
302
- ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
303
- attach_function :attach_rb_error_handler, [:pointer], :int
304
- const_set(:ErrorCallback, FFI::Function.new(:void, %i[string string]) do |error_type, message|
305
- error_type = error_type.to_sym
306
- is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
307
- error_type = is_exception_type ? Object.const_get(error_type) : RuntimeError
308
- raise error_type.new(message)
309
- end)
310
- attach_rb_error_handler(ErrorCallback)
309
+
310
+ IO.write(digest_file_name, get_cr_src_files_digest)
311
+ @compiled = true
312
+ attach_crystal_ruby_lib!
311
313
  end
312
314
 
313
315
  def self.attach!
@@ -317,10 +319,23 @@ module CrystalRuby
317
319
  @attached = true
318
320
  end
319
321
 
322
+ def self.get_cr_src_files_digest
323
+ file_digests = Dir.glob(CR_SRC_FILES_PATTERN).sort.map do |file_path|
324
+ content = File.read(file_path)
325
+ Digest::MD5.hexdigest(content)
326
+ end.join
327
+ Digest::MD5.hexdigest(file_digests)
328
+ end
329
+
330
+ def self.digest_file_name
331
+ @digest_file_name ||= "#{config.crystal_lib_dir}/#{config.crystal_lib_name}.digest"
332
+ end
333
+
334
+ def self.get_current_crystal_lib_digest
335
+ File.read(digest_file_name) if File.exist?(digest_file_name)
336
+ end
337
+
320
338
  def self.write_function(owner, name:, body:, &compile_callback)
321
- unless defined?(@compiled)
322
- @compiled = File.exist?("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/index.cr")
323
- end
324
339
  @block_store ||= []
325
340
  @block_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
326
341
  FileUtils.mkdir_p("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crystalruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-10 00:00:00.000000000 Z
11
+ date: 2024-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: digest