crystalruby 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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