t-ruby 0.0.37 → 0.0.39
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 +152 -3
- data/lib/t_ruby/config.rb +7 -0
- data/lib/t_ruby/ir.rb +90 -6
- data/lib/t_ruby/parser.rb +192 -7
- 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/version_checker.rb +11 -1
- data/lib/t_ruby/watcher.rb +26 -21
- data/lib/t_ruby.rb +3 -0
- metadata +5 -2
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# BodyParser - T-Ruby 메서드 본문을 IR 노드로 변환
|
|
5
|
+
# Prism은 순수 Ruby만 파싱하므로, T-Ruby 타입 어노테이션을 포함한
|
|
6
|
+
# 메서드 본문을 파싱하기 위해 자체 구현
|
|
7
|
+
class BodyParser
|
|
8
|
+
# 메서드 본문을 IR::Block으로 변환
|
|
9
|
+
# @param lines [Array<String>] 전체 소스 라인 배열
|
|
10
|
+
# @param start_line [Integer] 메서드 본문 시작 라인 (0-indexed)
|
|
11
|
+
# @param end_line [Integer] 메서드 본문 끝 라인 (exclusive)
|
|
12
|
+
# @return [IR::Block] 본문을 표현하는 IR 블록
|
|
13
|
+
def parse(lines, start_line, end_line)
|
|
14
|
+
statements = []
|
|
15
|
+
i = start_line
|
|
16
|
+
|
|
17
|
+
while i < end_line
|
|
18
|
+
line = lines[i]
|
|
19
|
+
stripped = line.strip
|
|
20
|
+
|
|
21
|
+
# 빈 줄이나 주석은 건너뛰기
|
|
22
|
+
if stripped.empty? || stripped.start_with?("#")
|
|
23
|
+
i += 1
|
|
24
|
+
next
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# if/unless 조건문 처리
|
|
28
|
+
if stripped.match?(/^(if|unless)\s+/)
|
|
29
|
+
node, next_i = parse_conditional(lines, i, end_line)
|
|
30
|
+
if node
|
|
31
|
+
statements << node
|
|
32
|
+
i = next_i
|
|
33
|
+
next
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
node = parse_statement(stripped, i)
|
|
38
|
+
statements << node if node
|
|
39
|
+
i += 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
IR::Block.new(statements: statements)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# if/unless/elsif 조건문 파싱
|
|
46
|
+
# @return [Array(IR::Conditional, Integer)] 조건문 노드와 다음 라인 인덱스
|
|
47
|
+
def parse_conditional(lines, start_line, block_end)
|
|
48
|
+
line = lines[start_line].strip
|
|
49
|
+
match = line.match(/^(if|unless|elsif)\s+(.+)$/)
|
|
50
|
+
return [nil, start_line] unless match
|
|
51
|
+
|
|
52
|
+
# elsif는 내부적으로 if처럼 처리
|
|
53
|
+
kind = match[1] == "elsif" ? :if : match[1].to_sym
|
|
54
|
+
condition = parse_expression(match[2])
|
|
55
|
+
|
|
56
|
+
# then/elsif/else/end 블록 찾기
|
|
57
|
+
then_statements = []
|
|
58
|
+
else_statements = []
|
|
59
|
+
current_branch = :then
|
|
60
|
+
depth = 1
|
|
61
|
+
i = start_line + 1
|
|
62
|
+
|
|
63
|
+
while i < block_end && depth.positive?
|
|
64
|
+
current_line = lines[i].strip
|
|
65
|
+
|
|
66
|
+
if current_line.match?(/^(if|unless|case|while|until|for|begin)\b/)
|
|
67
|
+
depth += 1
|
|
68
|
+
if current_branch == :then
|
|
69
|
+
then_statements << IR::RawCode.new(code: current_line)
|
|
70
|
+
else
|
|
71
|
+
else_statements << IR::RawCode.new(code: current_line)
|
|
72
|
+
end
|
|
73
|
+
elsif current_line == "end"
|
|
74
|
+
depth -= 1
|
|
75
|
+
break if depth.zero?
|
|
76
|
+
elsif depth == 1 && current_line.match?(/^elsif\s+/)
|
|
77
|
+
# elsif는 중첩된 if로 처리
|
|
78
|
+
nested_cond, next_i = parse_conditional(lines, i, block_end)
|
|
79
|
+
else_statements << nested_cond if nested_cond
|
|
80
|
+
i = next_i
|
|
81
|
+
break
|
|
82
|
+
elsif depth == 1 && current_line == "else"
|
|
83
|
+
current_branch = :else
|
|
84
|
+
elsif !current_line.empty? && !current_line.start_with?("#")
|
|
85
|
+
node = parse_statement(current_line, i)
|
|
86
|
+
next unless node
|
|
87
|
+
|
|
88
|
+
if current_branch == :then
|
|
89
|
+
then_statements << node
|
|
90
|
+
else
|
|
91
|
+
else_statements << node
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
i += 1
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
then_block = IR::Block.new(statements: then_statements)
|
|
99
|
+
else_block = else_statements.empty? ? nil : IR::Block.new(statements: else_statements)
|
|
100
|
+
|
|
101
|
+
conditional = IR::Conditional.new(
|
|
102
|
+
condition: condition,
|
|
103
|
+
then_branch: then_block,
|
|
104
|
+
else_branch: else_block,
|
|
105
|
+
kind: kind,
|
|
106
|
+
location: start_line
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
[conditional, i + 1]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# 단일 문장 파싱
|
|
115
|
+
def parse_statement(line, line_num)
|
|
116
|
+
case line
|
|
117
|
+
# return 문
|
|
118
|
+
when /^return\s+(.+)$/
|
|
119
|
+
IR::Return.new(
|
|
120
|
+
value: parse_expression(::Regexp.last_match(1).strip),
|
|
121
|
+
location: line_num
|
|
122
|
+
)
|
|
123
|
+
when /^return\s*$/
|
|
124
|
+
IR::Return.new(value: nil, location: line_num)
|
|
125
|
+
|
|
126
|
+
# 인스턴스 변수 할당: @name = value (== 제외)
|
|
127
|
+
when /^@(\w+)\s*=(?!=)\s*(.+)$/
|
|
128
|
+
IR::Assignment.new(
|
|
129
|
+
target: "@#{::Regexp.last_match(1)}",
|
|
130
|
+
value: parse_expression(::Regexp.last_match(2).strip),
|
|
131
|
+
location: line_num
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# 지역 변수 할당: name = value (==, != 제외)
|
|
135
|
+
when /^(\w+)\s*=(?!=)\s*(.+)$/
|
|
136
|
+
IR::Assignment.new(
|
|
137
|
+
target: ::Regexp.last_match(1),
|
|
138
|
+
value: parse_expression(::Regexp.last_match(2).strip),
|
|
139
|
+
location: line_num
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# 그 외는 표현식 (암묵적 반환값 가능)
|
|
143
|
+
else
|
|
144
|
+
parse_expression(line)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# 표현식 파싱
|
|
149
|
+
def parse_expression(expr)
|
|
150
|
+
return nil if expr.nil? || expr.empty?
|
|
151
|
+
|
|
152
|
+
expr = expr.strip
|
|
153
|
+
|
|
154
|
+
# 리터럴 파싱 시도
|
|
155
|
+
result = parse_literal(expr)
|
|
156
|
+
return result if result
|
|
157
|
+
|
|
158
|
+
# 복합 표현식 파싱 시도
|
|
159
|
+
result = parse_compound_expression(expr)
|
|
160
|
+
return result if result
|
|
161
|
+
|
|
162
|
+
# 연산자 파싱 시도
|
|
163
|
+
result = parse_operators(expr)
|
|
164
|
+
return result if result
|
|
165
|
+
|
|
166
|
+
# 변수 참조 파싱 시도
|
|
167
|
+
result = parse_variable_ref(expr)
|
|
168
|
+
return result if result
|
|
169
|
+
|
|
170
|
+
# 파싱할 수 없는 경우 RawCode로 래핑
|
|
171
|
+
IR::RawCode.new(code: expr)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# 리터럴 파싱 (문자열, 숫자, 심볼, 부울, nil)
|
|
175
|
+
def parse_literal(expr)
|
|
176
|
+
# 문자열 리터럴 (쌍따옴표)
|
|
177
|
+
return parse_string_literal(expr) if expr.match?(/^".*"$/)
|
|
178
|
+
|
|
179
|
+
# 문자열 리터럴 (홑따옴표)
|
|
180
|
+
if expr.match?(/^'.*'$/)
|
|
181
|
+
return IR::Literal.new(value: expr[1..-2], literal_type: :string)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# 심볼 리터럴
|
|
185
|
+
if (match = expr.match(/^:(\w+)$/))
|
|
186
|
+
return IR::Literal.new(value: match[1].to_sym, literal_type: :symbol)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# nil/부울 리터럴
|
|
190
|
+
return IR::Literal.new(value: nil, literal_type: :nil) if expr == "nil"
|
|
191
|
+
return IR::Literal.new(value: true, literal_type: :boolean) if expr == "true"
|
|
192
|
+
return IR::Literal.new(value: false, literal_type: :boolean) if expr == "false"
|
|
193
|
+
|
|
194
|
+
# 부동소수점 리터럴
|
|
195
|
+
if (match = expr.match(/^(-?\d+\.\d+)$/))
|
|
196
|
+
return IR::Literal.new(value: match[1].to_f, literal_type: :float)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# 정수 리터럴
|
|
200
|
+
if (match = expr.match(/^(-?\d+)$/))
|
|
201
|
+
return IR::Literal.new(value: match[1].to_i, literal_type: :integer)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
nil
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# 복합 표현식 파싱 (배열, 해시, 괄호, 메서드 호출)
|
|
208
|
+
def parse_compound_expression(expr)
|
|
209
|
+
# 배열 리터럴
|
|
210
|
+
return parse_array_literal(expr) if expr.start_with?("[") && expr.end_with?("]")
|
|
211
|
+
|
|
212
|
+
# 해시 리터럴
|
|
213
|
+
return parse_hash_literal(expr) if expr.start_with?("{") && expr.end_with?("}")
|
|
214
|
+
|
|
215
|
+
# 괄호로 감싼 표현식
|
|
216
|
+
return parse_expression(expr[1..-2]) if expr.start_with?("(") && expr.end_with?(")")
|
|
217
|
+
|
|
218
|
+
# 메서드 호출
|
|
219
|
+
parse_method_call(expr)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# 연산자 파싱 (이항, 단항)
|
|
223
|
+
def parse_operators(expr)
|
|
224
|
+
# 논리 연산자 (낮은 우선순위)
|
|
225
|
+
result = parse_binary_op(expr, ["||", "&&"])
|
|
226
|
+
return result if result
|
|
227
|
+
|
|
228
|
+
# 비교 연산자
|
|
229
|
+
result = parse_binary_op(expr, ["==", "!=", "<=", ">=", "<=>", "<", ">"])
|
|
230
|
+
return result if result
|
|
231
|
+
|
|
232
|
+
# 산술 연산자 (낮은 우선순위부터)
|
|
233
|
+
result = parse_binary_op(expr, ["+", "-"])
|
|
234
|
+
return result if result
|
|
235
|
+
|
|
236
|
+
result = parse_binary_op(expr, ["*", "/", "%"])
|
|
237
|
+
return result if result
|
|
238
|
+
|
|
239
|
+
result = parse_binary_op(expr, ["**"])
|
|
240
|
+
return result if result
|
|
241
|
+
|
|
242
|
+
# 단항 연산자
|
|
243
|
+
parse_unary_op(expr)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# 단항 연산자 파싱
|
|
247
|
+
def parse_unary_op(expr)
|
|
248
|
+
if expr.start_with?("!")
|
|
249
|
+
return IR::UnaryOp.new(operator: "!", operand: parse_expression(expr[1..]))
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
if expr.start_with?("-") && !expr.match?(/^-\d/)
|
|
253
|
+
return IR::UnaryOp.new(operator: "-", operand: parse_expression(expr[1..]))
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
nil
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# 변수 참조 파싱 (인스턴스, 클래스, 전역, 지역, 상수)
|
|
260
|
+
def parse_variable_ref(expr)
|
|
261
|
+
# 인스턴스 변수 참조
|
|
262
|
+
if (match = expr.match(/^@(\w+)$/))
|
|
263
|
+
return IR::VariableRef.new(name: "@#{match[1]}", scope: :instance)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# 클래스 변수 참조
|
|
267
|
+
if (match = expr.match(/^@@(\w+)$/))
|
|
268
|
+
return IR::VariableRef.new(name: "@@#{match[1]}", scope: :class)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# 전역 변수 참조
|
|
272
|
+
if (match = expr.match(/^\$(\w+)$/))
|
|
273
|
+
return IR::VariableRef.new(name: "$#{match[1]}", scope: :global)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# 지역 변수 또는 상수
|
|
277
|
+
if (match = expr.match(/^(\w+)$/))
|
|
278
|
+
name = match[1]
|
|
279
|
+
scope = name.match?(/^[A-Z]/) ? :constant : :local
|
|
280
|
+
return IR::VariableRef.new(name: name, scope: scope)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
nil
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# 문자열 보간 처리
|
|
287
|
+
def parse_string_literal(expr)
|
|
288
|
+
content = expr[1..-2] # 따옴표 제거
|
|
289
|
+
|
|
290
|
+
# 보간이 있는지 확인
|
|
291
|
+
if content.include?('#{')
|
|
292
|
+
# 보간이 있으면 보간 표현식들을 추출
|
|
293
|
+
parts = []
|
|
294
|
+
remaining = content
|
|
295
|
+
|
|
296
|
+
while (match = remaining.match(/#\{([^}]+)\}/))
|
|
297
|
+
# 보간 이전의 문자열 부분
|
|
298
|
+
unless match.pre_match.empty?
|
|
299
|
+
parts << IR::Literal.new(value: match.pre_match, literal_type: :string)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# 보간 표현식
|
|
303
|
+
interpolated_expr = parse_expression(match[1])
|
|
304
|
+
parts << IR::MethodCall.new(
|
|
305
|
+
receiver: interpolated_expr,
|
|
306
|
+
method_name: "to_s",
|
|
307
|
+
arguments: []
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
remaining = match.post_match
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# 남은 문자열 부분
|
|
314
|
+
unless remaining.empty?
|
|
315
|
+
parts << IR::Literal.new(value: remaining, literal_type: :string)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# 여러 부분이면 + 연산으로 연결
|
|
319
|
+
if parts.length == 1
|
|
320
|
+
parts.first
|
|
321
|
+
else
|
|
322
|
+
result = parts.first
|
|
323
|
+
parts[1..].each do |part|
|
|
324
|
+
result = IR::BinaryOp.new(operator: "+", left: result, right: part)
|
|
325
|
+
end
|
|
326
|
+
result
|
|
327
|
+
end
|
|
328
|
+
else
|
|
329
|
+
# 보간 없음
|
|
330
|
+
IR::Literal.new(value: content, literal_type: :string)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# 배열 리터럴 파싱
|
|
335
|
+
def parse_array_literal(expr)
|
|
336
|
+
content = expr[1..-2].strip # 괄호 제거
|
|
337
|
+
return IR::ArrayLiteral.new(elements: []) if content.empty?
|
|
338
|
+
|
|
339
|
+
elements = split_by_comma(content).map { |e| parse_expression(e.strip) }
|
|
340
|
+
IR::ArrayLiteral.new(elements: elements)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# 해시 리터럴 파싱
|
|
344
|
+
def parse_hash_literal(expr)
|
|
345
|
+
content = expr[1..-2].strip # 중괄호 제거
|
|
346
|
+
return IR::HashLiteral.new(pairs: []) if content.empty?
|
|
347
|
+
|
|
348
|
+
pairs = []
|
|
349
|
+
items = split_by_comma(content)
|
|
350
|
+
|
|
351
|
+
items.each do |item|
|
|
352
|
+
item = item.strip
|
|
353
|
+
|
|
354
|
+
# symbol: value 형태
|
|
355
|
+
if (match = item.match(/^(\w+):\s*(.+)$/))
|
|
356
|
+
key = IR::Literal.new(value: match[1].to_sym, literal_type: :symbol)
|
|
357
|
+
value = parse_expression(match[2].strip)
|
|
358
|
+
pairs << IR::HashPair.new(key: key, value: value)
|
|
359
|
+
|
|
360
|
+
# key => value 형태
|
|
361
|
+
elsif (match = item.match(/^(.+?)\s*=>\s*(.+)$/))
|
|
362
|
+
key = parse_expression(match[1].strip)
|
|
363
|
+
value = parse_expression(match[2].strip)
|
|
364
|
+
pairs << IR::HashPair.new(key: key, value: value)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
IR::HashLiteral.new(pairs: pairs)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# 이항 연산자 파싱 (우선순위 고려)
|
|
372
|
+
def parse_binary_op(expr, operators)
|
|
373
|
+
# 연산자를 찾되, 괄호/배열/해시/문자열 내부는 제외
|
|
374
|
+
depth = 0
|
|
375
|
+
in_string = false
|
|
376
|
+
string_char = nil
|
|
377
|
+
i = expr.length - 1
|
|
378
|
+
|
|
379
|
+
# 오른쪽에서 왼쪽으로 검색 (왼쪽 결합)
|
|
380
|
+
while i >= 0
|
|
381
|
+
char = expr[i]
|
|
382
|
+
|
|
383
|
+
# 문자열 처리
|
|
384
|
+
if !in_string && ['"', "'"].include?(char)
|
|
385
|
+
in_string = true
|
|
386
|
+
string_char = char
|
|
387
|
+
elsif in_string && char == string_char && (i.zero? || expr[i - 1] != "\\")
|
|
388
|
+
in_string = false
|
|
389
|
+
string_char = nil
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
unless in_string
|
|
393
|
+
case char
|
|
394
|
+
when ")", "]", "}"
|
|
395
|
+
depth += 1
|
|
396
|
+
when "(", "[", "{"
|
|
397
|
+
depth -= 1
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
if depth.zero?
|
|
401
|
+
operators.each do |op|
|
|
402
|
+
op_start = i - op.length + 1
|
|
403
|
+
next if op_start.negative?
|
|
404
|
+
|
|
405
|
+
next unless expr[op_start, op.length] == op
|
|
406
|
+
|
|
407
|
+
# 연산자 앞뒤에 피연산자가 있는지 확인
|
|
408
|
+
left_part = expr[0...op_start].strip
|
|
409
|
+
right_part = expr[(i + 1)..].strip
|
|
410
|
+
|
|
411
|
+
next if left_part.empty? || right_part.empty?
|
|
412
|
+
|
|
413
|
+
# 음수 처리: - 앞에 연산자가 있으면 단항 연산자
|
|
414
|
+
if op == "-"
|
|
415
|
+
prev_char = left_part[-1]
|
|
416
|
+
next if prev_char && ["+", "-", "*", "/", "%", "(", ",", "=", "<", ">", "!"].include?(prev_char)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
return IR::BinaryOp.new(
|
|
420
|
+
operator: op,
|
|
421
|
+
left: parse_expression(left_part),
|
|
422
|
+
right: parse_expression(right_part)
|
|
423
|
+
)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
i -= 1
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
nil
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# 메서드 호출 파싱
|
|
435
|
+
def parse_method_call(expr)
|
|
436
|
+
# receiver.method(args) 패턴
|
|
437
|
+
# 또는 method(args) 패턴
|
|
438
|
+
# 또는 receiver.method 패턴
|
|
439
|
+
|
|
440
|
+
depth = 0
|
|
441
|
+
in_string = false
|
|
442
|
+
string_char = nil
|
|
443
|
+
last_dot = nil
|
|
444
|
+
|
|
445
|
+
# 마지막 점 위치 찾기 (문자열/괄호 밖에서)
|
|
446
|
+
i = expr.length - 1
|
|
447
|
+
while i >= 0
|
|
448
|
+
char = expr[i]
|
|
449
|
+
|
|
450
|
+
if !in_string && ['"', "'"].include?(char)
|
|
451
|
+
in_string = true
|
|
452
|
+
string_char = char
|
|
453
|
+
elsif in_string && char == string_char && (i.zero? || expr[i - 1] != "\\")
|
|
454
|
+
in_string = false
|
|
455
|
+
string_char = nil
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
unless in_string
|
|
459
|
+
case char
|
|
460
|
+
when ")", "]", "}"
|
|
461
|
+
depth += 1
|
|
462
|
+
when "(", "[", "{"
|
|
463
|
+
depth -= 1
|
|
464
|
+
when "."
|
|
465
|
+
if depth.zero?
|
|
466
|
+
last_dot = i
|
|
467
|
+
break
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
i -= 1
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
if last_dot
|
|
476
|
+
receiver_str = expr[0...last_dot]
|
|
477
|
+
method_part = expr[(last_dot + 1)..]
|
|
478
|
+
|
|
479
|
+
# method_part에서 메서드 이름과 인자 분리
|
|
480
|
+
if (match = method_part.match(/^([\w?!]+)\s*\((.*)?\)$/))
|
|
481
|
+
method_name = match[1]
|
|
482
|
+
args_str = match[2] || ""
|
|
483
|
+
arguments = args_str.empty? ? [] : split_by_comma(args_str).map { |a| parse_expression(a.strip) }
|
|
484
|
+
|
|
485
|
+
return IR::MethodCall.new(
|
|
486
|
+
receiver: parse_expression(receiver_str),
|
|
487
|
+
method_name: method_name,
|
|
488
|
+
arguments: arguments
|
|
489
|
+
)
|
|
490
|
+
elsif (match = method_part.match(/^([\w?!]+)$/))
|
|
491
|
+
# 인자 없는 메서드 호출
|
|
492
|
+
return IR::MethodCall.new(
|
|
493
|
+
receiver: parse_expression(receiver_str),
|
|
494
|
+
method_name: match[1],
|
|
495
|
+
arguments: []
|
|
496
|
+
)
|
|
497
|
+
end
|
|
498
|
+
elsif (match = expr.match(/^([\w?!]+)\s*\((.*)?\)$/))
|
|
499
|
+
# receiver 없는 메서드 호출: method(args)
|
|
500
|
+
method_name = match[1]
|
|
501
|
+
args_str = match[2] || ""
|
|
502
|
+
|
|
503
|
+
# 내장 메서드가 아니면 nil
|
|
504
|
+
# (puts, print, p 등 최상위 메서드)
|
|
505
|
+
arguments = args_str.empty? ? [] : split_by_comma(args_str).map { |a| parse_expression(a.strip) }
|
|
506
|
+
|
|
507
|
+
return IR::MethodCall.new(
|
|
508
|
+
receiver: nil,
|
|
509
|
+
method_name: method_name,
|
|
510
|
+
arguments: arguments
|
|
511
|
+
)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
nil
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# 쉼표로 분리 (괄호/배열/해시/문자열 내부는 제외)
|
|
518
|
+
def split_by_comma(str)
|
|
519
|
+
result = []
|
|
520
|
+
current = ""
|
|
521
|
+
depth = 0
|
|
522
|
+
in_string = false
|
|
523
|
+
string_char = nil
|
|
524
|
+
|
|
525
|
+
str.each_char do |char|
|
|
526
|
+
if !in_string && ['"', "'"].include?(char)
|
|
527
|
+
in_string = true
|
|
528
|
+
string_char = char
|
|
529
|
+
current += char
|
|
530
|
+
elsif in_string && char == string_char
|
|
531
|
+
in_string = false
|
|
532
|
+
string_char = nil
|
|
533
|
+
current += char
|
|
534
|
+
elsif in_string
|
|
535
|
+
current += char
|
|
536
|
+
else
|
|
537
|
+
case char
|
|
538
|
+
when "(", "[", "{"
|
|
539
|
+
depth += 1
|
|
540
|
+
current += char
|
|
541
|
+
when ")", "]", "}"
|
|
542
|
+
depth -= 1
|
|
543
|
+
current += char
|
|
544
|
+
when ","
|
|
545
|
+
if depth.zero?
|
|
546
|
+
result << current.strip
|
|
547
|
+
current = ""
|
|
548
|
+
else
|
|
549
|
+
current += char
|
|
550
|
+
end
|
|
551
|
+
else
|
|
552
|
+
current += char
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
result << current.strip unless current.strip.empty?
|
|
558
|
+
result
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
end
|
data/lib/t_ruby/cli.rb
CHANGED
|
@@ -216,6 +216,9 @@ module TRuby
|
|
|
216
216
|
|
|
217
217
|
output_path = compiler.compile(input_file)
|
|
218
218
|
puts "Compiled: #{input_file} -> #{output_path}"
|
|
219
|
+
rescue TypeCheckError => e
|
|
220
|
+
puts "Type error: #{e.message}"
|
|
221
|
+
exit 1
|
|
219
222
|
rescue ArgumentError => e
|
|
220
223
|
puts "Error: #{e.message}"
|
|
221
224
|
exit 1
|