t-ruby 0.0.40 → 0.0.41

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54f0b9008d5a42a8cbf851eac8a0eb540670c3c87018dd87182baf6ab30c7d55
4
- data.tar.gz: 42929dd91ae55ea84e75fd6978298deea2fec1d3517a801bde06af6f70865557
3
+ metadata.gz: 6a7c7a93e1498a150b541ea7f7838c08435a33ead72774b7e4c0b8ead951e9d3
4
+ data.tar.gz: 1fbfb9ea6b276e130ef04891f39d691e362d5352dc4f0209b00009105f13c3a0
5
5
  SHA512:
6
- metadata.gz: 20916cbdb9a4d62bd9208c8496df8e8f425885c600d5caa9ef0d5a545c4d52a62fa66d05157bfc89a0c57328658f6ecb08889336352e76b4cfb757518365f50b
7
- data.tar.gz: b6125521d066808e825747418d794ca6adb94503e7d89b61fe5a9bb009e2c2fcc49952b0ea3fd12f5c0bb51de9d20de0ff3a7b4f0ab37358d0616010e1f9ca55
6
+ metadata.gz: 82cbc96204194f51b65f484aeda25383e285ce902c325e269766e77871e9dccacc548111e864ba2bfb27ca48fc69c7e22c5a0b1eee8e7c25e4763417ffe2c8fe
7
+ data.tar.gz: ed5f8ad72344e624e8a95d1e9a48078e0f6679d914f978974e0117b75bbc11ce2fbe08ece39360b20ce777ef5c5c333f37376f216272c1e6aa983eb18585fc92
@@ -505,6 +505,7 @@ module TRuby
505
505
  params = []
506
506
  current = ""
507
507
  depth = 0
508
+ brace_depth = 0
508
509
 
509
510
  params_str.each_char do |char|
510
511
  case char
@@ -514,9 +515,16 @@ module TRuby
514
515
  when ">", "]", ")"
515
516
  depth -= 1
516
517
  current += char
518
+ when "{"
519
+ brace_depth += 1
520
+ current += char
521
+ when "}"
522
+ brace_depth -= 1
523
+ current += char
517
524
  when ","
518
- if depth.zero?
519
- params << clean_param(current.strip)
525
+ if depth.zero? && brace_depth.zero?
526
+ cleaned = clean_param(current.strip)
527
+ params.concat(Array(cleaned)) if cleaned
520
528
  current = ""
521
529
  else
522
530
  current += char
@@ -526,12 +534,39 @@ module TRuby
526
534
  end
527
535
  end
528
536
 
529
- params << clean_param(current.strip) unless current.empty?
537
+ cleaned = clean_param(current.strip) unless current.empty?
538
+ params.concat(Array(cleaned)) if cleaned
530
539
  params.join(", ")
531
540
  end
532
541
 
533
542
  # Clean a single parameter (remove type annotation, preserve default value)
543
+ # Returns String or Array of Strings (for keyword args group)
534
544
  def clean_param(param)
545
+ param = param.strip
546
+ return nil if param.empty?
547
+
548
+ # 1. 더블 스플랫: **name: Type -> **name
549
+ if param.start_with?("**")
550
+ match = param.match(/^\*\*(\w+)(?::\s*.+)?$/)
551
+ return "**#{match[1]}" if match
552
+
553
+ return param
554
+ end
555
+
556
+ # 2. 키워드 인자 그룹: { ... } 또는 { ... }: InterfaceName
557
+ if param.start_with?("{")
558
+ return clean_keyword_args_group(param)
559
+ end
560
+
561
+ # 3. Hash 리터럴: name: { ... } -> name
562
+ if param.match?(/^\w+:\s*\{/)
563
+ match = param.match(/^(\w+):\s*\{.+\}(?::\s*\w+)?$/)
564
+ return match[1] if match
565
+
566
+ return param
567
+ end
568
+
569
+ # 4. 일반 파라미터: name: Type = value -> name = value 또는 name: Type -> name
535
570
  # Match: name: Type = value (with default value)
536
571
  if (match = param.match(/^(#{TRuby::IDENTIFIER_CHAR}+)\s*:\s*.+?\s*(=\s*.+)$/))
537
572
  "#{match[1]} #{match[2]}"
@@ -543,15 +578,83 @@ module TRuby
543
578
  end
544
579
  end
545
580
 
581
+ # 키워드 인자 그룹을 Ruby 키워드 인자로 변환
582
+ # { name: String, age: Integer = 0 } -> name:, age: 0
583
+ # { name:, age: 0 }: UserParams -> name:, age: 0
584
+ def clean_keyword_args_group(param)
585
+ # { ... }: InterfaceName 또는 { ... } 형태 파싱
586
+ interface_match = param.match(/^\{(.+)\}\s*:\s*\w+\s*$/)
587
+ inline_match = param.match(/^\{(.+)\}\s*$/) unless interface_match
588
+
589
+ inner_content = if interface_match
590
+ interface_match[1]
591
+ elsif inline_match
592
+ inline_match[1]
593
+ else
594
+ return param
595
+ end
596
+
597
+ # 내부 파라미터 분리
598
+ parts = split_nested_content(inner_content)
599
+ keyword_params = []
600
+
601
+ parts.each do |part|
602
+ part = part.strip
603
+ next if part.empty?
604
+
605
+ if interface_match
606
+ # interface 참조: name: default_value 또는 name:
607
+ if (match = part.match(/^(\w+):\s*(.*)$/))
608
+ name = match[1]
609
+ default_value = match[2].strip
610
+ keyword_params << if default_value.empty?
611
+ "#{name}:"
612
+ else
613
+ "#{name}: #{default_value}"
614
+ end
615
+ end
616
+ elsif (match = part.match(/^(\w+):\s*(.+)$/))
617
+ # 인라인 타입: name: Type = default 또는 name: Type
618
+ name = match[1]
619
+ type_and_default = match[2].strip
620
+
621
+ # Type = default 분리
622
+ default_value = extract_default_value(type_and_default)
623
+ keyword_params << if default_value
624
+ "#{name}: #{default_value}"
625
+ else
626
+ "#{name}:"
627
+ end
628
+ end
629
+ end
630
+
631
+ keyword_params
632
+ end
633
+
634
+ # 중첩된 내용을 콤마로 분리
635
+ def split_nested_content(content)
636
+ StringUtils.split_by_comma(content)
637
+ end
638
+
639
+ # 타입과 기본값에서 기본값만 추출
640
+ def extract_default_value(type_and_default)
641
+ StringUtils.extract_default_value(type_and_default)
642
+ end
643
+
546
644
  # Erase return type annotations
547
645
  def erase_return_types(source)
548
646
  result = source.dup
549
647
 
550
- # Remove return type: ): Type or ): Type<Foo> etc.
648
+ # Remove return type after parentheses: ): Type or ): Type<Foo> etc.
551
649
  result.gsub!(/\)\s*:\s*[^\n]+?(?=\s*$)/m) do |_match|
552
650
  ")"
553
651
  end
554
652
 
653
+ # Remove return type for methods without parentheses: def method_name: Type
654
+ result.gsub!(/^(\s*#{TRuby::VISIBILITY_PATTERN}def\s+#{TRuby::METHOD_NAME_PATTERN})\s*:\s*[^\n]+?(?=\s*$)/m) do |_match|
655
+ ::Regexp.last_match(1)
656
+ end
657
+
555
658
  result
556
659
  end
557
660
  end
data/lib/t_ruby/ir.rb CHANGED
@@ -147,15 +147,19 @@ module TRuby
147
147
 
148
148
  # Method parameter
149
149
  class Parameter < Node
150
- attr_accessor :name, :type_annotation, :default_value, :kind
150
+ attr_accessor :name, :type_annotation, :default_value, :kind, :interface_ref
151
151
 
152
- # kind: :required, :optional, :rest, :keyrest, :block
153
- def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, **opts)
152
+ # kind: :required, :optional, :rest, :keyrest, :block, :keyword
153
+ # :keyword - 키워드 인자 (구조분해): { name: String } → def foo(name:)
154
+ # :keyrest - 더블 스플랫: **opts: Type → def foo(**opts)
155
+ # interface_ref - interface 참조 타입 (예: }: UserParams 부분)
156
+ def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, interface_ref: nil, **opts)
154
157
  super(**opts)
155
158
  @name = name
156
159
  @type_annotation = type_annotation
157
160
  @default_value = default_value
158
161
  @kind = kind
162
+ @interface_ref = interface_ref
159
163
  end
160
164
  end
161
165
 
data/lib/t_ruby/parser.rb CHANGED
@@ -244,18 +244,36 @@ module TRuby
244
244
  param_list = split_params(params_str)
245
245
 
246
246
  param_list.each do |param|
247
- param_info = parse_single_parameter(param)
248
- parameters << param_info if param_info
247
+ param = param.strip
248
+
249
+ # 1. 더블 스플랫: **name: Type
250
+ if param.start_with?("**")
251
+ param_info = parse_double_splat_parameter(param)
252
+ parameters << param_info if param_info
253
+ # 2. 키워드 인자 그룹: { ... } 또는 { ... }: InterfaceName
254
+ elsif param.start_with?("{")
255
+ keyword_params = parse_keyword_args_group(param)
256
+ parameters.concat(keyword_params) if keyword_params
257
+ # 3. Hash 리터럴: name: { ... }
258
+ elsif param.match?(/^\w+:\s*\{/)
259
+ param_info = parse_hash_literal_parameter(param)
260
+ parameters << param_info if param_info
261
+ # 4. 일반 위치 인자: name: Type 또는 name: Type = default
262
+ else
263
+ param_info = parse_single_parameter(param)
264
+ parameters << param_info if param_info
265
+ end
249
266
  end
250
267
 
251
268
  parameters
252
269
  end
253
270
 
254
271
  def split_params(params_str)
255
- # Handle nested generics like Array<Map<String, Int>>
272
+ # Handle nested generics, braces, brackets
256
273
  result = []
257
274
  current = ""
258
275
  depth = 0
276
+ brace_depth = 0
259
277
 
260
278
  params_str.each_char do |char|
261
279
  case char
@@ -265,8 +283,14 @@ module TRuby
265
283
  when ">", "]", ")"
266
284
  depth -= 1
267
285
  current += char
286
+ when "{"
287
+ brace_depth += 1
288
+ current += char
289
+ when "}"
290
+ brace_depth -= 1
291
+ current += char
268
292
  when ","
269
- if depth.zero?
293
+ if depth.zero? && brace_depth.zero?
270
294
  result << current.strip
271
295
  current = ""
272
296
  else
@@ -281,8 +305,10 @@ module TRuby
281
305
  result
282
306
  end
283
307
 
284
- def parse_single_parameter(param)
285
- match = param.match(/^(\w+)(?::\s*(.+?))?$/)
308
+ # 더블 스플랫 파라미터 파싱: **opts: Type
309
+ def parse_double_splat_parameter(param)
310
+ # **name: Type
311
+ match = param.match(/^\*\*(\w+)(?::\s*(.+?))?$/)
286
312
  return nil unless match
287
313
 
288
314
  param_name = match[1]
@@ -291,6 +317,155 @@ module TRuby
291
317
  result = {
292
318
  name: param_name,
293
319
  type: type_str,
320
+ kind: :keyrest,
321
+ }
322
+
323
+ if type_str
324
+ type_result = @type_parser.parse(type_str)
325
+ result[:ir_type] = type_result[:type] if type_result[:success]
326
+ end
327
+
328
+ result
329
+ end
330
+
331
+ # 키워드 인자 그룹 파싱: { name: String, age: Integer = 0 } 또는 { name:, age: 0 }: InterfaceName
332
+ def parse_keyword_args_group(param)
333
+ # { ... }: InterfaceName 형태 확인
334
+ # 또는 { ... } 만 있는 형태 (인라인 타입)
335
+ interface_match = param.match(/^\{(.+)\}\s*:\s*(\w+)\s*$/)
336
+ inline_match = param.match(/^\{(.+)\}\s*$/) unless interface_match
337
+
338
+ if interface_match
339
+ inner_content = interface_match[1]
340
+ interface_name = interface_match[2]
341
+ parse_keyword_args_with_interface(inner_content, interface_name)
342
+ elsif inline_match
343
+ inner_content = inline_match[1]
344
+ parse_keyword_args_inline(inner_content)
345
+ end
346
+ end
347
+
348
+ # interface 참조 키워드 인자 파싱: { name:, age: 0 }: UserParams
349
+ def parse_keyword_args_with_interface(inner_content, interface_name)
350
+ parameters = []
351
+ parts = split_keyword_args(inner_content)
352
+
353
+ parts.each do |part|
354
+ part = part.strip
355
+ next if part.empty?
356
+
357
+ # name: default_value 또는 name: 형태
358
+ next unless part.match?(/^(\w+):\s*(.*)$/)
359
+
360
+ match = part.match(/^(\w+):\s*(.*)$/)
361
+ param_name = match[1]
362
+ default_value = match[2].strip
363
+ default_value = nil if default_value.empty?
364
+
365
+ parameters << {
366
+ name: param_name,
367
+ type: nil, # interface에서 타입을 가져옴
368
+ default_value: default_value,
369
+ kind: :keyword,
370
+ interface_ref: interface_name,
371
+ }
372
+ end
373
+
374
+ parameters
375
+ end
376
+
377
+ # 인라인 타입 키워드 인자 파싱: { name: String, age: Integer = 0 }
378
+ def parse_keyword_args_inline(inner_content)
379
+ parameters = []
380
+ parts = split_keyword_args(inner_content)
381
+
382
+ parts.each do |part|
383
+ part = part.strip
384
+ next if part.empty?
385
+
386
+ # name: Type = default 또는 name: Type 형태
387
+ next unless part.match?(/^(\w+):\s*(.+)$/)
388
+
389
+ match = part.match(/^(\w+):\s*(.+)$/)
390
+ param_name = match[1]
391
+ type_and_default = match[2].strip
392
+
393
+ # Type = default 분리
394
+ type_str, default_value = split_type_and_default(type_and_default)
395
+
396
+ result = {
397
+ name: param_name,
398
+ type: type_str,
399
+ default_value: default_value,
400
+ kind: :keyword,
401
+ }
402
+
403
+ if type_str
404
+ type_result = @type_parser.parse(type_str)
405
+ result[:ir_type] = type_result[:type] if type_result[:success]
406
+ end
407
+
408
+ parameters << result
409
+ end
410
+
411
+ parameters
412
+ end
413
+
414
+ # 키워드 인자 내부를 콤마로 분리 (중첩된 제네릭/배열/해시 고려)
415
+ def split_keyword_args(content)
416
+ StringUtils.split_by_comma(content)
417
+ end
418
+
419
+ # 타입과 기본값 분리: "String = 0" -> ["String", "0"]
420
+ def split_type_and_default(type_and_default)
421
+ StringUtils.split_type_and_default(type_and_default)
422
+ end
423
+
424
+ # Hash 리터럴 파라미터 파싱: config: { host: String, port: Integer }
425
+ def parse_hash_literal_parameter(param)
426
+ # name: { ... } 또는 name: { ... }: InterfaceName
427
+ match = param.match(/^(\w+):\s*(\{.+\})(?::\s*(\w+))?$/)
428
+ return nil unless match
429
+
430
+ param_name = match[1]
431
+ hash_type = match[2]
432
+ interface_name = match[3]
433
+
434
+ result = {
435
+ name: param_name,
436
+ type: interface_name || hash_type,
437
+ kind: :required,
438
+ hash_type_def: hash_type, # 원본 해시 타입 정의 저장
439
+ }
440
+
441
+ result[:interface_ref] = interface_name if interface_name
442
+
443
+ result
444
+ end
445
+
446
+ def parse_single_parameter(param)
447
+ # name: Type = default 또는 name: Type 또는 name
448
+ # 기본값이 있는 경우 먼저 처리
449
+ type_str = nil
450
+ default_value = nil
451
+
452
+ if param.include?(":")
453
+ match = param.match(/^(\w+):\s*(.+)$/)
454
+ return nil unless match
455
+
456
+ param_name = match[1]
457
+ type_and_default = match[2].strip
458
+ type_str, default_value = split_type_and_default(type_and_default)
459
+ else
460
+ # 타입 없이 이름만 있는 경우
461
+ param_name = param.strip
462
+ end
463
+
464
+ result = {
465
+ name: param_name,
466
+ type: type_str,
467
+ default_value: default_value,
468
+ kind: default_value ? :optional : :required,
294
469
  }
295
470
 
296
471
  # Parse type with combinator
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ # 문자열 파싱을 위한 공통 유틸리티 모듈
5
+ # 파서와 컴파일러에서 공유하는 중첩 괄호 처리 로직
6
+ module StringUtils
7
+ module_function
8
+
9
+ # 중첩된 괄호를 고려하여 콤마로 문자열 분리
10
+ # @param content [String] 분리할 문자열
11
+ # @return [Array<String>] 분리된 문자열 배열
12
+ def split_by_comma(content)
13
+ result = []
14
+ current = ""
15
+ depth = 0
16
+
17
+ content.each_char do |char|
18
+ case char
19
+ when "<", "[", "(", "{"
20
+ depth += 1
21
+ current += char
22
+ when ">", "]", ")", "}"
23
+ depth -= 1
24
+ current += char
25
+ when ","
26
+ if depth.zero?
27
+ result << current.strip
28
+ current = ""
29
+ else
30
+ current += char
31
+ end
32
+ else
33
+ current += char
34
+ end
35
+ end
36
+
37
+ result << current.strip unless current.empty?
38
+ result
39
+ end
40
+
41
+ # 타입과 기본값 분리: "String = 0" -> ["String", "0"]
42
+ # 중첩된 괄호 내부의 = 는 무시
43
+ # @param type_and_default [String] "Type = default" 형태의 문자열
44
+ # @return [Array] [type_str, default_value] 또는 [type_str, nil]
45
+ def split_type_and_default(type_and_default)
46
+ depth = 0
47
+ equals_pos = nil
48
+
49
+ type_and_default.each_char.with_index do |char, i|
50
+ case char
51
+ when "<", "[", "(", "{"
52
+ depth += 1
53
+ when ">", "]", ")", "}"
54
+ depth -= 1
55
+ when "="
56
+ if depth.zero?
57
+ equals_pos = i
58
+ break
59
+ end
60
+ end
61
+ end
62
+
63
+ if equals_pos
64
+ type_str = type_and_default[0...equals_pos].strip
65
+ default_value = type_and_default[(equals_pos + 1)..].strip
66
+ [type_str, default_value]
67
+ else
68
+ [type_and_default, nil]
69
+ end
70
+ end
71
+
72
+ # 기본값만 추출 (타입은 버림)
73
+ # @param type_and_default [String] "Type = default" 형태의 문자열
74
+ # @return [String, nil] 기본값 또는 nil
75
+ def extract_default_value(type_and_default)
76
+ _, default_value = split_type_and_default(type_and_default)
77
+ default_value
78
+ end
79
+ end
80
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRuby
4
- VERSION = "0.0.40"
4
+ VERSION = "0.0.41"
5
5
  end
data/lib/t_ruby.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "t_ruby/version_checker"
5
5
  require_relative "t_ruby/config"
6
6
 
7
7
  # Core infrastructure (must be loaded first)
8
+ require_relative "t_ruby/string_utils"
8
9
  require_relative "t_ruby/ir"
9
10
  require_relative "t_ruby/parser_combinator"
10
11
  require_relative "t_ruby/smt_solver"
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.40
4
+ version: 0.0.41
5
5
  platform: ruby
6
6
  authors:
7
7
  - Y. Fred Kim
@@ -61,6 +61,7 @@ files:
61
61
  - lib/t_ruby/parser_combinator.rb
62
62
  - lib/t_ruby/runtime_validator.rb
63
63
  - lib/t_ruby/smt_solver.rb
64
+ - lib/t_ruby/string_utils.rb
64
65
  - lib/t_ruby/type_alias_registry.rb
65
66
  - lib/t_ruby/type_checker.rb
66
67
  - lib/t_ruby/type_env.rb