Spec-Zone.ru › Nim 1

pegs

Простое соответствие PEG (Parsing expression grammar). Не использует запоминание, но использует супероператоры и встраивание символов для повышения производительности. Примечание: производительность сопоставления, надеемся, сопоставима с оптимизированными движками регулярных выражений.

Синтаксис и семантика PEG

PEG (Parsing expression grammar) — это простая детерминированная грамматика, которая может быть напрямую использована для разбора. Текущая реализация разработана как более мощная замена регулярным выражениям. Поддерживается UTF-8.

Нотация, используемая для PEG, похожа на EBNF:

нотация значение
A / ... / Z Последовательный выбор: Применяются выражения A, ..., Z, в этом порядке, к тексту впереди, пока одно из них не выполнится успешно и, возможно, не потребляет какой-то текст. Указывает успех, если одно из выражений выполнилось успешно. В противном случае не потребляет текст и указывает на неудачу.
A ... Z Последовательность: Применяются выражения A, ..., Z, в этом порядке, для потребления последовательных частей текста впереди, до тех пор, пока они выполняются успешно. Указывает успех, если все они выполнились. В противном случае не потребляет текст и указывает на неудачу. Приоритет последовательности выше, чем приоритет последовательного выбора: A B / C означает (A B) / Z, а не A (B / Z)
(E) Группирование: Скобки могут использоваться для изменения приоритета операторов.
{E} Захват: Применить выражение E и сохранить подстроку, которая соответствовала E, в захват, к которому можно получить доступ после процесса сопоставления.
$i Обращение к i-му захвату. i считает с 1.
$ Якор: Сопоставляется в конце входных данных. Ни один символ не потребляется. То же, что и !..
^ Якор: Сопоставляется в начале входных данных. Ни один символ не потребляется.
&E Предикат «и»: Указывает успех, если выражение E соответствует тексту впереди; в противном случае указывает на неудачу. Не потребляет текст.
!E Предикат «не»: Указывает неудачу, если выражение E соответствует тексту впереди; в противном случае указывает успех. Не потребляет текст.
E+ Один или более: Применяйте выражение E многократно, чтобы сопоставить текст впереди, пока это выполняется успешно. Потребляйте сопоставленный текст (если есть), и укажите успех, если было хотя бы одно совпадение. В противном случае укажите неудачу.
E* Ноль или более: Применяйте выражение E многократно, чтобы сопоставить текст впереди, пока это выполняется успешно. Потребляйте сопоставленный текст (если есть). Всегда указывайте успех.
E? Ноль или один: Если выражение E соответствует тексту впереди, потребляет его. Всегда указывайте успех.
[s] Класс символов: Если символ впереди появляется в строке s, потребляйте его и укажите успех. В противном случае укажите неудачу.
[a-b] Диапазон символов: Если символ впереди находится в диапазоне от a до b, потребляйте его и укажите успех. В противном случае укажите неудачу.
's' Строка: Если текст впереди является строкой s, потребляйте его и укажите успех. В противном случае укажите неудачу.
i's' Сопоставление строки, игнорируя регистр.
y's' Сопоставление строки, игнорируя стиль.
v's' Точное соответствие строке: Используйте это для переопределения глобального модификатора \i или \y.
i$j Сопоставление строки, игнорируя регистр для обращений к ссылкам.
y$j Сопоставление строки, игнорируя стиль для обращений к ссылкам.
v$j Точное соответствие строке для обращений к ссылкам.
. Любой символ: Если впереди есть символ, потребляйте его и укажите успех. В противном случае (то есть в конце входных данных) укажите неудачу.
_ Любой символ Юникода: Если впереди есть символ UTF-8, потребляйте его и укажите успех. В противном случае укажите неудачу.
@E Поиск: Краткая запись для (!E .)* E. (Цикл поиска шаблона E.)
{@} E Захваченный поиск: Краткая запись для {(!E .)*} E. (Цикл поиска шаблона E.) Все до E и без него, захватывается.
@@ E То же, что и {@} E.
A <- E Правило: Связывает выражение E с нетерминальным символом A. Леворекурсивные правила невозможны и приводят к сбоям движка сопоставления.
\identifier Встроенный макрос для более длинного выражения.
\ddd Символ с десятичным кодом ddd.
\", и т.д. Литерал ", и т.д.

Встроенные макросы

макрос значение
\d любая десятичная цифра: [0-9]
\D любой символ, который не является десятичной цифрой: [^0-9]
\s любой пробельный символ: [ \9-\13]
\S любой символ, который не является пробельным: [^ \9-\13]
\w любой символ "слова": [a-zA-Z0-9_]
\W любой символ "не-слова": [^a-zA-Z0-9_]
\a то же, что и [a-zA-Z]
\A то же, что и [^a-zA-Z]
\n любая комбинация новой строки: \10 / \13\10 / \13
\i игнорировать регистр при сопоставлении; использовать в начале PEG
\y игнорировать стиль при сопоставлении; использовать в начале PEG
\skip pat пропустить шаблон pat перед попыткой сопоставить другие токены; это полезно для пропуска пробелов, например: \skip(\s*) {\ident} ':' {\ident} сопоставляет пары ключ-значение, игнорируя пробелы вокруг ':'.
\ident стандартный ASCII идентификатор: [a-zA-Z_][a-zA-Z_0-9]*
\letter любая буква Юникода
\upper любая заглавная буква Юникода
\lower любая строчная буква Юникода
\title любая буква Юникода в стиле заголовка
\white любой пробельный символ Юникода

Обратный слэш, за которым следует буква, является встроенным макросом, в противном случае он используется для обычного экранирования:

нотация значение
\\ одиночный обратный слэш
\* то же, что и '*'
\t не табулятор, а (неизвестный) встроенный элемент

Поддерживаемая грамматика PEG

Парсер PEG реализует эту грамматику (написанную в синтаксисе PEG):

# Example grammar of PEG in PEG syntax.
# Comments start with '#'.
# First symbol is the start symbol.

grammar <- rule* / expr

identifier <- [A-Za-z][A-Za-z0-9_]*
charsetchar <- "\\" . / [^\]]
charset <- "[" "^"? (charsetchar ("-" charsetchar)?)+ "]"
stringlit <- identifier? ("\"" ("\\" . / [^"])* "\"" /
                          "'" ("\\" . / [^'])* "'")
builtin <- "\\" identifier / [^\13\10]

comment <- '#' @ \n
ig <- (\s / comment)* # things to ignore

rule <- identifier \s* "<-" expr ig
identNoArrow <- identifier !(\s* "<-")
prefixOpr <- ig '&' / ig '!' / ig '@' / ig '{@}' / ig '@@'
literal <- ig identifier? '$' [0-9]+ / '$' / '^' /
           ig identNoArrow /
           ig charset /
           ig stringlit /
           ig builtin /
           ig '.' /
           ig '_' /
           (ig "(" expr ig ")")
postfixOpr <- ig '?' / ig '*' / ig '+'
primary <- prefixOpr* (literal postfixOpr*)

# Concatenation has higher priority than choice:
# ``a b / c`` means ``(a b) / c``

seqExpr <- primary+
expr <- seqExpr (ig "/" expr)*

Примечание: В качестве специального синтаксического расширения, если весь PEG — это только одно выражение, идентификаторы не интерпретируются как нетерминалы, а интерпретируются как точная строка:

abc =~ peg"abc" # is true

Поэтому не нужно писать peg" 'abc' " в приведенном выше примере.

Примеры

Проверка, соответствует ли s ключевому слову "while" в Nim:

s =~ peg" y'while'"

Обмен (ключ, значение)-парами:

"key: val; key2: val2".replacef(peg"{\ident} \s* ':' \s* {\ident}", "$2: $1")

Определение файлов с расширением #include, содержащихся в файле C:

for line in lines("myfile.c"):
  if line =~ peg"""s <- ws '#include' ws '"' {[^"]+} '"' ws
                   comment <- '/*' @ '*/' / '//' .*
                   ws <- (comment / \s+)* """:
    echo matches[0]

PEG против регулярных выражений

Как регулярное выражение \[.*\] соответствует самому длинному возможному тексту между '[' и ']'. Как PEG, он никогда не соответствует чему-либо, потому что PEG детерминирован: .* потребляет остаток входных данных, поэтому \] никогда не соответствует. Как PEG это необходимо записать так: \[ ( !\] . )* \] (или \[ @ \]).

Обратите внимание, что регулярное выражение также не ведет себя как ожидалось: в примере * не должно быть жадным, поэтому \[.*?\] следует использовать вместо него.

Построение PEG

Существует два способа построения PEG в коде Nim:

  1. Разбор строки в AST, который состоит из Peg узлов с процедурой peg.
  2. Непосредственное построение AST с помощью вызовов процедур. Этот метод не поддерживает построение правил, только простые выражения и не так удобен. Его единственное преимущество заключается в том, что он не подключает весь парсер PEG в ваш исполняемый файл.

Импорты

strutils, macros, unicode

Типы

PegKind = enum
  pkEmpty, pkAny,           ## any character (.)
  pkAnyRune,                ## any Unicode character (_)
  pkNewLine,                ## CR-LF, LF, CR
  pkLetter,                 ## Unicode letter
  pkLower,                  ## Unicode lower case letter
  pkUpper,                  ## Unicode upper case letter
  pkTitle,                  ## Unicode title character
  pkWhitespace,             ## Unicode whitespace character
  pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar, ## single character to match
  pkCharChoice, pkNonTerminal, pkSequence, ## a b c ... --> Internal DSL: peg(a, b, c)
  pkOrderedChoice,          ## a / b / ... --> Internal DSL: a / b or /[a, b, c]
  pkGreedyRep,              ## a*     --> Internal DSL: *a
                             ## a+     --> (a a*)
  pkGreedyRepChar,          ## x* where x is a single character (superop)
  pkGreedyRepSet,           ## [set]* (superop)
  pkGreedyAny,              ## .* or _* (superop)
  pkOption,                 ## a?     --> Internal DSL: ?a
  pkAndPredicate,           ## &a     --> Internal DSL: &a
  pkNotPredicate,           ## !a     --> Internal DSL: !a
  pkCapture,                ## {a}    --> Internal DSL: capture(a)
  pkBackRef,                ## $i     --> Internal DSL: backref(i)
  pkBackRefIgnoreCase, pkBackRefIgnoreStyle, pkSearch, ## @a     --> Internal DSL: !*a
  pkCapturedSearch,         ## {@} a  --> Internal DSL: !*\a
  pkRule,                   ## a <- b
  pkList,                   ## a, b
  pkStartAnchor              ## ^      --> Internal DSL: startAnchor()
Источник Редактировать
NonTerminalFlag = enum
  ntDeclared, ntUsed
Источник Редактировать
Peg {...}{.shallow.} = object
  case kind: PegKind
  of pkEmpty .. pkWhitespace:
    nil
  of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle:
    term: string
  of pkChar, pkGreedyRepChar:
    ch: char
  of pkCharChoice, pkGreedyRepSet:
    charChoice: ref set[char]
  of pkNonTerminal:
    nt: NonTerminal
  of pkBackRef .. pkBackRefIgnoreStyle:
    index: range[0 .. MaxSubpatterns]
  else:
    sons: seq[Peg]
тип, представляющий PEG Источник Редактировать
NonTerminal = ref NonTerminalObj
Источник Редактировать
Captures = object
  matches: array[0 .. 20 - 1, tuple[first, last: int]]
  ml: int
  origStart: int
содержит захваченные подстроки. Источник Редактировать
EInvalidPeg = object of ValueError
выбрасывается, если обнаружен неверный PEG Источник Редактировать

Константы

MaxSubpatterns = 20
определяет максимальное количество подпатернов, которые могут быть захвачены. Больше подпатернов захвачено быть не может! Исходный код Изменить

Процедуры

proc kind(p: Peg): PegKind {...}{.raises: [], tags: [].}
Возвращает PegKind заданного объекта Peg. Исходный код Редактировать
proc term(p: Peg): string {...}{.raises: [], tags: [].}
Возвращает строковое представление заданного объекта варианта Peg, если он есть. Исходный код Редактировать
proc ch(p: Peg): char {...}{.raises: [], tags: [].}
Возвращает представление char заданного объекта варианта Peg, если оно есть. Исходный код Редактировать
proc charChoice(p: Peg): ref set[char] {...}{.raises: [], tags: [].}
Возвращает поле charChoice заданного объекта варианта Peg, если оно есть. Исходный код Редактировать
proc nt(p: Peg): NonTerminal {...}{.raises: [], tags: [].}
Возвращает объект NonTerminal заданного объекта варианта Peg, если он есть. Исходный код Редактировать
proc index(p: Peg): range[0 .. MaxSubpatterns] {...}{.raises: [], tags: [].}
Возвращает индекс обратной ссылки захваченного под-образца в объекте Captures для заданного объекта варианта Peg, если он есть. Исходный код Редактировать
proc name(nt: NonTerminal): string {...}{.raises: [], tags: [].}
Возвращает имя символа, представленного родительским объектом Peg вариантом заданного NonTerminal. Исходный код Редактировать
proc line(nt: NonTerminal): int {...}{.raises: [], tags: [].}
Возвращает номер строки определения родительского объекта Peg варианта заданного NonTerminal. Исходный код Редактировать
proc col(nt: NonTerminal): int {...}{.raises: [], tags: [].}
Возвращает номер столбца определения родительского объекта Peg варианта заданного NonTerminal. Исходный код Редактировать
proc flags(nt: NonTerminal): set[NonTerminalFlag] {...}{.raises: [], tags: [].}
Возвращает поле флагов типа NonTerminalFlag родительского объекта варианта Peg заданного NonTerminal. Исходный код Редактировать
proc rule(nt: NonTerminal): Peg {...}{.raises: [], tags: [].}
Возвращает объект Peg, представляющий определение правила родительского объекта Peg варианта заданного NonTerminal. Исходный код Редактировать
proc term(t: string): Peg {...}{.noSideEffect, gcsafe, extern: "npegs$1Str",
                            raises: [], tags: [].}
строит PEG из терминальной строки Исходный код Редактировать
proc termIgnoreCase(t: string): Peg {...}{.noSideEffect, gcsafe, extern: "npegs$1",
                                      raises: [], tags: [].}
строит PEG из терминальной строки; игнорировать регистр при соответствии Исходный код Редактировать
proc termIgnoreStyle(t: string): Peg {...}{.noSideEffect, gcsafe, extern: "npegs$1",
                                       raises: [], tags: [].}
строит PEG из терминальной строки; игнорировать стиль при соответствии Исходный код Редактировать
proc term(t: char): Peg {...}{.noSideEffect, gcsafe, extern: "npegs$1Char",
                          raises: [], tags: [].}
строит PEG из терминального символа Исходный код Редактировать
proc charSet(s: set[char]): Peg {...}{.noSideEffect, gcsafe, extern: "npegs$1",
                                  raises: [], tags: [].}
строит PEG из набора символов s Исходный код Редактировать
proc `/`(a: varargs[Peg]): Peg {...}{.noSideEffect, gcsafe,
                                 extern: "npegsOrderedChoice", raises: [],
                                 tags: [].}
строит упорядоченный выбор с PEGs в a Исходный код Редактировать
proc sequence(a: varargs[Peg]): Peg {...}{.noSideEffect, gcsafe, extern: "npegs$1",
                                      raises: [], tags: [].}
строит последовательность со всеми PEGs из a Исходный код Редактировать
proc `?`(a: Peg): Peg {...}{.noSideEffect, gcsafe, extern: "npegsOptional",
                        raises: [], tags: [].}
строит необязательное для PEG a Исходный код Редактировать
proc `*`(a: Peg): Peg {...}{.noSideEffect, gcsafe, extern: "npegsGreedyRep",
                        raises: [], tags: [].}
строит "жадное повторение" для PEG a Исходный код Редактировать
proc `!*`(a: Peg): Peg {...}{.noSideEffect, gcsafe, extern: "npegsSearch",
                         raises: [], tags: [].}
строит "поиск" для PEG a Исходный код Редактировать
proc `!*\`(a: Peg): Peg {...}{.noSideEffect, gcsafe, extern: "npgegsCapturedSearch",
                          raises: [], tags: [].}
строит "захваченный поиск" для PEG a Исходный код Редактировать
proc `+`(a: Peg): Peg {...}{.noSideEffect, gcsafe, extern: "npegsGreedyPosRep",
                        raises: [], tags: [].}
строит "жадное положительное повторение" с PEG a Исходный код Редактировать
proc `&`(a: Peg): Peg {...}{.noSideEffect, gcsafe, extern: "npegsAndPredicate",
                        raises: [], tags: [].}
строит "предикат и" с PEG a Исходный код Редактировать
proc `!`(a: Peg): Peg {...}{.noSideEffect, gcsafe, extern: "npegsNotPredicate",
                        raises: [], tags: [].}
строит "предикат не" с PEG a Исходный код Редактировать
proc any(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG любой символ (.) Исходный код Редактировать
proc anyRune(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG любой символ Юникод (_) Исходный код Редактировать
proc newLine(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG новая строка (\n) Исходный код Редактировать
proc unicodeLetter(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG \letter, который соответствует любому буквенному символу Юникод. Исходный код Редактировать
proc unicodeLower(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG \lower, который соответствует любому строчному буквенному символу Юникод. Исходный код Редактировать
proc unicodeUpper(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG \upper, который соответствует любому прописному буквенному символу Юникод. Исходный код Редактировать
proc unicodeTitle(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG \title, который соответствует любому буквенному символу Юникод в верхнем регистре. Исходный код Редактировать
proc unicodeWhitespace(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG \white, который соответствует любому пробельному символу Юникод. Исходный код Редактировать
proc startAnchor(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG ^, который соответствует началу входных данных. Исходный код Редактировать
proc endAnchor(): Peg {...}{.inline, raises: [], tags: [].}
строит PEG $, который соответствует концу входных данных. Исходный код Редактировать
proc capture(a: Peg): Peg {...}{.noSideEffect, gcsafe, extern: "npegsCapture",
                            raises: [], tags: [].}
строит захват с PEG a Исходный код Редактировать
proc backref(index: range[1 .. MaxSubpatterns]): Peg {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [], tags: [].}
создаёт обратную ссылку на указанный index. index начинается с 1. Исходный код Редактировать
proc backrefIgnoreCase(index: range[1 .. MaxSubpatterns]): Peg {...}{.noSideEffect,
    gcsafe, extern: "npegs$1", raises: [], tags: [].}
создаёт обратную ссылку на указанный index. index начинается с 1. Игнорирует регистр при сопоставлении. Исходный код Редактировать
proc backrefIgnoreStyle(index: range[1 .. MaxSubpatterns]): Peg {...}{.noSideEffect,
    gcsafe, extern: "npegs$1", raises: [], tags: [].}
создаёт обратную ссылку на указанный index. index начинается с 1. Игнорирует стиль при сопоставлении. Исходный код Редактировать
proc nonterminal(n: NonTerminal): Peg {...}{.noSideEffect, gcsafe, extern: "npegs$1",
                                        raises: [], tags: [].}
создаёт PEG, состоящий из нетерминального символа Исходный код Редактировать
proc newNonTerminal(name: string; line, column: int): NonTerminal {...}{.
    noSideEffect, gcsafe, extern: "npegs$1", raises: [], tags: [].}
создаёт нетерминальный символ Исходный код Редактировать
proc `$`(r: Peg): string {...}{.noSideEffect, gcsafe, extern: "npegsToString",
                           raises: [], tags: [].}
преобразует PEG в строковое представление Исходный код Редактировать
proc bounds(c: Captures; i: range[0 .. 20 - 1]): tuple[first, last: int] {...}{.
    raises: [], tags: [].}
возвращает границы [first..last] i-го захвата. Исходный код Редактировать
proc rawMatch(s: string; p: Peg; start: int; c: var Captures): int {...}{.
    noSideEffect, gcsafe, extern: "npegs$1", raises: [], tags: [].}
Процедура низкого уровня для сопоставления, реализующая интерпретатор PEG. Используйте для максимальной эффективности (каждая другая операция PEG в конечном итоге вызывает эту процедуру). Возвращает -1, если не совпадает, в противном случае возвращает длину совпадения. Исходный код Редактировать
proc matchLen(s: string; pattern: Peg; matches: var openArray[string]; start = 0): int {...}{.
    noSideEffect, gcsafe, extern: "npegs$1Capture", raises: [], tags: [].}
то же самое, что и match, но возвращает длину совпадения; если совпадения нет, возвращает -1. Обратите внимание, что длина совпадения может быть нулевой. Возможно, остаётся суффикс s, который не принадлежит совпадению. Исходный код Редактировать
proc matchLen(s: string; pattern: Peg; start = 0): int {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [], tags: [].}
то же самое, что и match, но возвращает длину совпадения; если совпадения нет, возвращает -1. Обратите внимание, что длина совпадения может быть нулевой. Возможно, остаётся суффикс s, который не принадлежит совпадению. Исходный код Редактировать
proc match(s: string; pattern: Peg; matches: var openArray[string]; start = 0): bool {...}{.
    noSideEffect, gcsafe, extern: "npegs$1Capture", raises: [], tags: [].}
возвращает true, если s[start..] соответствует pattern, а захваченные подстроки находятся в массиве matches. Если совпадение не найдено, ничего не записывается в matches, и возвращается false. Исходный код Редактировать
proc match(s: string; pattern: Peg; start = 0): bool {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [], tags: [].}
возвращает true, если s соответствует pattern, начиная с start. Исходный код Редактировать
proc find(s: string; pattern: Peg; matches: var openArray[string]; start = 0): int {...}{.
    noSideEffect, gcsafe, extern: "npegs$1Capture", raises: [], tags: [].}
возвращает начальную позицию pattern в s и захваченные подстроки в массиве matches. Если совпадение не найдено, ничего не записывается в matches, и возвращается -1. Исходный код Редактировать
proc findBounds(s: string; pattern: Peg; matches: var openArray[string];
                start = 0): tuple[first, last: int] {...}{.noSideEffect, gcsafe,
    extern: "npegs$1Capture", raises: [], tags: [].}
возвращает начальную и конечную позиции pattern в s и захваченные подстроки в массиве matches. Если совпадение не найдено, ничего не записывается в matches, и возвращается (-1,0). Исходный код Редактировать
proc find(s: string; pattern: Peg; start = 0): int {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [], tags: [].}
возвращает начальную позицию pattern в s. Если совпадение не найдено, возвращается -1. Исходный код Редактировать
proc findAll(s: string; pattern: Peg; start = 0): seq[string] {...}{.noSideEffect,
    gcsafe, extern: "npegs$1", raises: [], tags: [].}
возвращает все совпадающие подстроки s, которые соответствуют pattern. Если совпадения нет, возвращается @[]. Исходный код Редактировать
proc contains(s: string; pattern: Peg; start = 0): bool {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [], tags: [].}
то же, что и find(s, pattern, start) >= 0 Исходный код Редактировать
proc contains(s: string; pattern: Peg; matches: var openArray[string]; start = 0): bool {...}{.
    noSideEffect, gcsafe, extern: "npegs$1Capture", raises: [], tags: [].}
то же, что и find(s, pattern, matches, start) >= 0 Исходный код Редактировать
proc startsWith(s: string; prefix: Peg; start = 0): bool {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [], tags: [].}
возвращает true, если s начинается с шаблона prefix. Исходный код Редактировать
proc endsWith(s: string; suffix: Peg; start = 0): bool {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [], tags: [].}
возвращает true, если s заканчивается шаблоном suffix. Исходный код Редактировать
proc replacef(s: string; sub: Peg; by: string): string {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [ValueError], tags: [].}
Заменяет sub в s строкой by. К захватам можно получить доступ в by с использованием обозначений $i и $# (см. strutils.`%`). Примеры:
"var1=key; var2=key2".replacef(peg"{\ident}'='{\ident}", "$1<-$2$2")

Результаты:

"var1<-keykey; val2<-key2key2"
Исходный код Редактировать
proc replace(s: string; sub: Peg; by = ""): string {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [], tags: [].}
Заменяет sub в s строкой by. К захватам нельзя получить доступ в by. Исходный код Редактировать
proc parallelReplace(s: string; subs: varargs[tuple[pattern: Peg, repl: string]]): string {...}{.
    noSideEffect, gcsafe, extern: "npegs$1", raises: [ValueError], tags: [].}
Возвращает изменённую копию s с применёнными заменами из subs параллельно. Исходный код Редактировать
proc replace(s: string; sub: Peg;
             cb: proc (match: int; cnt: int; caps: openArray[string]): string): string {...}{.
    gcsafe, extern: "npegs$1cb", raises: [], tags: [].}
Заменяет sub в s результирующими строками из обратного вызова. Процедура обратного вызова получает индекс текущего совпадения (начиная с 0), количество захватов и открытый массив с захватами каждого совпадения. Примеры:
proc handleMatches*(m: int, n: int, c: openArray[string]): string =
  result = ""
  if m > 0:
    result.add ", "
  result.add case n:
    of 2: c[0].toLower & ": '" & c[1] & "'"
    of 1: c[0].toLower & ": ''"
    else: ""

let s = "Var1=key1;var2=Key2;   VAR3"
echo s.replace(peg"{\ident}('='{\ident})* ';'* \s*", handleMatches)

Результаты:

"var1: 'key1', var2: 'Key2', var3: ''"
Исходный код Редактировать
proc transformFile(infile, outfile: string;
                   subs: varargs[tuple[pattern: Peg, repl: string]]) {...}{.gcsafe,
    extern: "npegs$1", raises: [IOError, ValueError],
    tags: [ReadIOEffect, WriteIOEffect].}

читает файл infile, выполняет параллельную замену (вызывает parallelReplace) и записывает обратно в outfile. Вызывает IOError, если произошла ошибка. Предполагается использование для быстрой работы со скриптами.

Примечание: эта процедура не существует при использовании JS-бекенда.

Исходный код Редактировать
proc split(s: string; sep: Peg): seq[string] {...}{.noSideEffect, gcsafe,
    extern: "npegs$1", raises: [], tags: [].}
Разделяет строку s на подстроки. Исходный код Редактировать
proc parsePeg(pattern: string; filename = "pattern"; line = 1; col = 0): Peg {...}{.
    raises: [ValueError, EInvalidPeg, Exception], tags: [RootEffect].}
строит объект Peg из pattern. filename, line, col используются для сообщений об ошибках, но они предоставляют только начальные смещения. parsePeg отслеживает номера строк и столбцов в pattern. Исходный код Редактировать
proc peg(pattern: string): Peg {...}{.raises: [ValueError, EInvalidPeg, Exception],
                                 tags: [RootEffect].}
строит объект Peg из pattern. Короткий имя выбрано для того, чтобы стимулировать его использование в качестве модификатора строки без форматирования:
peg"{\ident} \s* '=' \s* {.*}"
Исходный код Редактировать
proc escapePeg(s: string): string {...}{.raises: [], tags: [].}
эскэпирует s так, что он будет сопоставлен дословно, когда используется в качестве PEG. Исходный код Редактировать

Итераторы

iterator items(p: Peg): Peg {...}{.inline, raises: [], tags: [].}
Возвращает дочерние узлы объекта варианта Peg, если они присутствуют. Исходный код Изменить
iterator pairs(p: Peg): (int, Peg) {...}{.inline, raises: [], tags: [].}
Возвращает индексы и дочерние узлы объекта варианта Peg, если они присутствуют. Исходный код Изменить
iterator findAll(s: string; pattern: Peg; start = 0): string {...}{.raises: [],
    tags: [].}
возвращает все совпадающие подстроки s , которые соответствуют pattern. Исходный код Изменить
iterator split(s: string; sep: Peg): string {...}{.raises: [], tags: [].}

Разбивает строку s на подстроки.

Подстроки разделены разделителем PEG sep. Примеры:

for word in split("00232this02939is39an22example111", peg"\d+"):
  writeLine(stdout, word)

Результаты:

"this"
"is"
"an"
"example"
Исходный код Изменить

Шаблоны

template letters(): Peg
расширяется до charset({'A'..'Z', 'a'..'z'}) Исходный код Изменить
template digits(): Peg
расширяется до charset({'0'..'9'}) Исходный код Изменить
template whitespace(): Peg
расширяется до charset({' ', '\9'..'\13'}) Исходный код Изменить
template identChars(): Peg
расширяется до charset({'a'..'z', 'A'..'Z', '0'..'9', '_'}) Исходный код Изменить
template identStartChars(): Peg
расширяется до charset({'A'..'Z', 'a'..'z', '_'}) Исходный код Изменить
template ident(): Peg
такое же, как [a-zA-Z_][a-zA-z_0-9]*; стандартный идентификатор Исходный код Изменить
template natural(): Peg
такое же, как \d+ Исходный код Изменить
template eventParser(pegAst, handlers: untyped): (proc (s: string): int)
Генерирует интерпретирующий парсер событий proc в соответствии с заданным PEG AST и блоками кода обработчика. proc может вызываться со строкой для разбора и будет выполнять блоки кода обработчика всякий раз, когда будет сопоставлен соответствующий элемент грамматики. Возвращает -1, если строка не соответствует, в противном случае возвращает длину полного соответствия. Следующий пример кода оценивает арифметическое выражение, определенное простым PEG:
import strutils, pegs

let
  pegAst = """
Expr    <- Sum
Sum     <- Product (('+' / '-')Product)*
Product <- Value (('*' / '/')Value)*
Value   <- [0-9]+ / '(' Expr ')'
  """.peg
  txt = "(5+3)/2-7*22"

var
  pStack: seq[string] = @[]
  valStack: seq[float] = @[]
  opStack = ""
let
  parseArithExpr = pegAst.eventParser:
    pkNonTerminal:
      enter:
        pStack.add p.nt.name
      leave:
        pStack.setLen pStack.high
        if length > 0:
          let matchStr = s.substr(start, start+length-1)
          case p.nt.name
          of "Value":
            try:
              valStack.add matchStr.parseFloat
              echo valStack
            except ValueError:
              discard
          of "Sum", "Product":
            try:
              let val = matchStr.parseFloat
            except ValueError:
              if valStack.len > 1 and opStack.len > 0:
                valStack[^2] = case opStack[^1]
                of '+': valStack[^2] + valStack[^1]
                of '-': valStack[^2] - valStack[^1]
                of '*': valStack[^2] * valStack[^1]
                else: valStack[^2] / valStack[^1]
                valStack.setLen valStack.high
                echo valStack
                opStack.setLen opStack.high
                echo opStack
    pkChar:
      leave:
        if length == 1 and "Value" != pStack[^1]:
          let matchChar = s[start]
          opStack.add matchChar
          echo opStack

let pLen = parseArithExpr(txt)

Параметр handlers содержит блоки кода для PegKinds, которые определяют элементы грамматики, представляющие интерес. Каждый блок может содержать код обработчика, который будет выполняться при входе парсера и выходе из совпадения текста с элементом грамматики. Обработчик enter может получить доступ к узлу конкретного PEG AST, сопоставляемому как p, к всей обработанной строке как s и к позиции совпадающего сегмента текста в s как start. Обработчик leave может получить доступ к p, s, start, а также к длине совпадающего сегмента текста как length. При неудачном сопоставлении обработчики enter и leave будут выполнены, при этом length будет установлено в -1.

Символы, объявленные в обработчике enter, могут стать видимыми в соответствующем обработчике leave, аннотируя их пragma inject.

Исходный код Изменить
template `=~`(s: string; pattern: Peg): bool
Это вызывает match с неявным объявленным массивом matches, который может быть использован в области действия вызова =~:
if line =~ peg"\s* {\w+} \s* '=' \s* {\w+}":
  # matches a key=value pair:
  echo("Key: ", matches[0])
  echo("Value: ", matches[1])
elif line =~ peg"\s*{'#'.*}":
  # matches a comment
  # note that the implicit ``matches`` array is different from the
  # ``matches`` array of the first branch
  echo("comment: ", matches[0])
else:
  echo("syntax error")
Исходный код Изменить

Экспорт

==

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

Spec-Zone.ru

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