crystalruby 0.1.5 → 0.1.7

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: e8dc48667be65ee6e6d36ba7efcfc87dfefb133a06cb375a22297db9643221e3
4
- data.tar.gz: 40753b3741ae9fb5dec86019d35968027517280a08ae6bffbcf8f2e207fe96b3
3
+ metadata.gz: fe68e58781b8314afb53b268ba9314472ee93c611565f7398d0ff29b445f8ece
4
+ data.tar.gz: acd5e80d5709cc6f831daa39f58f74fe95642e16a4c44d25c8fab24106b47bcd
5
5
  SHA512:
6
- metadata.gz: 1682a4405fd2baf66a93fc913af2146fa11e9806b90c8d6ac6d0eebc496b30415204474bd841377e3ea409401608f2907f58d7b717b21520cd4b1fe04d825d86
7
- data.tar.gz: 8682ebdcdd83bda6ab29261cdfc627afe1bd211343bc4caa95c9f08cbb30fba663f1aebcf4df1535e27d7a84377449d5028afcce145cc0a91c153721e2d261f1
6
+ metadata.gz: 85516d1372b2467147cb5c61e6d33dc27e6e2c0d11876b32dbee1007152096fd54551926177b56e066fa9bc8e4329b8ef13d133280305018428775267eb4a24e
7
+ data.tar.gz: 207284740bef726d1a8c5df35b21ebe33b8611725363acac451bbadc3b7c714585513027be2398dd4cdd6ff94761c00585c94546fb198a4b90047d91edbef2ce
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
@@ -133,6 +133,31 @@ def add(a, b)
133
133
  end
134
134
  ```
135
135
 
136
+ ## Getting Started
137
+
138
+ The below is a stand-alone one-file script that allows you to quickly see crystalruby in action.
139
+
140
+ ```ruby
141
+ # crystalrubytest.rb
142
+ require 'bundler/inline'
143
+
144
+ gemfile do
145
+ source 'https://rubygems.org'
146
+ gem 'crystalruby', path: '../crystalruby'
147
+ end
148
+
149
+ require 'crystalruby'
150
+
151
+ module Adder
152
+ crystalize [a: :int, b: :int] => :int
153
+ def add(a, b)
154
+ a + b
155
+ end
156
+ end
157
+
158
+ puts Adder.add(1, 2)
159
+ ```
160
+
136
161
  ## Types
137
162
 
138
163
  Currently primitive types are supported.
@@ -249,7 +274,7 @@ Remember to require these installed shards after installing them. E.g. inside `.
249
274
 
250
275
  You can edit the default paths for crystal source and library files from within the `./crystalruby.yaml` config file.
251
276
 
252
- ### Wrapping Crystal code in Ruby
277
+ ## Wrapping Crystal code in Ruby
253
278
 
254
279
  Sometimes you may want to wrap a Crystal method in Ruby, so that you can use Ruby before the Crystal code to prepare arguments, or after the Crystal code, to apply transformations to the result. A real-life example of this might be an ActionController method, where you might want to use Ruby to parse the request, perform auth etc., and then use Crystal to perform some heavy computation, before returning the result from Ruby.
255
280
  To do this, you simply pass a block to the `crystalize` method, which will serve as the Ruby entry point to the function. From within this block, you can invoke `super` to call the Crystal method, and then apply any Ruby transformations to the result.
@@ -270,7 +295,7 @@ end
270
295
  MyModule.add("1", "2")
271
296
  ```
272
297
 
273
- ### Release Builds
298
+ ## Release Builds
274
299
 
275
300
  You can control whether CrystalRuby builds in debug or release mode by setting following config option
276
301
 
@@ -303,7 +328,7 @@ CrystalRuby.compile!
303
328
 
304
329
  Then you can run this file as part of your build step, to ensure all Crystal code is compiled ahead of time.
305
330
 
306
- ### Troubleshooting
331
+ ## Troubleshooting
307
332
 
308
333
  The logic to detect when to JIT recompile is not robust and can end up in an inconsistent state. To remedy this it is useful to clear out all generated assets and build from scratch.
309
334
 
@@ -324,7 +349,6 @@ It should support escape hatches to allow it to coexist with code that performs
324
349
 
325
350
  The library is currently in its infancy. Planned additions are:
326
351
 
327
- - Replace existing checksum process, with one that combines results of inline and external crystal to more accurately detect when recompilation is necessary.
328
352
  - Simple mixin/concern that utilises `FFI::Struct` for bi-directional passing of Ruby objects and Crystal objects (by value).
329
353
  - Install command to generate a sample build script, and supports build command (which simply verifies then invokes this script)
330
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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crystalruby
4
- VERSION = "0.1.5"
4
+ VERSION = "0.1.7"
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
 
@@ -292,21 +301,15 @@ module CrystalRuby
292
301
  "crystal build --release --no-debug -o #{lib_target} #{config.crystal_main_file}"
293
302
  end
294
303
 
295
- raise "Error compiling crystal code" unless result = system(cmd)
296
-
297
- @compiled = true
298
- File.delete("#{config.crystal_codegen_dir}/index.cr") if File.exist?("#{config.crystal_codegen_dir}/index.cr")
304
+ unless result = system(cmd)
305
+ File.delete(digest_file_name) if File.exist?(digest_file_name)
306
+ raise "Error compiling crystal code"
307
+ end
299
308
  end
300
- extend FFI::Library
301
- ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
302
- attach_function :attach_rb_error_handler, [:pointer], :int
303
- const_set(:ErrorCallback, FFI::Function.new(:void, %i[string string]) do |error_type, message|
304
- error_type = error_type.to_sym
305
- is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
306
- error_type = is_exception_type ? Object.const_get(error_type) : RuntimeError
307
- raise error_type.new(message)
308
- end)
309
- 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!
310
313
  end
311
314
 
312
315
  def self.attach!
@@ -316,10 +319,23 @@ module CrystalRuby
316
319
  @attached = true
317
320
  end
318
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
+
319
338
  def self.write_function(owner, name:, body:, &compile_callback)
320
- unless defined?(@compiled)
321
- @compiled = File.exist?("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/index.cr")
322
- end
323
339
  @block_store ||= []
324
340
  @block_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
325
341
  FileUtils.mkdir_p("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crystalruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters