crystalruby 0.1.1 → 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 +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
@@ -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
@@ -1,33 +1,41 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "ffi"
|
2
|
+
require "digest"
|
3
|
+
require "fileutils"
|
4
|
+
require "method_source"
|
5
5
|
require_relative "crystalruby/config"
|
6
6
|
require_relative "crystalruby/version"
|
7
7
|
require_relative "crystalruby/typemaps"
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# Fix bigint issues
|
12
|
-
# * Initialize Crystal project
|
13
|
-
# * Clear build artifacts
|
14
|
-
# * Add config file
|
15
|
-
# * Set release flag (Changes build target locations)
|
16
|
-
# Struct Conversions
|
17
|
-
# Classes
|
18
|
-
# Test Nesting
|
19
|
-
|
8
|
+
require_relative "crystalruby/types"
|
9
|
+
require_relative "crystalruby/typebuilder"
|
10
|
+
require_relative "crystalruby/template"
|
20
11
|
|
21
12
|
module CrystalRuby
|
22
|
-
|
23
13
|
# Define a method to set the @crystalize proc if it doesn't already exist
|
24
|
-
def crystalize(type
|
14
|
+
def crystalize(type = :src, **options, &block)
|
25
15
|
(args,), returns = options.first
|
26
16
|
args ||= {}
|
27
|
-
raise "Arguments should be of the form name: :type. Got #{args}" unless args.
|
28
|
-
|
17
|
+
raise "Arguments should be of the form name: :type. Got #{args}" unless args.is_a?(Hash)
|
18
|
+
|
19
|
+
@crystalize_next = { raw: type.to_sym == :raw, args: args, returns: returns, block: block }
|
20
|
+
end
|
21
|
+
|
22
|
+
def crtype(&block)
|
23
|
+
TypeBuilder.with_injected_type_dsl(self) do
|
24
|
+
TypeBuilder.build(&block)
|
25
|
+
end
|
29
26
|
end
|
30
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
|
31
39
|
|
32
40
|
def method_added(method_name)
|
33
41
|
if @crystalize_next
|
@@ -42,7 +50,6 @@ module CrystalRuby
|
|
42
50
|
end
|
43
51
|
|
44
52
|
def attach_crystalized_method(method_name)
|
45
|
-
|
46
53
|
CrystalRuby.instantiate_crystal_ruby! unless CrystalRuby.instantiated?
|
47
54
|
|
48
55
|
function_body = instance_method(method_name).source.lines[
|
@@ -54,17 +61,18 @@ module CrystalRuby
|
|
54
61
|
args ||= {}
|
55
62
|
@crystalize_next = nil
|
56
63
|
function = build_function(self, method_name, args, returns, function_body)
|
57
|
-
|
58
|
-
CrystalRuby.write_function(self, **function) do
|
64
|
+
CrystalRuby.write_function(self, name: function[:name], body: function[:body]) do
|
59
65
|
extend FFI::Library
|
60
66
|
ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
|
61
|
-
attach_function "#{method_name}", fname,
|
62
|
-
attach_function
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
attach_function "#{method_name}", fname, function[:ffi_types], function[:return_ffi_type]
|
68
|
+
attach_function "init!", "init", [], :void
|
69
|
+
if block
|
70
|
+
[singleton_class, self].each do |receiver|
|
71
|
+
receiver.prepend(Module.new do
|
72
|
+
define_method(method_name, &block)
|
73
|
+
end)
|
74
|
+
end
|
75
|
+
end
|
68
76
|
|
69
77
|
init!
|
70
78
|
end
|
@@ -74,7 +82,15 @@ module CrystalRuby
|
|
74
82
|
define_method(method_name) do |*args|
|
75
83
|
CrystalRuby.compile! unless CrystalRuby.compiled?
|
76
84
|
CrystalRuby.attach! unless CrystalRuby.attached?
|
77
|
-
|
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
|
78
94
|
end
|
79
95
|
end)
|
80
96
|
end
|
@@ -82,63 +98,128 @@ module CrystalRuby
|
|
82
98
|
|
83
99
|
module_function
|
84
100
|
|
85
|
-
|
86
101
|
def build_function(owner, name, args, returns, body)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
96
130
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
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
|
102
135
|
|
103
136
|
{
|
104
|
-
|
105
|
-
|
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) }
|
106
145
|
}
|
107
146
|
end
|
108
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
|
+
|
109
155
|
def lib_type(type)
|
110
|
-
|
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}"
|
111
163
|
end
|
112
164
|
|
113
|
-
def
|
114
|
-
|
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}"
|
115
173
|
end
|
116
174
|
|
117
|
-
def
|
118
|
-
|
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}"
|
119
183
|
end
|
120
184
|
|
121
|
-
def
|
122
|
-
|
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
|
191
|
+
end
|
192
|
+
|
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
|
123
199
|
end
|
124
200
|
|
125
201
|
def self.instantiate_crystal_ruby!
|
126
|
-
|
202
|
+
unless system("which crystal > /dev/null 2>&1")
|
203
|
+
raise "Crystal executable not found. Please ensure Crystal is installed and in your PATH."
|
204
|
+
end
|
205
|
+
|
127
206
|
@instantiated = true
|
128
207
|
%w[crystal_lib_dir crystal_main_file crystal_src_dir crystal_lib_name].each do |config_key|
|
129
|
-
|
208
|
+
unless config.send(config_key)
|
209
|
+
raise "Missing config option `#{config_key}`. \nProvide this inside crystalruby.yaml (run `bundle exec crystalruby init` to generate this file with detaults)"
|
210
|
+
end
|
130
211
|
end
|
131
|
-
FileUtils.mkdir_p "#{config.crystal_src_dir}
|
212
|
+
FileUtils.mkdir_p "#{config.crystal_src_dir}/#{config.crystal_codegen_dir}"
|
132
213
|
FileUtils.mkdir_p "#{config.crystal_lib_dir}"
|
133
214
|
unless File.exist?("#{config.crystal_src_dir}/#{config.crystal_main_file}")
|
134
|
-
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")
|
135
216
|
end
|
136
|
-
|
137
|
-
|
217
|
+
return if File.exist?("#{config.crystal_src_dir}/shard.yml")
|
218
|
+
|
219
|
+
IO.write("#{config.crystal_src_dir}/shard.yml", <<~CRYSTAL)
|
138
220
|
name: src
|
139
221
|
version: 0.1.0
|
140
|
-
|
141
|
-
end
|
222
|
+
CRYSTAL
|
142
223
|
end
|
143
224
|
|
144
225
|
def self.instantiated?
|
@@ -153,39 +234,75 @@ module CrystalRuby
|
|
153
234
|
!!@attached
|
154
235
|
end
|
155
236
|
|
156
|
-
def self.
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
fun init(): Void
|
161
|
-
GC.init
|
162
|
-
ptr = FAKE_ARG.to_unsafe
|
163
|
-
LibCrystalMain.__crystal_main(1, pointerof(ptr))
|
164
|
-
end
|
165
|
-
CRYSTAL
|
237
|
+
def self.register_type!(type)
|
238
|
+
@types_cache ||= {}
|
239
|
+
@types_cache[type.name] = type.type_defn
|
240
|
+
end
|
166
241
|
|
167
|
-
|
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 += " "
|
250
|
+
end
|
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
|
259
|
+
|
260
|
+
def self.requires
|
261
|
+
@block_store.map do |function|
|
168
262
|
function_data = function[:body]
|
169
263
|
file_digest = Digest::MD5.hexdigest function_data
|
170
264
|
fname = function[:name]
|
171
265
|
"require \"./#{function[:owner].name}/#{fname}_#{file_digest}.cr\"\n"
|
172
266
|
end.join("\n")
|
267
|
+
end
|
173
268
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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)
|
182
291
|
|
183
292
|
@compiled = true
|
184
|
-
|
185
|
-
puts 'Error compiling crystal code'
|
186
|
-
puts e
|
187
|
-
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")
|
188
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)
|
189
306
|
end
|
190
307
|
|
191
308
|
def self.attach!
|
@@ -196,25 +313,25 @@ module CrystalRuby
|
|
196
313
|
end
|
197
314
|
|
198
315
|
def self.write_function(owner, name:, body:, &compile_callback)
|
199
|
-
@compiled = File.exist?("#{config.crystal_src_dir}/
|
316
|
+
@compiled = File.exist?("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/index.cr") unless defined?(@compiled)
|
200
317
|
@block_store ||= []
|
201
|
-
@block_store << {owner: owner, name: name, body: body, compile_callback: compile_callback}
|
202
|
-
FileUtils.mkdir_p("#{config.crystal_src_dir}
|
203
|
-
existing = Dir.glob("#{config.crystal_src_dir}
|
318
|
+
@block_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
|
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")
|
204
321
|
@block_store.each do |function|
|
205
322
|
owner_name = function[:owner].name
|
206
|
-
FileUtils.mkdir_p("#{config.crystal_src_dir}
|
323
|
+
FileUtils.mkdir_p("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/#{owner_name}")
|
207
324
|
function_data = function[:body]
|
208
325
|
fname = function[:name]
|
209
326
|
file_digest = Digest::MD5.hexdigest function_data
|
210
|
-
filename = "#{config.crystal_src_dir}
|
327
|
+
filename = "#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/#{owner_name}/#{fname}_#{file_digest}.cr"
|
211
328
|
unless existing.delete(filename)
|
212
329
|
@compiled = false
|
213
330
|
@attached = false
|
214
331
|
File.write(filename, function_data)
|
215
332
|
end
|
216
333
|
existing.select do |f|
|
217
|
-
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}
|
218
335
|
end.each do |fl|
|
219
336
|
File.delete(fl) unless fl.eql?(filename)
|
220
337
|
end
|