crystalruby 0.1.13 → 0.2.0

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.
@@ -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,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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crystalruby
4
- VERSION = "0.1.13"
4
+ VERSION = "0.2.0"
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.0
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-24 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