crystalruby 0.1.2 → 0.1.3
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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +52 -3
- data/exe/crystalruby +1 -0
- data/lib/crystalruby/config.rb +11 -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 +181 -59
- metadata +22 -2
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
@@ -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
|
|
data/exe/crystalruby
CHANGED
data/lib/crystalruby/config.rb
CHANGED
@@ -9,16 +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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
config = if File.exist?("crystalruby.yaml")
|
17
|
+
YAML.safe_load(IO.read("crystalruby.yaml")) rescue {}
|
18
|
+
else
|
19
|
+
{}
|
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")
|
22
26
|
end
|
23
27
|
end
|
24
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
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module CrystalRuby
|
2
|
+
module Types
|
3
|
+
class Type
|
4
|
+
attr_accessor :name, :error, :inner_types, :inner_keys, :accept_if, :convert
|
5
|
+
|
6
|
+
def initialize(name, error: nil, inner_types: nil, inner_keys: nil, accept_if: [], &convert)
|
7
|
+
self.name = name
|
8
|
+
self.error = error
|
9
|
+
self.inner_types = inner_types
|
10
|
+
self.inner_keys = inner_keys
|
11
|
+
self.accept_if = accept_if
|
12
|
+
self.convert = convert
|
13
|
+
end
|
14
|
+
|
15
|
+
def union_types
|
16
|
+
[self]
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid?
|
20
|
+
!error
|
21
|
+
end
|
22
|
+
|
23
|
+
def |(other)
|
24
|
+
raise "Cannot union non-crystal type #{other}" unless other.is_a?(Type) || (
|
25
|
+
other.is_a?(Class) && other.ancestors.include?(Typedef)
|
26
|
+
)
|
27
|
+
|
28
|
+
UnionType.new(*union_types, *other.union_types)
|
29
|
+
end
|
30
|
+
|
31
|
+
def type_expr
|
32
|
+
inspect
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
if !inner_types
|
37
|
+
name
|
38
|
+
elsif !inner_keys
|
39
|
+
"#{name}(#{inner_types.map(&:inspect).join(", ")})"
|
40
|
+
else
|
41
|
+
"#{name}(#{inner_keys.zip(inner_types).map { |k, v| "#{k}: #{v.inspect}" }.join(", ")})"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def interprets?(raw)
|
46
|
+
accept_if.any? { |type| raw.is_a?(type) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def interpret!(raw)
|
50
|
+
if interprets?(raw)
|
51
|
+
convert ? convert.call(raw) : raw
|
52
|
+
else
|
53
|
+
raise "Invalid deserialized value #{raw} for type #{inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.validate!(type)
|
58
|
+
unless type.is_a?(Types::Type) || (type.is_a?(Class) && type.ancestors.include?(Types::Typedef))
|
59
|
+
raise "Result #{type} is not a valid CrystalRuby type"
|
60
|
+
end
|
61
|
+
|
62
|
+
raise "Invalid type: #{type.error}" unless type.valid?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module CrystalRuby::Types
|
4
|
+
class TypeSerializer
|
5
|
+
class JSON < TypeSerializer
|
6
|
+
|
7
|
+
def lib_type
|
8
|
+
"UInt8*"
|
9
|
+
end
|
10
|
+
|
11
|
+
def crystal_type
|
12
|
+
type_expr
|
13
|
+
end
|
14
|
+
|
15
|
+
def error_value
|
16
|
+
'"{}".to_unsafe'
|
17
|
+
end
|
18
|
+
|
19
|
+
def ffi_type
|
20
|
+
:string
|
21
|
+
end
|
22
|
+
|
23
|
+
def prepare_argument(arg)
|
24
|
+
arg.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def prepare_retval(retval)
|
28
|
+
@typedef.interpret!(::JSON.parse(retval))
|
29
|
+
end
|
30
|
+
|
31
|
+
def lib_to_crystal_type_expr(expr)
|
32
|
+
"(#{type_expr}).from_json(String.new(%s))" % expr
|
33
|
+
end
|
34
|
+
|
35
|
+
def crystal_to_lib_type_expr(expr)
|
36
|
+
"%s.to_json.to_unsafe" % expr
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CrystalRuby::Types
|
2
|
+
class TypeSerializer
|
3
|
+
include FFI::DataConverter
|
4
|
+
def self.for(format)
|
5
|
+
case format
|
6
|
+
when :json then JSON
|
7
|
+
else raise "Unknown type format: #{format}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def error_value
|
12
|
+
0
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(typedef)
|
16
|
+
@typedef = typedef
|
17
|
+
end
|
18
|
+
|
19
|
+
def type_expr
|
20
|
+
@typedef.type_expr
|
21
|
+
end
|
22
|
+
|
23
|
+
def type_defn
|
24
|
+
@typedef.type_defn
|
25
|
+
end
|
26
|
+
|
27
|
+
def anonymous?
|
28
|
+
@typedef.anonymous?
|
29
|
+
end
|
30
|
+
|
31
|
+
def name
|
32
|
+
@typedef.name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
require_relative "type_serializer/json"
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative "type_serializer"
|
2
|
+
|
3
|
+
module CrystalRuby
|
4
|
+
module Types
|
5
|
+
class Typedef; end
|
6
|
+
|
7
|
+
def self.Typedef(type)
|
8
|
+
return type if type.kind_of?(Class) && type < Typedef
|
9
|
+
|
10
|
+
Class.new(Typedef) do
|
11
|
+
define_singleton_method(:union_types) do
|
12
|
+
[self]
|
13
|
+
end
|
14
|
+
|
15
|
+
define_singleton_method(:anonymous?) do
|
16
|
+
name.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
define_singleton_method(:valid?) do
|
20
|
+
type.valid?
|
21
|
+
end
|
22
|
+
|
23
|
+
define_singleton_method(:type) do
|
24
|
+
type
|
25
|
+
end
|
26
|
+
|
27
|
+
define_singleton_method(:type_expr) do
|
28
|
+
anonymous? ? type.type_expr : name
|
29
|
+
end
|
30
|
+
|
31
|
+
define_singleton_method(:type_defn) do
|
32
|
+
type.type_expr
|
33
|
+
end
|
34
|
+
|
35
|
+
define_singleton_method(:|) do |other|
|
36
|
+
raise "Cannot union non-crystal type #{other}" unless other.is_a?(Type) || other.is_a?(Typedef)
|
37
|
+
|
38
|
+
UnionType.new(*union_types, *other.union_types)
|
39
|
+
end
|
40
|
+
|
41
|
+
define_singleton_method(:inspect) do
|
42
|
+
"<#{name || "AnonymousType"} #{type.inspect}>"
|
43
|
+
end
|
44
|
+
|
45
|
+
define_singleton_method(:serialize_as) do |format|
|
46
|
+
TypeSerializer.for(format).new(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
define_singleton_method(:interpret!) do |raw|
|
50
|
+
type.interpret!(raw)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CrystalRuby
|
2
|
+
module Types
|
3
|
+
class UnionType < Type
|
4
|
+
attr_accessor :name, :union_types
|
5
|
+
|
6
|
+
def initialize(*union_types)
|
7
|
+
self.name = "UnionType"
|
8
|
+
self.union_types = union_types
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
union_types.all?(&:valid?)
|
13
|
+
end
|
14
|
+
|
15
|
+
def error
|
16
|
+
union_types.map(&:error).join(", ")
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
union_types.map(&:inspect).join(" | ")
|
21
|
+
end
|
22
|
+
|
23
|
+
def interprets?(raw)
|
24
|
+
union_types.any? { |type| type.interprets?(raw) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def interpret!(raw)
|
28
|
+
union_types.each do |type|
|
29
|
+
if type.interprets?(raw)
|
30
|
+
return type.interpret!(raw)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
raise "Invalid deserialized value #{raw} for type #{inspect}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative "types/type"
|
2
|
+
require_relative "types/union_type"
|
3
|
+
require_relative "types/typedef"
|
4
|
+
require_relative "types/string"
|
5
|
+
require_relative "types/time"
|
6
|
+
require_relative "types/symbol"
|
7
|
+
require_relative "types/array"
|
8
|
+
require_relative "types/hash"
|
9
|
+
require_relative "types/nil"
|
10
|
+
require_relative "types/bool"
|
11
|
+
require_relative "types/named_tuple"
|
12
|
+
require_relative "types/tuple"
|
13
|
+
require_relative "types/numbers"
|
14
|
+
|
15
|
+
module CrystalRuby
|
16
|
+
module Types
|
17
|
+
end
|
18
|
+
end
|
data/lib/crystalruby/version.rb
CHANGED
data/lib/crystalruby.rb
CHANGED
@@ -5,6 +5,9 @@ require "method_source"
|
|
5
5
|
require_relative "crystalruby/config"
|
6
6
|
require_relative "crystalruby/version"
|
7
7
|
require_relative "crystalruby/typemaps"
|
8
|
+
require_relative "crystalruby/types"
|
9
|
+
require_relative "crystalruby/typebuilder"
|
10
|
+
require_relative "crystalruby/template"
|
8
11
|
|
9
12
|
module CrystalRuby
|
10
13
|
# Define a method to set the @crystalize proc if it doesn't already exist
|
@@ -16,6 +19,24 @@ module CrystalRuby
|
|
16
19
|
@crystalize_next = { raw: type.to_sym == :raw, args: args, returns: returns, block: block }
|
17
20
|
end
|
18
21
|
|
22
|
+
def crtype(&block)
|
23
|
+
TypeBuilder.with_injected_type_dsl(self) do
|
24
|
+
TypeBuilder.build(&block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def json(&block)
|
29
|
+
crtype(&block).serialize_as(:json)
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_temporary_constant(constant_name, new_value)
|
33
|
+
old_value = const_get(constant_name)
|
34
|
+
const_set(constant_name, new_value)
|
35
|
+
yield
|
36
|
+
ensure
|
37
|
+
const_set(constant_name, old_value)
|
38
|
+
end
|
39
|
+
|
19
40
|
def method_added(method_name)
|
20
41
|
if @crystalize_next
|
21
42
|
attach_crystalized_method(method_name)
|
@@ -40,11 +61,10 @@ module CrystalRuby
|
|
40
61
|
args ||= {}
|
41
62
|
@crystalize_next = nil
|
42
63
|
function = build_function(self, method_name, args, returns, function_body)
|
43
|
-
|
44
|
-
CrystalRuby.write_function(self, **function) do
|
64
|
+
CrystalRuby.write_function(self, name: function[:name], body: function[:body]) do
|
45
65
|
extend FFI::Library
|
46
66
|
ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
|
47
|
-
attach_function "#{method_name}", fname,
|
67
|
+
attach_function "#{method_name}", fname, function[:ffi_types], function[:return_ffi_type]
|
48
68
|
attach_function "init!", "init", [], :void
|
49
69
|
if block
|
50
70
|
[singleton_class, self].each do |receiver|
|
@@ -62,7 +82,15 @@ module CrystalRuby
|
|
62
82
|
define_method(method_name) do |*args|
|
63
83
|
CrystalRuby.compile! unless CrystalRuby.compiled?
|
64
84
|
CrystalRuby.attach! unless CrystalRuby.attached?
|
65
|
-
|
85
|
+
args.each_with_index do |arg, i|
|
86
|
+
args[i] = function[:arg_maps][i][arg] if function[:arg_maps][i]
|
87
|
+
end
|
88
|
+
result = super(*args)
|
89
|
+
if function[:retval_map]
|
90
|
+
function[:retval_map][result]
|
91
|
+
else
|
92
|
+
result
|
93
|
+
end
|
66
94
|
end
|
67
95
|
end)
|
68
96
|
end
|
@@ -71,42 +99,103 @@ module CrystalRuby
|
|
71
99
|
module_function
|
72
100
|
|
73
101
|
def build_function(owner, name, args, returns, body)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
102
|
+
arg_types = args.transform_values(&method(:build_type_map))
|
103
|
+
return_type = build_type_map(returns)
|
104
|
+
function_body = Template.render(
|
105
|
+
Template::Function,
|
106
|
+
{
|
107
|
+
module_name: owner.name,
|
108
|
+
lib_fn_name: "#{owner.name.downcase}_#{name}",
|
109
|
+
fn_name: name,
|
110
|
+
fn_body: body,
|
111
|
+
fn_args: arg_types.map { |k, arg_type| "#{k} : #{arg_type[:crystal_type]}" }.join(","),
|
112
|
+
fn_ret_type: return_type[:crystal_type],
|
113
|
+
lib_fn_args: arg_types.map { |k, arg_type| "_#{k}: #{arg_type[:lib_type]}" }.join(","),
|
114
|
+
lib_fn_ret_type: return_type[:lib_type],
|
115
|
+
convert_lib_args: arg_types.map{|k, arg_type| "#{k} = #{arg_type[:convert_lib_to_crystal_type]["_#{k}"]}" }.join("\n "),
|
116
|
+
arg_names: args.keys.join(","),
|
117
|
+
convert_return_type: return_type[:convert_crystal_to_lib_type]["return_value"],
|
118
|
+
error_value: return_type[:error_value]
|
119
|
+
}
|
120
|
+
)
|
121
|
+
{
|
122
|
+
name: name,
|
123
|
+
body: function_body,
|
124
|
+
retval_map: returns.is_a?(Types::TypeSerializer) ? ->(rv) { returns.prepare_retval(rv) } : nil,
|
125
|
+
ffi_types: arg_types.map { |_k, arg_type| arg_type[:ffi_type] },
|
126
|
+
arg_maps: arg_types.map { |_k, arg_type| arg_type[:mapper] },
|
127
|
+
return_ffi_type: return_type[:return_ffi_type]
|
128
|
+
}
|
129
|
+
end
|
83
130
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
CRYSTAL
|
131
|
+
def build_type_map(crystalruby_type)
|
132
|
+
if crystalruby_type.is_a?(Types::TypeSerializer) && !crystalruby_type.anonymous?
|
133
|
+
CrystalRuby.register_type!(crystalruby_type)
|
134
|
+
end
|
89
135
|
|
90
136
|
{
|
91
|
-
|
92
|
-
|
137
|
+
ffi_type: ffi_type(crystalruby_type),
|
138
|
+
return_ffi_type: ffi_type(crystalruby_type),
|
139
|
+
crystal_type: crystal_type(crystalruby_type),
|
140
|
+
lib_type: lib_type(crystalruby_type),
|
141
|
+
error_value: error_value(crystalruby_type),
|
142
|
+
mapper: crystalruby_type.is_a?(Types::TypeSerializer) ? ->(arg) { crystalruby_type.prepare_argument(arg) } : nil,
|
143
|
+
convert_crystal_to_lib_type: ->(expr) { convert_crystal_to_lib_type(expr, crystalruby_type) },
|
144
|
+
convert_lib_to_crystal_type: ->(expr) { convert_lib_to_crystal_type(expr, crystalruby_type) }
|
93
145
|
}
|
94
146
|
end
|
95
147
|
|
148
|
+
def ffi_type(type)
|
149
|
+
case type
|
150
|
+
when Symbol then type
|
151
|
+
when Types::TypeSerializer then type.ffi_type
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
96
155
|
def lib_type(type)
|
97
|
-
|
156
|
+
if type.is_a?(Types::TypeSerializer)
|
157
|
+
type.lib_type
|
158
|
+
else
|
159
|
+
Typemaps::C_TYPE_MAP.fetch(type)
|
160
|
+
end
|
161
|
+
rescue StandardError => e
|
162
|
+
raise "Unsupported type #{type}"
|
98
163
|
end
|
99
164
|
|
100
|
-
def
|
101
|
-
|
165
|
+
def error_value(type)
|
166
|
+
if type.is_a?(Types::TypeSerializer)
|
167
|
+
type.error_value
|
168
|
+
else
|
169
|
+
Typemaps::ERROR_VALUE.fetch(type)
|
170
|
+
end
|
171
|
+
rescue StandardError => e
|
172
|
+
raise "Unsupported type #{type}"
|
102
173
|
end
|
103
174
|
|
104
|
-
def
|
105
|
-
|
175
|
+
def crystal_type(type)
|
176
|
+
if type.is_a?(Types::TypeSerializer)
|
177
|
+
type.crystal_type
|
178
|
+
else
|
179
|
+
Typemaps::CRYSTAL_TYPE_MAP.fetch(type)
|
180
|
+
end
|
181
|
+
rescue StandardError => e
|
182
|
+
raise "Unsupported type #{type}"
|
183
|
+
end
|
184
|
+
|
185
|
+
def convert_lib_to_crystal_type(expr, type)
|
186
|
+
if type.is_a?(Types::TypeSerializer)
|
187
|
+
type.lib_to_crystal_type_expr(expr)
|
188
|
+
else
|
189
|
+
Typemaps::C_TYPE_CONVERSIONS[type] ? Typemaps::C_TYPE_CONVERSIONS[type][:from] % expr : expr
|
190
|
+
end
|
106
191
|
end
|
107
192
|
|
108
|
-
def
|
109
|
-
|
193
|
+
def convert_crystal_to_lib_type(expr, type)
|
194
|
+
if type.is_a?(Types::TypeSerializer)
|
195
|
+
type.crystal_to_lib_type_expr(expr)
|
196
|
+
else
|
197
|
+
Typemaps::C_TYPE_CONVERSIONS[type] ? Typemaps::C_TYPE_CONVERSIONS[type][:to] % expr : expr
|
198
|
+
end
|
110
199
|
end
|
111
200
|
|
112
201
|
def self.instantiate_crystal_ruby!
|
@@ -120,10 +209,10 @@ module CrystalRuby
|
|
120
209
|
raise "Missing config option `#{config_key}`. \nProvide this inside crystalruby.yaml (run `bundle exec crystalruby init` to generate this file with detaults)"
|
121
210
|
end
|
122
211
|
end
|
123
|
-
FileUtils.mkdir_p "#{config.crystal_src_dir}
|
212
|
+
FileUtils.mkdir_p "#{config.crystal_src_dir}/#{config.crystal_codegen_dir}"
|
124
213
|
FileUtils.mkdir_p "#{config.crystal_lib_dir}"
|
125
214
|
unless File.exist?("#{config.crystal_src_dir}/#{config.crystal_main_file}")
|
126
|
-
IO.write("#{config.crystal_src_dir}/#{config.crystal_main_file}", "require \"
|
215
|
+
IO.write("#{config.crystal_src_dir}/#{config.crystal_main_file}", "require \"./#{config.crystal_codegen_dir}/index\"\n")
|
127
216
|
end
|
128
217
|
return if File.exist?("#{config.crystal_src_dir}/shard.yml")
|
129
218
|
|
@@ -145,42 +234,75 @@ module CrystalRuby
|
|
145
234
|
!!@attached
|
146
235
|
end
|
147
236
|
|
148
|
-
def self.
|
149
|
-
|
237
|
+
def self.register_type!(type)
|
238
|
+
@types_cache ||= {}
|
239
|
+
@types_cache[type.name] = type.type_defn
|
240
|
+
end
|
150
241
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
242
|
+
def type_modules
|
243
|
+
(@types_cache || {}).map do |type_name, expr|
|
244
|
+
typedef = ""
|
245
|
+
parts = type_name.split("::")
|
246
|
+
indent = ""
|
247
|
+
parts[0...-1].each do |part|
|
248
|
+
typedef << "#{indent} module #{part}\n"
|
249
|
+
indent += " "
|
157
250
|
end
|
158
|
-
|
251
|
+
typedef << "#{indent}alias #{parts[-1]} = #{expr}\n"
|
252
|
+
parts[0...-1].each do |_part|
|
253
|
+
indent = indent[0...-2]
|
254
|
+
typedef << "#{indent} end\n"
|
255
|
+
end
|
256
|
+
typedef
|
257
|
+
end.join("\n")
|
258
|
+
end
|
159
259
|
|
160
|
-
|
260
|
+
def self.requires
|
261
|
+
@block_store.map do |function|
|
161
262
|
function_data = function[:body]
|
162
263
|
file_digest = Digest::MD5.hexdigest function_data
|
163
264
|
fname = function[:name]
|
164
265
|
"require \"./#{function[:owner].name}/#{fname}_#{file_digest}.cr\"\n"
|
165
266
|
end.join("\n")
|
267
|
+
end
|
166
268
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
269
|
+
def self.compile!
|
270
|
+
return unless @block_store
|
271
|
+
|
272
|
+
index_content = Template.render(
|
273
|
+
Template::Index,
|
274
|
+
{
|
275
|
+
type_modules: type_modules,
|
276
|
+
requires: requires
|
277
|
+
}
|
278
|
+
)
|
279
|
+
|
280
|
+
File.write("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/index.cr", index_content)
|
281
|
+
lib_target = "#{Dir.pwd}/#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
|
282
|
+
|
283
|
+
Dir.chdir(config.crystal_src_dir) do
|
284
|
+
cmd = if config.debug
|
285
|
+
"crystal build -o #{lib_target} #{config.crystal_main_file}"
|
286
|
+
else
|
287
|
+
"crystal build --release --no-debug -o #{lib_target} #{config.crystal_main_file}"
|
288
|
+
end
|
289
|
+
|
290
|
+
raise "Error compiling crystal code" unless result = system(cmd)
|
177
291
|
|
178
292
|
@compiled = true
|
179
|
-
|
180
|
-
puts "Error compiling crystal code"
|
181
|
-
puts e
|
182
|
-
File.delete("#{config.crystal_src_dir}/generated/index.cr")
|
293
|
+
File.delete("#{config.crystal_codegen_dir}/index.cr") if File.exist?("#{config.crystal_codegen_dir}/index.cr")
|
183
294
|
end
|
295
|
+
extend FFI::Library
|
296
|
+
ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
|
297
|
+
attach_function :attach_rb_error_handler, [:pointer], :int
|
298
|
+
const_set(:ErrorCallback, FFI::Function.new(:void, [:string, :string]) do |error_type, message|
|
299
|
+
error_type = error_type.to_sym
|
300
|
+
error_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception) ?
|
301
|
+
Object.const_get(error_type) :
|
302
|
+
RuntimeError
|
303
|
+
raise error_type.new(message)
|
304
|
+
end)
|
305
|
+
attach_rb_error_handler(ErrorCallback)
|
184
306
|
end
|
185
307
|
|
186
308
|
def self.attach!
|
@@ -191,25 +313,25 @@ module CrystalRuby
|
|
191
313
|
end
|
192
314
|
|
193
315
|
def self.write_function(owner, name:, body:, &compile_callback)
|
194
|
-
@compiled = File.exist?("#{config.crystal_src_dir}/
|
316
|
+
@compiled = File.exist?("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/index.cr") unless defined?(@compiled)
|
195
317
|
@block_store ||= []
|
196
318
|
@block_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
|
197
|
-
FileUtils.mkdir_p("#{config.crystal_src_dir}
|
198
|
-
existing = Dir.glob("#{config.crystal_src_dir}
|
319
|
+
FileUtils.mkdir_p("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}")
|
320
|
+
existing = Dir.glob("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/**/*.cr")
|
199
321
|
@block_store.each do |function|
|
200
322
|
owner_name = function[:owner].name
|
201
|
-
FileUtils.mkdir_p("#{config.crystal_src_dir}
|
323
|
+
FileUtils.mkdir_p("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/#{owner_name}")
|
202
324
|
function_data = function[:body]
|
203
325
|
fname = function[:name]
|
204
326
|
file_digest = Digest::MD5.hexdigest function_data
|
205
|
-
filename = "#{config.crystal_src_dir}
|
327
|
+
filename = "#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/#{owner_name}/#{fname}_#{file_digest}.cr"
|
206
328
|
unless existing.delete(filename)
|
207
329
|
@compiled = false
|
208
330
|
@attached = false
|
209
331
|
File.write(filename, function_data)
|
210
332
|
end
|
211
333
|
existing.select do |f|
|
212
|
-
f =~ %r{#{config.crystal_src_dir}
|
334
|
+
f =~ %r{#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/#{owner_name}/#{fname}_[a-f0-9]{32}\.cr}
|
213
335
|
end.each do |fl|
|
214
336
|
File.delete(fl) unless fl.eql?(filename)
|
215
337
|
end
|
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.
|
4
|
+
version: 0.1.3
|
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-
|
11
|
+
date: 2024-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: digest
|
@@ -84,7 +84,27 @@ files:
|
|
84
84
|
- exe/crystalruby
|
85
85
|
- lib/crystalruby.rb
|
86
86
|
- lib/crystalruby/config.rb
|
87
|
+
- lib/crystalruby/template.rb
|
88
|
+
- lib/crystalruby/templates/function.cr
|
89
|
+
- lib/crystalruby/templates/index.cr
|
90
|
+
- lib/crystalruby/typebuilder.rb
|
87
91
|
- lib/crystalruby/typemaps.rb
|
92
|
+
- lib/crystalruby/types.rb
|
93
|
+
- lib/crystalruby/types/array.rb
|
94
|
+
- lib/crystalruby/types/bool.rb
|
95
|
+
- lib/crystalruby/types/hash.rb
|
96
|
+
- lib/crystalruby/types/named_tuple.rb
|
97
|
+
- lib/crystalruby/types/nil.rb
|
98
|
+
- lib/crystalruby/types/numbers.rb
|
99
|
+
- lib/crystalruby/types/string.rb
|
100
|
+
- lib/crystalruby/types/symbol.rb
|
101
|
+
- lib/crystalruby/types/time.rb
|
102
|
+
- lib/crystalruby/types/tuple.rb
|
103
|
+
- lib/crystalruby/types/type.rb
|
104
|
+
- lib/crystalruby/types/type_serializer.rb
|
105
|
+
- lib/crystalruby/types/type_serializer/json.rb
|
106
|
+
- lib/crystalruby/types/typedef.rb
|
107
|
+
- lib/crystalruby/types/union_type.rb
|
88
108
|
- lib/crystalruby/version.rb
|
89
109
|
- lib/module.rb
|
90
110
|
- sig/crystalruby.rbs
|