-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrtk.cr
More file actions
213 lines (170 loc) · 3.42 KB
/
rtk.cr
File metadata and controls
213 lines (170 loc) · 3.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# TODO: Remove in favor of `StringView` (moving most of these methods there).
module Rtk
extend self
alias R = Char::Reader*
class Error < Exception
end
# TODO: line, column
def err(r, message : String = "parse error")
raise Error.new(message)
end
def chr(r)
r.value.current_char
end
def ahead?(r, & : Char -> Bool) : Bool
yield chr(r)
end
def ahead?(r, charset : String | StringView) : Bool
charset = charset.to_s
ahead?(r, &.in_set?(charset))
end
def ahead?(r, charset : Char) : Bool
ahead?(r) { |char| char == charset }
end
def aheadsequ?(r, sequ : String | StringView) : Bool
save(r) do
sequ.each_char do |char|
return false unless ahead?(r, char)
forward(r)
end
end
true
end
def forward(r) : Bool
char = chr(r)
if at_end?(r)
return false
end
r.value.next_char
true
end
def skip?(r, arg)
if ahead?(r, arg)
forward(r)
true
else
false
end
end
def at_start?(r)
!r.value.has_previous?
end
def at_end?(r)
!r.value.has_next?
end
def view(r, &)
b = r.value.pos
yield
e = r.value.pos
StringView.new(r.value.string, b, e, r.value.string.single_byte_optimizable?)
end
def view2(r, &)
b = r.value.pos
result = yield
e = r.value.pos
{result, StringView.new(r.value.string, b, e, r.value.string.single_byte_optimizable?)}
end
def capture(r, io, &)
io << view(r) { yield }
end
def capture(r, &) : String
String.build do |io|
capture(r, io) { yield }
end
end
# Expects the block to return true or truthy. Otherwise rolls back.
def txn(r, &)
v0 = r.value
begin
res = yield
ensure
unless res
r.value = v0
end
end
end
def save(r, &)
txn(r) { return yield }
end
def expect(r, charset, *, error = "expected one of #{charset.inspect}")
unless ahead?(r, charset)
err(r, "#{error}")
end
forward(r)
end
def expectsequ(r, sequ : String | StringView)
sequ.each_char do |char|
expect(r, char, error: "expected #{sequ}")
end
end
def skip(r, charset)
while ahead?(r, charset)
forward(r)
end
end
def thru(r, & : Char -> Bool)
while ahead?(r) { |char| yield char }
forward(r)
end
end
def thru(r, arg)
while ahead?(r, arg)
forward(r)
end
end
def past?(r, arg)
if ahead?(r, arg)
forward(r)
true
else
false
end
end
def pastsequ?(r, sequ)
if aheadsequ?(r, sequ)
sequ.each_char do |chr|
break if chr == '\0'
r.value.next_char
end
true
else
false
end
end
def skip_to(r, charset) : Bool
loop do
return true if ahead?(r, charset)
return false unless forward(r)
end
end
def bytespan(r, &) : Int32
p0 = r.value.pos
yield
p1 = r.value.pos
p1 - p0
end
def pos(r)
r.value.pos
end
def ahead0(r)
Rtk.view(r) { }
end
def ahead1(r)
Rtk.save(r) { Rtk.view(r) { Rtk.forward(r) } }
end
def rest(r)
b = r.value.pos
e = r.value.max_pos
StringView.new(r.value.string, b, e, r.value.string.single_byte_optimizable?)
end
def hexdigit?(r) : Int32?
return unless Rtk.ahead?(r, "0-9a-fA-F")
char = Rtk.view(r) { Rtk.forward(r) }
char[0].to_i(base: 16)
end
end
struct Char::Reader
def max_pos : Int32
@string.bytesize
end
end