Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/t_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require_relative "t_ruby/config"

# Core infrastructure (must be loaded first)
require_relative "t_ruby/string_utils"
require_relative "t_ruby/ir"
require_relative "t_ruby/parser_combinator"
require_relative "t_ruby/smt_solver"
Expand Down
104 changes: 101 additions & 3 deletions lib/t_ruby/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ def remove_param_types(params_str)
params = []
current = ""
depth = 0
brace_depth = 0

params_str.each_char do |char|
case char
Expand All @@ -514,9 +515,16 @@ def remove_param_types(params_str)
when ">", "]", ")"
depth -= 1
current += char
when "{"
brace_depth += 1
current += char
when "}"
brace_depth -= 1
current += char
when ","
if depth.zero?
params << clean_param(current.strip)
if depth.zero? && brace_depth.zero?
cleaned = clean_param(current.strip)
params.concat(Array(cleaned)) if cleaned
current = ""
else
current += char
Expand All @@ -526,12 +534,39 @@ def remove_param_types(params_str)
end
end

params << clean_param(current.strip) unless current.empty?
cleaned = clean_param(current.strip) unless current.empty?
params.concat(Array(cleaned)) if cleaned
params.join(", ")
end

# Clean a single parameter (remove type annotation, preserve default value)
# Returns String or Array of Strings (for keyword args group)
def clean_param(param)
param = param.strip
return nil if param.empty?

# 1. 더블 스플랫: **name: Type -> **name
if param.start_with?("**")
match = param.match(/^\*\*(\w+)(?::\s*.+)?$/)
return "**#{match[1]}" if match

return param
end

# 2. 키워드 인자 그룹: { ... } 또는 { ... }: InterfaceName
if param.start_with?("{")
return clean_keyword_args_group(param)
end

# 3. Hash 리터럴: name: { ... } -> name
if param.match?(/^\w+:\s*\{/)
match = param.match(/^(\w+):\s*\{.+\}(?::\s*\w+)?$/)
return match[1] if match

return param
end

# 4. 일반 파라미터: name: Type = value -> name = value 또는 name: Type -> name
# Match: name: Type = value (with default value)
if (match = param.match(/^(#{TRuby::IDENTIFIER_CHAR}+)\s*:\s*.+?\s*(=\s*.+)$/))
"#{match[1]} #{match[2]}"
Expand All @@ -543,6 +578,69 @@ def clean_param(param)
end
end

# 키워드 인자 그룹을 Ruby 키워드 인자로 변환
# { name: String, age: Integer = 0 } -> name:, age: 0
# { name:, age: 0 }: UserParams -> name:, age: 0
def clean_keyword_args_group(param)
# { ... }: InterfaceName 또는 { ... } 형태 파싱
interface_match = param.match(/^\{(.+)\}\s*:\s*\w+\s*$/)
inline_match = param.match(/^\{(.+)\}\s*$/) unless interface_match

inner_content = if interface_match
interface_match[1]
elsif inline_match
inline_match[1]
else
return param
end

# 내부 파라미터 분리
parts = split_nested_content(inner_content)
keyword_params = []

parts.each do |part|
part = part.strip
next if part.empty?

if interface_match
# interface 참조: name: default_value 또는 name:
if (match = part.match(/^(\w+):\s*(.*)$/))
name = match[1]
default_value = match[2].strip
keyword_params << if default_value.empty?
"#{name}:"
else
"#{name}: #{default_value}"
end
end
elsif (match = part.match(/^(\w+):\s*(.+)$/))
# 인라인 타입: name: Type = default 또는 name: Type
name = match[1]
type_and_default = match[2].strip

# Type = default 분리
default_value = extract_default_value(type_and_default)
keyword_params << if default_value
"#{name}: #{default_value}"
else
"#{name}:"
end
end
end

keyword_params
end

# 중첩된 내용을 콤마로 분리
def split_nested_content(content)
StringUtils.split_by_comma(content)
end

# 타입과 기본값에서 기본값만 추출
def extract_default_value(type_and_default)
StringUtils.extract_default_value(type_and_default)
end

# Erase return type annotations
def erase_return_types(source)
result = source.dup
Expand Down
10 changes: 7 additions & 3 deletions lib/t_ruby/ir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,19 @@ def children

# Method parameter
class Parameter < Node
attr_accessor :name, :type_annotation, :default_value, :kind
attr_accessor :name, :type_annotation, :default_value, :kind, :interface_ref

# kind: :required, :optional, :rest, :keyrest, :block
def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, **opts)
# kind: :required, :optional, :rest, :keyrest, :block, :keyword
# :keyword - 키워드 인자 (구조분해): { name: String } → def foo(name:)
# :keyrest - 더블 스플랫: **opts: Type → def foo(**opts)
# interface_ref - interface 참조 타입 (예: }: UserParams 부분)
def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, interface_ref: nil, **opts)
super(**opts)
@name = name
@type_annotation = type_annotation
@default_value = default_value
@kind = kind
@interface_ref = interface_ref
end
end

Expand Down
187 changes: 181 additions & 6 deletions lib/t_ruby/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -244,18 +244,36 @@ def parse_parameters(params_str)
param_list = split_params(params_str)

param_list.each do |param|
param_info = parse_single_parameter(param)
parameters << param_info if param_info
param = param.strip

# 1. 더블 스플랫: **name: Type
if param.start_with?("**")
param_info = parse_double_splat_parameter(param)
parameters << param_info if param_info
# 2. 키워드 인자 그룹: { ... } 또는 { ... }: InterfaceName
elsif param.start_with?("{")
keyword_params = parse_keyword_args_group(param)
parameters.concat(keyword_params) if keyword_params
# 3. Hash 리터럴: name: { ... }
elsif param.match?(/^\w+:\s*\{/)
param_info = parse_hash_literal_parameter(param)
parameters << param_info if param_info
# 4. 일반 위치 인자: name: Type 또는 name: Type = default
else
param_info = parse_single_parameter(param)
parameters << param_info if param_info
end
end

parameters
end

def split_params(params_str)
# Handle nested generics like Array<Map<String, Int>>
# Handle nested generics, braces, brackets
result = []
current = ""
depth = 0
brace_depth = 0

params_str.each_char do |char|
case char
Expand All @@ -265,8 +283,14 @@ def split_params(params_str)
when ">", "]", ")"
depth -= 1
current += char
when "{"
brace_depth += 1
current += char
when "}"
brace_depth -= 1
current += char
when ","
if depth.zero?
if depth.zero? && brace_depth.zero?
result << current.strip
current = ""
else
Expand All @@ -281,8 +305,10 @@ def split_params(params_str)
result
end

def parse_single_parameter(param)
match = param.match(/^(\w+)(?::\s*(.+?))?$/)
# 더블 스플랫 파라미터 파싱: **opts: Type
def parse_double_splat_parameter(param)
# **name: Type
match = param.match(/^\*\*(\w+)(?::\s*(.+?))?$/)
return nil unless match

param_name = match[1]
Expand All @@ -291,6 +317,155 @@ def parse_single_parameter(param)
result = {
name: param_name,
type: type_str,
kind: :keyrest,
}

if type_str
type_result = @type_parser.parse(type_str)
result[:ir_type] = type_result[:type] if type_result[:success]
end

result
end

# 키워드 인자 그룹 파싱: { name: String, age: Integer = 0 } 또는 { name:, age: 0 }: InterfaceName
def parse_keyword_args_group(param)
# { ... }: InterfaceName 형태 확인
# 또는 { ... } 만 있는 형태 (인라인 타입)
interface_match = param.match(/^\{(.+)\}\s*:\s*(\w+)\s*$/)
inline_match = param.match(/^\{(.+)\}\s*$/) unless interface_match

if interface_match
inner_content = interface_match[1]
interface_name = interface_match[2]
parse_keyword_args_with_interface(inner_content, interface_name)
elsif inline_match
inner_content = inline_match[1]
parse_keyword_args_inline(inner_content)
end
end

# interface 참조 키워드 인자 파싱: { name:, age: 0 }: UserParams
def parse_keyword_args_with_interface(inner_content, interface_name)
parameters = []
parts = split_keyword_args(inner_content)

parts.each do |part|
part = part.strip
next if part.empty?

# name: default_value 또는 name: 형태
next unless part.match?(/^(\w+):\s*(.*)$/)

match = part.match(/^(\w+):\s*(.*)$/)
param_name = match[1]
default_value = match[2].strip
default_value = nil if default_value.empty?

parameters << {
name: param_name,
type: nil, # interface에서 타입을 가져옴
default_value: default_value,
kind: :keyword,
interface_ref: interface_name,
}
end

parameters
end

# 인라인 타입 키워드 인자 파싱: { name: String, age: Integer = 0 }
def parse_keyword_args_inline(inner_content)
parameters = []
parts = split_keyword_args(inner_content)

parts.each do |part|
part = part.strip
next if part.empty?

# name: Type = default 또는 name: Type 형태
next unless part.match?(/^(\w+):\s*(.+)$/)

match = part.match(/^(\w+):\s*(.+)$/)
param_name = match[1]
type_and_default = match[2].strip

# Type = default 분리
type_str, default_value = split_type_and_default(type_and_default)

result = {
name: param_name,
type: type_str,
default_value: default_value,
kind: :keyword,
}

if type_str
type_result = @type_parser.parse(type_str)
result[:ir_type] = type_result[:type] if type_result[:success]
end

parameters << result
end

parameters
end

# 키워드 인자 내부를 콤마로 분리 (중첩된 제네릭/배열/해시 고려)
def split_keyword_args(content)
StringUtils.split_by_comma(content)
end

# 타입과 기본값 분리: "String = 0" -> ["String", "0"]
def split_type_and_default(type_and_default)
StringUtils.split_type_and_default(type_and_default)
end

# Hash 리터럴 파라미터 파싱: config: { host: String, port: Integer }
def parse_hash_literal_parameter(param)
# name: { ... } 또는 name: { ... }: InterfaceName
match = param.match(/^(\w+):\s*(\{.+\})(?::\s*(\w+))?$/)
return nil unless match

param_name = match[1]
hash_type = match[2]
interface_name = match[3]

result = {
name: param_name,
type: interface_name || hash_type,
kind: :required,
hash_type_def: hash_type, # 원본 해시 타입 정의 저장
}

result[:interface_ref] = interface_name if interface_name

result
end

def parse_single_parameter(param)
# name: Type = default 또는 name: Type 또는 name
# 기본값이 있는 경우 먼저 처리
type_str = nil
default_value = nil

if param.include?(":")
match = param.match(/^(\w+):\s*(.+)$/)
return nil unless match

param_name = match[1]
type_and_default = match[2].strip
type_str, default_value = split_type_and_default(type_and_default)
else
# 타입 없이 이름만 있는 경우
param_name = param.strip
end

result = {
name: param_name,
type: type_str,
default_value: default_value,
kind: default_value ? :optional : :required,
}

# Parse type with combinator
Expand Down
Loading