t-ruby 0.0.38 → 0.0.40
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/LICENSE +21 -17
- data/README.md +3 -4
- data/lib/t_ruby/ast_type_inferrer.rb +511 -0
- data/lib/t_ruby/body_parser.rb +561 -0
- data/lib/t_ruby/cli.rb +3 -0
- data/lib/t_ruby/compiler.rb +177 -60
- data/lib/t_ruby/config.rb +7 -0
- data/lib/t_ruby/heredoc_detector.rb +74 -0
- data/lib/t_ruby/ir.rb +104 -7
- data/lib/t_ruby/lsp_server.rb +1 -1
- data/lib/t_ruby/parser.rb +228 -45
- data/lib/t_ruby/type_checker.rb +17 -14
- data/lib/t_ruby/type_env.rb +127 -0
- data/lib/t_ruby/version.rb +1 -1
- data/lib/t_ruby/watcher.rb +26 -21
- data/lib/t_ruby.rb +4 -1
- metadata +6 -3
- data/lib/t_ruby/rbs_generator.rb +0 -69
data/lib/t_ruby/parser.rb
CHANGED
|
@@ -7,13 +7,22 @@ module TRuby
|
|
|
7
7
|
# Type names that are recognized as valid
|
|
8
8
|
VALID_TYPES = %w[String Integer Boolean Array Hash Symbol void nil].freeze
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
# Pattern for method/variable names that supports Unicode characters
|
|
11
|
+
# \p{L} matches any Unicode letter, \p{N} matches any Unicode number
|
|
12
|
+
IDENTIFIER_CHAR = '[\p{L}\p{N}_]'
|
|
13
|
+
# Method names can end with ? or !
|
|
14
|
+
METHOD_NAME_PATTERN = "#{IDENTIFIER_CHAR}+[?!]?".freeze
|
|
15
|
+
# Visibility modifiers for method definitions
|
|
16
|
+
VISIBILITY_PATTERN = '(?:(?:private|protected|public)\s+)?'
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
attr_reader :source, :ir_program
|
|
19
|
+
|
|
20
|
+
def initialize(source, parse_body: true)
|
|
13
21
|
@source = source
|
|
14
22
|
@lines = source.split("\n")
|
|
15
|
-
@
|
|
16
|
-
@type_parser = ParserCombinator::TypeParser.new
|
|
23
|
+
@parse_body = parse_body
|
|
24
|
+
@type_parser = ParserCombinator::TypeParser.new
|
|
25
|
+
@body_parser = BodyParser.new if parse_body
|
|
17
26
|
@ir_program = nil
|
|
18
27
|
end
|
|
19
28
|
|
|
@@ -21,9 +30,19 @@ module TRuby
|
|
|
21
30
|
functions = []
|
|
22
31
|
type_aliases = []
|
|
23
32
|
interfaces = []
|
|
33
|
+
classes = []
|
|
24
34
|
i = 0
|
|
25
35
|
|
|
36
|
+
# Pre-detect heredoc regions to skip
|
|
37
|
+
heredoc_ranges = HeredocDetector.detect(@lines)
|
|
38
|
+
|
|
26
39
|
while i < @lines.length
|
|
40
|
+
# Skip lines inside heredoc content
|
|
41
|
+
if HeredocDetector.inside_heredoc?(i, heredoc_ranges)
|
|
42
|
+
i += 1
|
|
43
|
+
next
|
|
44
|
+
end
|
|
45
|
+
|
|
27
46
|
line = @lines[i]
|
|
28
47
|
|
|
29
48
|
# Match type alias definitions
|
|
@@ -42,10 +61,24 @@ module TRuby
|
|
|
42
61
|
end
|
|
43
62
|
end
|
|
44
63
|
|
|
45
|
-
# Match
|
|
46
|
-
if line.match?(/^\s*
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
# Match class definitions
|
|
65
|
+
if line.match?(/^\s*class\s+\w+/)
|
|
66
|
+
class_info, next_i = parse_class(i)
|
|
67
|
+
if class_info
|
|
68
|
+
classes << class_info
|
|
69
|
+
i = next_i
|
|
70
|
+
next
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Match function definitions (top-level only, not inside class)
|
|
75
|
+
if line.match?(/^\s*#{VISIBILITY_PATTERN}def\s+#{IDENTIFIER_CHAR}+/)
|
|
76
|
+
func_info, next_i = parse_function_with_body(i)
|
|
77
|
+
if func_info
|
|
78
|
+
functions << func_info
|
|
79
|
+
i = next_i
|
|
80
|
+
next
|
|
81
|
+
end
|
|
49
82
|
end
|
|
50
83
|
|
|
51
84
|
i += 1
|
|
@@ -56,13 +89,12 @@ module TRuby
|
|
|
56
89
|
functions: functions,
|
|
57
90
|
type_aliases: type_aliases,
|
|
58
91
|
interfaces: interfaces,
|
|
92
|
+
classes: classes,
|
|
59
93
|
}
|
|
60
94
|
|
|
61
|
-
# Build IR
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
@ir_program = builder.build(result, source: @source)
|
|
65
|
-
end
|
|
95
|
+
# Build IR
|
|
96
|
+
builder = IR::Builder.new
|
|
97
|
+
@ir_program = builder.build(result, source: @source)
|
|
66
98
|
|
|
67
99
|
result
|
|
68
100
|
end
|
|
@@ -73,16 +105,49 @@ module TRuby
|
|
|
73
105
|
@ir_program
|
|
74
106
|
end
|
|
75
107
|
|
|
76
|
-
# Parse a type expression using combinator
|
|
108
|
+
# Parse a type expression using combinator
|
|
77
109
|
def parse_type(type_string)
|
|
78
|
-
return nil unless @use_combinator
|
|
79
|
-
|
|
80
110
|
result = @type_parser.parse(type_string)
|
|
81
111
|
result[:success] ? result[:type] : nil
|
|
82
112
|
end
|
|
83
113
|
|
|
84
114
|
private
|
|
85
115
|
|
|
116
|
+
# 최상위 함수를 본문까지 포함하여 파싱
|
|
117
|
+
def parse_function_with_body(start_index)
|
|
118
|
+
line = @lines[start_index]
|
|
119
|
+
func_info = parse_function_definition(line)
|
|
120
|
+
return [nil, start_index] unless func_info
|
|
121
|
+
|
|
122
|
+
def_indent = line.match(/^(\s*)/)[1].length
|
|
123
|
+
i = start_index + 1
|
|
124
|
+
body_start = i
|
|
125
|
+
body_end = i
|
|
126
|
+
|
|
127
|
+
# end 키워드 찾기
|
|
128
|
+
while i < @lines.length
|
|
129
|
+
current_line = @lines[i]
|
|
130
|
+
|
|
131
|
+
if current_line.match?(/^\s*end\s*$/)
|
|
132
|
+
end_indent = current_line.match(/^(\s*)/)[1].length
|
|
133
|
+
if end_indent <= def_indent
|
|
134
|
+
body_end = i
|
|
135
|
+
break
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
i += 1
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# 본문 파싱 (parse_body 옵션이 활성화된 경우)
|
|
143
|
+
if @parse_body && @body_parser && body_start < body_end
|
|
144
|
+
func_info[:body_ir] = @body_parser.parse(@lines, body_start, body_end)
|
|
145
|
+
func_info[:body_range] = { start: body_start, end: body_end }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
[func_info, i]
|
|
149
|
+
end
|
|
150
|
+
|
|
86
151
|
def parse_type_alias(line)
|
|
87
152
|
match = line.match(/^\s*type\s+(\w+)\s*=\s*(.+?)\s*$/)
|
|
88
153
|
return nil unless match
|
|
@@ -90,16 +155,14 @@ module TRuby
|
|
|
90
155
|
alias_name = match[1]
|
|
91
156
|
definition = match[2].strip
|
|
92
157
|
|
|
93
|
-
# Use combinator for complex type parsing
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
end
|
|
158
|
+
# Use combinator for complex type parsing
|
|
159
|
+
type_result = @type_parser.parse(definition)
|
|
160
|
+
if type_result[:success]
|
|
161
|
+
return {
|
|
162
|
+
name: alias_name,
|
|
163
|
+
definition: definition,
|
|
164
|
+
ir_type: type_result[:type],
|
|
165
|
+
}
|
|
103
166
|
end
|
|
104
167
|
|
|
105
168
|
{
|
|
@@ -109,12 +172,19 @@ module TRuby
|
|
|
109
172
|
end
|
|
110
173
|
|
|
111
174
|
def parse_function_definition(line)
|
|
112
|
-
|
|
175
|
+
# Match methods with or without parentheses
|
|
176
|
+
# def foo(params): Type - with params and return type
|
|
177
|
+
# def foo(): Type - no params but with return type
|
|
178
|
+
# def foo(params) - with params, no return type
|
|
179
|
+
# def foo - no params, no return type
|
|
180
|
+
# Also supports visibility modifiers: private def, protected def, public def
|
|
181
|
+
match = line.match(/^\s*(?:(private|protected|public)\s+)?def\s+(#{METHOD_NAME_PATTERN})\s*(?:\((.*?)\))?\s*(?::\s*(.+?))?\s*$/)
|
|
113
182
|
return nil unless match
|
|
114
183
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
184
|
+
visibility = match[1] ? match[1].to_sym : :public
|
|
185
|
+
function_name = match[2]
|
|
186
|
+
params_str = match[3] || ""
|
|
187
|
+
return_type_str = match[4]&.strip
|
|
118
188
|
|
|
119
189
|
# Validate return type if present
|
|
120
190
|
if return_type_str
|
|
@@ -127,10 +197,11 @@ module TRuby
|
|
|
127
197
|
name: function_name,
|
|
128
198
|
params: params,
|
|
129
199
|
return_type: return_type_str,
|
|
200
|
+
visibility: visibility,
|
|
130
201
|
}
|
|
131
202
|
|
|
132
|
-
# Parse return type with combinator
|
|
133
|
-
if
|
|
203
|
+
# Parse return type with combinator
|
|
204
|
+
if return_type_str
|
|
134
205
|
type_result = @type_parser.parse(return_type_str)
|
|
135
206
|
result[:ir_return_type] = type_result[:type] if type_result[:success]
|
|
136
207
|
end
|
|
@@ -222,8 +293,8 @@ module TRuby
|
|
|
222
293
|
type: type_str,
|
|
223
294
|
}
|
|
224
295
|
|
|
225
|
-
# Parse type with combinator
|
|
226
|
-
if
|
|
296
|
+
# Parse type with combinator
|
|
297
|
+
if type_str
|
|
227
298
|
type_result = @type_parser.parse(type_str)
|
|
228
299
|
result[:ir_type] = type_result[:type] if type_result[:success]
|
|
229
300
|
end
|
|
@@ -231,6 +302,127 @@ module TRuby
|
|
|
231
302
|
result
|
|
232
303
|
end
|
|
233
304
|
|
|
305
|
+
def parse_class(start_index)
|
|
306
|
+
line = @lines[start_index]
|
|
307
|
+
match = line.match(/^\s*class\s+(\w+)(?:\s*<\s*(\w+))?/)
|
|
308
|
+
return [nil, start_index] unless match
|
|
309
|
+
|
|
310
|
+
class_name = match[1]
|
|
311
|
+
superclass = match[2]
|
|
312
|
+
methods = []
|
|
313
|
+
instance_vars = []
|
|
314
|
+
i = start_index + 1
|
|
315
|
+
class_indent = line.match(/^(\s*)/)[1].length
|
|
316
|
+
class_end = i
|
|
317
|
+
|
|
318
|
+
# 먼저 클래스의 끝을 찾음
|
|
319
|
+
temp_i = i
|
|
320
|
+
while temp_i < @lines.length
|
|
321
|
+
current_line = @lines[temp_i]
|
|
322
|
+
if current_line.match?(/^\s*end\s*$/)
|
|
323
|
+
end_indent = current_line.match(/^(\s*)/)[1].length
|
|
324
|
+
if end_indent <= class_indent
|
|
325
|
+
class_end = temp_i
|
|
326
|
+
break
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
temp_i += 1
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
while i < class_end
|
|
333
|
+
current_line = @lines[i]
|
|
334
|
+
|
|
335
|
+
# Match method definitions inside class
|
|
336
|
+
if current_line.match?(/^\s*#{VISIBILITY_PATTERN}def\s+#{IDENTIFIER_CHAR}+/)
|
|
337
|
+
method_info, next_i = parse_method_in_class(i, class_end)
|
|
338
|
+
if method_info
|
|
339
|
+
methods << method_info
|
|
340
|
+
i = next_i
|
|
341
|
+
next
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
i += 1
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# 메서드 본문에서 인스턴스 변수 추출
|
|
349
|
+
methods.each do |method_info|
|
|
350
|
+
extract_instance_vars_from_body(method_info[:body_ir], instance_vars)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Try to infer instance variable types from initialize parameters
|
|
354
|
+
init_method = methods.find { |m| m[:name] == "initialize" }
|
|
355
|
+
if init_method
|
|
356
|
+
instance_vars.each do |ivar|
|
|
357
|
+
# Find matching parameter (e.g., @name = name)
|
|
358
|
+
matching_param = init_method[:params]&.find { |p| p[:name] == ivar[:name] }
|
|
359
|
+
ivar[:type] = matching_param[:type] if matching_param && matching_param[:type]
|
|
360
|
+
ivar[:ir_type] = matching_param[:ir_type] if matching_param && matching_param[:ir_type]
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
[{
|
|
365
|
+
name: class_name,
|
|
366
|
+
superclass: superclass,
|
|
367
|
+
methods: methods,
|
|
368
|
+
instance_vars: instance_vars,
|
|
369
|
+
}, class_end,]
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# 클래스 내부의 메서드를 본문까지 포함하여 파싱
|
|
373
|
+
def parse_method_in_class(start_index, class_end)
|
|
374
|
+
line = @lines[start_index]
|
|
375
|
+
method_info = parse_function_definition(line)
|
|
376
|
+
return [nil, start_index] unless method_info
|
|
377
|
+
|
|
378
|
+
def_indent = line.match(/^(\s*)/)[1].length
|
|
379
|
+
i = start_index + 1
|
|
380
|
+
body_start = i
|
|
381
|
+
body_end = i
|
|
382
|
+
|
|
383
|
+
# 메서드의 end 키워드 찾기
|
|
384
|
+
while i < class_end
|
|
385
|
+
current_line = @lines[i]
|
|
386
|
+
|
|
387
|
+
if current_line.match?(/^\s*end\s*$/)
|
|
388
|
+
end_indent = current_line.match(/^(\s*)/)[1].length
|
|
389
|
+
if end_indent <= def_indent
|
|
390
|
+
body_end = i
|
|
391
|
+
break
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
i += 1
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# 본문 파싱 (parse_body 옵션이 활성화된 경우)
|
|
399
|
+
if @parse_body && @body_parser && body_start < body_end
|
|
400
|
+
method_info[:body_ir] = @body_parser.parse(@lines, body_start, body_end)
|
|
401
|
+
method_info[:body_range] = { start: body_start, end: body_end }
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
[method_info, i]
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# 본문 IR에서 인스턴스 변수 추출
|
|
408
|
+
def extract_instance_vars_from_body(body_ir, instance_vars)
|
|
409
|
+
return unless body_ir.is_a?(IR::Block)
|
|
410
|
+
|
|
411
|
+
body_ir.statements.each do |stmt|
|
|
412
|
+
case stmt
|
|
413
|
+
when IR::Assignment
|
|
414
|
+
if stmt.target.start_with?("@") && !stmt.target.start_with?("@@")
|
|
415
|
+
ivar_name = stmt.target[1..] # @ 제거
|
|
416
|
+
unless instance_vars.any? { |iv| iv[:name] == ivar_name }
|
|
417
|
+
instance_vars << { name: ivar_name }
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
when IR::Block
|
|
421
|
+
extract_instance_vars_from_body(stmt, instance_vars)
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
234
426
|
def parse_interface(start_index)
|
|
235
427
|
line = @lines[start_index]
|
|
236
428
|
match = line.match(/^\s*interface\s+([\w:]+)/)
|
|
@@ -253,10 +445,8 @@ module TRuby
|
|
|
253
445
|
}
|
|
254
446
|
|
|
255
447
|
# Parse member type with combinator
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
member[:ir_type] = type_result[:type] if type_result[:success]
|
|
259
|
-
end
|
|
448
|
+
type_result = @type_parser.parse(member[:type])
|
|
449
|
+
member[:ir_type] = type_result[:type] if type_result[:success]
|
|
260
450
|
|
|
261
451
|
members << member
|
|
262
452
|
end
|
|
@@ -268,11 +458,4 @@ module TRuby
|
|
|
268
458
|
[{ name: interface_name, members: members }, i]
|
|
269
459
|
end
|
|
270
460
|
end
|
|
271
|
-
|
|
272
|
-
# Legacy Parser for backward compatibility (regex-only)
|
|
273
|
-
class LegacyParser < Parser
|
|
274
|
-
def initialize(source)
|
|
275
|
-
super(source, use_combinator: false)
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
461
|
end
|
data/lib/t_ruby/type_checker.rb
CHANGED
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module TRuby
|
|
4
|
-
# Represents a type checking error
|
|
5
|
-
class TypeCheckError
|
|
6
|
-
attr_reader :
|
|
4
|
+
# Represents a type checking error (can be raised as an exception)
|
|
5
|
+
class TypeCheckError < StandardError
|
|
6
|
+
attr_reader :error_message, :location, :expected, :actual, :suggestion, :severity
|
|
7
7
|
|
|
8
8
|
def initialize(message:, location: nil, expected: nil, actual: nil, suggestion: nil, severity: :error)
|
|
9
|
-
@
|
|
9
|
+
@error_message = message
|
|
10
10
|
@location = location
|
|
11
11
|
@expected = expected
|
|
12
12
|
@actual = actual
|
|
13
13
|
@suggestion = suggestion
|
|
14
14
|
@severity = severity
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def to_s
|
|
18
|
-
parts = [@message]
|
|
19
|
-
parts << " Expected: #{@expected}" if @expected
|
|
20
|
-
parts << " Actual: #{@actual}" if @actual
|
|
21
|
-
parts << " Suggestion: #{@suggestion}" if @suggestion
|
|
22
|
-
parts << " at #{@location}" if @location
|
|
23
|
-
parts.join("\n")
|
|
15
|
+
super(build_full_message)
|
|
24
16
|
end
|
|
25
17
|
|
|
26
18
|
def to_diagnostic
|
|
27
19
|
{
|
|
28
20
|
severity: @severity,
|
|
29
|
-
message: @
|
|
21
|
+
message: @error_message,
|
|
30
22
|
location: @location,
|
|
31
23
|
expected: @expected,
|
|
32
24
|
actual: @actual,
|
|
33
25
|
suggestion: @suggestion,
|
|
34
26
|
}
|
|
35
27
|
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def build_full_message
|
|
32
|
+
parts = [@error_message]
|
|
33
|
+
parts << " Expected: #{@expected}" if @expected
|
|
34
|
+
parts << " Actual: #{@actual}" if @actual
|
|
35
|
+
parts << " Suggestion: #{@suggestion}" if @suggestion
|
|
36
|
+
parts << " at #{@location}" if @location
|
|
37
|
+
parts.join("\n")
|
|
38
|
+
end
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
# Type hierarchy for subtype checking
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# TypeEnv - 타입 환경 (스코프 체인)
|
|
5
|
+
# TypeScript의 Checker에서 심볼 타입을 추적하는 방식을 참고
|
|
6
|
+
class TypeEnv
|
|
7
|
+
attr_reader :parent, :bindings, :instance_vars
|
|
8
|
+
|
|
9
|
+
def initialize(parent = nil)
|
|
10
|
+
@parent = parent
|
|
11
|
+
@bindings = {} # 지역 변수 { name => type }
|
|
12
|
+
@instance_vars = {} # 인스턴스 변수 { name => type }
|
|
13
|
+
@class_vars = {} # 클래스 변수 { name => type }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# 지역 변수 타입 정의
|
|
17
|
+
# @param name [String] 변수 이름
|
|
18
|
+
# @param type [IR::TypeNode, String] 타입
|
|
19
|
+
def define(name, type)
|
|
20
|
+
@bindings[name] = type
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# 변수 타입 조회 (스코프 체인 탐색)
|
|
24
|
+
# @param name [String] 변수 이름
|
|
25
|
+
# @return [IR::TypeNode, String, nil] 타입 또는 nil
|
|
26
|
+
def lookup(name)
|
|
27
|
+
# 인스턴스 변수
|
|
28
|
+
if name.start_with?("@") && !name.start_with?("@@")
|
|
29
|
+
return lookup_instance_var(name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# 클래스 변수
|
|
33
|
+
if name.start_with?("@@")
|
|
34
|
+
return lookup_class_var(name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# 지역 변수 또는 메서드 파라미터
|
|
38
|
+
return @bindings[name] if @bindings.key?(name)
|
|
39
|
+
|
|
40
|
+
# 부모 스코프에서 검색
|
|
41
|
+
@parent&.lookup(name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 인스턴스 변수 타입 정의
|
|
45
|
+
# @param name [String] 변수 이름 (@포함)
|
|
46
|
+
# @param type [IR::TypeNode, String] 타입
|
|
47
|
+
def define_instance_var(name, type)
|
|
48
|
+
# @ 접두사 정규화
|
|
49
|
+
normalized = name.start_with?("@") ? name : "@#{name}"
|
|
50
|
+
@instance_vars[normalized] = type
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 인스턴스 변수 타입 조회
|
|
54
|
+
# @param name [String] 변수 이름 (@포함)
|
|
55
|
+
# @return [IR::TypeNode, String, nil] 타입 또는 nil
|
|
56
|
+
def lookup_instance_var(name)
|
|
57
|
+
normalized = name.start_with?("@") ? name : "@#{name}"
|
|
58
|
+
return @instance_vars[normalized] if @instance_vars.key?(normalized)
|
|
59
|
+
|
|
60
|
+
@parent&.lookup_instance_var(normalized)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# 클래스 변수 타입 정의
|
|
64
|
+
# @param name [String] 변수 이름 (@@포함)
|
|
65
|
+
# @param type [IR::TypeNode, String] 타입
|
|
66
|
+
def define_class_var(name, type)
|
|
67
|
+
normalized = name.start_with?("@@") ? name : "@@#{name}"
|
|
68
|
+
@class_vars[normalized] = type
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# 클래스 변수 타입 조회
|
|
72
|
+
# @param name [String] 변수 이름 (@@포함)
|
|
73
|
+
# @return [IR::TypeNode, String, nil] 타입 또는 nil
|
|
74
|
+
def lookup_class_var(name)
|
|
75
|
+
normalized = name.start_with?("@@") ? name : "@@#{name}"
|
|
76
|
+
return @class_vars[normalized] if @class_vars.key?(normalized)
|
|
77
|
+
|
|
78
|
+
@parent&.lookup_class_var(normalized)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 자식 스코프 생성 (블록, 람다 등)
|
|
82
|
+
# @return [TypeEnv] 새 자식 스코프
|
|
83
|
+
def child_scope
|
|
84
|
+
TypeEnv.new(self)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# 현재 스코프에서 정의된 모든 변수 이름
|
|
88
|
+
# @return [Array<String>] 변수 이름 배열
|
|
89
|
+
def local_names
|
|
90
|
+
@bindings.keys
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# 현재 스코프에서 정의된 모든 인스턴스 변수 이름
|
|
94
|
+
# @return [Array<String>] 인스턴스 변수 이름 배열
|
|
95
|
+
def instance_var_names
|
|
96
|
+
@instance_vars.keys
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# 변수가 현재 스코프에 정의되어 있는지 확인
|
|
100
|
+
# @param name [String] 변수 이름
|
|
101
|
+
# @return [Boolean]
|
|
102
|
+
def defined_locally?(name)
|
|
103
|
+
@bindings.key?(name)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# 스코프 깊이 (디버깅용)
|
|
107
|
+
# @return [Integer] 스코프 깊이
|
|
108
|
+
def depth
|
|
109
|
+
@parent ? @parent.depth + 1 : 0
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# 스코프 체인에서 모든 변수 수집
|
|
113
|
+
# @return [Hash] 모든 변수의 { name => type }
|
|
114
|
+
def all_bindings
|
|
115
|
+
parent_bindings = @parent ? @parent.all_bindings : {}
|
|
116
|
+
parent_bindings.merge(@bindings)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# 디버그 출력
|
|
120
|
+
def to_s
|
|
121
|
+
parts = ["TypeEnv(depth=#{depth})"]
|
|
122
|
+
parts << " locals: #{@bindings.keys.join(", ")}" if @bindings.any?
|
|
123
|
+
parts << " ivars: #{@instance_vars.keys.join(", ")}" if @instance_vars.any?
|
|
124
|
+
parts.join("\n")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
data/lib/t_ruby/version.rb
CHANGED
data/lib/t_ruby/watcher.rb
CHANGED
|
@@ -80,14 +80,18 @@ module TRuby
|
|
|
80
80
|
|
|
81
81
|
private
|
|
82
82
|
|
|
83
|
+
def watch_directory(path)
|
|
84
|
+
File.directory?(path) ? path : File.dirname(path)
|
|
85
|
+
end
|
|
86
|
+
|
|
83
87
|
def watch_directories
|
|
84
|
-
@paths
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
end
|
|
88
|
+
if @paths == [File.expand_path(".")]
|
|
89
|
+
# Default case: only watch source_include directories from config
|
|
90
|
+
@config.source_include.map { |dir| File.expand_path(dir) }.select { |dir| Dir.exist?(dir) }
|
|
91
|
+
else
|
|
92
|
+
# Specific paths provided: watch those paths
|
|
93
|
+
@paths.map { |path| watch_directory(path) }.uniq
|
|
94
|
+
end
|
|
91
95
|
end
|
|
92
96
|
|
|
93
97
|
def handle_changes(modified, added, removed)
|
|
@@ -243,20 +247,21 @@ module TRuby
|
|
|
243
247
|
def find_source_files_by_extension(ext)
|
|
244
248
|
files = []
|
|
245
249
|
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
250
|
+
# Always search in source_include directories only
|
|
251
|
+
source_paths = if @paths == [File.expand_path(".")]
|
|
252
|
+
@config.source_include.map { |dir| File.expand_path(dir) }
|
|
253
|
+
else
|
|
254
|
+
@paths.map { |path| File.expand_path(path) }
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
source_paths.each do |path|
|
|
258
|
+
if File.file?(path)
|
|
259
|
+
# Handle single file path
|
|
260
|
+
files << path if path.end_with?(ext) && !@config.excluded?(path)
|
|
261
|
+
elsif Dir.exist?(path)
|
|
262
|
+
# Handle directory path
|
|
263
|
+
Dir.glob(File.join(path, "**", "*#{ext}")).each do |file|
|
|
264
|
+
files << file unless @config.excluded?(file)
|
|
260
265
|
end
|
|
261
266
|
end
|
|
262
267
|
end
|
data/lib/t_ruby.rb
CHANGED
|
@@ -11,13 +11,14 @@ require_relative "t_ruby/smt_solver"
|
|
|
11
11
|
|
|
12
12
|
# Basic components
|
|
13
13
|
require_relative "t_ruby/type_alias_registry"
|
|
14
|
+
require_relative "t_ruby/heredoc_detector"
|
|
15
|
+
require_relative "t_ruby/body_parser"
|
|
14
16
|
require_relative "t_ruby/parser"
|
|
15
17
|
require_relative "t_ruby/union_type_parser"
|
|
16
18
|
require_relative "t_ruby/generic_type_parser"
|
|
17
19
|
require_relative "t_ruby/intersection_type_parser"
|
|
18
20
|
require_relative "t_ruby/type_erasure"
|
|
19
21
|
require_relative "t_ruby/error_handler"
|
|
20
|
-
require_relative "t_ruby/rbs_generator"
|
|
21
22
|
require_relative "t_ruby/declaration_generator"
|
|
22
23
|
require_relative "t_ruby/compiler"
|
|
23
24
|
require_relative "t_ruby/lsp_server"
|
|
@@ -29,6 +30,8 @@ require_relative "t_ruby/constraint_checker"
|
|
|
29
30
|
require_relative "t_ruby/type_inferencer"
|
|
30
31
|
require_relative "t_ruby/runtime_validator"
|
|
31
32
|
require_relative "t_ruby/type_checker"
|
|
33
|
+
require_relative "t_ruby/type_env"
|
|
34
|
+
require_relative "t_ruby/ast_type_inferrer"
|
|
32
35
|
require_relative "t_ruby/cache"
|
|
33
36
|
require_relative "t_ruby/package_manager"
|
|
34
37
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: t-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.40
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Y. Fred Kim
|
|
@@ -36,7 +36,9 @@ files:
|
|
|
36
36
|
- README.md
|
|
37
37
|
- bin/trc
|
|
38
38
|
- lib/t_ruby.rb
|
|
39
|
+
- lib/t_ruby/ast_type_inferrer.rb
|
|
39
40
|
- lib/t_ruby/benchmark.rb
|
|
41
|
+
- lib/t_ruby/body_parser.rb
|
|
40
42
|
- lib/t_ruby/bundler_integration.rb
|
|
41
43
|
- lib/t_ruby/cache.rb
|
|
42
44
|
- lib/t_ruby/cli.rb
|
|
@@ -50,17 +52,18 @@ files:
|
|
|
50
52
|
- lib/t_ruby/docs_example_verifier.rb
|
|
51
53
|
- lib/t_ruby/error_handler.rb
|
|
52
54
|
- lib/t_ruby/generic_type_parser.rb
|
|
55
|
+
- lib/t_ruby/heredoc_detector.rb
|
|
53
56
|
- lib/t_ruby/intersection_type_parser.rb
|
|
54
57
|
- lib/t_ruby/ir.rb
|
|
55
58
|
- lib/t_ruby/lsp_server.rb
|
|
56
59
|
- lib/t_ruby/package_manager.rb
|
|
57
60
|
- lib/t_ruby/parser.rb
|
|
58
61
|
- lib/t_ruby/parser_combinator.rb
|
|
59
|
-
- lib/t_ruby/rbs_generator.rb
|
|
60
62
|
- lib/t_ruby/runtime_validator.rb
|
|
61
63
|
- lib/t_ruby/smt_solver.rb
|
|
62
64
|
- lib/t_ruby/type_alias_registry.rb
|
|
63
65
|
- lib/t_ruby/type_checker.rb
|
|
66
|
+
- lib/t_ruby/type_env.rb
|
|
64
67
|
- lib/t_ruby/type_erasure.rb
|
|
65
68
|
- lib/t_ruby/type_inferencer.rb
|
|
66
69
|
- lib/t_ruby/union_type_parser.rb
|
|
@@ -69,7 +72,7 @@ files:
|
|
|
69
72
|
- lib/t_ruby/watcher.rb
|
|
70
73
|
homepage: https://type-ruby.github.io
|
|
71
74
|
licenses:
|
|
72
|
-
-
|
|
75
|
+
- BSD-2-Clause
|
|
73
76
|
metadata:
|
|
74
77
|
rubygems_mfa_required: 'true'
|
|
75
78
|
rdoc_options: []
|