t-ruby 0.0.42 → 0.0.43

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/lib/t_ruby/ast_type_inferrer.rb +2 -0
  3. data/lib/t_ruby/cache.rb +40 -10
  4. data/lib/t_ruby/cli.rb +13 -8
  5. data/lib/t_ruby/compiler.rb +168 -0
  6. data/lib/t_ruby/diagnostic.rb +115 -0
  7. data/lib/t_ruby/diagnostic_formatter.rb +162 -0
  8. data/lib/t_ruby/error_handler.rb +201 -35
  9. data/lib/t_ruby/error_reporter.rb +57 -0
  10. data/lib/t_ruby/ir.rb +39 -1
  11. data/lib/t_ruby/lsp_server.rb +40 -97
  12. data/lib/t_ruby/parser.rb +18 -4
  13. data/lib/t_ruby/parser_combinator/combinators/alternative.rb +20 -0
  14. data/lib/t_ruby/parser_combinator/combinators/chain_left.rb +34 -0
  15. data/lib/t_ruby/parser_combinator/combinators/choice.rb +29 -0
  16. data/lib/t_ruby/parser_combinator/combinators/flat_map.rb +21 -0
  17. data/lib/t_ruby/parser_combinator/combinators/label.rb +22 -0
  18. data/lib/t_ruby/parser_combinator/combinators/lookahead.rb +21 -0
  19. data/lib/t_ruby/parser_combinator/combinators/many.rb +29 -0
  20. data/lib/t_ruby/parser_combinator/combinators/many1.rb +32 -0
  21. data/lib/t_ruby/parser_combinator/combinators/map.rb +17 -0
  22. data/lib/t_ruby/parser_combinator/combinators/not_followed_by.rb +21 -0
  23. data/lib/t_ruby/parser_combinator/combinators/optional.rb +21 -0
  24. data/lib/t_ruby/parser_combinator/combinators/sep_by.rb +34 -0
  25. data/lib/t_ruby/parser_combinator/combinators/sep_by1.rb +34 -0
  26. data/lib/t_ruby/parser_combinator/combinators/sequence.rb +23 -0
  27. data/lib/t_ruby/parser_combinator/combinators/skip_right.rb +23 -0
  28. data/lib/t_ruby/parser_combinator/declaration_parser.rb +147 -0
  29. data/lib/t_ruby/parser_combinator/dsl.rb +115 -0
  30. data/lib/t_ruby/parser_combinator/parse_error.rb +48 -0
  31. data/lib/t_ruby/parser_combinator/parse_result.rb +46 -0
  32. data/lib/t_ruby/parser_combinator/parser.rb +84 -0
  33. data/lib/t_ruby/parser_combinator/primitives/end_of_input.rb +16 -0
  34. data/lib/t_ruby/parser_combinator/primitives/fail.rb +16 -0
  35. data/lib/t_ruby/parser_combinator/primitives/lazy.rb +18 -0
  36. data/lib/t_ruby/parser_combinator/primitives/literal.rb +21 -0
  37. data/lib/t_ruby/parser_combinator/primitives/pure.rb +16 -0
  38. data/lib/t_ruby/parser_combinator/primitives/regex.rb +25 -0
  39. data/lib/t_ruby/parser_combinator/primitives/satisfy.rb +21 -0
  40. data/lib/t_ruby/parser_combinator/token/expression_parser.rb +541 -0
  41. data/lib/t_ruby/parser_combinator/token/statement_parser.rb +644 -0
  42. data/lib/t_ruby/parser_combinator/token/token_alternative.rb +20 -0
  43. data/lib/t_ruby/parser_combinator/token/token_body_parser.rb +54 -0
  44. data/lib/t_ruby/parser_combinator/token/token_declaration_parser.rb +920 -0
  45. data/lib/t_ruby/parser_combinator/token/token_dsl.rb +16 -0
  46. data/lib/t_ruby/parser_combinator/token/token_label.rb +22 -0
  47. data/lib/t_ruby/parser_combinator/token/token_many.rb +29 -0
  48. data/lib/t_ruby/parser_combinator/token/token_many1.rb +32 -0
  49. data/lib/t_ruby/parser_combinator/token/token_map.rb +17 -0
  50. data/lib/t_ruby/parser_combinator/token/token_matcher.rb +29 -0
  51. data/lib/t_ruby/parser_combinator/token/token_optional.rb +21 -0
  52. data/lib/t_ruby/parser_combinator/token/token_parse_result.rb +40 -0
  53. data/lib/t_ruby/parser_combinator/token/token_parser.rb +62 -0
  54. data/lib/t_ruby/parser_combinator/token/token_sep_by.rb +34 -0
  55. data/lib/t_ruby/parser_combinator/token/token_sep_by1.rb +34 -0
  56. data/lib/t_ruby/parser_combinator/token/token_sequence.rb +23 -0
  57. data/lib/t_ruby/parser_combinator/token/token_skip_right.rb +23 -0
  58. data/lib/t_ruby/parser_combinator/type_parser.rb +103 -0
  59. data/lib/t_ruby/parser_combinator.rb +64 -936
  60. data/lib/t_ruby/scanner.rb +883 -0
  61. data/lib/t_ruby/version.rb +1 -1
  62. data/lib/t_ruby/watcher.rb +67 -75
  63. data/lib/t_ruby.rb +15 -1
  64. metadata +51 -2
  65. data/lib/t_ruby/body_parser.rb +0 -561
data/lib/t_ruby/parser.rb CHANGED
@@ -15,6 +15,9 @@ module TRuby
15
15
  # Visibility modifiers for method definitions
16
16
  VISIBILITY_PATTERN = '(?:(?:private|protected|public)\s+)?'
17
17
 
18
+ # TODO: Replace regex-based parsing with TokenDeclarationParser
19
+ # See: lib/t_ruby/parser_combinator/token/token_declaration_parser.rb
20
+
18
21
  attr_reader :source, :ir_program
19
22
 
20
23
  def initialize(source, parse_body: true)
@@ -22,7 +25,7 @@ module TRuby
22
25
  @lines = source.split("\n")
23
26
  @parse_body = parse_body
24
27
  @type_parser = ParserCombinator::TypeParser.new
25
- @body_parser = BodyParser.new if parse_body
28
+ @body_parser = ParserCombinator::TokenBodyParser.new if parse_body
26
29
  @ir_program = nil
27
30
  end
28
31
 
@@ -97,6 +100,8 @@ module TRuby
97
100
  @ir_program = builder.build(result, source: @source)
98
101
 
99
102
  result
103
+ rescue Scanner::ScanError => e
104
+ raise ParseError.new(e.message, line: e.line, column: e.column)
100
105
  end
101
106
 
102
107
  # Parse to IR directly (new API)
@@ -116,10 +121,14 @@ module TRuby
116
121
  # 최상위 함수를 본문까지 포함하여 파싱
117
122
  def parse_function_with_body(start_index)
118
123
  line = @lines[start_index]
119
- func_info = parse_function_definition(line)
124
+ func_info = parse_function_definition(line, line_number: start_index + 1)
120
125
  return [nil, start_index] unless func_info
121
126
 
127
+ # Add location info (1-based line number, column is 1 + indentation)
122
128
  def_indent = line.match(/^(\s*)/)[1].length
129
+ func_info[:line] = start_index + 1
130
+ func_info[:column] = def_indent + 1
131
+
123
132
  i = start_index + 1
124
133
  body_start = i
125
134
  body_end = i
@@ -171,13 +180,14 @@ module TRuby
171
180
  }
172
181
  end
173
182
 
174
- def parse_function_definition(line)
183
+ def parse_function_definition(line, line_number: 1) # rubocop:disable Lint/UnusedMethodArgument
175
184
  # Match methods with or without parentheses
176
185
  # def foo(params): Type - with params and return type
177
186
  # def foo(): Type - no params but with return type
178
187
  # def foo(params) - with params, no return type
179
188
  # def foo - no params, no return type
180
189
  # Also supports visibility modifiers: private def, protected def, public def
190
+
181
191
  match = line.match(/^\s*(?:(private|protected|public)\s+)?def\s+(#{METHOD_NAME_PATTERN})\s*(?:\((.*?)\))?\s*(?::\s*(.+?))?\s*$/)
182
192
  return nil unless match
183
193
 
@@ -547,10 +557,14 @@ module TRuby
547
557
  # 클래스 내부의 메서드를 본문까지 포함하여 파싱
548
558
  def parse_method_in_class(start_index, class_end)
549
559
  line = @lines[start_index]
550
- method_info = parse_function_definition(line)
560
+ method_info = parse_function_definition(line, line_number: start_index + 1)
551
561
  return [nil, start_index] unless method_info
552
562
 
563
+ # Add location info (1-based line number, column is 1 + indentation)
553
564
  def_indent = line.match(/^(\s*)/)[1].length
565
+ method_info[:line] = start_index + 1
566
+ method_info[:column] = def_indent + 1
567
+
554
568
  i = start_index + 1
555
569
  body_start = i
556
570
  body_end = i
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Alternative: try first, if fails try second
6
+ class Alternative < Parser
7
+ def initialize(left, right)
8
+ @left = left
9
+ @right = right
10
+ end
11
+
12
+ def parse(input, position = 0)
13
+ result = @left.parse(input, position)
14
+ return result if result.success?
15
+
16
+ @right.parse(input, position)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Chainl: left-associative chain
6
+ class ChainLeft < Parser
7
+ def initialize(term, op)
8
+ @term = term
9
+ @op = op
10
+ end
11
+
12
+ def parse(input, position = 0)
13
+ first = @term.parse(input, position)
14
+ return first if first.failure?
15
+
16
+ result = first.value
17
+ current_pos = first.position
18
+
19
+ loop do
20
+ op_result = @op.parse(input, current_pos)
21
+ break if op_result.failure?
22
+
23
+ term_result = @term.parse(input, op_result.position)
24
+ break if term_result.failure?
25
+
26
+ result = op_result.value.call(result, term_result.value)
27
+ current_pos = term_result.position
28
+ end
29
+
30
+ ParseResult.success(result, input, current_pos)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Choice: try multiple parsers in order
6
+ class Choice < Parser
7
+ def initialize(*parsers)
8
+ @parsers = parsers
9
+ end
10
+
11
+ def parse(input, position = 0)
12
+ best_error = nil
13
+ best_position = position
14
+
15
+ @parsers.each do |parser|
16
+ result = parser.parse(input, position)
17
+ return result if result.success?
18
+
19
+ if result.position >= best_position
20
+ best_error = result.error
21
+ best_position = result.position
22
+ end
23
+ end
24
+
25
+ ParseResult.failure(best_error || "No alternative matched", input, best_position)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # FlatMap (bind)
6
+ class FlatMap < Parser
7
+ def initialize(parser, func)
8
+ @parser = parser
9
+ @func = func
10
+ end
11
+
12
+ def parse(input, position = 0)
13
+ result = @parser.parse(input, position)
14
+ return result if result.failure?
15
+
16
+ next_parser = @func.call(result.value)
17
+ next_parser.parse(input, result.position)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Label for error messages
6
+ class Label < Parser
7
+ def initialize(parser, name)
8
+ @parser = parser
9
+ @name = name
10
+ end
11
+
12
+ def parse(input, position = 0)
13
+ result = @parser.parse(input, position)
14
+ if result.failure?
15
+ ParseResult.failure("Expected #{@name}", input, position)
16
+ else
17
+ result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Lookahead: check without consuming
6
+ class Lookahead < Parser
7
+ def initialize(parser)
8
+ @parser = parser
9
+ end
10
+
11
+ def parse(input, position = 0)
12
+ result = @parser.parse(input, position)
13
+ if result.success?
14
+ ParseResult.success(result.value, input, position)
15
+ else
16
+ result
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Many: zero or more
6
+ class Many < Parser
7
+ def initialize(parser)
8
+ @parser = parser
9
+ end
10
+
11
+ def parse(input, position = 0)
12
+ results = []
13
+ current_pos = position
14
+
15
+ loop do
16
+ result = @parser.parse(input, current_pos)
17
+ break if result.failure?
18
+
19
+ results << result.value
20
+ break if result.position == current_pos # Prevent infinite loop
21
+
22
+ current_pos = result.position
23
+ end
24
+
25
+ ParseResult.success(results, input, current_pos)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Many1: one or more
6
+ class Many1 < Parser
7
+ def initialize(parser)
8
+ @parser = parser
9
+ end
10
+
11
+ def parse(input, position = 0)
12
+ first = @parser.parse(input, position)
13
+ return first if first.failure?
14
+
15
+ results = [first.value]
16
+ current_pos = first.position
17
+
18
+ loop do
19
+ result = @parser.parse(input, current_pos)
20
+ break if result.failure?
21
+
22
+ results << result.value
23
+ break if result.position == current_pos
24
+
25
+ current_pos = result.position
26
+ end
27
+
28
+ ParseResult.success(results, input, current_pos)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Map result
6
+ class Map < Parser
7
+ def initialize(parser, func)
8
+ @parser = parser
9
+ @func = func
10
+ end
11
+
12
+ def parse(input, position = 0)
13
+ @parser.parse(input, position).map(&@func)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Not followed by
6
+ class NotFollowedBy < Parser
7
+ def initialize(parser)
8
+ @parser = parser
9
+ end
10
+
11
+ def parse(input, position = 0)
12
+ result = @parser.parse(input, position)
13
+ if result.failure?
14
+ ParseResult.success(nil, input, position)
15
+ else
16
+ ParseResult.failure("Unexpected match", input, position)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Optional: zero or one
6
+ class Optional < Parser
7
+ def initialize(parser)
8
+ @parser = parser
9
+ end
10
+
11
+ def parse(input, position = 0)
12
+ result = @parser.parse(input, position)
13
+ if result.success?
14
+ result
15
+ else
16
+ ParseResult.success(nil, input, position)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Separated by delimiter
6
+ class SepBy < Parser
7
+ def initialize(parser, delimiter)
8
+ @parser = parser
9
+ @delimiter = delimiter
10
+ end
11
+
12
+ def parse(input, position = 0)
13
+ first = @parser.parse(input, position)
14
+ return ParseResult.success([], input, position) if first.failure?
15
+
16
+ results = [first.value]
17
+ current_pos = first.position
18
+
19
+ loop do
20
+ delim_result = @delimiter.parse(input, current_pos)
21
+ break if delim_result.failure?
22
+
23
+ item_result = @parser.parse(input, delim_result.position)
24
+ break if item_result.failure?
25
+
26
+ results << item_result.value
27
+ current_pos = item_result.position
28
+ end
29
+
30
+ ParseResult.success(results, input, current_pos)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Separated by 1 (at least one)
6
+ class SepBy1 < Parser
7
+ def initialize(parser, delimiter)
8
+ @parser = parser
9
+ @delimiter = delimiter
10
+ end
11
+
12
+ def parse(input, position = 0)
13
+ first = @parser.parse(input, position)
14
+ return first if first.failure?
15
+
16
+ results = [first.value]
17
+ current_pos = first.position
18
+
19
+ loop do
20
+ delim_result = @delimiter.parse(input, current_pos)
21
+ break if delim_result.failure?
22
+
23
+ item_result = @parser.parse(input, delim_result.position)
24
+ break if item_result.failure?
25
+
26
+ results << item_result.value
27
+ current_pos = item_result.position
28
+ end
29
+
30
+ ParseResult.success(results, input, current_pos)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Sequence two parsers
6
+ class Sequence < Parser
7
+ def initialize(left, right)
8
+ @left = left
9
+ @right = right
10
+ end
11
+
12
+ def parse(input, position = 0)
13
+ result1 = @left.parse(input, position)
14
+ return result1 if result1.failure?
15
+
16
+ result2 = @right.parse(input, result1.position)
17
+ return result2 if result2.failure?
18
+
19
+ ParseResult.success([result1.value, result2.value], input, result2.position)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Skip right: parse both, return left
6
+ class SkipRight < Parser
7
+ def initialize(left, right)
8
+ @left = left
9
+ @right = right
10
+ end
11
+
12
+ def parse(input, position = 0)
13
+ result1 = @left.parse(input, position)
14
+ return result1 if result1.failure?
15
+
16
+ result2 = @right.parse(input, result1.position)
17
+ return result2 if result2.failure?
18
+
19
+ ParseResult.success(result1.value, input, result2.position)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Declaration Parser - Parse T-Ruby declarations
6
+ class DeclarationParser
7
+ include DSL
8
+
9
+ def initialize
10
+ @type_parser = TypeParser.new
11
+ build_parsers
12
+ end
13
+
14
+ def parse(input)
15
+ result = @declaration.parse(input.strip)
16
+ if result.success?
17
+ { success: true, declarations: result.value }
18
+ else
19
+ { success: false, error: result.error, position: result.position }
20
+ end
21
+ end
22
+
23
+ def parse_file(input)
24
+ result = @program.parse(input)
25
+ if result.success?
26
+ { success: true, declarations: result.value.compact }
27
+ else
28
+ { success: false, error: result.error, position: result.position }
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def build_parsers
35
+ # Type expression (delegate to TypeParser)
36
+ lazy { parse_type_inline }
37
+
38
+ # Keywords
39
+ kw_type = lexeme(string("type"))
40
+ kw_interface = lexeme(string("interface"))
41
+ kw_def = lexeme(string("def"))
42
+ kw_end = lexeme(string("end"))
43
+ lexeme(string("class"))
44
+ lexeme(string("module"))
45
+
46
+ # Type alias: type Name = Definition
47
+ type_alias = (
48
+ kw_type >>
49
+ lexeme(identifier) <<
50
+ lexeme(char("=")) >>
51
+ regex(/[^\n]+/).map(&:strip)
52
+ ).map do |((_, name), definition)|
53
+ type_result = @type_parser.parse(definition)
54
+ if type_result[:success]
55
+ IR::TypeAlias.new(name: name, definition: type_result[:type])
56
+ end
57
+ end
58
+
59
+ # Interface member: name: Type
60
+ interface_member = (
61
+ lexeme(identifier) <<
62
+ lexeme(char(":")) >>
63
+ regex(/[^\n]+/).map(&:strip)
64
+ ).map do |(name, type_str)|
65
+ type_result = @type_parser.parse(type_str)
66
+ if type_result[:success]
67
+ IR::InterfaceMember.new(name: name, type_signature: type_result[:type])
68
+ end
69
+ end
70
+
71
+ # Interface: interface Name ... end
72
+ interface_body = (interface_member << (newline | spaces)).many
73
+
74
+ interface_decl = (
75
+ kw_interface >>
76
+ lexeme(identifier) <<
77
+ (newline | spaces) >>
78
+ interface_body <<
79
+ kw_end
80
+ ).map do |((_, name), members)|
81
+ IR::Interface.new(name: name, members: members.compact)
82
+ end
83
+
84
+ # Parameter: name: Type or name
85
+ param = (
86
+ identifier >>
87
+ (lexeme(char(":")) >> regex(/[^,)]+/).map(&:strip)).optional
88
+ ).map do |(name, type_str)|
89
+ type_node = if type_str
90
+ type_str_val = type_str.is_a?(Array) ? type_str.last : type_str
91
+ result = @type_parser.parse(type_str_val)
92
+ result[:success] ? result[:type] : nil
93
+ end
94
+ IR::Parameter.new(name: name, type_annotation: type_node)
95
+ end
96
+
97
+ # Parameters list
98
+ params_list = (
99
+ lexeme(char("(")) >>
100
+ param.sep_by(lexeme(char(","))) <<
101
+ lexeme(char(")"))
102
+ ).map { |(_, params)| params }
103
+
104
+ # Return type annotation
105
+ return_type = (
106
+ lexeme(char(":")) >>
107
+ regex(/[^\n]+/).map(&:strip)
108
+ ).map { |(_, type_str)| type_str }.optional
109
+
110
+ # Method definition: def name(params): ReturnType
111
+ method_def = (
112
+ kw_def >>
113
+ identifier >>
114
+ params_list.optional >>
115
+ return_type
116
+ ).map do |(((_, name), params), ret_str)|
117
+ ret_type = if ret_str
118
+ result = @type_parser.parse(ret_str)
119
+ result[:success] ? result[:type] : nil
120
+ end
121
+ IR::MethodDef.new(
122
+ name: name,
123
+ params: params || [],
124
+ return_type: ret_type
125
+ )
126
+ end
127
+
128
+ # Any declaration
129
+ @declaration = choice(
130
+ type_alias,
131
+ interface_decl,
132
+ method_def
133
+ )
134
+
135
+ # Line (declaration or empty)
136
+ line = (@declaration << (newline | eof)) | (spaces >> newline).map { nil }
137
+
138
+ # Program (multiple declarations)
139
+ @program = line.many
140
+ end
141
+
142
+ def parse_type_inline
143
+ Lazy.new { @type_parser.instance_variable_get(:@type_expr) }
144
+ end
145
+ end
146
+ end
147
+ end