Spec-Zone.ru › Nim

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 scanf(input: string; pattern: static[string]; results: varargs[typed]): bool
См. основную документацию данного модуля о том, как работает scanf. Source Edit
macro scanp(input, idx: typed; pattern: varargs[untyped]): bool
См. основную документацию данного модуля о том, как работает scanp. Source Edit
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

Шаблоны

template atom(input: string; idx: int; c: char): bool
Используется в scanp для сопоставления атомов (обычно символов). EOF сопоставляется как '\0'. Source Edit
template atom(input: string; idx: int; s: set[char]): bool
Source Edit
template hasNxt(input: string; idx: int): bool
Source Edit
template nxt(input: string; idx, step: int = 1)
Source Edit
template success(x: int): bool
Source Edit

© 2006–2024 Andreas Rumpf
Licensed under the MIT License.
https://nim-lang.org/docs/strscans.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API