rubocop-boochtek 0.2.0 → 0.2.1

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +115 -5
  3. data/LICENSE.md +2 -2
  4. data/README.md +53 -106
  5. data/lib/disable_ssl_verify.rb +5 -0
  6. data/lib/extensions/argf.rb +8 -0
  7. data/lib/extensions/boolean.rb +48 -0
  8. data/lib/extensions/class.rb +10 -0
  9. data/lib/extensions/enumerable.rb +42 -0
  10. data/lib/extensions/integer.rb +7 -0
  11. data/lib/extensions/llvm_module.rb +101 -0
  12. data/lib/extensions/module.rb +34 -0
  13. data/lib/extensions/string.rb +29 -0
  14. data/lib/stone/ast/block.rb +88 -0
  15. data/lib/stone/ast/boolean_literal.rb +43 -0
  16. data/lib/stone/ast/computed_property_definition.rb +32 -0
  17. data/lib/stone/ast/constant_definition.rb +124 -0
  18. data/lib/stone/ast/expression.rb +12 -0
  19. data/lib/stone/ast/function_call.rb +291 -0
  20. data/lib/stone/ast/function_type_annotation.rb +31 -0
  21. data/lib/stone/ast/integer_literal.rb +46 -0
  22. data/lib/stone/ast/lambda.rb +126 -0
  23. data/lib/stone/ast/null_literal.rb +29 -0
  24. data/lib/stone/ast/program_unit/top_function.rb +209 -0
  25. data/lib/stone/ast/program_unit.rb +412 -0
  26. data/lib/stone/ast/property_access.rb +557 -0
  27. data/lib/stone/ast/record_definition.rb +180 -0
  28. data/lib/stone/ast/record_instantiation.rb +141 -0
  29. data/lib/stone/ast/reference.rb +145 -0
  30. data/lib/stone/ast/string_literal.rb +79 -0
  31. data/lib/stone/ast/two_phase_processing.rb +36 -0
  32. data/lib/stone/ast/type_annotation.rb +26 -0
  33. data/lib/stone/ast/type_declaration.rb +28 -0
  34. data/lib/stone/ast/type_of_expression.rb +41 -0
  35. data/lib/stone/ast/type_reference.rb +28 -0
  36. data/lib/stone/ast/union_type_annotation.rb +26 -0
  37. data/lib/stone/ast.rb +64 -0
  38. data/lib/stone/built_ins.rb +245 -0
  39. data/lib/stone/error/argument_error.rb +7 -0
  40. data/lib/stone/error/arity_error.rb +7 -0
  41. data/lib/stone/error/overflow.rb +18 -0
  42. data/lib/stone/error/property_error.rb +7 -0
  43. data/lib/stone/error/reference_error.rb +4 -0
  44. data/lib/stone/error/type_error.rb +7 -0
  45. data/lib/stone/error.rb +12 -0
  46. data/lib/stone/grammar.rb +124 -0
  47. data/lib/stone/libc.rb +27 -0
  48. data/lib/stone/prelude.stone +11 -0
  49. data/lib/stone/rtti.rb +182 -0
  50. data/lib/stone/scope.rb +85 -0
  51. data/lib/stone/transform.rb +410 -0
  52. data/lib/stone/type.rb +323 -0
  53. data/lib/stone/type_context.rb +38 -0
  54. data/lib/stone/type_registry.rb +73 -0
  55. data/lib/stone/types.rb +104 -0
  56. data/lib/stone.rb +70 -0
  57. metadata +54 -24
  58. data/CHANGELOG.md +0 -28
  59. data/Rakefile +0 -5
  60. data/config/default.yml +0 -277
  61. data/lib/rubocop/boochtek/plugin.rb +0 -43
  62. data/lib/rubocop/boochtek/version.rb +0 -7
  63. data/lib/rubocop/boochtek.rb +0 -18
  64. data/lib/rubocop/cop/boochtek/compact_endless_methods.rb +0 -103
  65. data/lib/rubocop-boochtek.rb +0 -3
@@ -0,0 +1,412 @@
1
+ require "stone/ast"
2
+ require "stone/ast/program_unit/top_function"
3
+ require "stone/built_ins"
4
+ require "llvm/core"
5
+ require "llvm/execution_engine"
6
+
7
+
8
+ module Stone
9
+ class AST
10
+ class ProgramUnit < Stone::AST
11
+
12
+ def initialize(children)
13
+ super(:program_unit, children)
14
+ @top_function = TopFunction.new(children)
15
+ LLVM.init_jit
16
+ end
17
+
18
+ def to_llir
19
+ module_ref
20
+ end
21
+
22
+ def eval(function_name = "__top__")
23
+ result, result_type = run_function(global_function(function_name))
24
+ convert_to_ruby(result, result_type)
25
+ ensure
26
+ @type_constant_addresses = nil # Clear cached addresses on dispose
27
+ jit_engine&.dispose
28
+ end
29
+
30
+ private def run_function(func)
31
+ result = jit_engine.run_function(func)
32
+ [result, last_expression_type]
33
+ end
34
+
35
+ # Determine the Stone type of the last expression for proper Ruby conversion
36
+ # Must match the logic in TopFunction#compute_return_type
37
+ private def last_expression_type
38
+ last_child = children&.last
39
+ return :null unless last_child
40
+
41
+ # Check for tagged union field access first (e.g., Bool | Int)
42
+ # These return a pointer to the union struct for runtime type dispatch
43
+ return :tagged_union_ptr if tagged_union_field_access?(last_child)
44
+
45
+ # Check for mixed union field access (PropertyAccess on mixed union field)
46
+ # Mixed unions now return i64 (payload extracted directly)
47
+ return :mixed_union_value if mixed_union_field_access?(last_child)
48
+
49
+ # Check for homogeneous union field access
50
+ return :union_value if union_field_access?(last_child)
51
+
52
+ # Check for string-returning properties (Type.as_String, FieldList.name)
53
+ return :string if string_returning_property?(last_child)
54
+
55
+ # Use the Stone type system for everything else
56
+ # Rescue errors since computed properties may not be registered in type system
57
+ stone_type = safe_get_type(last_child)
58
+ stone_type_to_result_type(stone_type)
59
+ end
60
+
61
+ private def lookup_type_declaration(identifier)
62
+ type_decl = children&.find { |c|
63
+ c.is_a?(Stone::AST::TypeDeclaration) && c.identifier == identifier
64
+ }
65
+ return nil unless type_decl
66
+
67
+ type_decl.type_annotation.to_type(Stone::TypeRegistry.instance)
68
+ end
69
+
70
+ private def safe_get_type(node)
71
+ node.type(module_ref)
72
+ rescue Stone::PropertyError, Stone::TypeError
73
+ # Type couldn't be determined (computed properties, etc.) - default to nil (becomes i64)
74
+ nil
75
+ end
76
+
77
+ # PropertyAccess patterns that return string pointers
78
+ private def string_returning_property?(node)
79
+ return false unless node.is_a?(Stone::AST::PropertyAccess)
80
+
81
+ property = node.property
82
+ # Type.as_String returns a string pointer
83
+ return true if property == "as_String"
84
+ # FieldList.name returns a string pointer
85
+ return true if property == "name"
86
+
87
+ false
88
+ end
89
+
90
+ private def stone_type_to_result_type(stone_type)
91
+ # Default to i64 for unknown types (matches original behavior where all values were i64)
92
+ return :i64 unless stone_type
93
+
94
+ # Handle union types
95
+ return union_result_type(stone_type) if stone_type.is_a?(Stone::Type::Union)
96
+
97
+ case stone_type.name
98
+ when "Null" then :null
99
+ when "Bool" then :boolean
100
+ when "Int" then :i64
101
+ when "String" then :string
102
+ else :pointer # Records and other pointer types
103
+ end
104
+ end
105
+
106
+ private def union_result_type(_union_type)
107
+ # All union variables return the union struct, which is extracted by MixedUnionExtractor
108
+ :mixed_union_ptr
109
+ end
110
+
111
+ private def tagged_union_field_access?(node)
112
+ return false unless node.is_a?(Stone::AST::PropertyAccess)
113
+ return false unless node.union_field_access?(module_ref)
114
+
115
+ union_type = get_union_type_for_property_access(node)
116
+ union_type&.needs_runtime_type_tag?
117
+ end
118
+
119
+ private def mixed_union_field_access?(node)
120
+ return false unless node.is_a?(Stone::AST::PropertyAccess)
121
+ return false unless node.union_field_access?(module_ref)
122
+
123
+ union_type = get_union_type_for_property_access(node)
124
+ union_type && !union_type.homogeneous?
125
+ end
126
+
127
+ private def union_field_access?(node)
128
+ return false unless node.is_a?(Stone::AST::PropertyAccess)
129
+
130
+ node.union_field_access?(module_ref)
131
+ end
132
+
133
+ RESULT_CONVERTERS = {
134
+ "i64" => ->(r, _) { r.to_i },
135
+ "boolean" => ->(r, _) { r.to_i != 0 },
136
+ "null" => ->(_r, _) { nil },
137
+ "pointer" => ->(r, _) { r.to_value_ptr.to_i }
138
+ }.freeze
139
+
140
+ # Convert JIT result to Ruby value based on result type.
141
+ # - i64 results use result.to_i
142
+ # - ptr results use result.to_value_ptr (not to_ptr!)
143
+ private def convert_to_ruby(result, result_type)
144
+ converter = RESULT_CONVERTERS[result_type.to_s]
145
+ return converter.call(result, self) if converter
146
+
147
+ convert_complex_result(result, result_type)
148
+ end
149
+
150
+ private def convert_complex_result(result, result_type)
151
+ case result_type.to_s
152
+ when "string" then read_string_from_pointer(result.to_value_ptr.to_i)
153
+ when "union_value" then convert_union_value_to_ruby(result.to_i)
154
+ when "mixed_union_value" then convert_mixed_union_value(result.to_i)
155
+ when "tagged_union_ptr" then convert_tagged_union_ptr(result.to_value_ptr.to_i)
156
+ else fail "Don't know how to convert result type to Ruby: #{result_type}"
157
+ end
158
+ end
159
+
160
+ private def convert_tagged_union_ptr(ptr_addr)
161
+ return nil if ptr_addr.zero?
162
+
163
+ last_child = children&.last
164
+ union_type = get_union_type_for_property_access(last_child)
165
+ return nil unless union_type
166
+
167
+ TaggedUnionConverter.new(ptr_addr, union_type, method(:type_constant_address)).convert
168
+ end
169
+
170
+ # Get the JIT address of a type constant by name.
171
+ # Caches addresses since they're stable for the JIT session.
172
+ private def type_constant_address(type_name)
173
+ @type_constant_addresses ||= {}
174
+ @type_constant_addresses[type_name] ||= lookup_type_constant_address(type_name)
175
+ end
176
+
177
+ private def lookup_type_constant_address(type_name)
178
+ global_name = "Stone.Type.#{type_name}"
179
+ global = module_ref.globals[global_name]
180
+ return 0 unless global
181
+
182
+ jit_engine.pointer_to_global(global).to_i
183
+ end
184
+
185
+ private def convert_union_value_to_ruby(value)
186
+ # Get the union type to determine how to interpret the value
187
+ last_child = children&.last
188
+ return value unless last_child.is_a?(Stone::AST::PropertyAccess)
189
+
190
+ union_type = get_union_type_for_property_access(last_child)
191
+ return value unless union_type
192
+
193
+ convert_based_on_union_type(value, union_type)
194
+ end
195
+
196
+ private def convert_mixed_union_value(value)
197
+ last_child = children&.last
198
+ union_type = get_union_type_for_last_child(last_child)
199
+ return value unless union_type
200
+
201
+ MixedUnionValueConverter.new(value, union_type).convert
202
+ end
203
+
204
+ private def get_union_type_for_last_child(node)
205
+ case node
206
+ when Stone::AST::PropertyAccess
207
+ get_union_type_for_property_access(node)
208
+ when Stone::AST::Reference
209
+ # For References, look up the type declaration
210
+ lookup_type_declaration(node.identifier)
211
+ end
212
+ end
213
+
214
+ private def get_union_type_for_property_access(property_access)
215
+ record_type_name = property_access.get_record_type_name(module_ref)
216
+ return nil unless record_type_name
217
+
218
+ record_def = module_ref.record_types[record_type_name]
219
+ return nil unless record_def
220
+
221
+ annotation = record_def.field_type_annotation(property_access.property)
222
+ return nil unless Stone::AST::FieldHelpers.union_annotation?(annotation)
223
+
224
+ Stone::AST::FieldHelpers.resolve_field_type({type: annotation})
225
+ end
226
+
227
+ private def convert_based_on_union_type(value, union_type)
228
+ UnionValueConverter.new(value, union_type).convert
229
+ end
230
+
231
+ # Converts tagged union structs from memory to Ruby values using the runtime type tag.
232
+ # The union struct layout is: { ptr type_tag, [N x i8] payload }
233
+ # The type_tag is a pointer to a Stone::Type constant, which we compare to known type constants.
234
+ class TaggedUnionConverter
235
+ def initialize(union_ptr_addr, union_type, address_lookup)
236
+ @union_ptr = FFI::Pointer.new(union_ptr_addr)
237
+ @union_type = union_type
238
+ @alternatives = union_type.alternatives
239
+ @address_lookup = address_lookup # Proc that takes type_name and returns JIT address
240
+ end
241
+
242
+ def convert
243
+ type_tag_addr = read_type_tag
244
+ type_name = identify_type(type_tag_addr)
245
+ read_and_convert_payload(type_name)
246
+ end
247
+
248
+ private def read_type_tag
249
+ # Type tag is at offset 0, it's a pointer (8 bytes)
250
+ @union_ptr.read_pointer.to_i
251
+ end
252
+
253
+ private def payload_ptr
254
+ # Payload starts at offset 8 (after the type tag pointer)
255
+ @union_ptr + 8
256
+ end
257
+
258
+ private def identify_type(type_tag_addr)
259
+ # Compare the type tag address against known type constants
260
+ @alternatives.each do |alt|
261
+ expected_addr = @address_lookup.call(alt.name)
262
+ return alt.name if type_tag_addr == expected_addr
263
+ end
264
+
265
+ # Fallback: if we can't identify the type, return the first non-null alternative
266
+ @alternatives.find { |alt| alt.name != "Null" }&.name || "Int"
267
+ end
268
+
269
+ # Read payload with the correct size based on type, then convert to Ruby
270
+ private def read_and_convert_payload(type_name)
271
+ case type_name
272
+ when "Null" then nil
273
+ when "Bool" then payload_ptr.read_uint8 != 0
274
+ when "Int" then payload_ptr.read_int64
275
+ when "String" then read_string(payload_ptr.read_pointer.to_i)
276
+ else payload_ptr.read_int64 # Records and other pointer types
277
+ end
278
+ end
279
+
280
+ private def read_string(ptr_addr)
281
+ return "" if ptr_addr.zero?
282
+
283
+ FFI::Pointer.new(ptr_addr).read_string.force_encoding(Encoding::UTF_8)
284
+ end
285
+ end
286
+
287
+ # Converts raw i64 values from LLVM to Ruby values based on union type alternatives.
288
+ # NOTE: This is a temporary solution until proper runtime type dispatch is implemented.
289
+ # The limitation is that Int(0) in Int | Null will return nil, not 0.
290
+ class UnionValueConverter
291
+ def initialize(value, union_type)
292
+ @value = value
293
+ @alternatives = union_type.alternatives
294
+ end
295
+
296
+ def convert
297
+ return nil if null_value?
298
+ return convert_string if string_only?
299
+ return convert_record if record_only?
300
+ return @value if has?("Int")
301
+ return @value != 0 if has?("Bool")
302
+
303
+ @value
304
+ end
305
+
306
+ private def null_value? = @value.zero? && has?("Null")
307
+ private def string_only? = has?("String") && !has?("Int") && !has?("Bool") && !any_record?
308
+ private def record_only? = any_record? && !has?("Int") && !has?("Bool") && !has?("String")
309
+ private def has?(name) = @alternatives.any? { |alt| alt.name == name }
310
+ private def any_record? = @alternatives.any?(&:record?)
311
+
312
+ private def convert_string
313
+ return "" if @value.zero?
314
+
315
+ FFI::Pointer.new(@value).read_string.force_encoding(Encoding::UTF_8)
316
+ end
317
+
318
+ private def convert_record
319
+ return nil if @value.zero?
320
+
321
+ # Returns raw pointer value. Chained property access (e.g., o.value.x)
322
+ # requires compile-time handling in PropertyAccess, not Ruby conversion.
323
+ @value
324
+ end
325
+ end
326
+
327
+ # Converts raw i64 payload values from mixed unions to Ruby values.
328
+ # For mixed unions (e.g., Int | String), the payload is extracted as i64.
329
+ # Without runtime type tag access, we use heuristics based on the alternatives:
330
+ # - If Int is an alternative, and value looks like a valid Int, return as Int
331
+ # - If String is an alternative, and value looks like a pointer, read as String
332
+ # NOTE: This has limitations - we can't distinguish Int(0) from NULL, or
333
+ # a small Int that happens to look like a valid pointer address.
334
+ class MixedUnionValueConverter
335
+ def initialize(value, union_type)
336
+ @value = value
337
+ @alternatives = union_type.alternatives
338
+ end
339
+
340
+ def convert
341
+ return nil if null_value?
342
+ return convert_int_or_string if has?("Int") && has?("String")
343
+
344
+ convert_single_type
345
+ end
346
+
347
+ private def null_value? = @value.zero? && has?("Null")
348
+
349
+ private def convert_int_or_string
350
+ looks_like_pointer? ? read_string : @value
351
+ end
352
+
353
+ private def convert_single_type
354
+ return @value if has?("Int")
355
+ return @value != 0 if has?("Bool")
356
+ return read_string if has?("String")
357
+
358
+ @value
359
+ end
360
+
361
+ private def has?(name) = @alternatives.any? { |alt| alt.name == name }
362
+
363
+ private def looks_like_pointer?
364
+ # Heuristic: heap pointers are typically large addresses, aligned to 8 bytes
365
+ # Small values (< 0x1000) are likely integers
366
+ # This is imperfect but works for common cases
367
+ @value > 0x1000 && (@value % 8).zero?
368
+ end
369
+
370
+ private def read_string
371
+ return "" if @value.zero?
372
+
373
+ FFI::Pointer.new(@value).read_string.force_encoding(Encoding::UTF_8)
374
+ end
375
+ end
376
+
377
+ private def read_string_from_pointer(ptr_addr)
378
+ return "" if ptr_addr.zero?
379
+
380
+ FFI::Pointer.new(ptr_addr).read_string.force_encoding(Encoding::UTF_8)
381
+ end
382
+
383
+ private def jit_engine
384
+ @jit_engine ||= LLVM::JITCompiler.new(module_ref)
385
+ end
386
+
387
+ private def global_function(function_name)
388
+ module_ref.functions[function_name]
389
+ end
390
+
391
+ private def module_ref
392
+ @module_ref ||= create_module
393
+ end
394
+
395
+ private def create_module
396
+ LLVM::Module.new("__program_unit__").tap do |mod|
397
+ Stone::BuiltIns.new(mod).setup
398
+ generate_top_function(mod)
399
+ end
400
+ end
401
+
402
+ # Generate IR for all code that's directly in the module.
403
+ # TODO: Maybe pass in `ARGV` and `ENV`.
404
+ # ... for `ARGV`, we'll probably need to implement `main(argc, argv, envp)`.
405
+ # ... for `ENV`, we can probably call `getenv`, maybe `environ`.
406
+ # Look into run_function_as_main(engine, fn, argc, argv, envp)
407
+ private def generate_top_function(mod)
408
+ @top_function.generate(mod)
409
+ end
410
+ end
411
+ end
412
+ end