std/strscans
Source EditЭтот модуль содержит макрос 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]*. |
$c |
Сопоставляет одиночный символ ASCII. |
$s |
Пропускает необязательные пробелы. |
$$ |
Сопоставляет одиночный знак доллара. |
$. |
Сопоставляет, если конец входной строки достигнут. |
$* |
Сопоставляет до токена, следующего за $*. Сопоставление допускается нулевой длины. |
$+ |
Сопоставляет до токена, следующего за $+. Сопоставление должно состоять как минимум из одного символа. |
${foo} |
Пользовательское определение сопоставления. Использует процедуру foo для выполнения сопоставления. Более подробная информация ниже. |
$[foo] |
Вызов пользовательской процедуры foo для пропуска некоторых необязательных частей во входной строке. Более подробная информация ниже. |
Несмотря на то, что $* и $+ похожи на регулярные выражения .* и .+, они работают по-разному. Нет недетерминированной конечного автомата, а сопоставления являются нежадными. [$*] сопоставляет [xyz] с помощью parseutils.parseUntil.
Кроме того, обратная подстановка не выполняется. Если анализ терпит неудачу после того, как значение уже было присвоено сопоставленной подвыражению, это значение не восстанавливается до своего первоначального значения. Это редко вызывает проблемы на практике, и если это происходит, легко присвоить значение временной переменной в первую очередь.
Сопоставление начала и полное сопоставление
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 должен быть перегружен для обработки как char, так и set[char].
import std/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, обратная подстановка не выполняется.
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, since
Макросы
macro scanTuple(input: untyped; pattern: static[string]; matcherTypes: varargs[untyped]): untyped- Работает аналогично scanf, но вместо предварительного объявления переменных возвращает кортеж. Кортеж начинается с булевого значения, которое указывает, был ли анализ успешным, за которым следуют запрошенные данные. При использовании пользовательского сопоставителя укажите типы в порядке их появления после шаблона:
line.scanTuple("${yourMatcher()}", int)Пример:
let (success, year, month, day, time) = scanTuple("1000-01-01 00:00:00", "$i-$i-$i$s$+") if success: assert year == 1000 assert month == 1 assert day == 1 assert time == "00:00:00"Source Edit
Шаблоны
© 2006–2024 Andreas Rumpf
Licensed under the MIT License.
https://nim-lang.org/docs/strscans.html