• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

type-ruby / t-ruby / 20560974963

28 Dec 2025 11:21PM UTC coverage: 79.076% (+1.7%) from 77.331%
20560974963

push

github

web-flow
refactor: migrate parser from regex to token-based parser combinator (#29)

* refactor: migrate parser from regex to token-based parser combinator

- Replace monolithic parser_combinator.rb (2833 lines) with modular architecture
- Add Scanner for tokenization with regex literal support
- Create IR::InterpolatedString for string interpolation parsing
- Fix type inference for interpolated strings (returns String)
- Add TRuby::ParseError for unified error handling
- Organize parsers into primitives/, combinators/, and token/ directories
- Each file contains exactly one class (snake_case filename matches PascalCase class)

* fix: enhance parser to support ternary, splat args, and statement expressions

- Add ternary operator (? :) parsing in ExpressionParser
- Support double splat (**opts) and single splat (*args) in method calls
- Support keyword arguments (name: value) in method calls
- Allow case/if/unless/begin as assignment right-hand side values
- Improve generic type compatibility (Array[untyped] with Array[T])

Fixes type inference errors in keyword_args samples.

* style: fix RuboCop violations and adjust metrics limits

* fix: require set for Ruby 3.1 compatibility

1849 of 2098 new or added lines in 53 files covered. (88.13%)

6 existing lines in 2 files now uncovered.

6644 of 8402 relevant lines covered (79.08%)

908.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.86
/lib/t_ruby/parser_combinator/dsl.rb
1
# frozen_string_literal: true
2

3
module TRuby
1✔
4
  module ParserCombinator
1✔
5
    # DSL Module - Convenience methods
6
    module DSL
1✔
7
      def literal(str)
1✔
8
        Literal.new(str)
21✔
9
      end
10

11
      def regex(pattern, description = nil)
1✔
12
        Regex.new(pattern, description)
31✔
13
      end
14

15
      def satisfy(description = "character", &predicate)
1✔
16
        Satisfy.new(predicate, description)
11,079✔
17
      end
18

19
      def char(c)
1✔
20
        Literal.new(c)
5,503✔
21
      end
22

23
      def string(str)
1✔
24
        Literal.new(str)
428✔
25
      end
26

27
      def eof
1✔
28
        EndOfInput.new
9✔
29
      end
30

31
      def pure(value)
1✔
NEW
32
        Pure.new(value)
×
33
      end
34

35
      def fail(message)
1✔
NEW
36
        Fail.new(message)
×
37
      end
38

39
      def lazy(&)
1✔
40
        Lazy.new(&)
366✔
41
      end
42

43
      def choice(*parsers)
1✔
44
        Choice.new(*parsers)
366✔
45
      end
46

47
      def sequence(*parsers)
1✔
NEW
48
        parsers.reduce { |acc, p| acc >> p }
×
49
      end
50

51
      # Common character parsers
52
      def digit
1✔
53
        satisfy("digit") { |c| c =~ /[0-9]/ }
31✔
54
      end
55

56
      def letter
1✔
57
        satisfy("letter") { |c| c =~ /[a-zA-Z]/ }
8,051✔
58
      end
59

60
      def alphanumeric
1✔
61
        satisfy("alphanumeric") { |c| c =~ /[a-zA-Z0-9]/ }
42,447✔
62
      end
63

64
      def whitespace
1✔
65
        satisfy("whitespace") { |c| c =~ /\s/ }
34,150✔
66
      end
67

68
      def spaces
1✔
69
        whitespace.many.map(&:join)
10,273✔
70
      end
71

72
      def spaces1
1✔
NEW
73
        whitespace.many1.map(&:join)
×
74
      end
75

76
      def newline
1✔
77
        char("\n") | string("\r\n")
28✔
78
      end
79

80
      def identifier
1✔
81
        (letter >> (alphanumeric | char("_")).many).map do |(first, rest)|
397✔
82
          first + rest.join
7,651✔
83
        end
84
      end
85

86
      def integer
1✔
87
        (char("-").optional >> digit.many1).map do |(sign, digits)|
2✔
88
          num = digits.join.to_i
2✔
89
          sign ? -num : num
2✔
90
        end
91
      end
92

93
      def float
1✔
94
        regex(/-?\d+\.\d+/, "float").map(&:to_f)
1✔
95
      end
96

97
      def quoted_string(quote = '"')
1✔
98
        content = satisfy("string character") { |c| c != quote && c != "\\" }
13✔
99
        escape = (char("\\") >> satisfy("escape char")).map { |(_bs, c)| c }
1✔
100

101
        (char(quote) >> (content | escape).many.map(&:join) << char(quote)).map { |(_, str)| str }
2✔
102
      end
103

104
      # Skip whitespace around parser
105
      def lexeme(parser)
1✔
106
        (spaces >> parser << spaces).map { |(_, val)| val }
5,352✔
107
      end
108

109
      # Chain for left-associative operators
110
      def chainl(term, op)
1✔
111
        ChainLeft.new(term, op)
2✔
112
      end
113
    end
114
  end
115
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc