aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/strings/textscanner
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/vlib/strings/textscanner')
-rw-r--r--v_windows/v/vlib/strings/textscanner/textscanner.v154
-rw-r--r--v_windows/v/vlib/strings/textscanner/textscanner_test.v159
2 files changed, 313 insertions, 0 deletions
diff --git a/v_windows/v/vlib/strings/textscanner/textscanner.v b/v_windows/v/vlib/strings/textscanner/textscanner.v
new file mode 100644
index 0000000..5525137
--- /dev/null
+++ b/v_windows/v/vlib/strings/textscanner/textscanner.v
@@ -0,0 +1,154 @@
+module textscanner
+
+// TextScanner simplifies writing small scanners/parsers
+// by providing safe methods to scan texts character by
+// character, peek for the next characters, go back, etc.
+pub struct TextScanner {
+pub:
+ input string
+ ilen int
+mut:
+ pos int // current position; pos is *always* kept in [0,ilen]
+}
+
+// new returns a stack allocated instance of TextScanner.
+pub fn new(input string) TextScanner {
+ return TextScanner{
+ input: input
+ ilen: input.len
+ }
+}
+
+// free frees all allocated resources.
+[unsafe]
+pub fn (mut ss TextScanner) free() {
+ unsafe {
+ ss.input.free()
+ }
+}
+
+// remaining returns how many characters remain from current position.
+[inline]
+pub fn (ss &TextScanner) remaining() int {
+ return ss.ilen - ss.pos
+}
+
+// next returns the next character code from the input text.
+// next returns `-1` if it can't reach the next character.
+// next advances the scanner position.
+[direct_array_access; inline]
+pub fn (mut ss TextScanner) next() int {
+ if ss.pos < ss.ilen {
+ opos := ss.pos
+ ss.pos++
+ return ss.input[opos]
+ }
+ return -1
+}
+
+// skip skips one character ahead; `skip()` is slightly faster than `.next()`.
+// `skip()` does not return a result.
+[inline]
+pub fn (mut ss TextScanner) skip() {
+ if ss.pos + 1 < ss.ilen {
+ ss.pos++
+ }
+}
+
+// skip_n skips ahead `n` characters, stopping at the end of the input.
+[inline]
+pub fn (mut ss TextScanner) skip_n(n int) {
+ ss.pos += n
+ if ss.pos > ss.ilen {
+ ss.pos = ss.ilen
+ }
+}
+
+// peek returns the *next* character code from the input text.
+// peek returns `-1` if it can't peek the next character.
+// unlike `next()`, `peek()` does not change the state of the scanner.
+[direct_array_access; inline]
+pub fn (ss &TextScanner) peek() int {
+ if ss.pos < ss.ilen {
+ return ss.input[ss.pos]
+ }
+ return -1
+}
+
+// peek_n returns the character code from the input text at position + `n`.
+// peek_n returns `-1` if it can't peek `n` characters ahead.
+// ts.peek_n(0) == ts.current() .
+// ts.peek_n(1) == ts.peek() .
+[direct_array_access; inline]
+pub fn (ss &TextScanner) peek_n(n int) int {
+ if ss.pos + n < ss.ilen {
+ return ss.input[ss.pos + n]
+ }
+ return -1
+}
+
+// back goes back one character from the current scanner position.
+[inline]
+pub fn (mut ss TextScanner) back() {
+ if ss.pos > 0 {
+ ss.pos--
+ }
+}
+
+// back_n goes back `n` characters from the current scanner position.
+pub fn (mut ss TextScanner) back_n(n int) {
+ ss.pos -= n
+ if ss.pos < 0 {
+ ss.pos = 0
+ }
+ if ss.pos > ss.ilen {
+ ss.pos = ss.ilen
+ }
+}
+
+// peek_back returns the *previous* character code from the input text.
+// peek_back returns `-1` if it can't peek the previous character.
+// unlike `back()`, `peek_back()` does not change the state of the scanner.
+[direct_array_access; inline]
+pub fn (ss &TextScanner) peek_back() int {
+ return ss.peek_back_n(1)
+}
+
+// peek_back_n returns the character code from the input text at position - `n`.
+// peek_back_n returns `-1` if it can't peek `n` characters back.
+// ts.peek_back_n(0) == ts.current()
+// ts.peek_back_n(1) == ts.peek_back()
+[direct_array_access; inline]
+pub fn (ss &TextScanner) peek_back_n(n int) int {
+ offset := n + 1
+ if ss.pos >= offset {
+ return ss.input[ss.pos - offset]
+ }
+ return -1
+}
+
+// current returns the current character code from the input text.
+// current returns `-1` at the start of the input text.
+// NB: after `c := ts.next()`, `ts.current()` will also return `c`.
+[direct_array_access; inline]
+pub fn (mut ss TextScanner) current() int {
+ if ss.pos > 0 {
+ return ss.input[ss.pos - 1]
+ }
+ return -1
+}
+
+// reset resets the internal state of the scanner
+// After calling .reset(), .next() will start reading
+// again from the start of the input text.
+pub fn (mut ss TextScanner) reset() {
+ ss.pos = 0
+}
+
+// goto_end has the same effect as `for ts.next() != -1 {}`
+// i.e. after calling .goto_end(), the scanner will be at
+// the end of the input text. Further .next() calls will
+// return -1, unless you go back.
+pub fn (mut ss TextScanner) goto_end() {
+ ss.pos = ss.ilen
+}
diff --git a/v_windows/v/vlib/strings/textscanner/textscanner_test.v b/v_windows/v/vlib/strings/textscanner/textscanner_test.v
new file mode 100644
index 0000000..e9d2487
--- /dev/null
+++ b/v_windows/v/vlib/strings/textscanner/textscanner_test.v
@@ -0,0 +1,159 @@
+import strings.textscanner
+
+fn test_remaining() {
+ mut s := textscanner.new('abc')
+ assert s.remaining() == 3
+ s.next()
+ s.next()
+ assert s.remaining() == 1
+ s.next()
+ assert s.remaining() == 0
+ s.next()
+ s.next()
+ assert s.remaining() == 0
+ s.reset()
+ assert s.remaining() == 3
+}
+
+fn test_next() {
+ mut s := textscanner.new('abc')
+ assert s.next() == `a`
+ assert s.next() == `b`
+ assert s.next() == `c`
+ assert s.next() == -1
+ assert s.next() == -1
+ assert s.next() == -1
+}
+
+fn test_skip() {
+ mut s := textscanner.new('abc')
+ assert s.next() == `a`
+ s.skip()
+ assert s.next() == `c`
+ assert s.next() == -1
+}
+
+fn test_skip_n() {
+ mut s := textscanner.new('abc')
+ s.skip_n(2)
+ assert s.next() == `c`
+ assert s.next() == -1
+}
+
+fn test_peek() {
+ mut s := textscanner.new('abc')
+ assert s.peek() == `a`
+ assert s.peek() == `a`
+ assert s.peek() == `a`
+ //
+ assert s.next() == `a`
+ assert s.next() == `b`
+ assert s.next() == `c`
+ assert s.next() == -1
+}
+
+fn test_peek_n() {
+ mut s := textscanner.new('abc')
+ assert s.peek_n(0) == `a`
+ assert s.peek_n(1) == `b`
+ assert s.peek_n(2) == `c`
+ assert s.peek_n(3) == -1
+ assert s.peek_n(4) == -1
+ //
+ assert s.next() == `a`
+ assert s.next() == `b`
+ assert s.next() == `c`
+ assert s.next() == -1
+}
+
+fn test_back() {
+ mut s := textscanner.new('abc')
+ assert s.next() == `a`
+ s.back()
+ assert s.next() == `a`
+ assert s.next() == `b`
+ s.back()
+ assert s.next() == `b`
+ assert s.next() == `c`
+ assert s.next() == -1
+}
+
+fn test_back_n() {
+ mut s := textscanner.new('abc')
+ assert s.next() == `a`
+ s.back_n(10)
+ assert s.next() == `a`
+ assert s.next() == `b`
+ assert s.next() == `c`
+ s.back_n(2)
+ assert s.next() == `b`
+}
+
+fn test_peek_back() {
+ mut s := textscanner.new('abc')
+ assert s.next() == `a`
+ assert s.next() == `b`
+ // check that calling .peek_back() multiple times
+ // does not change the state:
+ assert s.peek_back() == `a`
+ assert s.peek_back() == `a`
+ assert s.peek_back() == `a`
+ // advance, then peek_back again:
+ assert s.next() == `c`
+ assert s.peek_back() == `b`
+ // peeking before the start:
+ s.reset()
+ assert s.peek_back() == -1
+ // peeking right at the end:
+ s.goto_end()
+ assert s.peek_back() == `b`
+}
+
+fn test_peek_back_n() {
+ mut s := textscanner.new('abc')
+ s.goto_end()
+ assert s.peek_back_n(0) == `c`
+ assert s.peek_back_n(1) == `b`
+ assert s.peek_back_n(2) == `a`
+ assert s.peek_back_n(3) == -1
+ assert s.peek_back_n(4) == -1
+}
+
+fn test_reset() {
+ mut s := textscanner.new('abc')
+ assert s.next() == `a`
+ s.next()
+ s.next()
+ assert s.next() == -1
+ s.reset()
+ assert s.next() == `a`
+}
+
+fn test_current() {
+ mut s := textscanner.new('abc')
+ assert s.current() == -1
+ assert s.next() == `a`
+ assert s.current() == `a`
+ assert s.current() == `a`
+ assert s.peek_back() == -1
+ assert s.next() == `b`
+ assert s.current() == `b`
+ assert s.current() == `b`
+ assert s.peek_back() == `a`
+ assert s.next() == `c`
+ assert s.current() == `c`
+ assert s.next() == -1
+ assert s.current() == `c`
+ assert s.next() == -1
+ assert s.current() == `c`
+ s.reset()
+ assert s.current() == -1
+ assert s.next() == `a`
+ assert s.current() == `a`
+}
+
+fn test_goto_end() {
+ mut s := textscanner.new('abc')
+ s.goto_end()
+ assert s.current() == `c`
+}