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.
- checksums.yaml +4 -4
- data/.rubocop.yml +115 -5
- data/LICENSE.md +2 -2
- data/README.md +53 -106
- data/lib/disable_ssl_verify.rb +5 -0
- data/lib/extensions/argf.rb +8 -0
- data/lib/extensions/boolean.rb +48 -0
- data/lib/extensions/class.rb +10 -0
- data/lib/extensions/enumerable.rb +42 -0
- data/lib/extensions/integer.rb +7 -0
- data/lib/extensions/llvm_module.rb +101 -0
- data/lib/extensions/module.rb +34 -0
- data/lib/extensions/string.rb +29 -0
- data/lib/stone/ast/block.rb +88 -0
- data/lib/stone/ast/boolean_literal.rb +43 -0
- data/lib/stone/ast/computed_property_definition.rb +32 -0
- data/lib/stone/ast/constant_definition.rb +124 -0
- data/lib/stone/ast/expression.rb +12 -0
- data/lib/stone/ast/function_call.rb +291 -0
- data/lib/stone/ast/function_type_annotation.rb +31 -0
- data/lib/stone/ast/integer_literal.rb +46 -0
- data/lib/stone/ast/lambda.rb +126 -0
- data/lib/stone/ast/null_literal.rb +29 -0
- data/lib/stone/ast/program_unit/top_function.rb +209 -0
- data/lib/stone/ast/program_unit.rb +412 -0
- data/lib/stone/ast/property_access.rb +557 -0
- data/lib/stone/ast/record_definition.rb +180 -0
- data/lib/stone/ast/record_instantiation.rb +141 -0
- data/lib/stone/ast/reference.rb +145 -0
- data/lib/stone/ast/string_literal.rb +79 -0
- data/lib/stone/ast/two_phase_processing.rb +36 -0
- data/lib/stone/ast/type_annotation.rb +26 -0
- data/lib/stone/ast/type_declaration.rb +28 -0
- data/lib/stone/ast/type_of_expression.rb +41 -0
- data/lib/stone/ast/type_reference.rb +28 -0
- data/lib/stone/ast/union_type_annotation.rb +26 -0
- data/lib/stone/ast.rb +64 -0
- data/lib/stone/built_ins.rb +245 -0
- data/lib/stone/error/argument_error.rb +7 -0
- data/lib/stone/error/arity_error.rb +7 -0
- data/lib/stone/error/overflow.rb +18 -0
- data/lib/stone/error/property_error.rb +7 -0
- data/lib/stone/error/reference_error.rb +4 -0
- data/lib/stone/error/type_error.rb +7 -0
- data/lib/stone/error.rb +12 -0
- data/lib/stone/grammar.rb +124 -0
- data/lib/stone/libc.rb +27 -0
- data/lib/stone/prelude.stone +11 -0
- data/lib/stone/rtti.rb +182 -0
- data/lib/stone/scope.rb +85 -0
- data/lib/stone/transform.rb +410 -0
- data/lib/stone/type.rb +323 -0
- data/lib/stone/type_context.rb +38 -0
- data/lib/stone/type_registry.rb +73 -0
- data/lib/stone/types.rb +104 -0
- data/lib/stone.rb +70 -0
- metadata +54 -24
- data/CHANGELOG.md +0 -28
- data/Rakefile +0 -5
- data/config/default.yml +0 -277
- data/lib/rubocop/boochtek/plugin.rb +0 -43
- data/lib/rubocop/boochtek/version.rb +0 -7
- data/lib/rubocop/boochtek.rb +0 -18
- data/lib/rubocop/cop/boochtek/compact_endless_methods.rb +0 -103
- 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
|
data/lib/stone/scope.rb
ADDED
|
@@ -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
|