crystalruby 0.1.13 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CrystalRuby
2
4
  module Typemaps
3
5
  CRYSTAL_TYPE_MAP = {
@@ -66,6 +68,90 @@ module CrystalRuby
66
68
  void: {
67
69
  to: "nil"
68
70
  }
69
- }
71
+ }.tap do |hash|
72
+ hash.define_singleton_method(:convert) do |type, dir, expr|
73
+ if hash.key?(type)
74
+ conversion_string = hash[type][dir]
75
+ conversion_string =~ /%/ ? conversion_string % expr : conversion_string
76
+ else
77
+ expr
78
+ end
79
+ end
80
+ end
81
+
82
+ def build_type_map(crystalruby_type)
83
+ {
84
+ ffi_type: ffi_type(crystalruby_type),
85
+ ffi_ret_type: ffi_type(crystalruby_type),
86
+ crystal_type: crystal_type(crystalruby_type),
87
+ lib_type: lib_type(crystalruby_type),
88
+ error_value: error_value(crystalruby_type),
89
+ arg_mapper: if crystalruby_type.is_a?(Types::TypeSerializer)
90
+ lambda { |arg|
91
+ crystalruby_type.prepare_argument(arg)
92
+ }
93
+ end,
94
+ retval_mapper: if crystalruby_type.is_a?(Types::TypeSerializer)
95
+ lambda { |arg|
96
+ crystalruby_type.prepare_retval(arg)
97
+ }
98
+ end,
99
+ convert_crystal_to_lib_type: ->(expr) { convert_crystal_to_lib_type(expr, crystalruby_type) },
100
+ convert_lib_to_crystal_type: ->(expr) { convert_lib_to_crystal_type(expr, crystalruby_type) }
101
+ }
102
+ end
103
+
104
+ def ffi_type(type)
105
+ case type
106
+ when Symbol then type
107
+ when Types::TypeSerializer then type.ffi_type
108
+ end
109
+ end
110
+
111
+ def lib_type(type)
112
+ if type.is_a?(Types::TypeSerializer)
113
+ type.lib_type
114
+ else
115
+ C_TYPE_MAP.fetch(type)
116
+ end
117
+ rescue StandardError
118
+ raise "Unsupported type #{type}"
119
+ end
120
+
121
+ def error_value(type)
122
+ if type.is_a?(Types::TypeSerializer)
123
+ type.error_value
124
+ else
125
+ ERROR_VALUE.fetch(type)
126
+ end
127
+ rescue StandardError
128
+ raise "Unsupported type #{type}"
129
+ end
130
+
131
+ def crystal_type(type)
132
+ if type.is_a?(Types::TypeSerializer)
133
+ type.crystal_type
134
+ else
135
+ CRYSTAL_TYPE_MAP.fetch(type)
136
+ end
137
+ rescue StandardError
138
+ raise "Unsupported type #{type}"
139
+ end
140
+
141
+ def convert_lib_to_crystal_type(expr, type)
142
+ if type.is_a?(Types::TypeSerializer)
143
+ type.lib_to_crystal_type_expr(expr)
144
+ else
145
+ C_TYPE_CONVERSIONS.convert(type, :from, expr)
146
+ end
147
+ end
148
+
149
+ def convert_crystal_to_lib_type(expr, type)
150
+ if type.is_a?(Types::TypeSerializer)
151
+ type.crystal_to_lib_type_expr(expr)
152
+ else
153
+ C_TYPE_CONVERSIONS.convert(type, :to, expr)
154
+ end
155
+ end
70
156
  end
71
157
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CrystalRuby::Types
2
4
  Array = Type.new(
3
5
  :Array,
@@ -6,9 +8,8 @@ module CrystalRuby::Types
6
8
 
7
9
  def self.Array(type)
8
10
  Type.validate!(type)
9
- Type.new("Array", inner_types: [type], accept_if: [::Array]
10
- ) do |a|
11
- a.map!{|v| type.interpret!(v) }
11
+ Type.new("Array", inner_types: [type], accept_if: [::Array]) do |a|
12
+ a.map! { |v| type.interpret!(v) }
12
13
  end
13
14
  end
14
15
  end
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CrystalRuby::Types
2
4
  Hash = Type.new(
3
5
  :Hash,
4
- error: "Hash type must have 2 type parameters. E.g. Hash(Float64, String)",
6
+ error: "Hash type must have 2 type parameters. E.g. Hash(Float64, String)"
5
7
  )
6
8
 
7
9
  def self.Hash(key_type, value_type)
8
10
  Type.validate!(key_type)
9
11
  Type.validate!(value_type)
10
12
  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
+ h.transform_keys! { |k| key_type.interpret!(k) }
14
+ h.transform_values! { |v| value_type.interpret!(v) }
13
15
  end
14
16
  end
15
17
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CrystalRuby::Types
2
4
  NamedTuple = Type.new(
3
5
  :NamedTuple,
@@ -6,7 +8,7 @@ module CrystalRuby::Types
6
8
 
7
9
  def self.NamedTuple(types_hash)
8
10
  types_hash.keys.each do |key|
9
- raise "NamedTuple keys must be symbols" unless key.kind_of?(::Symbol) || key.respond_to?(:to_sym)
11
+ raise "NamedTuple keys must be symbols" unless key.is_a?(::Symbol) || key.respond_to?(:to_sym)
10
12
  end
11
13
  types_hash.values.each do |value_type|
12
14
  Type.validate!(value_type)
@@ -14,9 +16,10 @@ module CrystalRuby::Types
14
16
  keys = types_hash.keys.map(&:to_sym)
15
17
  values = types_hash.values
16
18
  Type.new("NamedTuple", inner_types: values, inner_keys: keys, accept_if: [::Hash]) do |h|
17
- h.transform_keys!{|k| k.to_sym }
19
+ h.transform_keys! { |k| k.to_sym }
18
20
  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)}
21
+ raise "Invalid keys for named tuple" unless h.keys.all? { |k| keys.include?(k) }
22
+
20
23
  h.each do |key, value|
21
24
  h[key] = values[keys.index(key)].interpret!(value)
22
25
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CrystalRuby::Types
2
- require 'date'
4
+ require "date"
3
5
  Time = Type.new(:Time, accept_if: [::Time, ::String]) do |v|
4
6
  DateTime.parse(v)
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CrystalRuby::Types
2
4
  Tuple = Type.new(
3
5
  :Tuple,
@@ -9,7 +11,7 @@ module CrystalRuby::Types
9
11
  Type.validate!(value_type)
10
12
  end
11
13
  Type.new("Tuple", inner_types: types, accept_if: [::Array]) do |a|
12
- a.map!.with_index{|v, i| self.inner_types[i].interpret!(v) }
14
+ a.map!.with_index { |v, i| inner_types[i].interpret!(v) }
13
15
  end
14
16
  end
15
17
  end
@@ -1,9 +1,10 @@
1
- require 'json'
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
2
4
 
3
5
  module CrystalRuby::Types
4
6
  class TypeSerializer
5
7
  class JSON < TypeSerializer
6
-
7
8
  def lib_type
8
9
  "UInt8*"
9
10
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "type_serializer"
2
4
 
3
5
  module CrystalRuby
@@ -5,7 +7,7 @@ module CrystalRuby
5
7
  class Typedef; end
6
8
 
7
9
  def self.Typedef(type)
8
- return type if type.kind_of?(Class) && type < Typedef
10
+ return type if type.is_a?(Class) && type < Typedef
9
11
 
10
12
  Class.new(Typedef) do
11
13
  define_singleton_method(:union_types) do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CrystalRuby
2
4
  module Types
3
5
  class UnionType < Type
@@ -26,12 +28,12 @@ module CrystalRuby
26
28
 
27
29
  def interpret!(raw)
28
30
  union_types.each do |type|
29
- if type.interprets?(raw)
30
- begin
31
- return type.interpret!(raw)
32
- rescue
33
- # Pass
34
- end
31
+ next unless type.interprets?(raw)
32
+
33
+ begin
34
+ return type.interpret!(raw)
35
+ rescue StandardError
36
+ # Pass
35
37
  end
36
38
  end
37
39
  raise "Invalid deserialized value #{raw} for type #{inspect}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crystalruby
4
- VERSION = "0.1.13"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/crystalruby.rb CHANGED
@@ -15,277 +15,48 @@ require_relative "crystalruby/template"
15
15
  require_relative "crystalruby/compilation"
16
16
  require_relative "crystalruby/adapter"
17
17
  require_relative "crystalruby/reactor"
18
+ require_relative "crystalruby/library"
19
+ require_relative "crystalruby/function"
20
+ require_relative "module"
18
21
 
19
22
  module CrystalRuby
20
- CR_SRC_FILES_PATTERN = "./**/*.cr"
21
- CR_COMPILE_MUX = Mutex.new
22
-
23
23
  module_function
24
24
 
25
- def build_function(owner, lib_fn_name, name, args, returns, body)
26
- log_debug(".build_function #{{ owner: owner, name: name, args: args, returns: returns, body: body[0..50] }}")
27
-
28
- arg_types = args.transform_values(&method(:build_type_map))
29
- return_type = build_type_map(returns)
30
- lib_fn_args = arg_types.map { |k, arg_type| "_#{k} : #{arg_type[:lib_type]}" }.join(",")
31
- lib_fn_args += ", " unless lib_fn_args.empty?
32
- lib_fn_arg_names = arg_types.map { |k, _arg_type| "_#{k}" }.join(",")
33
- lib_fn_arg_names += ", " unless lib_fn_args.empty?
34
-
35
- function_body = Template::Function.render(
36
- {
37
- module_name: owner.name,
38
- lib_fn_name: lib_fn_name,
39
- fn_name: name,
40
- fn_body: body,
41
- callback_call: returns == :void ? "callback.call(thread_id)" : "callback.call(thread_id, converted)",
42
- callback_type: return_type[:ffi_type] == :void ? "UInt32 -> Void" : " UInt32, #{return_type[:lib_type]} -> Void",
43
- fn_args: arg_types.map { |k, arg_type| "#{k} : #{arg_type[:crystal_type]}" }.join(","),
44
- fn_ret_type: return_type[:crystal_type],
45
- lib_fn_args: lib_fn_args,
46
- lib_fn_arg_names: lib_fn_arg_names,
47
- lib_fn_ret_type: return_type[:lib_type],
48
- convert_lib_args: arg_types.map do |k, arg_type|
49
- "#{k} = #{arg_type[:convert_lib_to_crystal_type]["_#{k}"]}"
50
- end.join("\n "),
51
- arg_names: args.keys.join(","),
52
- convert_return_type: return_type[:convert_crystal_to_lib_type]["return_value"],
53
- error_value: return_type[:error_value]
54
- }
55
- )
56
- {
57
- name: name,
58
- body: function_body,
59
- retval_map: returns.is_a?(Types::TypeSerializer) ? ->(rv) { returns.prepare_retval(rv) } : nil,
60
- ffi_types: arg_types.map { |_k, arg_type| arg_type[:ffi_type] },
61
- arg_maps: arg_types.map { |_k, arg_type| arg_type[:mapper] },
62
- ffi_ret_type: return_type[:ffi_ret_type]
63
- }
64
- end
65
-
66
- def build_type_map(crystalruby_type)
67
- if crystalruby_type.is_a?(Types::TypeSerializer) && !crystalruby_type.anonymous?
68
- CrystalRuby.register_type!(crystalruby_type)
69
- end
70
-
71
- {
72
- ffi_type: ffi_type(crystalruby_type),
73
- ffi_ret_type: ffi_type(crystalruby_type),
74
- crystal_type: crystal_type(crystalruby_type),
75
- lib_type: lib_type(crystalruby_type),
76
- error_value: error_value(crystalruby_type),
77
- mapper: crystalruby_type.is_a?(Types::TypeSerializer) ? ->(arg) { crystalruby_type.prepare_argument(arg) } : nil,
78
- convert_crystal_to_lib_type: ->(expr) { convert_crystal_to_lib_type(expr, crystalruby_type) },
79
- convert_lib_to_crystal_type: ->(expr) { convert_lib_to_crystal_type(expr, crystalruby_type) }
80
- }
81
- end
82
-
83
- def ffi_type(type)
84
- case type
85
- when Symbol then type
86
- when Types::TypeSerializer then type.ffi_type
87
- end
88
- end
89
-
90
- def lib_type(type)
91
- if type.is_a?(Types::TypeSerializer)
92
- type.lib_type
93
- else
94
- Typemaps::C_TYPE_MAP.fetch(type)
95
- end
96
- rescue StandardError => e
97
- raise "Unsupported type #{type}"
98
- end
99
-
100
- def error_value(type)
101
- if type.is_a?(Types::TypeSerializer)
102
- type.error_value
103
- else
104
- Typemaps::ERROR_VALUE.fetch(type)
105
- end
106
- rescue StandardError => e
107
- raise "Unsupported type #{type}"
108
- end
109
-
110
- def crystal_type(type)
111
- if type.is_a?(Types::TypeSerializer)
112
- type.crystal_type
113
- else
114
- Typemaps::CRYSTAL_TYPE_MAP.fetch(type)
115
- end
116
- rescue StandardError => e
117
- raise "Unsupported type #{type}"
118
- end
119
-
120
- def convert_lib_to_crystal_type(expr, type)
121
- if type.is_a?(Types::TypeSerializer)
122
- type.lib_to_crystal_type_expr(expr)
123
- else
124
- Typemaps::C_TYPE_CONVERSIONS[type] ? Typemaps::C_TYPE_CONVERSIONS[type][:from] % expr : expr
125
- end
126
- end
127
-
128
- def convert_crystal_to_lib_type(expr, type)
129
- if type.is_a?(Types::TypeSerializer)
130
- type.crystal_to_lib_type_expr(expr)
131
- else
132
- Typemaps::C_TYPE_CONVERSIONS[type] ? Typemaps::C_TYPE_CONVERSIONS[type][:to] % expr : expr
133
- end
134
- end
135
-
136
- def self.instantiate_crystal_ruby!
137
- unless system("which crystal > /dev/null 2>&1")
138
- raise "Crystal executable not found. Please ensure Crystal is installed and in your PATH."
139
- end
140
-
141
- @instantiated = true
142
- %w[crystal_lib_dir crystal_main_file crystal_src_dir crystal_lib_name].each do |config_key|
143
- unless config.send(config_key)
144
- raise "Missing config option `#{config_key}`. \nProvide this inside crystalruby.yaml (run `bundle exec crystalruby init` to generate this file with detaults)"
145
- end
146
- end
147
- FileUtils.mkdir_p config.crystal_codegen_dir_abs
148
- FileUtils.mkdir_p config.crystal_lib_dir_abs
149
- FileUtils.mkdir_p config.crystal_src_dir_abs
150
- unless File.exist?(config.crystal_src_dir_abs / config.crystal_main_file)
151
- IO.write(
152
- config.crystal_src_dir_abs / config.crystal_main_file,
153
- "require \"./#{config.crystal_codegen_dir}/index\"\n"
154
- )
155
- end
156
-
157
- return if File.exist?(config.crystal_src_dir / "shard.yml")
158
-
159
- IO.write("#{config.crystal_src_dir}/shard.yml", <<~YAML)
160
- name: src
161
- version: 0.1.0
162
- YAML
25
+ def initialized?
26
+ !!@initialized
163
27
  end
164
28
 
165
- def self.instantiated?
166
- @instantiated
167
- end
29
+ def initialize_crystal_ruby!
30
+ return if initialized?
168
31
 
169
- def self.compiled?
170
- @compiled = get_current_crystal_lib_digest == get_cr_src_files_digest unless defined?(@compiled)
171
- @compiled
32
+ check_crystal_ruby!
33
+ check_config!
34
+ @initialized = true
172
35
  end
173
36
 
174
- def self.attached?
175
- !!@attached
176
- end
37
+ def check_crystal_ruby!
38
+ return if system("which crystal > /dev/null 2>&1")
177
39
 
178
- def self.register_type!(type)
179
- @types_cache ||= {}
180
- @types_cache[type.name] = type.type_defn
40
+ raise "Crystal executable not found. Please ensure Crystal is installed and in your PATH." \
41
+ "See https://crystal-lang.org/install/"
181
42
  end
182
43
 
183
- def type_modules
184
- (@types_cache || {}).map do |type_name, expr|
185
- parts = type_name.split("::")
186
- typedef = parts[0...-1].each_with_index.reduce("") do |acc, (part, index)|
187
- acc + "#{" " * index}module #{part}\n"
188
- end
189
- typedef += "#{" " * (parts.size - 1)}alias #{parts.last} = #{expr}\n"
190
- typedef + parts[0...-1].reverse.each_with_index.reduce("") do |acc, (_part, index)|
191
- acc + "#{" " * (parts.size - 2 - index)}end\n"
192
- end
193
- end.join("\n")
194
- end
44
+ def check_config!
45
+ return if config.crystal_src_dir
195
46
 
196
- def self.requires
197
- chunk_store.map do |function|
198
- function_data = function[:body]
199
- file_digest = Digest::MD5.hexdigest function_data
200
- fname = function[:name]
201
- "require \"./#{function[:owner].name}/#{fname}_#{file_digest}.cr\"\n"
202
- end.join("\n")
47
+ raise "Missing config option `crystal_src_dir`. \nProvide this inside crystalruby.yaml "\
48
+ "(run `bundle exec crystalruby init` to generate this file with detaults)"
203
49
  end
204
50
 
205
- def self.build!
206
- log_debug(".build!")
207
-
208
- CR_COMPILE_MUX.synchronize do
209
- return if @compiled
51
+ %w[debug info warn error].each do |level|
52
+ define_method("log_#{level}") do |*msg|
53
+ prefix = config.colorize_log_output ? "\e[33mcrystalruby\e[0m\e[90m [#{Thread.current.object_id}]\e[0m" : "[crystalruby] #{Thread.current.object_id}"
210
54
 
211
- File.write config.crystal_codegen_dir_abs / "index.cr", Template::Index.render(
212
- type_modules: type_modules,
213
- requires: requires
214
- )
215
- if @compiled = CrystalRuby::Compilation.compile!(
216
- verbose: config.verbose,
217
- debug: config.debug
218
- )
219
- IO.write(digest_file_name, get_cr_src_files_digest)
220
- else
221
- File.delete(digest_file_name) if File.exist?(digest_file_name)
222
- raise "Error compiling crystal code"
223
- end
55
+ config.logger.send(level, "#{prefix} #{msg.join(", ")}")
224
56
  end
225
57
  end
226
58
 
227
- def self.attach!
228
- log_debug(".attach!")
229
- @chunk_store.each do |function|
230
- function[:compile_callback]&.call
231
- end
232
- log_debug(".attach_crystal_ruby_lib. Single thread mode: #{config.single_thread_mode}")
233
- if config.single_thread_mode
234
- Reactor.init_single_thread_mode!
235
- else
236
- Reactor.start!
237
- end
238
- @attached = true
239
- end
240
-
241
- def self.get_cr_src_files_digest
242
- file_digests = Dir.glob(CR_SRC_FILES_PATTERN).sort.map do |file_path|
243
- content = File.read(file_path)
244
- Digest::MD5.hexdigest(content)
245
- end.join
246
- Digest::MD5.hexdigest(file_digests)
247
- end
248
-
249
- def self.digest_file_name
250
- @digest_file_name ||= config.crystal_lib_dir_abs / "#{config.crystal_lib_name}.digest"
251
- end
252
-
253
- def self.chunk_store
254
- @chunk_store ||= []
255
- end
256
-
257
- def self.get_current_crystal_lib_digest
258
- File.read(digest_file_name) if File.exist?(digest_file_name)
259
- end
260
-
261
- def self.write_chunk(owner, body:, name: Digest::MD5.hexdigest(body), &compile_callback)
262
- log_debug(".write_chunk!")
263
- chunk_store.delete_if { |chnk| chnk[:owner].name == owner.name && chnk[:name] == name }
264
- chunk_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
265
- FileUtils.mkdir_p(config.crystal_codegen_dir_abs)
266
- existing = Dir.glob("#{config.crystal_codegen_dir_abs}/**/*.cr")
267
- chunk_store.each do |function|
268
- log_debug(".processing_chunk", function[0..60])
269
- owner_name = function[:owner].name
270
- FileUtils.mkdir_p(config.crystal_codegen_dir_abs / owner_name)
271
- function_data = function[:body]
272
- fname = function[:name]
273
- file_digest = Digest::MD5.hexdigest function_data
274
- filename = config.crystal_codegen_dir_abs / owner_name / "#{fname}_#{file_digest}.cr"
275
-
276
- unless existing.delete(filename.to_s)
277
- log_debug("Chunk invalidated", filename.to_s)
278
- @attached = false
279
- @compiled = false
280
- File.write(filename, function_data)
281
- end
282
- existing.select do |f|
283
- f =~ /#{config.crystal_codegen_dir / owner_name / "#{fname}_[a-f0-9]{32}\.cr"}/
284
- end.each do |fl|
285
- File.delete(fl) unless fl.eql?(filename.to_s)
286
- end
287
- end
59
+ def compile!
60
+ CrystalRuby::Library.all.each(&:build!)
288
61
  end
289
62
  end
290
-
291
- require_relative "module"
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.13
4
+ version: 0.2.1
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-16 00:00:00.000000000 Z
11
+ date: 2024-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: digest
@@ -74,6 +74,7 @@ executables:
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - ".dockerignore"
77
78
  - ".rubocop.yml"
78
79
  - CHANGELOG.md
79
80
  - CODE_OF_CONDUCT.md
@@ -88,6 +89,8 @@ files:
88
89
  - lib/crystalruby/adapter.rb
89
90
  - lib/crystalruby/compilation.rb
90
91
  - lib/crystalruby/config.rb
92
+ - lib/crystalruby/function.rb
93
+ - lib/crystalruby/library.rb
91
94
  - lib/crystalruby/reactor.rb
92
95
  - lib/crystalruby/template.rb
93
96
  - lib/crystalruby/templates/function.cr