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.
- checksums.yaml +4 -4
- data/lib/t_ruby/ast_type_inferrer.rb +2 -0
- data/lib/t_ruby/cache.rb +40 -10
- data/lib/t_ruby/cli.rb +13 -8
- data/lib/t_ruby/compiler.rb +168 -0
- data/lib/t_ruby/diagnostic.rb +115 -0
- data/lib/t_ruby/diagnostic_formatter.rb +162 -0
- data/lib/t_ruby/error_handler.rb +201 -35
- data/lib/t_ruby/error_reporter.rb +57 -0
- data/lib/t_ruby/ir.rb +39 -1
- data/lib/t_ruby/lsp_server.rb +40 -97
- data/lib/t_ruby/parser.rb +18 -4
- data/lib/t_ruby/parser_combinator/combinators/alternative.rb +20 -0
- data/lib/t_ruby/parser_combinator/combinators/chain_left.rb +34 -0
- data/lib/t_ruby/parser_combinator/combinators/choice.rb +29 -0
- data/lib/t_ruby/parser_combinator/combinators/flat_map.rb +21 -0
- data/lib/t_ruby/parser_combinator/combinators/label.rb +22 -0
- data/lib/t_ruby/parser_combinator/combinators/lookahead.rb +21 -0
- data/lib/t_ruby/parser_combinator/combinators/many.rb +29 -0
- data/lib/t_ruby/parser_combinator/combinators/many1.rb +32 -0
- data/lib/t_ruby/parser_combinator/combinators/map.rb +17 -0
- data/lib/t_ruby/parser_combinator/combinators/not_followed_by.rb +21 -0
- data/lib/t_ruby/parser_combinator/combinators/optional.rb +21 -0
- data/lib/t_ruby/parser_combinator/combinators/sep_by.rb +34 -0
- data/lib/t_ruby/parser_combinator/combinators/sep_by1.rb +34 -0
- data/lib/t_ruby/parser_combinator/combinators/sequence.rb +23 -0
- data/lib/t_ruby/parser_combinator/combinators/skip_right.rb +23 -0
- data/lib/t_ruby/parser_combinator/declaration_parser.rb +147 -0
- data/lib/t_ruby/parser_combinator/dsl.rb +115 -0
- data/lib/t_ruby/parser_combinator/parse_error.rb +48 -0
- data/lib/t_ruby/parser_combinator/parse_result.rb +46 -0
- data/lib/t_ruby/parser_combinator/parser.rb +84 -0
- data/lib/t_ruby/parser_combinator/primitives/end_of_input.rb +16 -0
- data/lib/t_ruby/parser_combinator/primitives/fail.rb +16 -0
- data/lib/t_ruby/parser_combinator/primitives/lazy.rb +18 -0
- data/lib/t_ruby/parser_combinator/primitives/literal.rb +21 -0
- data/lib/t_ruby/parser_combinator/primitives/pure.rb +16 -0
- data/lib/t_ruby/parser_combinator/primitives/regex.rb +25 -0
- data/lib/t_ruby/parser_combinator/primitives/satisfy.rb +21 -0
- data/lib/t_ruby/parser_combinator/token/expression_parser.rb +541 -0
- data/lib/t_ruby/parser_combinator/token/statement_parser.rb +644 -0
- data/lib/t_ruby/parser_combinator/token/token_alternative.rb +20 -0
- data/lib/t_ruby/parser_combinator/token/token_body_parser.rb +54 -0
- data/lib/t_ruby/parser_combinator/token/token_declaration_parser.rb +920 -0
- data/lib/t_ruby/parser_combinator/token/token_dsl.rb +16 -0
- data/lib/t_ruby/parser_combinator/token/token_label.rb +22 -0
- data/lib/t_ruby/parser_combinator/token/token_many.rb +29 -0
- data/lib/t_ruby/parser_combinator/token/token_many1.rb +32 -0
- data/lib/t_ruby/parser_combinator/token/token_map.rb +17 -0
- data/lib/t_ruby/parser_combinator/token/token_matcher.rb +29 -0
- data/lib/t_ruby/parser_combinator/token/token_optional.rb +21 -0
- data/lib/t_ruby/parser_combinator/token/token_parse_result.rb +40 -0
- data/lib/t_ruby/parser_combinator/token/token_parser.rb +62 -0
- data/lib/t_ruby/parser_combinator/token/token_sep_by.rb +34 -0
- data/lib/t_ruby/parser_combinator/token/token_sep_by1.rb +34 -0
- data/lib/t_ruby/parser_combinator/token/token_sequence.rb +23 -0
- data/lib/t_ruby/parser_combinator/token/token_skip_right.rb +23 -0
- data/lib/t_ruby/parser_combinator/type_parser.rb +103 -0
- data/lib/t_ruby/parser_combinator.rb +64 -936
- data/lib/t_ruby/scanner.rb +883 -0
- data/lib/t_ruby/version.rb +1 -1
- data/lib/t_ruby/watcher.rb +67 -75
- data/lib/t_ruby.rb +15 -1
- metadata +51 -2
- 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 =
|
|
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
|