crystalruby 0.1.7 → 0.1.9

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: fe68e58781b8314afb53b268ba9314472ee93c611565f7398d0ff29b445f8ece
4
- data.tar.gz: acd5e80d5709cc6f831daa39f58f74fe95642e16a4c44d25c8fab24106b47bcd
3
+ metadata.gz: ca096d0563b6201c3ef4f084fff3304cfa212fda88e81085580b2de5ff10536f
4
+ data.tar.gz: ccc68dc96c30f973c9bafcbaec75a2461bbaa275e32913a0781c4cc5488f034f
5
5
  SHA512:
6
- metadata.gz: 85516d1372b2467147cb5c61e6d33dc27e6e2c0d11876b32dbee1007152096fd54551926177b56e066fa9bc8e4329b8ef13d133280305018428775267eb4a24e
7
- data.tar.gz: 207284740bef726d1a8c5df35b21ebe33b8611725363acac451bbadc3b7c714585513027be2398dd4cdd6ff94761c00585c94546fb198a4b90047d91edbef2ce
6
+ metadata.gz: 134e9888d563673d78dc0b9075a239ee3bab6c4a07eb63ff903c748b97684ec87e9bb33011e9f276504f855edd2a25c29860ebc626aa181f74350f68ace8b9e8
7
+ data.tar.gz: f3f7a25d12459178e5c8d3cb6a46cfa0e375f65099cdcde15c834a1ba0872198b8475919ec7e5472fa10529bfb71ede67e0edfb88105be5eb220226f2d0ba4ad
data/README.md CHANGED
@@ -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'
@@ -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
@@ -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,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.7"
4
+ VERSION = "0.1.9"
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]
@@ -226,6 +239,7 @@ module CrystalRuby
226
239
  extend FFI::Library
227
240
  ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
228
241
  attach_function "init!", :init, [:pointer], :void
242
+ send(:remove_const, :ErrorCallback) if defined?(ErrorCallback)
229
243
  const_set(:ErrorCallback, FFI::Function.new(:void, %i[string string]) do |error_type, message|
230
244
  error_type = error_type.to_sym
231
245
  is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
@@ -314,7 +328,7 @@ module CrystalRuby
314
328
 
315
329
  def self.attach!
316
330
  @block_store.each do |function|
317
- function[:compile_callback].call
331
+ function[:compile_callback]&.call
318
332
  end
319
333
  @attached = true
320
334
  end
@@ -335,7 +349,7 @@ module CrystalRuby
335
349
  File.read(digest_file_name) if File.exist?(digest_file_name)
336
350
  end
337
351
 
338
- def self.write_function(owner, name:, body:, &compile_callback)
352
+ def self.write_chunk(owner, body:, name: Digest::MD5.hexdigest(body), &compile_callback)
339
353
  @block_store ||= []
340
354
  @block_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
341
355
  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.7
4
+ version: 0.1.9
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
@@ -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