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
data/lib/stone/rtti.rb ADDED
@@ -0,0 +1,182 @@
1
+ require "llvm/core"
2
+
3
+
4
+ module Stone
5
+ # Runtime Type Information (RTTI) infrastructure.
6
+ # Generates global type constants in LLVM IR for runtime type introspection.
7
+ #
8
+ # Type kind enum:
9
+ # 0 = primitive (Int, Bool, String, Null)
10
+ # 1 = record
11
+ # 2 = union
12
+ # 3 = function
13
+ # 4 = type (metatype)
14
+ class RTTI
15
+
16
+ KIND_PRIMITIVE = 0
17
+ KIND_RECORD = 1
18
+ KIND_UNION = 2
19
+ KIND_FUNCTION = 3
20
+ KIND_TYPE = 4
21
+
22
+ def initialize(mod)
23
+ @mod = mod
24
+ end
25
+
26
+ def setup
27
+ define_type_struct
28
+ generate_primitive_type_constants
29
+ end
30
+
31
+ # Get or create a type constant for the given Stone type
32
+ def self.type_constant_for(mod, type)
33
+ name = type_constant_name(type)
34
+ mod.globals[name] || create_type_constant(mod, type)
35
+ end
36
+
37
+ def self.type_constant_name(type)
38
+ "Stone.Type.#{type.name}"
39
+ end
40
+
41
+ private def define_type_struct
42
+ @type_struct = self.class.type_struct_type
43
+ end
44
+
45
+ # Cache the struct type at the class level since it's the same for all modules
46
+ def self.type_struct_type
47
+ @type_struct_type ||= create_type_struct_type
48
+ end
49
+
50
+ def self.create_type_struct_type
51
+ # %Stone.Type = type { ptr, i64, i8, ptr }
52
+ # Fields: name (string ptr), size (bytes), kind (enum), fields (FieldList ptr)
53
+ LLVM::Type.struct([
54
+ LLVM::Type.pointer, # name - pointer to null-terminated string
55
+ LLVM::Int64.type, # size - size in bytes
56
+ LLVM::Int8.type, # kind - type kind enum
57
+ LLVM::Type.pointer # fields - pointer to FieldList (or null for primitives)
58
+ ], false)
59
+ end
60
+
61
+ # %Stone.FieldList = type { ptr, ptr, ptr }
62
+ # Fields: name (string ptr), field_type (Type ptr), tail (FieldList ptr or null)
63
+ def self.field_list_struct_type
64
+ @field_list_struct_type ||= LLVM::Type.struct([
65
+ LLVM::Type.pointer, # name - pointer to null-terminated string
66
+ LLVM::Type.pointer, # field_type - pointer to Type constant
67
+ LLVM::Type.pointer # tail - pointer to next FieldList (or null)
68
+ ], false)
69
+ end
70
+
71
+ private def type_struct
72
+ @type_struct ||= self.class.type_struct_type
73
+ end
74
+
75
+ private def generate_primitive_type_constants
76
+ generate_type_constant("Int", 8, KIND_PRIMITIVE)
77
+ generate_type_constant("Bool", 1, KIND_PRIMITIVE)
78
+ generate_type_constant("String", 8, KIND_PRIMITIVE) # pointer size
79
+ generate_type_constant("Null", 0, KIND_PRIMITIVE)
80
+ generate_type_constant("Type", 8, KIND_TYPE) # pointer size
81
+ end
82
+
83
+ private def generate_type_constant(name, size, kind, fields_ptr = nil)
84
+ name_global = create_name_string(name)
85
+ fields_value = fields_ptr || LLVM::Type.pointer.null_pointer
86
+ values = [name_global, LLVM::Int64.from_i(size), LLVM::Int8.from_i(kind), fields_value]
87
+ add_type_global("Stone.Type.#{name}", values)
88
+ end
89
+
90
+ private def add_type_global(constant_name, values)
91
+ @mod.globals.add(type_struct, constant_name).tap do |global|
92
+ global.initializer = LLVM::ConstantStruct.named_const(type_struct, values)
93
+ global.linkage = :internal
94
+ global.global_constant = true
95
+ end
96
+ end
97
+
98
+ private def create_name_string(name)
99
+ string_name = "Stone.Type.#{name}.name"
100
+ @mod.globals[string_name] || create_string_constant(string_name, name)
101
+ end
102
+
103
+ private def create_string_constant(global_name, value)
104
+ array_type = LLVM::Type.array(LLVM::Int8.type, value.bytesize + 1)
105
+ @mod.globals.add(array_type, global_name).tap do |global|
106
+ global.initializer = LLVM::ConstantArray.string(value, true)
107
+ global.linkage = :private
108
+ global.global_constant = true
109
+ end
110
+ end
111
+
112
+ # Generate a type constant for a record type with field information
113
+ def self.generate_record_type_constant(mod, record_name, size_bytes, fields = [])
114
+ rtti = new(mod)
115
+ rtti.__send__(:define_type_struct)
116
+ fields_ptr = rtti.__send__(:generate_field_list, record_name, fields)
117
+ rtti.__send__(:generate_type_constant, record_name, size_bytes, KIND_RECORD, fields_ptr)
118
+ end
119
+
120
+ private def generate_field_list(record_name, fields)
121
+ return LLVM::Type.pointer.null_pointer if fields.empty?
122
+
123
+ # Generate field entries in reverse order so we can link them correctly
124
+ field_entries = []
125
+ fields.reverse_each.with_index do |field, reverse_index|
126
+ index = fields.length - 1 - reverse_index
127
+ rest_ptr = field_entries.last || LLVM::Type.pointer.null_pointer
128
+ entry = generate_field_list_entry(record_name, field, index, rest_ptr)
129
+ field_entries << entry
130
+ end
131
+
132
+ # Return the first field entry (which is last in our reversed list)
133
+ field_entries.last
134
+ end
135
+
136
+ private def generate_field_list_entry(record_name, field, index, rest_ptr)
137
+ name_global = create_field_name_string(record_name, field[:name])
138
+ type_ptr = field_type_constant(field)
139
+ values = [name_global, type_ptr, rest_ptr]
140
+ add_field_list_global("Stone.Field.#{record_name}.#{index}.#{field[:name]}", values)
141
+ end
142
+
143
+ private def field_type_constant(field)
144
+ field_type = Stone::AST::FieldHelpers.resolve_field_type(field) || Stone::Type::Int
145
+ self.class.type_constant_for(@mod, field_type)
146
+ end
147
+
148
+ private def add_field_list_global(constant_name, values)
149
+ struct = self.class.field_list_struct_type
150
+ @mod.globals.add(struct, constant_name).tap do |global|
151
+ global.initializer = LLVM::ConstantStruct.named_const(struct, values)
152
+ global.linkage = :internal
153
+ global.global_constant = true
154
+ end
155
+ end
156
+
157
+ private def create_field_name_string(record_name, field_name)
158
+ string_name = "Stone.Field.#{record_name}.#{field_name}.name"
159
+ @mod.globals[string_name] || create_string_constant(string_name, field_name)
160
+ end
161
+
162
+ private_class_method def self.create_type_constant(mod, type)
163
+ rtti = new(mod)
164
+ rtti.__send__(:define_type_struct)
165
+ size = type.size_bytes
166
+ kind = type_kind(type)
167
+ rtti.__send__(:generate_type_constant, type.name, size, kind)
168
+ mod.globals[type_constant_name(type)]
169
+ end
170
+
171
+ private_class_method def self.type_kind(type)
172
+ return KIND_PRIMITIVE if type.primitive?
173
+ return KIND_RECORD if type.record?
174
+ return KIND_UNION if type.union?
175
+ return KIND_FUNCTION if type.function?
176
+ return KIND_TYPE if type == Stone::Type::Type
177
+
178
+ KIND_PRIMITIVE
179
+ end
180
+
181
+ end
182
+ end
@@ -0,0 +1,85 @@
1
+ module Stone
2
+ class Scope
3
+ attr_reader :parent, :definitions, :type_declarations
4
+
5
+ def initialize(parent = nil)
6
+ @parent = parent
7
+ @definitions = {}
8
+ @type_declarations = {}
9
+ end
10
+
11
+ def child
12
+ Scope.new(self)
13
+ end
14
+
15
+ def define(name, value:, location: nil)
16
+ @definitions[name] = {value:, location:}
17
+ end
18
+
19
+ def declare_type(name, type:, location: nil)
20
+ @type_declarations[name] = {type:, location:}
21
+ end
22
+
23
+ def lookup(name)
24
+ @definitions[name] || @parent&.lookup(name)
25
+ end
26
+
27
+ def lookup_local(name)
28
+ @definitions[name]
29
+ end
30
+
31
+ def lookup_type_declaration(name)
32
+ @type_declarations[name] || @parent&.lookup_type_declaration(name)
33
+ end
34
+
35
+ def defined_locally?(name)
36
+ @definitions.key?(name)
37
+ end
38
+
39
+ def type_declared_locally?(name)
40
+ @type_declarations.key?(name)
41
+ end
42
+
43
+ def declared_type(name)
44
+ decl = lookup_type_declaration(name)
45
+ decl&.dig(:type)
46
+ end
47
+
48
+ def type_declaration_location(name)
49
+ decl = lookup_type_declaration(name)
50
+ decl&.dig(:location)
51
+ end
52
+
53
+ # Resolve a type name to verify it exists in scope.
54
+ # Returns the type name if found, nil otherwise.
55
+ # Checks: built-in types, type declarations, definitions (constants).
56
+ # Note: lookup_type_declaration and lookup already traverse parent chain.
57
+ def lookup_type(name)
58
+ return name if builtin_type?(name)
59
+ return name if lookup_type_declaration(name)
60
+ return name if lookup(name)
61
+
62
+ nil
63
+ end
64
+
65
+ private def builtin_type?(name)
66
+ %w[Int Bool String Type Null].include?(name)
67
+ end
68
+
69
+ def depth
70
+ @parent ? @parent.depth + 1 : 0
71
+ end
72
+
73
+ def top_level?
74
+ @parent.nil?
75
+ end
76
+
77
+ def self.top_level
78
+ @top_level ||= new
79
+ end
80
+
81
+ def self.reset_top_level!
82
+ @top_level = nil
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,410 @@
1
+ require "stone/ast"
2
+ require "stone/ast/boolean_literal"
3
+ require "stone/ast/integer_literal"
4
+ require "stone/ast/null_literal"
5
+ require "stone/ast/string_literal"
6
+ require "stone/ast/reference"
7
+ require "stone/ast/type_reference"
8
+ require "stone/ast/type_of_expression"
9
+ require "stone/ast/type_annotation"
10
+ require "stone/ast/function_type_annotation"
11
+ require "stone/ast/union_type_annotation"
12
+ require "stone/ast/type_declaration"
13
+ require "stone/ast/function_call"
14
+ require "stone/ast/property_access"
15
+ require "stone/ast/program_unit"
16
+ require "stone/ast/constant_definition"
17
+ require "stone/ast/computed_property_definition"
18
+ require "stone/ast/lambda"
19
+ require "stone/ast/block"
20
+ require "stone/ast/record_definition"
21
+ require "stone/ast/record_instantiation"
22
+ require "stone/error/overflow"
23
+ require "grammy/tree/transformation"
24
+
25
+
26
+ module Stone
27
+ class Transform
28
+ include Grammy::Tree::Transformation
29
+
30
+ transform(:program_unit) do |node|
31
+ statement_list = node.find_child(:statement_list)
32
+ transformed_children = statement_list ? extract_all_statements(statement_list) : []
33
+ Stone::AST::ProgramUnit.new(transformed_children)
34
+ end
35
+
36
+ transform(:statement) do |node|
37
+ # Find the definition or expression within the statement.
38
+ meaningful_child = node.children.find { |child|
39
+ child.respond_to?(:name) && %i[definition expression].include?(child.name)
40
+ }
41
+ transform(meaningful_child) if meaningful_child
42
+ end
43
+
44
+ transform(:definition) do |node|
45
+ identifier_token = node.children.first
46
+ expression_node = node.find_child(:expression)
47
+ value_expression = transform(expression_node)
48
+
49
+ identifier_text = identifier_token.text
50
+
51
+ if identifier_text.include?("@")
52
+ # Computed property definition: Type@property
53
+ type_name, property_name = identifier_text.split("@", 2)
54
+ Stone::AST::ComputedPropertyDefinition.new(type_name, property_name, value_expression)
55
+ else
56
+ Stone::AST::ConstantDefinition.new(identifier_text, value_expression)
57
+ end
58
+ end
59
+
60
+ transform(:literal_boolean) do |node|
61
+ token = node.children.first
62
+ Stone::AST::BooleanLiteral.parse(token.text, token.start_location)
63
+ end
64
+
65
+ transform(:literal_null) do |node|
66
+ token = node.children.first
67
+ Stone::AST::NullLiteral.parse(token.text, token.start_location)
68
+ end
69
+
70
+ transform(:literal_string) do |node|
71
+ token = node.children.first
72
+ Stone::AST::StringLiteral.parse(token.text, token.start_location)
73
+ end
74
+
75
+ transform(:literal_i64) do |node|
76
+ token = node.children.first
77
+ Stone::AST::IntegerLiteral.parse(token.text, token.start_location)
78
+ end
79
+
80
+ transform(:reference) do |node|
81
+ identifier_token = node.children.first
82
+ Stone::AST::Reference.new(identifier_token.text)
83
+ end
84
+
85
+ transform(:type_reference) do |_node|
86
+ Stone::AST::TypeReference.new
87
+ end
88
+
89
+ transform(:type_of_expression) do |node|
90
+ expression_node = node.find_child(:expression)
91
+ inner_expression = transform(expression_node)
92
+ Stone::AST::TypeOfExpression.new(inner_expression)
93
+ end
94
+
95
+ transform(:primary) do |node|
96
+ # primary can be: literal | reference | lambda | block | parens(expression)
97
+ # For parenthesized expressions, find and transform the inner expression
98
+ expression_node = node.find_child(:expression)
99
+ if expression_node
100
+ transform(expression_node)
101
+ else
102
+ # For other primary nodes (literal, reference, etc.), let default recursion handle them
103
+ result = nil
104
+ node.children.each do |child|
105
+ if child.respond_to?(:name)
106
+ result = transform(child)
107
+ break if result
108
+ end
109
+ end
110
+ result
111
+ end
112
+ end
113
+
114
+ transform(:postfix_expression) do |node|
115
+ # Grammar: primary + (argument_list | property_accessor)[0..]
116
+ # Build up expression from left to right: primary -> func_call -> property_access -> ...
117
+ primary_node = node.find_child(:primary)
118
+ base = transform(primary_node)
119
+
120
+ # Get all postfix operations (argument_list and property_accessor nodes)
121
+ postfix_ops = node.children.select { |child|
122
+ child.respond_to?(:name) && %i[argument_list property_accessor].include?(child.name)
123
+ }
124
+
125
+ # Process each postfix operation
126
+ postfix_ops.reduce(base) do |receiver, op_node|
127
+ if op_node.respond_to?(:name) && op_node.name == :argument_list
128
+ # Function call: receiver(args)
129
+ arguments = extract_expressions_from(op_node)
130
+ # If receiver is a Reference, use its identifier as function name
131
+ fail "Function calls on non-reference receivers not yet supported" unless receiver.is_a?(Stone::AST::Reference)
132
+ Stone::AST::FunctionCall.new(receiver.identifier, arguments)
133
+ elsif op_node.respond_to?(:name) && op_node.name == :property_accessor
134
+ # Property access: receiver.property
135
+ # The property_accessor node contains: str(".") + identifier
136
+ # Find the identifier (it's the last Match that's not a dot)
137
+ identifier_match = op_node.children.reverse.find { |c| c.is_a?(Grammy::Match) && c.text != "." }
138
+ Stone::AST::PropertyAccess.new(receiver, identifier_match.text)
139
+ else
140
+ receiver
141
+ end
142
+ end
143
+ end
144
+
145
+ transform(:comparison_operation) do |node|
146
+ # Desugar comparison operations to function calls:
147
+ # - Binary: `5 < 3` → `<(5, 3)`
148
+ # - Chained: `1 < 2 < 3` → `<(1, 2, 3)`
149
+ boolean_operations = node.children.select { |c| c.respond_to?(:name) && c.name == :boolean_operation }
150
+
151
+ # All operators in a chain must be the same (e.g., all `<` or all `==`)
152
+ operators = node.children.select { |c| c.is_a?(Grammy::Match) && c.text !~ /\s/ }
153
+ operator_name = operators.first.text
154
+
155
+ # Verify all operators are the same
156
+ fail "Mixed comparison operators not allowed: use parentheses to clarify precedence" unless operators.all? { |op| op.text == operator_name }
157
+
158
+ # Collect all operands and create varargs function call
159
+ operands = boolean_operations.map { |expr| transform(expr) }
160
+ Stone::AST::FunctionCall.new(operator_name, operands)
161
+ end
162
+
163
+ transform(:boolean_operation) do |node|
164
+ # Desugar boolean operations to function calls:
165
+ # - Binary: `TRUE ∧ FALSE` → `∧(TRUE, FALSE)`
166
+ # - Chained: `TRUE ∧ FALSE ∧ TRUE` → `∧(TRUE, FALSE, TRUE)`
167
+ # - Zero operators: `5` → just return the postfix_expression
168
+ postfix_expressions = node.children.select { |c| c.respond_to?(:name) && c.name == :postfix_expression }
169
+
170
+ # All operators in a chain must be the same (e.g., all `∧` or all `∨`)
171
+ operators = node.children.select { |c| c.is_a?(Grammy::Match) && c.text !~ /\s/ }
172
+
173
+ # If there are no boolean operators, just return the single postfix_expression
174
+ next transform(postfix_expressions.first) if operators.empty?
175
+
176
+ operator_name = operators.first.text
177
+
178
+ # Verify all operators are the same
179
+ fail "Mixed boolean operators not allowed: use parentheses to clarify precedence" unless operators.all? { |op| op.text == operator_name }
180
+
181
+ # TODO: Consider detecting mixing of comparison and boolean operators without parentheses
182
+ # (e.g., `a < b ∧ c > d` parsed as `a < (b ∧ c) > d`). This is valid in the current grammar
183
+ # but may produce unexpected results.
184
+
185
+ # Collect all operands and create varargs function call
186
+ operands = postfix_expressions.map { |expr| transform(expr) }
187
+ Stone::AST::FunctionCall.new(operator_name, operands)
188
+ end
189
+
190
+ transform(:lambda) do |node|
191
+ parameter_list_node = node.find_child(:parameter_list)
192
+ block_node = node.find_child(:block)
193
+ block_body_node = block_node ? block_node.find_child(:statement_list) : nil
194
+
195
+ parameters = extract_parameters_from(parameter_list_node)
196
+ body_statements = block_body_node ? extract_all_statements(block_body_node) : []
197
+
198
+ Stone::AST::Lambda.new(parameters, body_statements)
199
+ end
200
+
201
+ transform(:block) do |node|
202
+ statement_list_node = node.find_child(:statement_list)
203
+ body_statements = statement_list_node ? extract_all_statements(statement_list_node) : []
204
+
205
+ Stone::AST::Block.new(body_statements)
206
+ end
207
+
208
+ transform(:type_declaration) do |node|
209
+ identifier = extract_field_name(node)
210
+ location = extract_location(node)
211
+ type_annotation_node = node.find_child(:type_annotation)
212
+ type_annotation = transform_type_annotation(type_annotation_node)
213
+
214
+ Stone::AST::TypeDeclaration.new(identifier, type_annotation, location:)
215
+ end
216
+
217
+ transform(:record_definition) do |node|
218
+ type_declarations = node.children.select { |child| child.respond_to?(:name) && child.name == :type_declaration }
219
+
220
+ fields = type_declarations.map { |type_decl|
221
+ extract_field_info(type_decl)
222
+ }
223
+
224
+ Stone::AST::RecordDefinition.new(fields)
225
+ end
226
+
227
+ private def extract_all_statements(statement_list_node)
228
+ statements = []
229
+ collect_statements(statement_list_node, statements)
230
+ statements
231
+ end
232
+
233
+ private def extract_from_children(node)
234
+ children = node.respond_to?(:children) ? node.children : (node if node.is_a?(Array))
235
+ return [] unless children
236
+
237
+ children.flat_map { |child| extract_expressions_from(child) }
238
+ end
239
+
240
+ private def extract_expressions_from(node)
241
+ return [] unless node
242
+ return [transform(node)] if expression_node?(node)
243
+
244
+ extract_from_children(node)
245
+ end
246
+
247
+ private def expression_node?(node)
248
+ node.respond_to?(:name) && node.name == :expression
249
+ end
250
+
251
+ private def extract_parameters_from(parameter_list_node)
252
+ return [] unless parameter_list_node
253
+
254
+ identifiers = []
255
+ collect_identifiers(parameter_list_node, identifiers)
256
+ identifiers
257
+ end
258
+
259
+ private def collect_identifiers(node, identifiers)
260
+ return unless node
261
+ return if add_token_identifier(node, identifiers)
262
+ return if recurse_into_children(node, identifiers)
263
+
264
+ add_match_identifier(node, identifiers)
265
+ end
266
+
267
+ private def add_token_identifier(node, identifiers)
268
+ return false unless token_identifier?(node)
269
+
270
+ identifiers << node.text
271
+ true
272
+ end
273
+
274
+ private def token_identifier?(node)
275
+ node.respond_to?(:name) && node.name == :identifier
276
+ end
277
+
278
+ private def recurse_into_children(node, identifiers)
279
+ return false unless node.respond_to?(:children) && !node.children.empty?
280
+
281
+ node.children.each do |child|
282
+ collect_identifiers(child, identifiers)
283
+ end
284
+ true
285
+ end
286
+
287
+ private def add_match_identifier(node, identifiers)
288
+ return if node.respond_to?(:name) # Skip ParseTree nodes without children
289
+ return unless valid_identifier_match?(node)
290
+
291
+ identifiers << node.to_s
292
+ end
293
+
294
+ private def valid_identifier_match?(node)
295
+ node.respond_to?(:to_s) && node.to_s.match?(/^[a-zA-Z_][a-zA-Z0-9_]*$/)
296
+ end
297
+
298
+ private def collect_statements(node, statements)
299
+ return unless node
300
+
301
+ # Look for statement, definition, or expression nodes
302
+ if node.respond_to?(:name) && %i[statement definition expression].include?(node.name)
303
+ transformed = transform(node)
304
+ statements << transformed if transformed
305
+ elsif node.respond_to?(:children)
306
+ node.children.each { |child| collect_statements(child, statements) }
307
+ end
308
+ end
309
+
310
+ private def extract_location(node)
311
+ # Find the first Match child to get the start location
312
+ first_match = node.children.find { |child| child.is_a?(Grammy::Match) }
313
+ return nil unless first_match
314
+ return nil unless first_match.respond_to?(:start_location)
315
+
316
+ loc = first_match.start_location
317
+ {line: loc.line, column: loc.column}
318
+ end
319
+
320
+ private def transform_type_annotation(node)
321
+ return nil unless node
322
+
323
+ type_union_node = node.find_child(:type_union)
324
+ type_union_node ? transform_type_union(type_union_node) : transform_type_term(node)
325
+ end
326
+
327
+ private def transform_type_union(node)
328
+ return nil unless node
329
+
330
+ type_terms = node.children.select { |c| c.respond_to?(:name) && c.name == :type_term }
331
+ wrap_in_union_if_multiple(type_terms.map { |t| transform_type_term(t) })
332
+ end
333
+
334
+ private def transform_type_term(node)
335
+ return nil unless node
336
+ return transform_type_function(node.find_child(:type_function)) if node.find_child(:type_function)
337
+ return transform_type_annotation(node.find_child(:type_annotation)) if node.find_child(:type_annotation)
338
+
339
+ type_name_node = node.find_child(:type_name)
340
+ type_name_node ? Stone::AST::TypeAnnotation.new(extract_identifier_from(type_name_node)) : nil
341
+ end
342
+
343
+ private def transform_type_function(node)
344
+ return nil unless node
345
+
346
+ Stone::AST::FunctionTypeAnnotation.new(
347
+ extract_type_params(node.find_child(:type_params)),
348
+ transform_type_return(node.find_child(:type_return))
349
+ )
350
+ end
351
+
352
+ private def transform_type_return(node)
353
+ return nil unless node
354
+
355
+ type_return_union = node.find_child(:type_return_union)
356
+ return transform_type_return_union(type_return_union) if type_return_union
357
+
358
+ type_annotation = node.find_child(:type_annotation)
359
+ type_annotation ? transform_type_annotation(type_annotation) : nil
360
+ end
361
+
362
+ private def transform_type_return_union(node)
363
+ return nil unless node
364
+
365
+ type_names = node.children.select { |c| c.respond_to?(:name) && c.name == :type_name }
366
+ wrap_in_union_if_multiple(type_names.map { |tn| Stone::AST::TypeAnnotation.new(extract_identifier_from(tn)) })
367
+ end
368
+
369
+ private def wrap_in_union_if_multiple(alternatives)
370
+ alternatives.length == 1 ? alternatives.first : Stone::AST::UnionTypeAnnotation.new(alternatives)
371
+ end
372
+
373
+ private def extract_type_params(params_node)
374
+ return [] unless params_node
375
+
376
+ type_annotation_nodes = params_node.children.select { |child|
377
+ child.respond_to?(:name) && child.name == :type_annotation
378
+ }
379
+ type_annotation_nodes.map { |node| transform_type_annotation(node) }
380
+ end
381
+
382
+ private def extract_identifier_from(node)
383
+ return nil unless node
384
+
385
+ # Find the identifier Match within the node
386
+ if node.is_a?(Grammy::Match)
387
+ node.text
388
+ else
389
+ match = node.children.find { |child| child.is_a?(Grammy::Match) }
390
+ match&.text
391
+ end
392
+ end
393
+
394
+ private def extract_field_info(type_decl)
395
+ # type_declaration: identifier + ws! + str("::") + ws! + type_annotation
396
+ type_annotation_node = type_decl.find_child(:type_annotation)
397
+ type_annotation = transform_type_annotation(type_annotation_node)
398
+ {name: extract_field_name(type_decl), type: type_annotation, type_name: type_annotation&.to_s}
399
+ end
400
+
401
+ private def extract_field_name(type_decl)
402
+ # Find the field name - it's the first Match with text (not whitespace or ::)
403
+ type_decl.children.each do |child|
404
+ return child.text if child.is_a?(Grammy::Match) && child.text && !child.text.strip.empty? && child.text != "::"
405
+ end
406
+ nil
407
+ end
408
+
409
+ end
410
+ end