Skip to content

Commit a4d1b59

Browse files
committed
Split match primitive into str and reg
Also split Scanner#match into match_string and match_regexp.
1 parent e5d63f5 commit a4d1b59

20 files changed

Lines changed: 166 additions & 104 deletions

README.md

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ class Arithmetic < Grammy::Grammar
6767
root :expression
6868

6969
# Define the rules.
70-
rule(:expression) { term + (match("+") + term)[0..] }
71-
rule(:term) { factor + (match("*") + factor)[0..] }
70+
rule(:expression) { term + (str("+") + term)[0..] }
71+
rule(:term) { factor + (str("*") + factor)[0..] }
7272
rule(:factor) { number | parens(expression) }
73-
rule(:number) { match(/\d+/) }
73+
rule(:number) { reg(/\d+/) }
7474

7575
# Define any custom combinators.
76-
def parens(exp) = match("(") + expression + match(")")
76+
def parens(exp) = str("(") + expression + str(")")
7777
end
7878
~~~
7979

@@ -87,13 +87,13 @@ class Arithmetic < Grammy::Grammar
8787
root :expression
8888

8989
# Define the rules.
90-
rule def expression = term + (match("+") + term)[0..]
91-
rule def term = factor + (match("*") + factor)[0..]
90+
rule def expression = term + (str("+") + term)[0..]
91+
rule def term = factor + (str("*") + factor)[0..]
9292
rule def factor = number | parens(expression)
93-
rule def number = match(/\d+/)
93+
rule def number = reg(/\d+/)
9494

9595
# Define any custom combinators.
96-
def parens(exp) = match("(") + expression + match(")")
96+
def parens(exp) = str("(") + expression + str(")")
9797
end
9898
~~~
9999

@@ -107,13 +107,13 @@ class Arithmetic < Grammy::Grammar
107107
start :expression
108108

109109
# Define the rules.
110-
def expression = rule { term + (match("+") + term)[0..] }
111-
def term = rule { factor + (match("*") + factor)[0..] }
110+
def expression = rule { term + (str("+") + term)[0..] }
111+
def term = rule { factor + (str("*") + factor)[0..] }
112112
def factor = rule { number | parens(expression) }
113-
def number = rule { match(/\d+/) }
113+
def number = rule { reg(/\d+/) }
114114

115115
# Define any custom combinators.
116-
def parens(exp) = match("(") + expression + match(")")
116+
def parens(exp) = str("(") + expression + str(")")
117117
end
118118
~~~
119119

@@ -124,23 +124,31 @@ Combinators are functions that take one or more parsers as arguments and return
124124

125125
Only a few primitive combinators are needed.
126126

127-
### Match
127+
### String
128128

129-
The `match` combinator is used to match a string or a regular expression.
129+
The `str` combinator is used to match a string.
130130

131131
~~~ ruby
132-
match("return")
133-
match(/\d+/)
132+
str("return")
134133
~~~
135134

135+
### Regex
136+
137+
The `reg` combinator is used to match a regular expression.
138+
139+
~~~ ruby
140+
reg(/\d+/)
141+
~~~
142+
143+
136144
### Sequence
137145

138146
You use the `seq` combinator to specify a sequence of what to match.
139147

140148
This example matches a sequence of a term, a plus sign, and another term.
141149

142150
~~~ ruby
143-
seq(term, match("+"), term)
151+
seq(term, str("+"), term)
144152
~~~
145153

146154
### Alternatives
@@ -150,14 +158,15 @@ The `alt` combinator is used to specify alternatives (multiple choices) of what
150158
This example matches either a plus sign or a minus sign.
151159

152160
~~~ ruby
153-
alt(match("+"), match("-"))
161+
alt(str("+"), str("-"))
154162
~~~
155163

156-
NOTE: This would most likely done with a single `match` call:
157-
158-
~~~ ruby
159-
match(/[+-]/)
160-
~~~
164+
> [!NOTE]
165+
> In practical use, this would most likely be done with a single `reg` call:
166+
>
167+
> ~~~ ruby
168+
> reg(/[+-]/)
169+
> ~~~
161170
162171
### Repetition
163172
@@ -167,7 +176,7 @@ You can specify the minimum and maximum number of repetitions, using a range.
167176
This example matches one or more digits.
168177
169178
~~~ ruby
170-
rep(match(/\d+/), 1..)
179+
rep(reg(/\d+/), 1..)
171180
~~~
172181
173182
### Operator DSL
@@ -180,8 +189,8 @@ The `[]` operator can be used in place of the `rep` combinator.
180189
For example, the following two lines are equivalent:
181190

182191
~~~ ruby
183-
seq(term, match("+"), term)
184-
term + match("+") + term
192+
seq(term, str("+"), term)
193+
term + str("+") + term
185194
~~~
186195

187196
## Parse Tree

docs/TODO.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ Here's a list of things I'd like to complete to make Grammy **great**.
1313

1414
## Grammar
1515

16+
- [x] primitive combinators: `str` and `reg`
1617
- [x] primitive combinators: `seq`, `alt`, `rep`
1718
- [x] add `_` to canonical names
18-
- [ ] separate `match` and `regex`?
19-
- [ ] maybe rename `match` to `str` and `rex`?
2019
- [ ] try out the decorated methods syntax
2120
- [ ] try out the instance methods syntax
2221

lib/grammy/combinator/primitives.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
require "grammy/matcher"
1+
require "grammy/matcher/string"
2+
require "grammy/matcher/regexp"
23
require "grammy/matcher/sequence"
34
require "grammy/matcher/alternative"
45
require "grammy/matcher/repetition"
@@ -8,16 +9,18 @@ module Grammy
89
module Combinator
910
module Primitives
1011

11-
protected def match(pattern) = Grammy::Matcher.new(pattern)
12+
protected def str(pattern) = Grammy::Matcher::String.new(pattern)
13+
protected def reg(pattern) = Grammy::Matcher::Regexp.new(pattern)
1214
protected def seq(*matchers) = Grammy::Matcher::Sequence.new(*matchers)
1315
protected def alt(*matchers) = Grammy::Matcher::Alternative.new(*matchers)
1416
protected def rep(matcher, count_range) = Grammy::Matcher::Repetition.new(matcher, count_range)
1517

1618
# Use these aliases if you have naming conflicts with your grammar.
17-
alias _match match
1819
alias _seq seq
1920
alias _alt alt
2021
alias _rep rep
22+
alias _str str
23+
alias _reg reg
2124

2225
end
2326
end

lib/grammy/grammar.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
require "grammy/combinator/primitives"
2-
require "grammy/matcher"
32
require "grammy/errors"
43
require "grammy/scanner"
54
require "grammy/parse_tree"

lib/grammy/matcher.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ class Matcher
66

77
def initialize(pattern) = @pattern = pattern
88

9-
# Returns a Match, if the pattern matches, else `nil`.
10-
def match(scanner) = scanner.match(@pattern)
9+
def match(scanner) = fail NotImplementedError, "abstract method -- must override in derived classes"
1110

1211
# DSL for sequence, alternative, and repetition.
1312
def +(other) = Matcher::Sequence.new(self, other)

lib/grammy/matcher/regexp.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
require "grammy/matcher"
2+
3+
4+
module Grammy
5+
class Matcher
6+
class Regexp < Matcher
7+
8+
def initialize(pattern) = @pattern = pattern
9+
10+
def match(scanner)
11+
scanner.match_regexp(@pattern)
12+
end
13+
14+
end
15+
end
16+
end

lib/grammy/matcher/string.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
require "grammy/matcher"
2+
3+
4+
module Grammy
5+
class Matcher
6+
class String < Matcher
7+
8+
def initialize(string) = @string = string
9+
10+
def match(scanner)
11+
scanner.match_string(@string)
12+
end
13+
14+
end
15+
end
16+
end

lib/grammy/scanner.rb

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,20 @@ def initialize(input)
1616
@marks = [] # stack of Locations
1717
end
1818

19-
# Try to match given String or Regexp at current location.
19+
# Try to match given String at current location.
2020
# Returns `nil` if pattern does not match.
2121
# Otherwise, updates @location and returns a Match object.
22-
def match(pattern)
23-
return nil if @location.index >= @input.size
24-
25-
if pattern.is_a?(String)
26-
match_string(pattern)
27-
elsif pattern.is_a?(Regexp)
28-
match_regex(pattern)
29-
else
30-
fail ArgumentError, "Unsupported pattern type: #{pattern.class}"
31-
end
32-
end
33-
3422
def match_string(pattern)
23+
return nil if @location.index >= @input.size
3524
return nil unless remaining_input.start_with?(pattern)
3625
match_text(pattern)
3726
end
3827

39-
def match_regex(pattern)
28+
# Try to match given Regexp at current location.
29+
# Returns `nil` if pattern does not match.
30+
# Otherwise, updates @location and returns a Match object.
31+
def match_regexp(pattern)
32+
return nil if @location.index >= @input.size
4033
anchored_regex = Regexp.new("\\A(?:#{pattern.source})", pattern.options)
4134
match = remaining_input.match(anchored_regex)
4235
return nil unless match

spec/alternative_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
subject(:matcher) { described_class.new(*alternatives) }
88
let(:alternatives) {
99
[
10-
Grammy::Matcher.new(/\d+/),
11-
Grammy::Matcher.new(/abc/)
10+
Grammy::Matcher::Regexp.new(/\d+/),
11+
Grammy::Matcher::Regexp.new(/abc/)
1212
]
1313
}
1414
let(:scanner) { Grammy::Scanner.new(input) }

spec/grammar/alt_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
class AltGrammar < Grammy::Grammar
44
root(:greeting)
5-
rule(:greeting) { alt(match("hello"), match("hi"), match("hey")) }
5+
rule(:greeting) { alt(str("hello"), str("hi"), str("hey")) }
66
end
77

88
RSpec.describe AltGrammar do

0 commit comments

Comments
 (0)