Проблемы с неоднозначной грамматикой и PEG.js (примеры не найдены)
Я хочу проанализировать файл со строками следующего содержания:
simple word abbr -8. (012) word, simple phrase, one another phrase - (simply dummy text of the printing; Lorem Ipsum : "Lorem" - has been the industry's standard dummy text, ever since the 1500s!; "It is a long established!"; "Sometimes by accident, sometimes on purpose (injected humour and the like)"; "sometimes on purpose") This is the end of the line
поэтому теперь объясним части (не все пробелы описаны из-за разметки здесь):
simple word
это одно или несколько слов (фраз), разделенных пробеломabbr -
является фиксированной частью строки (никогда не изменяется)8
- необязательный номер.
- всегда включенword, simple phrase, one another phrase
- одно или несколько слов или фраз, разделенных запятой- (
- фиксированная часть, всегда включенаsimply dummy text of the printing; Lorem Ipsum : "Lorem" - has been the industry's standard dummy text, ever since the 1500s!;
- (необязательно) одна или несколько фраз, разделенных;
"It is a long established!"; "Sometimes by accident, sometimes on purpose (injected humour and the like)"; "sometimes on purpose"
- (необязательно) одна или несколько фраз с кавычками"
разделены по;
) This is the end of the line
- всегда включен
В худшем случае в предложении нет фраз, но это редко: должна быть фраза без кавычек (phrase1
типа) или с ними (phrase2
тип).
Таким образом, фразы являются предложениями на естественном языке (со всеми возможными пунктуацией)...
НО:
- внутреннее содержание не имеет значения (т.е. мне не нужно разбирать сам естественный язык в значении НЛП)
- это просто необходимо пометить его как
phrase1
или жеphrase2
типы:- те, которые без и с кавычками, т. е. если фраза, которая находится между
(
а также;
или же;
а также;
или же;
а также)
или даже между(
а также)
дополнен кавычками, то этоphrase2
тип - в противном случае, если фраза начинается или заканчивается без кавычек, хотя она может содержать все метки внутри фразы, это
phrase1
тип
- те, которые без и с кавычками, т. е. если фраза, которая находится между
Так как писать Regex (PCRE) для такого ввода - это перебор, поэтому я обратился к подходу синтаксического анализа (EBNF или подобному). Я закончил с генератором парсера PEG.js. Я создал базовые варианты грамматики (даже не обрабатывая часть с разными фразами в предложении):
start = term _ "abbr" _ "-" .+
term = word (_? word !(_ "abbr" _ "-"))+
word = letters:letter+ {return letters.join("")}
letter = [A-Za-z]
_ "whitespace"
= [ \t\n\r]*
или (разница только в " abbr -"
а также "_ "abbr" _ "-""
):
start = term " abbr -" .+
term = word (_? word !(" abbr -"))+
word = letters:letter+ {return letters.join("")}
letter = [A-Za-z]
_ "whitespace"
= [ \t\n\r]*
Но даже эта простая грамматика не может разобрать начало строки. Ошибки:
Parse Error Expected [A-Za-z] but " " found.
Parse Error Expected "abbr" but "-" found.
- и т.п.
Так что, похоже, проблема в неоднозначности: "abbr"
потребляется с term
как word
маркер. Хотя я определил правило !(" abbr -")
, который я думал, имеет значение, что следующий word
токен будет использован только, если следующая подстрока не " abbr -"
Добрый.
Я не нашел хороших примеров, объясняющих следующие выражения PEG.js, что представляется мне возможным решением вышеупомянутой проблемы [от: http://pegjs.majda.cz/documentation%5D:
& expression
! expression
$ expression
& { predicate }
! { predicate }
TL; DR:
связанные с PEG.js:
Есть ли примеры применения правил:
& expression
! expression
$ expression
& { predicate }
! { predicate }
общий вопрос:
- Каков возможный подход для обработки таких сложных строк с помощью интуитивно понятных неоднозначных грамматик? Это все еще не естественный язык, и похоже, что он имеет некоторую формальную структуру, только с несколькими дополнительными частями. Одна из идей состоит в том, чтобы разбить строки путем предварительной обработки (с помощью регулярных выражений в местах фиксированных элементов, например, "abbr -" ") Это конец строки"), а затем создать для каждой разделенной части a отдельная грамматика. Но, похоже, есть проблемы с производительностью и масштабируемостью (то есть - что, если фиксированные элементы немного изменятся - например, не будет
-
полукокса больше.)
Update1:
Я нашел правило, которое решает проблему с соответствием "abbr -"
двусмысленность:
term = term:(word (!" abbr -" _? word))+ {return term.join("")}
но результат выглядит странно:
[
"simple, ,word",
" abbr -",
[
"8",
...
],
...
]
если удалить предикат: term = term:(word (!" abbr -" _? word))+
:
[
[
"simple",
[
[
undefined,
[
" "
],
"word"
]
]
],
" abbr -",
[
"8",
".",
" ",
"(",
...
],
...
]
Я ожидал что-то вроде:
[
[
"simple word"
],
" abbr -",
[
"8",
".",
" ",
"(",
...
],
...
]
или по крайней мере:
[
[
"simple",
[
" ",
"word"
]
],
" abbr -",
[
"8",
".",
" ",
"(",
...
],
...
]
Выражение сгруппировано, так почему оно разделено на множество уровней вложенности и даже undefined
входит в вывод? Существуют ли общие правила для сложения результата на основе выражения в правиле?
Update2:
Я создал грамматику так, чтобы она анализировалась по желанию, хотя я еще не определил четкий процесс создания такой грамматики:
start
= (term:term1 (" abbr -" number "." _ "("number:number") "{return number}) terms:terms2 ((" - (" phrases:phrases ")" .+){return phrases}))
//start //alternative way = looks better
// = (term:term1 " abbr -" number "." _ "("number:number") " terms:terms2 " - (" phrases:phrases ")" .+){return {term: term, number: number, phrases:phrases}}
term1
= term1:(
start_word:word
(rest_words:(
rest_word:(
(non_abbr:!" abbr -"{return non_abbr;})
(space:_?{return space[0];}) word){return rest_word.join("");})+{return rest_words.join("")}
)) {return term1.join("");}
terms2
= terms2:(start_word:word (rest_words:(!" - (" ","?" "? word)+){rest_words = rest_words.map(function(array) {
return array.filter(function(n){return n != null;}).join("");
}); return start_word + rest_words.join("")})
phrases
// = ((phrase_t:(phrase / '"' phrase '"') ";"?" "?){return phrase_t})+
= (( (phrase:(phrase2 / phrase1) ";"?" "?) {return phrase;})+)
phrase2
= (('"'pharse2:(phrase)'"'){return {phrase2: pharse2}})
phrase1
= ((pharse1:phrase){return {phrase1: pharse1}})
phrase
= (general_phrase:(!(';' / ')' / '";' / '")') .)+ ){return general_phrase.map(function(array){return array[1]}).join("")}
word = letters:letter+ {return letters.join("")}
letter = [A-Za-z]
number = digits:digit+{return digits.join("")}
digit = [0-9]
_ "whitespace"
= [ \t\n\r]*
Его можно протестировать либо на сайте автора PEG.js: [ http://pegjs.majda.cz/online%5D либо на веб-IDE PEG.js: [ http://peg.arcanis.fr/%5D.
Если у кого-то есть ответы на предыдущие вопросы (т.е. общий подход к устранению неоднозначности грамматики, примеры выражений, доступных в PEG.js), а также советы по улучшению самой грамматики (я думаю, что это далеко от идеальной грамматики сейчас) Я был бы очень признателен!
1 ответ
так почему он разделен на столько уровней вложенности и даже неопределенный включен в вывод?
Если вы посмотрите документацию для PEG.js, вы увидите, что почти каждый оператор собирает результаты своих операндов в массив. undefined
возвращается !
оператор.
$
оператор обходит все это вложение и просто выдает соответствующую строку, например: [a-z]+
даст массив букв, но $[a-z]+
даст строку букв.
Я думаю, что большая часть разбора здесь следует шаблону: "дай мне все, пока я не увижу эту строку". Вы должны выразить это в PEG, сначала используя !
чтобы убедиться, что вы не нажали завершающую строку, а затем просто взяли следующий символ. Например, чтобы получить все до " abbr -":
(!" abbr -" .)+
Если завершающая строка представляет собой один символ, вы можете использовать [^]
как краткая форма этого, например: [^x]+
это более короткий способ сказать (!"x" .)+
,
Разбирать фразы, разделенные запятой / точкой с запятой, а не фразы, заканчивающиеся запятой / точкой с запятой, немного раздражает, но кажется, что обработка их как необязательных терминаторов работает (с некоторыми trim
луг).
start = $(!" abbr -" .)+ " abbr -" $num "." [ ]? "(012)"
phrase_comma+ "- (" noq_phrase_semi+ q_phrase_semi+ ")"
$.*
phrase_comma = p:$[^-,]+ [, ]* { return p.trim() }
noq_phrase_semi = !'"' p:$[^;]+ [; ]* { return p.trim() }
q_phrase_semi = '"' p:$[^"]+ '"' [; ]* { return p }
num = [0-9]+
дает
[
"simple word",
" abbr -",
"8",
".",
" ",
"(012)",
[
"word",
"simple phrase",
"one another phrase"
],
"- (",
[
"simply dummy text of the printing",
"Lorem Ipsum : \"Lorem\" - has been the industry's standard dummy text, ever since the 1500s!"
],
[
"It is a long established!",
"Sometimes by accident, sometimes on purpose (injected humour and the like)",
"sometimes on purpose"
],
")",
" This is the end of the line"
]