crystalruby 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +131 -8
- data/crystalruby.gemspec +5 -5
- data/exe/crystalruby +1 -0
- data/lib/crystalruby/config.rb +12 -7
- data/lib/crystalruby/template.rb +13 -0
- data/lib/crystalruby/templates/function.cr +20 -0
- data/lib/crystalruby/templates/index.cr +38 -0
- data/lib/crystalruby/typebuilder.rb +179 -0
- data/lib/crystalruby/typemaps.rb +26 -0
- data/lib/crystalruby/types/array.rb +14 -0
- data/lib/crystalruby/types/bool.rb +3 -0
- data/lib/crystalruby/types/hash.rb +15 -0
- data/lib/crystalruby/types/named_tuple.rb +25 -0
- data/lib/crystalruby/types/nil.rb +3 -0
- data/lib/crystalruby/types/numbers.rb +5 -0
- data/lib/crystalruby/types/string.rb +3 -0
- data/lib/crystalruby/types/symbol.rb +3 -0
- data/lib/crystalruby/types/time.rb +6 -0
- data/lib/crystalruby/types/tuple.rb +15 -0
- data/lib/crystalruby/types/type.rb +66 -0
- data/lib/crystalruby/types/type_serializer/json.rb +40 -0
- data/lib/crystalruby/types/type_serializer.rb +37 -0
- data/lib/crystalruby/types/typedef.rb +55 -0
- data/lib/crystalruby/types/union_type.rb +37 -0
- data/lib/crystalruby/types.rb +18 -0
- data/lib/crystalruby/version.rb +1 -1
- data/lib/crystalruby.rb +210 -93
- metadata +25 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 157d03e574c1ef5e06073e71c29e259741c26373113f2ae51c692b4aaef429f9
|
4
|
+
data.tar.gz: 94c1f670b52a39b78c961a3050c2587c540a1cb1593c30e87d6bd05d90a4d736
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b7972639977a290fd9a42b737edd1f3eec28a240a6e0e773619190bf646ec0707520d317ddf6e6152f507753b16fa66db0d2903c18fd23e58ebce4422e02555
|
7
|
+
data.tar.gz: bc3d03de05c21a14a6d6d9b6de76ff04efcd660999c0dd2c166cc306707d0f93be01053e34ba8a531c9e0a9154e887bb1c6796b141564e290e25d537eeab88b4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.1.3] - 2024-04-10
|
4
|
+
|
5
|
+
- Support exceptions thrown in Crystal being caught in Ruby
|
6
|
+
- Support complex Ruby type passing (while preserving type checking), using `JSON` as serialization format.
|
7
|
+
|
3
8
|
## [0.1.0] - 2024-04-07
|
4
9
|
|
5
10
|
- Initial release
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# crystalruby
|
2
2
|
|
3
|
-
|
3
|
+
`crystalruby` is a gem that allows you to write Crystal code, inlined in Ruby. All you need is a modern crystal compiler installed on your system.
|
4
4
|
|
5
5
|
You can then turn simple methods into Crystal methods as easily as demonstrated below:
|
6
6
|
|
@@ -120,8 +120,10 @@ end
|
|
120
120
|
|
121
121
|
## Types
|
122
122
|
|
123
|
-
Currently
|
124
|
-
|
123
|
+
Currently primitive types are supported.
|
124
|
+
Composite types are supported using JSON serialization.
|
125
|
+
C-Structures are a WIP.
|
126
|
+
To see the list of currently supported primitive type mappings of FFI types to crystal types, you can check: `CrystalRuby::Typemaps::CRYSTAL_TYPE_MAP`
|
125
127
|
E.g.
|
126
128
|
|
127
129
|
```ruby
|
@@ -151,7 +153,54 @@ CrystalRuby::Typemaps::CRYSTAL_TYPE_MAP
|
|
151
153
|
:string=>"String"}
|
152
154
|
```
|
153
155
|
|
154
|
-
|
156
|
+
## Composite Types (using JSON serialization)
|
157
|
+
|
158
|
+
The library allows you to pass complex nested structures using JSON as a serialization format.
|
159
|
+
The type signatures for composite types can use ordinary Crystal Type syntax.
|
160
|
+
Type conversion is applied automatically.
|
161
|
+
|
162
|
+
E.g.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
crystalize [a: json{ Int64 | Float64 | Nil }, b: json{ String | Array(Bool) } ] => :void
|
166
|
+
def complex_argument_types
|
167
|
+
puts "Got #{a} and #{b}"
|
168
|
+
end
|
169
|
+
|
170
|
+
crystalize [] => json{ Int32 | String | Hash(String, Array(NamedTuple(hello: Int32)) | Time)}
|
171
|
+
def complex_return_type
|
172
|
+
return {
|
173
|
+
"hello" => [
|
174
|
+
{
|
175
|
+
hello: 1,
|
176
|
+
},
|
177
|
+
],
|
178
|
+
"world" => Time.utc
|
179
|
+
}
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
Type signatures validations are applied to both arguments and return types.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
[1] pry(main)> Foo.complex_argument_types(nil, "test")
|
187
|
+
Got and test
|
188
|
+
=> nil
|
189
|
+
|
190
|
+
[2] pry(main)> Foo.complex_argument_types(88, [true, false, true])
|
191
|
+
Got 88 and [true, false, true]
|
192
|
+
=> nil
|
193
|
+
|
194
|
+
[3] pry(main)> Foo.complex_argument_types(88, [true, false, 88])
|
195
|
+
ArgumentError: Expected Bool but was Int at line 1, column 15
|
196
|
+
from crystalruby.rb:303:in `block in compile!'
|
197
|
+
```
|
198
|
+
|
199
|
+
## Exceptions
|
200
|
+
|
201
|
+
Exceptions thrown in Crystal code can be caught in Ruby.
|
202
|
+
|
203
|
+
## Installing shards and writing non-embedded Crystal code
|
155
204
|
|
156
205
|
You can use any Crystal shards and write ordinary, stand-alone Crystal code.
|
157
206
|
|
@@ -169,6 +218,60 @@ Remember to require these installed shards after installing them. E.g. inside `.
|
|
169
218
|
|
170
219
|
You can edit the default paths for crystal source and library files from within the `./crystalruby.yaml` config file.
|
171
220
|
|
221
|
+
### Wrapping Crystal code in Ruby
|
222
|
+
|
223
|
+
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.
|
224
|
+
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.
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
module MyModule
|
228
|
+
crystalize [a: :int32, b: :int32] => :int32 do |a, b|
|
229
|
+
# In this example, we perform automated conversion to integers inside Ruby.
|
230
|
+
# Then add 1 to the result of the Crystal method.
|
231
|
+
result = super(a.to_i, b.to_i)
|
232
|
+
result + 1
|
233
|
+
end
|
234
|
+
def add(a, b)
|
235
|
+
a + b
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
MyModule.add("1", "2")
|
240
|
+
```
|
241
|
+
|
242
|
+
### Release Builds
|
243
|
+
|
244
|
+
You can control whether CrystalRuby builds in debug or release mode by setting following config option
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
CrystalRuby.configure do |config|
|
248
|
+
config.debug = false
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
By default, Crystal code is only JIT compiled. In production, you likely want to compile the Crystal code ahead of time. To do this, you can create a dedicated file which
|
253
|
+
|
254
|
+
- Preloads all files Ruby code with embedded crystal
|
255
|
+
- Forces compilation.
|
256
|
+
|
257
|
+
E.g.
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
# E.g. crystalruby_build.rb
|
261
|
+
require "crystalruby"
|
262
|
+
|
263
|
+
CrystalRuby.configure do |config|
|
264
|
+
config.debug = false
|
265
|
+
end
|
266
|
+
|
267
|
+
require_relative "foo"
|
268
|
+
require_relative "bar"
|
269
|
+
|
270
|
+
CrystalRuby.compile!
|
271
|
+
```
|
272
|
+
|
273
|
+
Then you can run this file as part of your build step, to ensure all Crystal code is compiled ahead of time.
|
274
|
+
|
172
275
|
### Troubleshooting
|
173
276
|
|
174
277
|
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.
|
@@ -179,6 +282,26 @@ To do this execute:
|
|
179
282
|
bundle exec crystalruby clean
|
180
283
|
```
|
181
284
|
|
285
|
+
## Design Goals
|
286
|
+
|
287
|
+
`crystalruby`'s primary purpose is provide ergonomic access to Crystal from Ruby, over FFI.
|
288
|
+
For simple usage, advanced knowledge of Crystal should not be required.
|
289
|
+
|
290
|
+
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.
|
291
|
+
|
292
|
+
It should support escape hatches to allow it to coexist with code that performs a more direct [FFI](https://github.com/ffi/ffi) integration to implement advanced functionality not supported by `crystalruby`.
|
293
|
+
|
294
|
+
The library is currently in its infancy. Planned additions are:
|
295
|
+
|
296
|
+
- Replace existing checksum process, with one that combines results of inline and external crystal to more accurately detect when recompilation is necessary.
|
297
|
+
- Support for automatic serialization of nested data structures (holding _ONLY_ primitives), using JSON as our serialization protocol (prioritizing portability over raw serialization performance. JSON generation and parsing is bundled into the stdlib in both languages).
|
298
|
+
- Simple mixin/concern that utilises `FFI::Struct` for bi-directional passing of Ruby objects and Crystal objects (by value).
|
299
|
+
- Install command to generate a sample build script, and supports build command (which simply verifies then invokes this script)
|
300
|
+
- Call Ruby from Crystal using FFI callbacks (implement `.expose_to_crystal`)
|
301
|
+
- Support long-lived synchronized objects (through use of synchronized memory arena to prevent GC).
|
302
|
+
- Support for passing `crystalruby` types by reference (need to contend with GC).
|
303
|
+
- Explore mechanisms to safely expose true parallelism using [FFI over Ractors](https://github.com/ffi/ffi/wiki/Ractors)
|
304
|
+
|
182
305
|
## Installation
|
183
306
|
|
184
307
|
To get started, add this line to your application's Gemfile:
|
@@ -199,7 +322,7 @@ Or install it yourself as:
|
|
199
322
|
$ gem install crystalruby
|
200
323
|
```
|
201
324
|
|
202
|
-
|
325
|
+
`crystalruby` requires some basic initialization options inside a crystalruby.yaml file in the root of your project.
|
203
326
|
You can run `crystalruby init` to generate a configuration file with sane defaults.
|
204
327
|
|
205
328
|
```bash
|
@@ -221,7 +344,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
221
344
|
|
222
345
|
## Contributing
|
223
346
|
|
224
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
347
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wouterken/crystalruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/wouterken/crystalruby/blob/master/CODE_OF_CONDUCT.md).
|
225
348
|
|
226
349
|
## License
|
227
350
|
|
@@ -229,4 +352,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
229
352
|
|
230
353
|
## Code of Conduct
|
231
354
|
|
232
|
-
Everyone interacting in the
|
355
|
+
Everyone interacting in the `crystalruby` project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/wouterken/crystalruby/blob/master/CODE_OF_CONDUCT.md).
|
data/crystalruby.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = "Embed Crystal code directly in Ruby."
|
13
13
|
spec.homepage = "https://github.com/wouterken/crystalruby"
|
14
14
|
spec.license = "MIT"
|
15
|
-
spec.required_ruby_version = ">=
|
15
|
+
spec.required_ruby_version = ">= 2.7.2"
|
16
16
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
18
|
spec.metadata["source_code_uri"] = spec.homepage
|
@@ -32,10 +32,10 @@ Gem::Specification.new do |spec|
|
|
32
32
|
|
33
33
|
# Uncomment to register a new dependency of your gem
|
34
34
|
# spec.add_dependency "example-gem", "~> 1.0"
|
35
|
-
spec.add_dependency
|
36
|
-
spec.add_dependency
|
37
|
-
spec.add_dependency
|
38
|
-
spec.add_dependency
|
35
|
+
spec.add_dependency "digest"
|
36
|
+
spec.add_dependency "ffi"
|
37
|
+
spec.add_dependency "fileutils"
|
38
|
+
spec.add_dependency "method_source"
|
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/exe/crystalruby
CHANGED
data/lib/crystalruby/config.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "singleton"
|
2
|
+
require "yaml"
|
3
3
|
|
4
4
|
module CrystalRuby
|
5
5
|
def self.config
|
@@ -9,15 +9,20 @@ module CrystalRuby
|
|
9
9
|
# Define a nested Config class
|
10
10
|
class Config
|
11
11
|
include Singleton
|
12
|
-
attr_accessor :debug, :crystal_src_dir, :crystal_lib_dir, :crystal_main_file, :crystal_lib_name
|
12
|
+
attr_accessor :debug, :crystal_src_dir, :crystal_lib_dir, :crystal_main_file, :crystal_lib_name, :crystal_codegen_dir
|
13
13
|
|
14
14
|
def initialize
|
15
|
-
# Set default configuration options
|
16
15
|
@debug = true
|
17
|
-
if File.exist?("crystalruby.yaml")
|
18
|
-
|
19
|
-
|
16
|
+
config = if File.exist?("crystalruby.yaml")
|
17
|
+
YAML.safe_load(IO.read("crystalruby.yaml")) rescue {}
|
18
|
+
else
|
19
|
+
{}
|
20
20
|
end
|
21
|
+
@crystal_src_dir = config.fetch("crystal_src_dir", "./crystalruby/src")
|
22
|
+
@crystal_lib_dir = config.fetch("crystal_lib_dir", "./crystalruby/lib")
|
23
|
+
@crystal_main_file = config.fetch("crystal_main_file", "main.cr")
|
24
|
+
@crystal_lib_name = config.fetch("crystal_lib_name", "crlib")
|
25
|
+
@crystal_codegen_dir = config.fetch("crystal_codegen_dir", "generated")
|
21
26
|
end
|
22
27
|
end
|
23
28
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'pry-byebug'
|
2
|
+
module CrystalRuby
|
3
|
+
module Template
|
4
|
+
Dir[File.join(File.dirname(__FILE__), "templates", "*.cr")].each do |file|
|
5
|
+
template_name = File.basename(file, File.extname(file)).capitalize
|
6
|
+
const_set(template_name, File.read(file))
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.render(template, context)
|
10
|
+
template % context
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module %{module_name}
|
2
|
+
def self.%{fn_name}(%{fn_args}) : %{fn_ret_type}
|
3
|
+
%{fn_body}
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
fun %{lib_fn_name}(%{lib_fn_args}): %{lib_fn_ret_type}
|
8
|
+
begin
|
9
|
+
%{convert_lib_args}
|
10
|
+
begin
|
11
|
+
return_value = %{module_name}.%{fn_name}(%{arg_names})
|
12
|
+
return %{convert_return_type}
|
13
|
+
rescue ex
|
14
|
+
CrystalRuby.report_error("RuntimeError", ex.message.to_s)
|
15
|
+
end
|
16
|
+
rescue ex
|
17
|
+
CrystalRuby.report_error("ArgumentError", ex.message.to_s)
|
18
|
+
end
|
19
|
+
return %{error_value}
|
20
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
FAKE_ARG = "crystal"
|
2
|
+
alias Callback = (Pointer(UInt8), Pointer(UInt8) -> Void)
|
3
|
+
|
4
|
+
module CrystalRuby
|
5
|
+
@@initialized = false
|
6
|
+
def self.init
|
7
|
+
if @@initialized
|
8
|
+
return
|
9
|
+
end
|
10
|
+
@@initialized = true
|
11
|
+
GC.init
|
12
|
+
ptr = FAKE_ARG.to_unsafe
|
13
|
+
LibCrystalMain.__crystal_main(1, pointerof(ptr))
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.attach_rb_error_handler(cb : Callback)
|
17
|
+
@@rb_error_handler = cb
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.report_error(error_type : String, str : String)
|
21
|
+
handler = @@rb_error_handler
|
22
|
+
if handler
|
23
|
+
handler.call(error_type.to_unsafe, str.to_unsafe)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
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)
|
35
|
+
end
|
36
|
+
|
37
|
+
%{type_modules}
|
38
|
+
%{requires}
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require_relative "types"
|
2
|
+
|
3
|
+
module CrystalRuby
|
4
|
+
module TypeBuilder
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def with_injected_type_dsl(context, &block)
|
8
|
+
with_constants(context) do
|
9
|
+
with_methods(context, &block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def with_methods(context)
|
14
|
+
restores = []
|
15
|
+
%i[Array Hash NamedTuple Tuple].each do |method_name|
|
16
|
+
old_method = begin
|
17
|
+
context.instance_method(method_name)
|
18
|
+
rescue StandardError
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
restores << [context, method_name, old_method]
|
22
|
+
context.define_singleton_method(method_name) do |*args|
|
23
|
+
Types.send(method_name, *args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
yield
|
27
|
+
ensure
|
28
|
+
restores.each do |context, method_name, old_method|
|
29
|
+
context.define_singleton_method(method_name, old_method) if old_method
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_constants(context)
|
34
|
+
previous_const_pairs = CrystalRuby::Types.constants.map do |type|
|
35
|
+
[type, begin
|
36
|
+
context.const_get(type)
|
37
|
+
rescue StandardError
|
38
|
+
nil
|
39
|
+
end]
|
40
|
+
end
|
41
|
+
CrystalRuby::Types.constants.each do |type|
|
42
|
+
begin
|
43
|
+
context.send(:remove_const, type)
|
44
|
+
rescue StandardError
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
context.const_set(type, CrystalRuby::Types.const_get(type))
|
48
|
+
end
|
49
|
+
yield
|
50
|
+
ensure
|
51
|
+
previous_const_pairs.each do |const_name, const_value|
|
52
|
+
begin
|
53
|
+
context.send(:remove_const, const_name)
|
54
|
+
rescue StandardError
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
context.const_set(const_name, const_value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def build
|
62
|
+
result = yield
|
63
|
+
Types::Type.validate!(result)
|
64
|
+
Types::Typedef(result)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# class UnionType
|
70
|
+
# attr_reader :inner
|
71
|
+
|
72
|
+
# def initialize(*inner)
|
73
|
+
# @inner = inner
|
74
|
+
# end
|
75
|
+
|
76
|
+
# def |(other)
|
77
|
+
# UnionType.new(*inner, *other.inner)
|
78
|
+
# end
|
79
|
+
|
80
|
+
# def inspect
|
81
|
+
# elements = inner.map(&:inspect).join(" | ")
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
|
85
|
+
# class Type
|
86
|
+
# attr_reader :inner, :contains
|
87
|
+
|
88
|
+
# def initialize(name)
|
89
|
+
# @name = name
|
90
|
+
# @contains = contains
|
91
|
+
# @inner = [self]
|
92
|
+
# end
|
93
|
+
|
94
|
+
# def |(other)
|
95
|
+
# UnionType.new(*inner, *other.inner)
|
96
|
+
# end
|
97
|
+
|
98
|
+
# def inspect
|
99
|
+
# if @contains
|
100
|
+
# "#{@name}(#{@contains.inspect})"
|
101
|
+
# else
|
102
|
+
# @name
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
|
107
|
+
# module_function
|
108
|
+
|
109
|
+
# %w[
|
110
|
+
# Bool Uint8 Uint16 Uint32 Uint64 Int8 Int16 Int32 Int64 Float32 Float64 String Time Symbol
|
111
|
+
# Null
|
112
|
+
# ].map do |t|
|
113
|
+
# cls = Class.new(Type)
|
114
|
+
# const_set(t, cls)
|
115
|
+
# define_method(t.downcase) do
|
116
|
+
# cls.new(t)
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
|
120
|
+
# def build(&blk)
|
121
|
+
# instance_exec(&blk)
|
122
|
+
# end
|
123
|
+
|
124
|
+
# def hash(key_type, value_type)
|
125
|
+
# Hash.new(key_type, value_type)
|
126
|
+
# end
|
127
|
+
|
128
|
+
# def array(type)
|
129
|
+
# Array.new(type)
|
130
|
+
# end
|
131
|
+
|
132
|
+
# def tuple(*types)
|
133
|
+
# Tuple.new(*types)
|
134
|
+
# end
|
135
|
+
|
136
|
+
# def named_tuple(type_hash)
|
137
|
+
# NamedTuple.new(type_hash)
|
138
|
+
# end
|
139
|
+
|
140
|
+
# def NamedTuple(type_hash)
|
141
|
+
# NamedTuple.new(type_hash)
|
142
|
+
# end
|
143
|
+
|
144
|
+
# class Hash < Type
|
145
|
+
# HASH_KEY_TYPES = %w[String Symbol].freeze
|
146
|
+
# def initialize(key_type, value_type)
|
147
|
+
# super("Hash")
|
148
|
+
# @key_type = key_type
|
149
|
+
# @value_type = value_type
|
150
|
+
# raise "Invalid key type" unless [Uint8, Uint16, Uint32, Uint64, Int8, Int16, Int32, Int64,
|
151
|
+
# String].include?(key_type)
|
152
|
+
# raise "Invalid value type" unless value_type.is_a?(Type)
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
|
156
|
+
# class Array < Type
|
157
|
+
# def initialize(value_type)
|
158
|
+
# super("Array")
|
159
|
+
# @value_type = value_type
|
160
|
+
# raise "Invalid value type" unless value_type.is_a?(Type)
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
|
164
|
+
# class NamedTuple < Type
|
165
|
+
# def initialize(types_hash)
|
166
|
+
# raise "keys must be symbols" unless types_hash.keys.all? { |k| k.is_a?(Symbol) }
|
167
|
+
# raise "Invalid value type" unless types_hash.values.all? { |v| v.is_a?(Type) }
|
168
|
+
|
169
|
+
# super("NamedTuple")
|
170
|
+
# @types_hash = types_hash
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
|
174
|
+
# class Tuple < Type
|
175
|
+
# def initialize(*value_types)
|
176
|
+
# super("Tuple")
|
177
|
+
# raise "Invalid value type" unless value_types.all? { |v| v.is_a?(Type) }
|
178
|
+
# end
|
179
|
+
# end
|
data/lib/crystalruby/typemaps.rb
CHANGED
@@ -26,6 +26,32 @@ module CrystalRuby
|
|
26
26
|
:string => "String" # String type
|
27
27
|
}
|
28
28
|
|
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
|
53
|
+
}
|
54
|
+
|
29
55
|
C_TYPE_MAP = CRYSTAL_TYPE_MAP.merge({
|
30
56
|
:string => "UInt8*"
|
31
57
|
})
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module CrystalRuby::Types
|
2
|
+
Array = Type.new(
|
3
|
+
:Array,
|
4
|
+
error: "Array type must have a type parameter. E.g. Array(Float64)"
|
5
|
+
)
|
6
|
+
|
7
|
+
def self.Array(type)
|
8
|
+
Type.validate!(type)
|
9
|
+
Type.new("Array", inner_types: [type], accept_if: [::Array]
|
10
|
+
) do |a|
|
11
|
+
a.map!{|v| type.interpret!(v) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CrystalRuby::Types
|
2
|
+
Hash = Type.new(
|
3
|
+
:Hash,
|
4
|
+
error: "Hash type must have 2 type parameters. E.g. Hash(Float64, String)",
|
5
|
+
)
|
6
|
+
|
7
|
+
def self.Hash(key_type, value_type)
|
8
|
+
Type.validate!(key_type)
|
9
|
+
Type.validate!(value_type)
|
10
|
+
Type.new("Hash", inner_types: [key_type, value_type], accept_if: [::Hash]) do |h|
|
11
|
+
h.transform_keys!{|k| key_type.interpret!(k) }
|
12
|
+
h.transform_values!{|v| value_type.interpret!(v) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CrystalRuby::Types
|
2
|
+
NamedTuple = Type.new(
|
3
|
+
:NamedTuple,
|
4
|
+
error: "NamedTuple type must contain one or more symbol -> type pairs. E.g. NamedTuple(hello: Int32, world: String)"
|
5
|
+
)
|
6
|
+
|
7
|
+
def self.NamedTuple(types_hash)
|
8
|
+
types_hash.keys.each do |key|
|
9
|
+
raise "NamedTuple keys must be symbols" unless key.kind_of?(::Symbol) || key.respond_to?(:to_sym)
|
10
|
+
end
|
11
|
+
types_hash.values.each do |value_type|
|
12
|
+
Type.validate!(value_type)
|
13
|
+
end
|
14
|
+
keys = types_hash.keys.map(&:to_sym)
|
15
|
+
values = types_hash.values
|
16
|
+
Type.new("NamedTuple", inner_types: values, inner_keys: keys, accept_if: [::Hash]) do |h|
|
17
|
+
h.transform_keys!{|k| k.to_sym }
|
18
|
+
raise "Invalid keys for named tuple" unless h.keys.length == keys.length
|
19
|
+
raise "Invalid keys for named tuple" unless h.keys.all?{|k| keys.include?(k)}
|
20
|
+
h.each do |key, value|
|
21
|
+
h[key] = values[keys.index(key)].interpret!(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CrystalRuby::Types
|
2
|
+
Tuple = Type.new(
|
3
|
+
:Tuple,
|
4
|
+
error: "Tuple type must contain one or more types E.g. Tuple(Int32, String)"
|
5
|
+
)
|
6
|
+
|
7
|
+
def self.Tuple(*types)
|
8
|
+
types.each do |value_type|
|
9
|
+
Type.validate!(value_type)
|
10
|
+
end
|
11
|
+
Type.new("Tuple", inner_types: types, accept_if: [::Array]) do |a|
|
12
|
+
a.map!.with_index{|v, i| self.inner_types[i].interpret!(v) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|