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.
@@ -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