crystalruby 0.1.8 → 0.1.10

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: 95d9eecc4d47a8d9f83417779c6174eea95ebeea593e5127a89b6cfbc7884664
4
- data.tar.gz: b4f241f826e4f2b6c704bda0fd01c201e79a9bc26027bd4702717fd371687650
3
+ metadata.gz: '0499023eb26e35e6db77566876a96357202c0e6cdfafbb49d2aaa67e070c38cd'
4
+ data.tar.gz: 4e5f185097b4ad5091b079fc87b43d8d950119c7fbe659a7d76fe473d8cc7cd9
5
5
  SHA512:
6
- metadata.gz: d98f1e4a671679cfb8d9da45298954e01def202802bd5a64db9d0d9cba08de0776971d031648642f402fe0310d741dc6b2d163253884ad25082298226c1be1a0
7
- data.tar.gz: 141658885f763ca3088a7058131e7227434920d6213e755e21f85d6bb4d68a062a2a3163127f4e9f0db03a8c9a27a0549837714b38789647028075cc3b77660c
6
+ metadata.gz: 0d27665a59a9ecc7345bcca13c5e057d71166684350d54bc158bcefe82655025fc6a8c7fd08069f92c17abe849ad465ee995f223b32391e3f49f2ff7f30c8b92
7
+ data.tar.gz: ee56f2aead72e533e989b3e80c9550a65fcf78b2677dc070f697d656b1293eefecf0782fa43ac45255fced794a6a54040c32e21571723a1208b3d27babe71851
data/README.md CHANGED
@@ -87,7 +87,7 @@ module Cache
87
87
  end
88
88
 
89
89
  crystalize [key: :string, value: :string] => :string
90
- def redis_set_and_return(key)
90
+ def redis_set_and_return(key, value)
91
91
  redis = Redis::Client.new
92
92
  redis.set(key, value)
93
93
  Cache.redis_get(key)
@@ -295,6 +295,131 @@ end
295
295
  MyModule.add("1", "2")
296
296
  ```
297
297
 
298
+ ## Inline Chunks
299
+
300
+ `crystalruby` also allows you to write inline Crystal code that does not require binding to Ruby. This can be useful for e.g. performing setup or teardown operations.
301
+
302
+ Follow these steps for a toy example of how we can use crystalized ruby and inline chunks to expose the [crystal-redis](https://github.com/stefanwille/crystal-redis) library to Ruby.
303
+
304
+ 1. Start our toy project
305
+
306
+ ```bash
307
+ mkdir crystalredis
308
+ cd crystalredis
309
+ bundle init
310
+ ```
311
+
312
+ 2. Add dependencies to our Gemfile and run `bundle install`
313
+
314
+ ```ruby
315
+ # frozen_string_literal: true
316
+
317
+ source "https://rubygems.org"
318
+
319
+ gem 'crystalruby'
320
+
321
+ # Let's see if performance is comparable to that of the redis gem.
322
+ gem 'benchmark-ips'
323
+ gem 'redis'
324
+ ```
325
+
326
+ 3. Write our Redis client
327
+
328
+ ```ruby
329
+ # Filename: crystalredis.rb
330
+ require 'crystalruby'
331
+
332
+ module CrystalRedis
333
+
334
+ crystal do
335
+ CLIENT = Redis.new
336
+ def self.client
337
+ CLIENT
338
+ end
339
+ end
340
+
341
+ crystalize [key: :string, value: :string] => :void
342
+ def set(key, value)
343
+ client.set(key, value)
344
+ end
345
+
346
+ crystalize [key: :string] => :string
347
+ def get(key)
348
+ client.get(key).to_s
349
+ end
350
+ end
351
+ ```
352
+
353
+ 4. Load the modules (without running them) to generate our Crystal project skeleton.
354
+
355
+ ```bash
356
+ bundle exec ruby crystalredis.rb
357
+ ```
358
+
359
+ 5. Add the missing Redis dependency to our shard.yml
360
+
361
+ ```yaml
362
+ # filename: crystalruby/src/shard.yml
363
+ dependencies:
364
+ redis:
365
+ github: stefanwille/crystal-redis
366
+ ```
367
+
368
+ ```ruby
369
+ # filename: main.cr
370
+ require "redis"
371
+ require "./generated/index"
372
+ ```
373
+
374
+ ```bash
375
+ bundle exec crystalruby install
376
+ ```
377
+
378
+ 6. Compile and benchmark our new module in Ruby
379
+
380
+ ```ruby
381
+ # Filename: benchmark.rb
382
+ # Let's compare the performance of our CrystalRedis module to the Ruby Redis gem
383
+ require_relative "crystalredis"
384
+ require 'redis'
385
+ require 'benchmark/ips'
386
+
387
+ Benchmark.ips do |x|
388
+ rbredis = Redis.new
389
+
390
+ x.report(:crredis) do
391
+ CrystalRedis.set("hello", "world")
392
+ CrystalRedis.get("hello")
393
+ end
394
+
395
+ x.report(:rbredis) do
396
+ rbredis.set("hello", "world")
397
+ rbredis.get("hello")
398
+ end
399
+ end
400
+ ```
401
+
402
+ 7. Run the benchmark
403
+
404
+ ```bash
405
+ $ bundle exec ruby benchmark.rb
406
+ ```
407
+
408
+ ### Output
409
+
410
+ ```bash
411
+
412
+ #crystalredis wins! (Warm up during first run will be slow for crredis, due to first compilation)
413
+
414
+ ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22]
415
+ Warming up --------------------------------------
416
+ crredis 1.946k i/100ms
417
+ rbredis 1.749k i/100ms
418
+ Calculating -------------------------------------
419
+ crredis 22.319k (± 1.7%) i/s - 112.868k in 5.058448s
420
+ rbredis 16.861k (± 9.1%) i/s - 83.952k in 5.024941s
421
+ ```
422
+
298
423
  ## Release Builds
299
424
 
300
425
  You can control whether CrystalRuby builds in debug or release mode by setting following config option
@@ -340,7 +465,7 @@ bundle exec crystalruby clean
340
465
 
341
466
  ## Design Goals
342
467
 
343
- `crystalruby`'s primary purpose is provide ergonomic access to Crystal from Ruby, over FFI.
468
+ `crystalruby`'s primary purpose is to provide ergonomic access to Crystal from Ruby, over FFI.
344
469
  For simple usage, advanced knowledge of Crystal should not be required.
345
470
 
346
471
  However, the abstraction it provides should remain simple, transparent, and easy to hack on and it should not preclude users from supplementing its capabilities with a more direct integration using ffi primtives.
@@ -1,7 +1,7 @@
1
1
  module CrystalRuby
2
2
  module Template
3
3
  Dir[File.join(File.dirname(__FILE__), "templates", "*.cr")].each do |file|
4
- template_name = File.basename(file, File.extname(file)).capitalize
4
+ template_name = File.basename(file, File.extname(file)).split("_").map(&:capitalize).join
5
5
  const_set(template_name, File.read(file))
6
6
  end
7
7
 
@@ -0,0 +1,6 @@
1
+ # This is the template used for writing inline chunks of Crystal code without direct
2
+ # Ruby integration
3
+
4
+ module %{module_name}
5
+ %{body}
6
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crystalruby
4
- VERSION = "0.1.8"
4
+ VERSION = "0.1.10"
5
5
  end
data/lib/crystalruby.rb CHANGED
@@ -22,6 +22,19 @@ module CrystalRuby
22
22
  @crystalize_next = { raw: type.to_sym == :raw, args: args, returns: returns, block: block }
23
23
  end
24
24
 
25
+ def crystal(type = :src, &block)
26
+ inline_crystal_body = Template.render(
27
+ Template::InlineChunk,
28
+ {
29
+ module_name: name,
30
+ body: block.source.lines[
31
+ type == :raw ? 2...-2 : 1...-1
32
+ ].join("\n")
33
+ }
34
+ )
35
+ CrystalRuby.write_chunk(self, body: inline_crystal_body)
36
+ end
37
+
25
38
  def crtype(&block)
26
39
  TypeBuilder.with_injected_type_dsl(self) do
27
40
  TypeBuilder.build(&block)
@@ -56,7 +69,7 @@ module CrystalRuby
56
69
  args ||= {}
57
70
  @crystalize_next = nil
58
71
  function = build_function(self, method_name, args, returns, function_body)
59
- CrystalRuby.write_function(self, name: function[:name], body: function[:body]) do
72
+ CrystalRuby.write_chunk(self, name: function[:name], body: function[:body]) do
60
73
  extend FFI::Library
61
74
  ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
62
75
  attach_function method_name, fname, function[:ffi_types], function[:return_ffi_type]
@@ -73,7 +86,10 @@ module CrystalRuby
73
86
  receiver.prepend(Module.new do
74
87
  define_method(method_name) do |*args|
75
88
  CrystalRuby.compile! unless CrystalRuby.compiled?
76
- CrystalRuby.attach! unless CrystalRuby.attached?
89
+ unless CrystalRuby.attached?
90
+ CrystalRuby.attach!
91
+ return send(method_name, *args) if block
92
+ end
77
93
  args.each_with_index do |arg, i|
78
94
  args[i] = function[:arg_maps][i][arg] if function[:arg_maps][i]
79
95
  end
@@ -226,6 +242,7 @@ module CrystalRuby
226
242
  extend FFI::Library
227
243
  ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
228
244
  attach_function "init!", :init, [:pointer], :void
245
+ send(:remove_const, :ErrorCallback) if defined?(ErrorCallback)
229
246
  const_set(:ErrorCallback, FFI::Function.new(:void, %i[string string]) do |error_type, message|
230
247
  error_type = error_type.to_sym
231
248
  is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
@@ -314,7 +331,7 @@ module CrystalRuby
314
331
 
315
332
  def self.attach!
316
333
  @block_store.each do |function|
317
- function[:compile_callback].call
334
+ function[:compile_callback]&.call
318
335
  end
319
336
  @attached = true
320
337
  end
@@ -335,7 +352,7 @@ module CrystalRuby
335
352
  File.read(digest_file_name) if File.exist?(digest_file_name)
336
353
  end
337
354
 
338
- def self.write_function(owner, name:, body:, &compile_callback)
355
+ def self.write_chunk(owner, body:, name: Digest::MD5.hexdigest(body), &compile_callback)
339
356
  @block_store ||= []
340
357
  @block_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
341
358
  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.8
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
@@ -87,6 +87,7 @@ files:
87
87
  - lib/crystalruby/template.rb
88
88
  - lib/crystalruby/templates/function.cr
89
89
  - lib/crystalruby/templates/index.cr
90
+ - lib/crystalruby/templates/inline_chunk.cr
90
91
  - lib/crystalruby/typebuilder.rb
91
92
  - lib/crystalruby/typemaps.rb
92
93
  - lib/crystalruby/types.rb