crystalruby 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +23 -2
- data/README.md +41 -40
- data/crystalruby.gemspec +1 -1
- data/examples/adder/adder.rb +1 -1
- data/lib/crystalruby/adapter.rb +29 -25
- data/lib/crystalruby/function.rb +5 -5
- data/lib/crystalruby/library.rb +4 -3
- data/lib/crystalruby/reactor.rb +38 -36
- data/lib/crystalruby/source_reader.rb +30 -24
- data/lib/crystalruby/types/fixed_width/tagged_union.rb +4 -0
- data/lib/crystalruby/types/type.rb +16 -6
- data/lib/crystalruby/version.rb +1 -1
- data/lib/crystalruby.rb +1 -1
- metadata +5 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fde36eb6893b41f156d747996c94e803227552f4ef12097ead408bb823619d59
|
4
|
+
data.tar.gz: 95e9860ee479571f1f38f8f7507a7f5eb921256116894c01ce358a8c0f22dc70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b6c00ce61edf03f729bcd4f6605aa0cecb6042d9c046700a351d1ccf39b861fd5c259f7955ea02459984ee497533483adf3affbbae697fe5937ddd6ab01c625
|
7
|
+
data.tar.gz: 383e9f782c49da8751535ea8ba8ac1fd5638f17fe4d5d6b163c7b9df9ea4171794effa51df0bde51ad892ddbb58c545d542455ea973cf60fe2cb530a898e34c4
|
data/Dockerfile
CHANGED
@@ -1,16 +1,37 @@
|
|
1
|
+
# Stage 1: Use the Crystal official image to build Crystal
|
2
|
+
FROM crystallang/crystal:1.14 AS crystal-builder
|
3
|
+
|
4
|
+
# Stage 2: Use the Ruby image and copy Crystal from the previous stage
|
1
5
|
FROM ruby:3.3
|
2
6
|
|
3
7
|
MAINTAINER Wouter Coppieters <wc@pico.net.nz>
|
4
8
|
|
5
|
-
|
6
|
-
RUN
|
9
|
+
# Install necessary dependencies for Ruby and Crystal
|
10
|
+
RUN apt-get update && apt-get install -y \
|
11
|
+
curl \
|
12
|
+
gnupg2 \
|
13
|
+
software-properties-common \
|
14
|
+
build-essential \
|
15
|
+
lsb-release
|
16
|
+
|
17
|
+
# Copy Crystal binaries and libraries from the Crystal image
|
18
|
+
COPY --from=crystal-builder /usr/share/crystal /usr/share/crystal
|
19
|
+
COPY --from=crystal-builder /usr/lib/crystal /usr/lib/crystal
|
20
|
+
COPY --from=crystal-builder /usr/bin/crystal /usr/bin/crystal
|
21
|
+
COPY --from=crystal-builder /usr/bin/shards /usr/bin/shards
|
22
|
+
|
23
|
+
# Set the working directory
|
7
24
|
WORKDIR /usr/src/app
|
8
25
|
|
26
|
+
# Copy the Ruby dependencies
|
9
27
|
COPY Gemfile Gemfile.lock ./
|
10
28
|
COPY crystalruby.gemspec ./
|
11
29
|
COPY lib/crystalruby/version.rb ./lib/crystalruby/version.rb
|
12
30
|
|
31
|
+
# Install Ruby dependencies
|
13
32
|
RUN bundle install
|
33
|
+
|
34
|
+
# Copy the rest of your application
|
14
35
|
COPY . .
|
15
36
|
|
16
37
|
# Define the command to run your application
|
data/README.md
CHANGED
@@ -26,7 +26,7 @@ require 'crystalruby'
|
|
26
26
|
# The below method will be replaced by a compiled Crystal version
|
27
27
|
# linked using FFI.
|
28
28
|
|
29
|
-
|
29
|
+
crystallize
|
30
30
|
def add(a: Int32, b: Int32, returns: Int32)
|
31
31
|
a + b
|
32
32
|
end
|
@@ -42,7 +42,7 @@ E.g.
|
|
42
42
|
require 'crystalruby'
|
43
43
|
require 'benchmark'
|
44
44
|
|
45
|
-
|
45
|
+
crystallize :int32
|
46
46
|
def count_primes_upto_cr(n: Int32)
|
47
47
|
(2..n).each.count do |i|
|
48
48
|
is_prime = true
|
@@ -81,8 +81,8 @@ puts Benchmark.realtime { count_primes_upto_cr(1_000_000) }
|
|
81
81
|
_Note_: The first, unprimed run of the Crystal code will be slower, as it needs to compile the code first. The subsequent runs will be much faster.
|
82
82
|
|
83
83
|
You can call embedded crystal code, from within other embedded crystal code.
|
84
|
-
The below
|
85
|
-
Note the use of the shard command to define the Redis shard dependency of the
|
84
|
+
The below crystallized method `redis_set_and_return` calls the `redis_get` method, which is also crystallized.
|
85
|
+
Note the use of the shard command to define the Redis shard dependency of the crystallized code.
|
86
86
|
E.g.
|
87
87
|
|
88
88
|
```ruby
|
@@ -92,13 +92,13 @@ module Cache
|
|
92
92
|
|
93
93
|
shard :redis, github: 'jgaskins/redis'
|
94
94
|
|
95
|
-
|
95
|
+
crystallize :string
|
96
96
|
def redis_get(key: String)
|
97
97
|
rds = Redis::Client.new
|
98
98
|
value = rds.get(key).to_s
|
99
99
|
end
|
100
100
|
|
101
|
-
|
101
|
+
crystallize :string
|
102
102
|
def redis_set_and_return(key: String, value: String)
|
103
103
|
redis = Redis::Client.new
|
104
104
|
redis.set(key, value)
|
@@ -115,7 +115,7 @@ $ abc
|
|
115
115
|
|
116
116
|
## Syntax
|
117
117
|
|
118
|
-
To define a method that will be compiled as Crystal, you can use the `
|
118
|
+
To define a method that will be compiled as Crystal, you can use the `crystallize` method.
|
119
119
|
You must also provide types, for the parameters and return type.
|
120
120
|
|
121
121
|
### Method Signatures
|
@@ -125,24 +125,25 @@ E.g.
|
|
125
125
|
def foo(a: Int32, b: Array(Int), c: String)
|
126
126
|
```
|
127
127
|
|
128
|
-
Return types are specified using either a lambda, returning the type, as the first argument to the
|
128
|
+
Return types are specified using either a lambda, returning the type, as the first argument to the crystallize method, or the special `returns` kwarg.
|
129
129
|
|
130
130
|
E.g.
|
131
131
|
|
132
132
|
```ruby
|
133
133
|
# Returns an Int32
|
134
|
-
|
134
|
+
crystallize ->{ Int32 }
|
135
135
|
def returns_int32
|
136
136
|
3
|
137
137
|
end
|
138
138
|
|
139
139
|
# You can use the symbol shortcode for primitive types
|
140
|
-
|
140
|
+
crystallize :int32
|
141
141
|
def returns_int32
|
142
142
|
3
|
143
|
+
end
|
143
144
|
|
144
145
|
# Define the return type directly using the `returns` kwarg
|
145
|
-
|
146
|
+
crystallize
|
146
147
|
def returns_int32(returns: Int32)
|
147
148
|
3
|
148
149
|
end
|
@@ -155,7 +156,7 @@ It'll be compiled as Crystal automatically.
|
|
155
156
|
E.g.
|
156
157
|
|
157
158
|
```ruby
|
158
|
-
|
159
|
+
crystallize :int
|
159
160
|
def add(a: :int, b: :int)
|
160
161
|
puts "Adding #{a} and #{b}"
|
161
162
|
a + b
|
@@ -167,7 +168,7 @@ Some Crystal syntax is not valid Ruby, for methods of this form, we need to
|
|
167
168
|
define our functions using the `raw: true` option
|
168
169
|
|
169
170
|
```ruby
|
170
|
-
|
171
|
+
crystallize raw: true
|
171
172
|
def add(a: :int, b: :int)
|
172
173
|
<<~CRYSTAL
|
173
174
|
c = 0_u64
|
@@ -179,24 +180,24 @@ end
|
|
179
180
|
### Upgrading from version 0.2.x
|
180
181
|
|
181
182
|
#### Change in type signatures
|
182
|
-
In version 0.2.x, argument and return types were passed to the `
|
183
|
+
In version 0.2.x, argument and return types were passed to the `crystallize` method using a different syntax:
|
183
184
|
|
184
185
|
```ruby
|
185
186
|
# V <= 0.2.x
|
186
|
-
|
187
|
+
crystallize [arg1: :arg1_type , arg2: :arg2_type] => :return_type
|
187
188
|
def foo(arg1, arg2)
|
188
189
|
```
|
189
190
|
|
190
191
|
In crystalruby > 0.3.x, argument types are now passed as keyword arguments, and the return type is passed either as a keyword argument
|
191
|
-
or as the first argument to
|
192
|
+
or as the first argument to crystallize (either using symbol shorthand, or a Lambda returning a Crystal type).
|
192
193
|
|
193
194
|
```ruby
|
194
195
|
# V >= 0.3.x
|
195
|
-
|
196
|
+
crystallize :return_type
|
196
197
|
def foo(arg1: :arg1_type, arg2: :arg2_type)
|
197
198
|
|
198
199
|
# OR use the `returns` kwarg
|
199
|
-
|
200
|
+
crystallize
|
200
201
|
def foo(arg1: :arg1_type, arg2: :arg2_type, returns: :return_type)
|
201
202
|
```
|
202
203
|
|
@@ -216,7 +217,7 @@ end
|
|
216
217
|
|
217
218
|
require 'crystalruby'
|
218
219
|
|
219
|
-
|
220
|
+
crystallize :int
|
220
221
|
def add(a: :int, b: :int)
|
221
222
|
a + b
|
222
223
|
end
|
@@ -249,13 +250,13 @@ E.g.
|
|
249
250
|
```ruby
|
250
251
|
require 'crystalruby'
|
251
252
|
|
252
|
-
|
253
|
+
crystallize
|
253
254
|
def complex_argument_types(a: Int64 | Float64 | Nil, b: String | Array(Bool))
|
254
255
|
puts "Got #{a} and #{b}"
|
255
256
|
end
|
256
257
|
|
257
258
|
|
258
|
-
|
259
|
+
crystallize
|
259
260
|
def complex_return_type(returns: Int32 | String | Hash(String, Array(NamedTuple(hello: Int32)) | Time))
|
260
261
|
return {
|
261
262
|
"hello" => [
|
@@ -303,7 +304,7 @@ E.g.
|
|
303
304
|
|
304
305
|
IntArrOrBoolArr = CRType{ Array(Bool) | Array(Int32) }
|
305
306
|
|
306
|
-
|
307
|
+
crystallize
|
307
308
|
def method_with_named_types(a: IntArrOrBoolArr, returns: IntArrOrBoolArr)
|
308
309
|
return a
|
309
310
|
end
|
@@ -324,7 +325,7 @@ require 'crystalruby'
|
|
324
325
|
|
325
326
|
IntArray = CRType{ Array(Int32) }
|
326
327
|
|
327
|
-
|
328
|
+
crystallize
|
328
329
|
def array_doubler(a: IntArray)
|
329
330
|
a.map! { |x| x * 2 }
|
330
331
|
end
|
@@ -354,7 +355,7 @@ class Person < CRType{ NamedTuple(name: String, age: Int32) }
|
|
354
355
|
"Hello from Ruby. My name is #{self.name.value}"
|
355
356
|
end
|
356
357
|
|
357
|
-
|
358
|
+
crystallize :string
|
358
359
|
def greet_cr
|
359
360
|
"Hello from Crystal, My name is #{self.name.value}"
|
360
361
|
end
|
@@ -379,7 +380,7 @@ module Adder
|
|
379
380
|
a + b
|
380
381
|
end
|
381
382
|
|
382
|
-
|
383
|
+
crystallize :int32
|
383
384
|
def add_crystal(a: Int32, b: Int32)
|
384
385
|
return add_rb(a, b)
|
385
386
|
end
|
@@ -398,7 +399,7 @@ require 'crystalruby'
|
|
398
399
|
|
399
400
|
shard :kemal, github: 'kemalcr/kemal'
|
400
401
|
|
401
|
-
|
402
|
+
crystallize async: true
|
402
403
|
def start_server
|
403
404
|
Kemal.run(3000, [""])
|
404
405
|
end
|
@@ -459,7 +460,7 @@ See notes on how to define a Proc type in Crystal [here](https://crystal-lang.or
|
|
459
460
|
```ruby
|
460
461
|
require 'crystalruby'
|
461
462
|
|
462
|
-
|
463
|
+
crystallize
|
463
464
|
def yielder_cr(a: Int32, b: Int32, yield: Proc(Int32, Nil))
|
464
465
|
yield a + b
|
465
466
|
end
|
@@ -469,7 +470,7 @@ def yielder_rb(a: Int32, b: Int32, yield: Proc(Int32, Nil))
|
|
469
470
|
yield a + b
|
470
471
|
end
|
471
472
|
|
472
|
-
|
473
|
+
crystallize
|
473
474
|
def invoke_yielder_rb(a: Int32, b: Int32)
|
474
475
|
yielder_rb(a, b) do |sum|
|
475
476
|
puts sum
|
@@ -504,10 +505,10 @@ If your shard file gets out of sync with your Ruby file, you can run `crystalrub
|
|
504
505
|
## Wrapping Crystal code in Ruby
|
505
506
|
|
506
507
|
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.
|
507
|
-
To do this, you simply pass a block to the `
|
508
|
+
To do this, you simply pass a block to the `crystallize` 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.
|
508
509
|
|
509
510
|
```ruby
|
510
|
-
|
511
|
+
crystallize :int32 do |a, b|
|
511
512
|
# In this example, we perform automated conversion to integers inside Ruby.
|
512
513
|
# Then add 1 to the result of the Crystal method.
|
513
514
|
result = super(a.to_i, b.to_i)
|
@@ -524,7 +525,7 @@ puts convert_to_i_and_add_and_succ("1", "2")
|
|
524
525
|
|
525
526
|
`crystalruby` also allows you to write top-level Crystal code outside of method definitions. This can be useful for e.g. performing setup operations or initializations.
|
526
527
|
|
527
|
-
Follow these steps for a toy example of how we can use
|
528
|
+
Follow these steps for a toy example of how we can use crystallized ruby and inline chunks to expose the [crystal-redis](https://github.com/stefanwille/crystal-redis) library to Ruby.
|
528
529
|
|
529
530
|
1. Start our toy project
|
530
531
|
|
@@ -565,12 +566,12 @@ module CrystalRedis
|
|
565
566
|
end
|
566
567
|
end
|
567
568
|
|
568
|
-
|
569
|
+
crystallize
|
569
570
|
def set(key: String, value: String)
|
570
571
|
client.set(key, value)
|
571
572
|
end
|
572
573
|
|
573
|
-
|
574
|
+
crystallize :string
|
574
575
|
def get(key: String)
|
575
576
|
client.get(key).to_s
|
576
577
|
end
|
@@ -604,12 +605,12 @@ module CrystalRedis
|
|
604
605
|
end
|
605
606
|
end
|
606
607
|
|
607
|
-
|
608
|
+
crystallize
|
608
609
|
def set(key: String, value: String)
|
609
610
|
client.set(key, value)
|
610
611
|
end
|
611
612
|
|
612
|
-
|
613
|
+
crystallize :string
|
613
614
|
def get(key: String)
|
614
615
|
client.get(key).to_s
|
615
616
|
end
|
@@ -678,18 +679,18 @@ To safely utilise `crystalruby` in a multithreaded environment, `crystalruby` im
|
|
678
679
|
|
679
680
|
By default `crystalruby` methods are blocking/synchronous, this means that for blocking operations, a single crystalruby call can block the entire reactor across _all_ threads.
|
680
681
|
|
681
|
-
To allow you to benefit from Crystal's fiber based concurrency, you can use the `async: true` option on
|
682
|
+
To allow you to benefit from Crystal's fiber based concurrency, you can use the `async: true` option on crystallized ruby methods. This allows several Ruby threads to invoke Crystal code simultaneously.
|
682
683
|
|
683
684
|
E.g.
|
684
685
|
|
685
686
|
```ruby
|
686
687
|
module Sleeper
|
687
|
-
|
688
|
+
crystallize
|
688
689
|
def sleep_sync
|
689
690
|
sleep 2.seconds
|
690
691
|
end
|
691
692
|
|
692
|
-
|
693
|
+
crystallize async: true
|
693
694
|
def sleep_async
|
694
695
|
sleep 2.seconds
|
695
696
|
end
|
@@ -723,12 +724,12 @@ recompile Crystal code only when it detects changes to the embedded function or
|
|
723
724
|
## Multi-library support
|
724
725
|
|
725
726
|
Large Crystal projects are known to have long compile times. To mitigate this, `crystalruby` supports splitting your Crystal code into multiple libraries. This allows you to only recompile any libraries that have changed, rather than all crystal code within the project.
|
726
|
-
To indicate which library a piece of embedded Crystal code belongs to, you can use the `lib` option in the `
|
727
|
+
To indicate which library a piece of embedded Crystal code belongs to, you can use the `lib` option in the `crystallize` and `crystal` methods.
|
727
728
|
If the `lib` option is not provided, the code will be compiled into the default library (simply named `crystalruby`).
|
728
729
|
|
729
730
|
```ruby
|
730
731
|
module Foo
|
731
|
-
|
732
|
+
crystallize lib: "foo"
|
732
733
|
def bar
|
733
734
|
puts "Hello from Foo"
|
734
735
|
end
|
data/crystalruby.gemspec
CHANGED
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.add_dependency "digest"
|
36
36
|
spec.add_dependency "ffi"
|
37
37
|
spec.add_dependency "fileutils"
|
38
|
-
spec.add_dependency "
|
38
|
+
spec.add_dependency "prism"
|
39
39
|
# For more information and examples about making a new gem, check out our
|
40
40
|
# guide at: https://bundler.io/guides/creating_gem.html
|
41
41
|
end
|
data/examples/adder/adder.rb
CHANGED
data/lib/crystalruby/adapter.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
module CrystalRuby
|
2
2
|
module Adapter
|
3
|
-
# Use this method to annotate a Ruby method that should be
|
3
|
+
# Use this method to annotate a Ruby method that should be crystallized.
|
4
4
|
# Compilation and attachment of the method is done lazily.
|
5
5
|
# You can force compilation by calling `CrystalRuby.compile!`
|
6
|
-
# It's important that all code using
|
6
|
+
# It's important that all code using crystallized methods is
|
7
7
|
# loaded before any manual calls to compile.
|
8
8
|
#
|
9
9
|
# E.g.
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# crystallize :int32
|
12
12
|
# def add(a: :int32, b: :int32)
|
13
13
|
# a + b
|
14
14
|
# end
|
@@ -16,7 +16,7 @@ module CrystalRuby
|
|
16
16
|
# Pass `raw: true` to pass Raw crystal code to the compiler as a string instead.
|
17
17
|
# (Useful for cases where the Crystal method body is not valid Ruby)
|
18
18
|
# E.g.
|
19
|
-
#
|
19
|
+
# crystallize :int32, raw: true
|
20
20
|
# def add(a: :int32, b: :int32)
|
21
21
|
# <<~CRYSTAL
|
22
22
|
# a + b
|
@@ -36,9 +36,9 @@ module CrystalRuby
|
|
36
36
|
# @option options [Boolean] :async (false) Mark the method as async (allows multiplexing).
|
37
37
|
# @option options [String] :lib ("crystalruby") The name of the library to compile the Crystal code into.
|
38
38
|
# @option options [Proc] :block An optional wrapper Ruby block that wraps around any invocations of the crystal code
|
39
|
-
def
|
39
|
+
def crystallize( returns=:void, raw: false, async: false, lib: "crystalruby", &block)
|
40
40
|
(self == TOPLEVEL_BINDING.receiver ? Object : self).instance_eval do
|
41
|
-
@
|
41
|
+
@crystallize_next = {
|
42
42
|
raw: raw,
|
43
43
|
async: async,
|
44
44
|
returns: returns,
|
@@ -48,8 +48,11 @@ module CrystalRuby
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
# Alias for `crystallize`
|
52
|
+
alias :crystalize :crystallize
|
53
|
+
|
51
54
|
# Exposes a Ruby method to one or more Crystal libraries.
|
52
|
-
# Type annotations follow the same rules as the `
|
55
|
+
# Type annotations follow the same rules as the `crystallize` method, but are
|
53
56
|
# applied in reverse.
|
54
57
|
# @param returns The return type of the method. Optional (defaults to :void).
|
55
58
|
# @param [Hash] options The options hash.
|
@@ -73,7 +76,7 @@ module CrystalRuby
|
|
73
76
|
|
74
77
|
# Use this method to define inline Crystal code that does not need to be bound to a Ruby method.
|
75
78
|
# This is useful for defining classes, modules, performing set-up tasks etc.
|
76
|
-
# See: docs for .
|
79
|
+
# See: docs for .crystallize to understand the `raw` and `lib` parameters.
|
77
80
|
def crystal(raw: false, lib: "crystalruby", &block)
|
78
81
|
inline_crystal_body = respond_to?(:name) ? Template::InlineChunk.render(
|
79
82
|
{
|
@@ -84,7 +87,7 @@ module CrystalRuby
|
|
84
87
|
}) :
|
85
88
|
SourceReader.extract_source_from_proc(block, raw: raw)
|
86
89
|
|
87
|
-
CrystalRuby::Library[lib].
|
90
|
+
CrystalRuby::Library[lib].crystallize_chunk(
|
88
91
|
self,
|
89
92
|
Digest::MD5.hexdigest(inline_crystal_body),
|
90
93
|
inline_crystal_body
|
@@ -101,29 +104,29 @@ module CrystalRuby
|
|
101
104
|
|
102
105
|
private
|
103
106
|
|
104
|
-
# We trigger attaching of
|
105
|
-
# If a method is added after a
|
107
|
+
# We trigger attaching of crystallized instance methods here.
|
108
|
+
# If a method is added after a crystallize annotation we assume it's the target of the crystallize annotation.
|
106
109
|
# @param [Symbol] method_name The name of the method being added.
|
107
110
|
def method_added(method_name)
|
108
|
-
|
111
|
+
define_crystallized_method(instance_method(method_name)) if should_crystallize_next?
|
109
112
|
expose_ruby_method_to_crystal(instance_method(method_name)) if should_expose_next?
|
110
113
|
super
|
111
114
|
end
|
112
115
|
|
113
|
-
# We trigger attaching of
|
114
|
-
# If a method is added after a
|
116
|
+
# We trigger attaching of crystallized class methods here.
|
117
|
+
# If a method is added after a crystallize annotation we assume it's the target of the crystallize annotation.
|
115
118
|
# @note This method is called when a method is added to the singleton class of the object.
|
116
119
|
# @param [Symbol] method_name The name of the method being added.
|
117
120
|
def singleton_method_added(method_name)
|
118
|
-
|
121
|
+
define_crystallized_method(singleton_method(method_name)) if should_crystallize_next?
|
119
122
|
expose_ruby_method_to_crystal(singleton_method(method_name)) if should_expose_next?
|
120
123
|
super
|
121
124
|
end
|
122
125
|
|
123
|
-
# Helper method to determine if the next method added should be
|
124
|
-
# @return [Boolean] True if the next method added should be
|
125
|
-
def
|
126
|
-
defined?(@
|
126
|
+
# Helper method to determine if the next method added should be crystallized.
|
127
|
+
# @return [Boolean] True if the next method added should be crystallized.
|
128
|
+
def should_crystallize_next?
|
129
|
+
defined?(@crystallize_next) && @crystallize_next
|
127
130
|
end
|
128
131
|
|
129
132
|
# Helper method to determine if the next method added should be exposed to Crystal libraries.
|
@@ -161,7 +164,7 @@ module CrystalRuby
|
|
161
164
|
end
|
162
165
|
end
|
163
166
|
|
164
|
-
# We attach
|
167
|
+
# We attach crystallized class methods here.
|
165
168
|
# This function is responsible for
|
166
169
|
# - Generating the Crystal source code
|
167
170
|
# - Overwriting the method and class methods by the same name in the caller.
|
@@ -169,20 +172,21 @@ module CrystalRuby
|
|
169
172
|
# - We also optionally prepend a block (if given) to the owner, to allow Ruby code to wrap around Crystal code.
|
170
173
|
# @param [Symbol] method_name The name of the method being added.
|
171
174
|
# @param [UnboundMethod] method The method being added.
|
172
|
-
def
|
173
|
-
CrystalRuby.log_debug("Defining
|
175
|
+
def define_crystallized_method(method)
|
176
|
+
CrystalRuby.log_debug("Defining crystallized method #{name}.#{method.name}")
|
174
177
|
|
175
|
-
returns, block, async, lib, raw = @
|
176
|
-
@
|
178
|
+
returns, block, async, lib, raw = @crystallize_next.values_at(:returns, :block, :async, :lib, :raw)
|
179
|
+
@crystallize_next = nil
|
177
180
|
|
178
181
|
args, source = SourceReader.extract_args_and_source_from_method(method, raw: raw)
|
179
182
|
|
180
183
|
# We can safely claim the `yield` argument name for typing the yielded block
|
181
184
|
# because this is an illegal identifier in Crystal anyway.
|
182
185
|
args[:__yield_to] = args.delete(:yield) if args[:yield]
|
186
|
+
|
183
187
|
returns = args.delete(:returns) if args[:returns] && returns == :void
|
184
188
|
|
185
|
-
CrystalRuby::Library[lib].
|
189
|
+
CrystalRuby::Library[lib].crystallize_method(
|
186
190
|
method,
|
187
191
|
args,
|
188
192
|
returns,
|
data/lib/crystalruby/function.rb
CHANGED
@@ -37,12 +37,12 @@ module CrystalRuby
|
|
37
37
|
end
|
38
38
|
|
39
39
|
# This is where we write/overwrite the class and instance methods
|
40
|
-
# with their
|
40
|
+
# with their crystallized equivalents.
|
41
41
|
# We also perform JIT compilation and JIT attachment of the FFI functions.
|
42
42
|
# Crystalized methods can be redefined without restarting, if running in a live-reloading environment.
|
43
43
|
# If they are redefined with a different function body, the new function body
|
44
44
|
# will result in a new digest and the FFI function will be recompiled and reattached.
|
45
|
-
def
|
45
|
+
def define_crystallized_methods!(lib)
|
46
46
|
func = self
|
47
47
|
receivers = instance_method ? [owner] : [owner, owner.singleton_class]
|
48
48
|
receivers.each do |receiver|
|
@@ -109,9 +109,9 @@ module CrystalRuby
|
|
109
109
|
Reactor.schedule_work!(lib, :"register_#{name.to_s.gsub("?", "q").gsub("=", "eq").gsub("!", "bang")}_callback", @callback_func, :void, blocking: true, async: false)
|
110
110
|
end
|
111
111
|
|
112
|
-
# Attaches the
|
113
|
-
# If a wrapper block has been passed to the
|
114
|
-
# then the we also wrap the
|
112
|
+
# Attaches the crystallized FFI functions to their related Ruby modules and classes.
|
113
|
+
# If a wrapper block has been passed to the crystallize function,
|
114
|
+
# then the we also wrap the crystallized function using a prepended Module.
|
115
115
|
def attach_ffi_func!
|
116
116
|
argtypes = ffi_types
|
117
117
|
rettype = ffi_ret_type
|
data/lib/crystalruby/library.rb
CHANGED
@@ -61,7 +61,7 @@ module CrystalRuby
|
|
61
61
|
|
62
62
|
# Generates and stores a reference to a new CrystalRuby::Function
|
63
63
|
# and triggers the generation of the crystal code. (See write_chunk)
|
64
|
-
def
|
64
|
+
def crystallize_method(method, args, returns, function_body, async, &block)
|
65
65
|
CR_ATTACH_MUX.synchronize do
|
66
66
|
methods.each_value(&:unattach!)
|
67
67
|
method_key = "#{method.owner.name}/#{method.name}"
|
@@ -74,7 +74,7 @@ module CrystalRuby
|
|
74
74
|
lib: self,
|
75
75
|
&block
|
76
76
|
).tap do |func|
|
77
|
-
func.
|
77
|
+
func.define_crystallized_methods!(self)
|
78
78
|
func.register_custom_types!(self)
|
79
79
|
write_chunk(func.owner_name, method.name, func.chunk)
|
80
80
|
end
|
@@ -110,7 +110,7 @@ module CrystalRuby
|
|
110
110
|
src_dir / "shard.yml"
|
111
111
|
end
|
112
112
|
|
113
|
-
def
|
113
|
+
def crystallize_chunk(mod, chunk_name, body)
|
114
114
|
write_chunk(mod.respond_to?(:name) ? name : "main", chunk_name, body)
|
115
115
|
end
|
116
116
|
|
@@ -271,6 +271,7 @@ module CrystalRuby
|
|
271
271
|
end
|
272
272
|
|
273
273
|
def write_chunk(module_name, chunk_name, body)
|
274
|
+
module_name = module_name.gsub("::", "__MOD__")
|
274
275
|
chunks.delete_if { |chnk| chnk[:module_name] == module_name && chnk[:chunk_name] == chunk_name }
|
275
276
|
chunk = { module_name: module_name, chunk_name: chunk_name, body: body }
|
276
277
|
chunks << chunk
|
data/lib/crystalruby/reactor.rb
CHANGED
@@ -61,15 +61,16 @@ module CrystalRuby
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def await_result!
|
64
|
-
mux, cond = thread_conditions.values_at(:mux, :cond)
|
65
|
-
cond.wait(mux)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
mux, cond, result, err = thread_conditions.values_at(:mux, :cond, :result, :error)
|
65
|
+
cond.wait(mux) unless (result || err)
|
66
|
+
result, err, thread_conditions[:result], thread_conditions[:error] = thread_conditions.values_at(:result, :error)
|
67
|
+
if err
|
68
|
+
combined_backtrace = err.backtrace[0..(err.backtrace.index{|m| m.include?('call_blocking_function')} || 2) - 3] + caller[5..-1]
|
69
|
+
err.set_backtrace(combined_backtrace)
|
70
|
+
raise err
|
70
71
|
end
|
71
72
|
|
72
|
-
|
73
|
+
result
|
73
74
|
end
|
74
75
|
|
75
76
|
def halt_loop!
|
@@ -90,7 +91,10 @@ module CrystalRuby
|
|
90
91
|
@main_thread_id = Thread.current.object_id
|
91
92
|
CrystalRuby.log_debug("Starting reactor")
|
92
93
|
CrystalRuby.log_debug("CrystalRuby initialized")
|
93
|
-
|
94
|
+
while true
|
95
|
+
handler, *args = REACTOR_QUEUE.pop
|
96
|
+
send(handler, *args)
|
97
|
+
end
|
94
98
|
rescue StopReactor => e
|
95
99
|
rescue StandardError => e
|
96
100
|
CrystalRuby.log_error "Error: #{e}"
|
@@ -107,6 +111,29 @@ module CrystalRuby
|
|
107
111
|
nil
|
108
112
|
end
|
109
113
|
|
114
|
+
def invoke_async!(receiver, op_name, *args, thread_id, callback, lib)
|
115
|
+
receiver.send(op_name, *args, thread_id, callback)
|
116
|
+
yield!(lib: lib, time: 0)
|
117
|
+
end
|
118
|
+
|
119
|
+
def invoke_blocking!(receiver, op_name, *args, tvars)
|
120
|
+
tvars[:error] = nil
|
121
|
+
begin
|
122
|
+
tvars[:result] = receiver.send(op_name, *args)
|
123
|
+
rescue StopReactor => e
|
124
|
+
tvars[:cond].signal
|
125
|
+
raise
|
126
|
+
rescue StandardError => e
|
127
|
+
tvars[:error] = e
|
128
|
+
end
|
129
|
+
tvars[:cond].signal
|
130
|
+
end
|
131
|
+
|
132
|
+
def invoke_await!(receiver, op_name, *args, lib)
|
133
|
+
outstanding_jobs = receiver.send(op_name, *args)
|
134
|
+
yield!(lib: lib, time: 0) unless outstanding_jobs == 0
|
135
|
+
end
|
136
|
+
|
110
137
|
def schedule_work!(receiver, op_name, *args, return_type, blocking: true, async: true, lib: nil)
|
111
138
|
if @single_thread_mode || (Thread.current.object_id == @main_thread_id && op_name != :yield)
|
112
139
|
unless Thread.current.object_id == @main_thread_id
|
@@ -121,34 +148,9 @@ module CrystalRuby
|
|
121
148
|
tvars[:mux].synchronize do
|
122
149
|
REACTOR_QUEUE.push(
|
123
150
|
case true
|
124
|
-
when async
|
125
|
-
|
126
|
-
|
127
|
-
op_name, *args, tvars[:thread_id],
|
128
|
-
CALLBACKS_MAP[return_type]
|
129
|
-
)
|
130
|
-
yield!(lib: lib, time: 0)
|
131
|
-
}
|
132
|
-
when blocking
|
133
|
-
lambda {
|
134
|
-
tvars[:error] = nil
|
135
|
-
should_halt = false
|
136
|
-
begin
|
137
|
-
result = receiver.send(op_name, *args)
|
138
|
-
rescue StopReactor => e
|
139
|
-
should_halt = true
|
140
|
-
rescue StandardError => e
|
141
|
-
tvars[:error] = e
|
142
|
-
end
|
143
|
-
tvars[:result] = result unless tvars[:error]
|
144
|
-
tvars[:cond].signal
|
145
|
-
raise StopReactor if should_halt
|
146
|
-
}
|
147
|
-
else
|
148
|
-
lambda {
|
149
|
-
outstanding_jobs = receiver.send(op_name, *args)
|
150
|
-
yield!(lib: lib, time: 0) unless outstanding_jobs == 0
|
151
|
-
}
|
151
|
+
when async then [:invoke_async!, receiver, op_name, *args, tvars[:thread_id], CALLBACKS_MAP[return_type], lib]
|
152
|
+
when blocking then [:invoke_blocking!, receiver, op_name, *args, tvars]
|
153
|
+
else [:invoke_await!, receiver, op_name, *args, lib]
|
152
154
|
end
|
153
155
|
)
|
154
156
|
return await_result! if blocking
|
@@ -6,46 +6,47 @@ module CrystalRuby
|
|
6
6
|
def extract_expr_from_source_location(source_location)
|
7
7
|
lines = source_location.then{|f,l| IO.readlines(f)[l-1..]}
|
8
8
|
lines[0] = lines[0][/CRType.*/] if lines[0] =~ /<\s+CRType/ || lines[0] =~ /= CRType/
|
9
|
-
lines.each.with_object(
|
10
|
-
break expr_source if (
|
9
|
+
lines.each.with_object([]) do |line, expr_source|
|
10
|
+
break expr_source.join("") if (Prism.parse((expr_source << line).join("")).success?)
|
11
11
|
end
|
12
12
|
rescue
|
13
13
|
raise "Failed to extract expression from source location: #{source_location}. Ensure the file exists and the line number is correct. Extraction from a REPL is not supported"
|
14
14
|
end
|
15
15
|
|
16
|
+
def search_node(result, node_type)
|
17
|
+
result.breadth_first_search do |node|
|
18
|
+
node_type === node
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
16
22
|
# Given a proc, extracts the source code of the block passed to it
|
17
23
|
# If raw is true, the source is expected to be Raw Crystal code captured
|
18
24
|
# in a string or Heredoc literal. Otherwise the Ruby code (assumed to be valid Crystal)
|
19
25
|
# is extracted.
|
20
26
|
def extract_source_from_proc(block, raw: false)
|
21
27
|
block_source = extract_expr_from_source_location(block.source_location)
|
22
|
-
|
28
|
+
parsed_source = Prism.parse(block_source).value
|
29
|
+
|
30
|
+
node = parsed_source.statements.body[0].arguments&.arguments&.find{|x| search_node(x, Prism::StatementsNode) }
|
31
|
+
node ||= parsed_source.statements.body[0]
|
32
|
+
body_node = search_node(node, Prism::StatementsNode)
|
33
|
+
|
23
34
|
return raw ?
|
24
|
-
|
35
|
+
extract_raw_string_node(body_node) :
|
25
36
|
node_to_s(body_node)
|
26
37
|
end
|
27
38
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
@visitor.mutate("ReturnNode") do |node|
|
32
|
-
node
|
39
|
+
def extract_raw_string_node(node)
|
40
|
+
search_node(node, Prism::InterpolatedStringNode)&.parts&.map(&:unescaped)&.join("") ||
|
41
|
+
search_node(node, Prism::StringNode).unescaped
|
33
42
|
end
|
34
43
|
|
44
|
+
|
35
45
|
# Simple helper function to turn a SyntaxTree node back into a Ruby string
|
36
46
|
# The default formatter will turn a break/return of [1,2,3] into a brackless 1,2,3
|
37
47
|
# Can't have that in Crystal as it turns it into a Tuple
|
38
48
|
def node_to_s(node)
|
39
|
-
|
40
|
-
def format(quer)
|
41
|
-
first_arg = self.node.arguments.child_nodes
|
42
|
-
if first_arg[0].kind_of?(SyntaxTree::ArrayLiteral)
|
43
|
-
return format_arguments(quer, " [", "]")
|
44
|
-
end
|
45
|
-
super(quer)
|
46
|
-
end
|
47
|
-
end)
|
48
|
-
SyntaxTree::Formatter.format("", node.accept(@visitor))
|
49
|
+
node&.slice || ''
|
49
50
|
end
|
50
51
|
|
51
52
|
# Given a method, extracts the source code of the block passed to it
|
@@ -67,11 +68,16 @@ module CrystalRuby
|
|
67
68
|
# is extracted.
|
68
69
|
def extract_args_and_source_from_method(method, raw: false)
|
69
70
|
method_source = extract_expr_from_source_location(method.source_location)
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
71
|
+
parsed_source = Prism.parse(method_source).value
|
72
|
+
params = search_node(parsed_source, Prism::ParametersNode)
|
73
|
+
args = params ? params.keywords.map{|kw| [kw.name, node_to_s(kw.value)] }.to_h : {}
|
74
|
+
body_node = parsed_source.statements.body[0].body
|
75
|
+
if body_node.respond_to?(:rescue_clause) && body_node.rescue_clause
|
76
|
+
wrapped = %{begin\n#{body_node.statements.slice}\n#{body_node.rescue_clause.slice}\nend}
|
77
|
+
body_node = Prism.parse(wrapped).value
|
78
|
+
end
|
79
|
+
body = raw ? extract_raw_string_node(body_node) : node_to_s(body_node)
|
80
|
+
|
75
81
|
args.transform_values! do |type_exp|
|
76
82
|
if CrystalRuby::Typemaps::CRYSTAL_TYPE_MAP.key?(type_exp[1..-1].to_sym)
|
77
83
|
type_exp[1..-1].to_sym
|
@@ -93,6 +93,10 @@ module CrystalRuby::Types
|
|
93
93
|
union_types.map(&:native_type_expr).join(" | ")
|
94
94
|
end
|
95
95
|
|
96
|
+
define_singleton_method(:named_type_expr) do
|
97
|
+
union_types.map(&:named_type_expr).join(" | ")
|
98
|
+
end
|
99
|
+
|
96
100
|
define_singleton_method(:type_expr) do
|
97
101
|
anonymous? ? native_type_expr : name
|
98
102
|
end
|
@@ -80,6 +80,16 @@ module CrystalRuby
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
+
def self.named_type_expr
|
84
|
+
if !inner_types
|
85
|
+
"::#{name || typename}"
|
86
|
+
elsif !inner_keys
|
87
|
+
"::#{name || typename}(#{inner_types.map(&:named_type_expr).join(", ")})"
|
88
|
+
else
|
89
|
+
"::#{name || typename}(#{inner_keys.zip(inner_types).map { |k, v| "#{k}: #{v.named_type_expr}" }.join(", ")})"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
83
93
|
def self.valid_cast?(raw)
|
84
94
|
raw.is_a?(self) || convert_if.any? { |type| raw.is_a?(type) }
|
85
95
|
end
|
@@ -94,12 +104,12 @@ module CrystalRuby
|
|
94
104
|
end
|
95
105
|
|
96
106
|
def self.crystal_class_name
|
97
|
-
name ||
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
107
|
+
name || named_type_expr.split(",").join("_and_")
|
108
|
+
.split("|").join("_or_")
|
109
|
+
.split("(").join("_of_")
|
110
|
+
.gsub(/[^a-zA-Z0-9_]/, "")
|
111
|
+
.split("_")
|
112
|
+
.map(&:capitalize).join << "_#{type_digest[0..6]}"
|
103
113
|
end
|
104
114
|
|
105
115
|
def self.base_crystal_class_name
|
data/lib/crystalruby/version.rb
CHANGED
data/lib/crystalruby.rb
CHANGED
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.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
|
+
original_platform: ''
|
6
7
|
authors:
|
7
8
|
- Wouter Coppieters
|
8
|
-
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: digest
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: prism
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -147,7 +147,6 @@ metadata:
|
|
147
147
|
homepage_uri: https://github.com/wouterken/crystalruby
|
148
148
|
source_code_uri: https://github.com/wouterken/crystalruby
|
149
149
|
changelog_uri: https://github.com/wouterken/crystalruby/CHANGELOG.md
|
150
|
-
post_install_message:
|
151
150
|
rdoc_options: []
|
152
151
|
require_paths:
|
153
152
|
- lib
|
@@ -162,8 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
161
|
- !ruby/object:Gem::Version
|
163
162
|
version: '0'
|
164
163
|
requirements: []
|
165
|
-
rubygems_version: 3.
|
166
|
-
signing_key:
|
164
|
+
rubygems_version: 3.6.0.dev
|
167
165
|
specification_version: 4
|
168
166
|
summary: Embed Crystal code directly in Ruby.
|
169
167
|
test_files: []
|