@@ -39,13 +39,16 @@ struct Char
3939 # Returns the reader's String.
4040 getter string : String
4141
42- # Returns the current character.
42+ # Returns the current character, or `'\0'` if the reader is at the end of
43+ # the string.
4344 #
4445 # ```
4546 # reader = Char::Reader.new("ab")
4647 # reader.current_char # => 'a'
4748 # reader.next_char
4849 # reader.current_char # => 'b'
50+ # reader.next_char
51+ # reader.current_char # => '\0'
4952 # ```
5053 getter current_char : Char
5154
@@ -59,7 +62,7 @@ struct Char
5962 # ```
6063 getter current_char_width : Int32
6164
62- # Returns the position of the current character.
65+ # Returns the byte position of the current character.
6366 #
6467 # ```
6568 # reader = Char::Reader.new("ab")
@@ -93,40 +96,81 @@ struct Char
9396 decode_previous_char
9497 end
9598
96- # Returns `true` if there is a character left to read.
97- # The terminating byte `'\0'` is considered a valid character
98- # by this method.
99+ # Returns the current character.
100+ #
101+ # Returns `nil` if the reader is at the end of the string.
102+ def current_char ? : Char ?
103+ if has_next?
104+ current_char
105+ end
106+ end
107+
108+ # Returns `true` if the reader is not at the end of the string.
109+ #
110+ # NOTE: This only means `#next_char` will successfully increment `#pos`; if
111+ # the reader is already at the last character, `#next_char` will return the
112+ # terminating null byte because there isn't really a next character.
99113 #
100114 # ```
101- # reader = Char::Reader.new("a")
102- # reader.has_next? # => true
103- # reader.peek_next_char # => '\0'
115+ # reader = Char::Reader.new("ab")
116+ # reader.has_next? # => true
117+ # reader.next_char # => 'b'
118+ # reader.has_next? # => true
119+ # reader.next_char # => '\0'
120+ # reader.has_next? # => false
104121 # ```
105122 def has_next ? : Bool
106123 @pos < @string .bytesize
107124 end
108125
109- # Reads the next character in the string,
110- # `#pos` is incremented. Raises `IndexError` if the reader is
111- # at the end of the `#string`.
126+ # Tries to read the next character in the string.
127+ #
128+ # If the reader is at the end of the string before or after incrementing
129+ # `#pos`, returns `nil`.
112130 #
113131 # ```
114- # reader = Char::Reader.new("ab")
132+ # reader = Char::Reader.new("abc")
133+ # reader.next_char? # => 'b'
134+ # reader.next_char? # => 'c'
135+ # reader.next_char? # => nil
136+ # reader.current_char # => '\0'
137+ # ```
138+ def next_char ? : Char ?
139+ next_pos = @pos + @current_char_width
140+ if next_pos <= @string .bytesize
141+ @pos = next_pos
142+ decode_current_char
143+ current_char?
144+ end
145+ end
146+
147+ # Reads the next character in the string.
148+ #
149+ # If the reader is at the end of the string after incrementing `#pos`,
150+ # returns `'\0'`. If the reader is already at the end beforehand, raises
151+ # `IndexError`.
152+ #
153+ # ```
154+ # reader = Char::Reader.new("abc")
115155 # reader.next_char # => 'b'
156+ # reader.next_char # => 'c'
157+ # reader.next_char # => '\0'
158+ # reader.next_char # raise IndexError
116159 # ```
117160 def next_char : Char
118- @pos += @current_char_width
119- if @pos > @string .bytesize
161+ next_pos = @pos + @current_char_width
162+ if next_pos <= @string .bytesize
163+ @pos = next_pos
164+ decode_current_char
165+ else
120166 raise IndexError .new
121167 end
122-
123- decode_current_char
124168 end
125169
126- # Returns the next character in the `#string`
127- # without incrementing `#pos`.
128- # Raises `IndexError ` if the reader is at
129- # the end of the `#string` .
170+ # Returns the next character in the `#string` without incrementing `#pos`.
171+ #
172+ # Returns `'\0' ` if the reader is at the last character of the string.
173+ # Raises `IndexError` if the reader is at the end .
130174 #
131175 # ```
132176 # reader = Char::Reader.new("ab")
@@ -145,16 +189,39 @@ struct Char
145189 end
146190 end
147191
148- # Returns `true` if there are characters before
149- # the current one.
192+ # Returns `true` if the reader is not at the beginning of the string.
150193 def has_previous ? : Bool
151194 @pos > 0
152195 end
153196
154- # Returns the previous character, `#pos`
155- # is decremented.
156- # Raises `IndexError` if the reader is at the beginning of
157- # the `#string`
197+ # Tries to read the previous character in the string.
198+ #
199+ # Returns `nil` if the reader is already at the beginning of the string.
200+ # Otherwise decrements `#pos`.
201+ #
202+ # ```
203+ # reader = Char::Reader.new(at_end: "abc")
204+ # reader.previous_char? # => 'b'
205+ # reader.previous_char? # => 'a'
206+ # reader.previous_char? # => nil
207+ # ```
208+ def previous_char ? : Char ?
209+ if has_previous?
210+ decode_previous_char
211+ end
212+ end
213+
214+ # Reads the previous character in the string.
215+ #
216+ # Raises `IndexError` if the reader is already at the beginning of the
217+ # string. Otherwise decrements `#pos`.
218+ #
219+ # ```
220+ # reader = Char::Reader.new(at_end: "abc")
221+ # reader.previous_char # => 'b'
222+ # reader.previous_char # => 'a'
223+ # reader.previous_char # raises IndexError
224+ # ```
158225 def previous_char : Char
159226 unless has_previous?
160227 raise IndexError .new
0 commit comments