strscans
Этот модуль содержит макрос scanf, который можно использовать для извлечения подстрок из входной строки. Это часто проще, чем использование регулярных выражений. Некоторые примеры для начала:
# check if input string matches a triple of integers: const input = "(1,2,4)" var x, y, z: int if scanf(input, "($i,$i,$i)", x, y, z): echo "matches and x is ", x, " y is ", y, " z is ", z # check if input string matches an ISO date followed by an identifier followed # by whitespace and a floating point number: var year, month, day: int var identifier: string var myfloat: float if scanf(input, "$i-$i-$i $w$s$f", year, month, day, identifier, myfloat): echo "yes, we have a match!"
Как видно из примеров, строки сопоставляются дословно, за исключением подстрок, начинающихся с $. Доступны следующие конструкции:
$b |
Сопоставляет двоичное целое число. Использует parseutils.parseBin. |
$o |
Сопоставляет восьмеричное целое число. Использует parseutils.parseOct. |
$i |
Сопоставляет десятичное целое число. Использует parseutils.parseInt. |
$h |
Сопоставляет шестнадцатеричное целое число. Использует parseutils.parseHex. |
$f |
Сопоставляет число с плавающей точкой. Использует parseFloat. |
$w |
Сопоставляет ASCII идентификатор: [A-Za-z_][A-Za-z_0-9]*. |
$s |
Пропускает необязательные пробелы. |
$$ |
Сопоставляет одиночный символ доллара. |
$. |
Сопоставляет конец входной строки. |
$* |
Сопоставляет символы до токена, следующего за $*. Сопоставление допускается длиной 0. |
$+ |
Сопоставляет символы до токена, следующего за $+. Сопоставление должно содержать как минимум один символ. |
${foo} |
Пользовательское сопоставление. Использует процедуру foo для выполнения сопоставления. Более подробная информация ниже. |
$[foo] |
Вызывает пользовательскую процедуру foo для пропуска некоторых необязательных частей во входной строке. Более подробная информация ниже. |
Несмотря на то, что $* и $+ похожи на регулярные выражения .* и .+, они работают совершенно по-другому, не используется недетерминированная конечная автомат, и сопоставления не жадные. [$*] сопоставляется с [xyz] через parseutils.parseUntil.
Кроме того, не выполняется откат (backtracking), если разбор терпит неудачу после того, как значение уже было привязано к сопоставленной подвыражению, это значение не восстанавливается до первоначального. На практике это редко вызывает проблемы, и если это происходит, то легко привязать к временной переменной.
Сопоставление по началу строки vs полное сопоставление
scanf возвращает true, если входная строка начинается с указанного шаблона. Если вместо этого он должен возвращать true только в том случае, если в входе больше ничего не осталось, добавьте $. к вашему шаблону.
Пользовательские сопоставления
Одно из больших преимуществ перед регулярными выражениями заключается в том, что scanf расширяется обычными процедурами Nim. Процедура заключена либо в ${}, либо в $[]. ${} сопоставляет и привязывает результат к переменной (переданной в макрос scanf), в то время как $[] просто сопоставляет необязательные токены без привязки результата.
В этом примере мы определяем вспомогательную процедуру someSep, которая пропускает некоторые разделители, которые мы затем используем в нашем шаблоне scanf, чтобы помочь в процессе сопоставления:
proc someSep(input: string; start: int; seps: set[char] = {':','-','.'}): int =
# Note: The parameters and return value must match to what ``scanf`` requires
result = 0
while start+result < input.len and input[start+result] in seps: inc result
if scanf(input, "$w$[someSep]$w", key, value):
...
Также возможно передавать аргументы в пользовательские функции сопоставления:
proc ndigits(input: string; intVal: var int; start: int; n: int): int =
# matches exactly ``n`` digits. Matchers need to return 0 if nothing
# matched or otherwise the number of processed chars.
var x = 0
var i = 0
while i < n and i+start < input.len and input[i+start] in {'0'..'9'}:
x = x * 10 + input[i+start].ord - '0'.ord
inc i
# only overwrite if we had a match
if i == n:
result = n
intVal = x
# match an ISO date extracting year, month, day at the same time.
# Also ensure the input ends after the ISO date:
var year, month, day: int
if scanf("2013-01-03", "${ndigits(4)}-${ndigits(2)}-${ndigits(2)}$.", year, month, day):
... Макрос scanp
Этот модуль также реализует макрос scanp, синтаксис которого несколько похож на грамматику EBNF или PEG, за исключением того, что он использует синтаксис выражений Nim, поэтому должен использовать префиксные вместо постфиксных операторов.
(E) |
Группировка |
*E |
Ноль или более |
+E |
Один или более |
?E |
Ноль или один |
E{n,m} |
От n до m раз E
|
~E |
Не предикат |
a ^* b |
Сокращение для ?(a *(b a)). Обычно используется для разделителей. |
a ^+ b |
Сокращение для ?(a +(b a)). Обычно используется для разделителей. |
'a' |
Сопоставляет один символ |
{'a'..'b'} |
Сопоставляет набор символов |
"s" |
Сопоставляет строку |
E -> a |
Привязывает сопоставление к некоторому действию |
$_ |
Доступ к текущему сопоставленному символу |
Обратите внимание, что неопределённые или упорядоченные операторы выбора (/, |) не реализованы.
Простой пример, который анализирует файл /etc/passwd построчно:
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
"""
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
scanp преобразует код грамматики в код Nim, который выполняет разбор. Разбор выполняется с помощью 3 вспомогательных шаблонов, которые могут быть реализованы для пользовательского типа.
Эти шаблоны должны именоваться atom и nxt. atom должен быть перегружен для обработки как отдельных символов, так и наборов символов.
import streams
template atom(input: Stream; idx: int; c: char): bool =
## Used in scanp for the matching of atoms (usually chars).
peekChar(input) == c
template atom(input: Stream; idx: int; s: set[char]): bool =
peekChar(input) in s
template nxt(input: Stream; idx, step: int = 1) =
inc(idx, step)
setPosition(input, idx)
if scanp(content, idx, +( ~{'\L', '\0'} -> entry.add(peekChar($input))), '\L'):
result.add entry
Вызов обычных процедур Nim внутри макроса возможен:
proc digits(s: string; intVal: var int; start: int): int =
var x = 0
while result+start < s.len and s[result+start] in {'0'..'9'} and s[result+start] != ':':
x = x * 10 + s[result+start].ord - '0'.ord
inc result
intVal = x
proc extractUsers(content: string): seq[string] =
# Extracts the username and home directory
# of each entry (with UID greater than 1000)
const
digits = {'0'..'9'}
result = @[]
var idx = 0
while true:
var login = ""
var uid = 0
var homedir = ""
if scanp(content, idx, *(~ {':', '\0'}) -> login.add($_), ':', * ~ ':', ':',
digits($input, uid, $index), ':', *`digits`, ':', * ~ ':', ':',
*('/', * ~{':', '/'}) -> homedir.add($_), ':', *('/', * ~{'\L', '/'}), '\L'):
if uid >= 1000:
result.add login & " " & homedir
else:
break
При использовании для сопоставления, помните, что, как и в scanf, не выполняется откат (backtracking).
proc skipUntil(s: string; until: string; unless = '\0'; start: int): int =
# Skips all characters until the string `until` is found. Returns 0
# if the char `unless` is found first or the end is reached.
var i = start
var u = 0
while true:
if i >= s.len or s[i] == unless:
return 0
elif s[i] == until[0]:
u = 1
while i+u < s.len and u < until.len and s[i+u] == until[u]:
inc u
if u >= until.len: break
inc(i)
result = i+u-start
iterator collectLinks(s: string): string =
const quote = {'\'', '"'}
var idx, old = 0
var res = ""
while idx < s.len:
old = idx
if scanp(s, idx, "<a", skipUntil($input, "href=", '>', $index),
`quote`, *( ~`quote`) -> res.add($_)):
yield res
res = ""
idx = old + 1
for r in collectLinks(body):
echo r
В этом примере оба макроса сочетаются для максимальной эффективности и выполнения различных проверок.
iterator parseIps*(soup: string): string =
## ipv4 only!
const digits = {'0'..'9'}
var a, b, c, d: int
var buf = ""
var idx = 0
while idx < soup.len:
if scanp(soup, idx, (`digits`{1,3}, '.', `digits`{1,3}, '.',
`digits`{1,3}, '.', `digits`{1,3}) -> buf.add($_)):
discard buf.scanf("$i.$i.$i.$i", a, b, c, d)
if (a >= 0 and a <= 254) and
(b >= 0 and b <= 254) and
(c >= 0 and c <= 254) and
(d >= 0 and d <= 254):
yield buf
buf.setLen(0) # need to clear `buf` each time, cause it might contain garbage
idx.inc Импорты
- macros, parseutils
Макросы
macro scanf(input: string; pattern: static[string]; results: varargs[typed]): bool
- См. документацию этого модуля о том, как работает
scanf. Исходный код Изменить macro scanp(input, idx: typed; pattern: varargs[untyped]): bool
- См. документацию этого модуля о том, как работает
scanp. Исходный код Изменить
Шаблоны
template atom(input: string; idx: int; c: char): bool
- Используется в scanp для сопоставления атомов (обычно символов). EOF сопоставляется как
'\0'. Исходный код Изменить template atom(input: string; idx: int; s: set[char]): bool
- Исходный код Изменить
template hasNxt(input: string; idx: int): bool
- Исходный код Изменить
template success(x: int): bool
- Исходный код Изменить
template nxt(input: string; idx, step: int = 1)
- Исходный код Изменить
© 2006–2021 Andreas Rumpf
Licensed under the MIT License.
https://nim-lang.org/docs/strscans.html