summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/strscans.nim7
-rw-r--r--tests/stdlib/tstrscans.nim86
2 files changed, 90 insertions, 3 deletions
diff --git a/lib/pure/strscans.nim b/lib/pure/strscans.nim
index 77763ff43..c1c535e55 100644
--- a/lib/pure/strscans.nim
+++ b/lib/pure/strscans.nim
@@ -129,7 +129,7 @@ to use prefix instead of postfix operators.
 ``+E``           One or more
 ``?E``           Zero or One
 ``E{n,m}``       From ``n`` up to ``m`` times ``E``
-``~Ε``           Not predicate
+``~E``           Not predicate
 ``a ^* b``       Shortcut for ``?(a *(b a))``. Usually used for separators.
 ``a ^* b``       Shortcut for ``?(a +(b a))``. Usually used for separators.
 ``'a'``          Matches a single character
@@ -456,10 +456,11 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b
 
 template atom*(input: string; idx: int; c: char): bool =
   ## Used in scanp for the matching of atoms (usually chars).
-  idx < input.len and input[idx] == c
+  ## EOF is matched as ``'\0'``.
+  (idx < input.len and input[idx] == c) or (idx == input.len and c == '\0')
 
 template atom*(input: string; idx: int; s: set[char]): bool =
-  idx < input.len and input[idx] in s
+  (idx < input.len and input[idx] in s) or (idx == input.len and '\0' in s)
 
 template hasNxt*(input: string; idx: int): bool = idx < input.len
 
diff --git a/tests/stdlib/tstrscans.nim b/tests/stdlib/tstrscans.nim
new file mode 100644
index 000000000..08fc14e45
--- /dev/null
+++ b/tests/stdlib/tstrscans.nim
@@ -0,0 +1,86 @@
+discard """
+  output: ""
+"""
+
+import strscans
+
+block ParsePasswd:
+  proc parsePasswd(content: string): seq[string] =
+    result = @[]
+    var idx = 0
+    while true:
+      var entry = ""
+      if scanp(content, idx, +(~{'\L', '\0'} -> entry.add($_)), '\L'):
+        result.add entry
+      else:
+        break
+
+  const etc_passwd = """root:x:0:0:root:/root:/bin/bash
+daemon:x:1:1:daemon:/usr/sbin:/bin/sh
+bin:x:2:2:bin:/bin:/bin/sh
+sys:x:3:3:sys:/dev:/bin/sh
+nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
+messagebus:x:103:107::/var/run/dbus:/bin/false
+"""
+
+  const parsed_etc_passwd = @[
+    "root:x:0:0:root:/root:/bin/bash",
+    "daemon:x:1:1:daemon:/usr/sbin:/bin/sh",
+    "bin:x:2:2:bin:/bin:/bin/sh",
+    "sys:x:3:3:sys:/dev:/bin/sh",
+    "nobody:x:65534:65534:nobody:/nonexistent:/bin/sh",
+    "messagebus:x:103:107::/var/run/dbus:/bin/false",
+    ]
+  doAssert etc_passwd.parsePasswd == parsed_etc_passwd
+
+block LastNot:
+  var idx : int
+
+  idx = 0
+  doAssert scanp("foo", idx,  'f', 'o', ~'a')
+
+  idx = 0
+  doAssert scanp("foo", idx,  'f', 'o', ~'o') == false
+
+  idx = 0
+  doAssert scanp("foox", idx,  'f', 'o', ~'o') == false
+
+  idx = 0
+  doAssert scanp("foox", idx,  'f', 'o', ~'a')
+
+block LastOptional:
+  var idx = 0
+  doAssert scanp("foo", idx, 'f', 'o', 'o', ?'o')
+
+block Tuple:
+  var idx = 0
+  doAssert scanp("foo", idx,  ('f', 'o', 'o'))
+
+block NotWithOptional:
+  var idx : int
+
+  idx = 0
+  doAssert scanp("bc", idx, ~(?'b', 'c')) == false
+
+  idx = 0
+  doAssert scanp("c", idx, ~(?'b', 'c')) == false
+
+  idx = 0
+  doAssert scanp("b", idx, ~(?'b', 'c'))
+
+block NotEmpty:
+  var idx = 0
+  doAssert scanp("", idx, ~()) == false
+
+block EmptyTuple:
+  var idx = 0
+  doAssert scanp("ab", idx, 'a', (), 'b')
+
+block Arrow:
+  let text = "foo;bar;baz;"
+  var idx = 0
+  var res = ""
+  doAssert scanp(text, idx, +(~{';','\0'} -> (discard $_)), ';')
+  doAssert scanp(text, idx, +(~{';','\0'} -> (discard $_)), ';')
+  doAssert scanp(text, idx, +(~{';','\0'} -> (discard $_)), ';')
+  doAssert scanp(text, idx, +(~{';','\0'} -> (discard $_)), ';') == false