Ім'я для цього типу аналізатора, АБО чому його не існує


27

Звичайні парсери споживають весь свій внесок і виробляють єдине дерево розбору. Я шукаю той, який споживає безперервний потік і створює ліс розбору [ редагувати: див. Обговорення в коментарях щодо того, чому використання цього терміна може бути нетрадиційним ]. Моя кишка говорить, що я не можу бути першою людиною, яка потребує (або вважаю, що мені потрібна) такий синтаксичний аналіз, але я шукав і шукав місяці безрезультатно.

Я визнаю, що мене може захопити проблема XY. Моя кінцева мета - проаналізувати потік тексту, ігноруючи більшість його, та створити потік дерев розбору з розпізнаних розділів.

Отже, моє запитання умовне: якщо клас парсерів з цими характеристиками існує, як він називається? А якщо ні, то чому б і ні? Яка альтернатива? Можливо, мені не вистачає способу змусити звичайні парсери робити те, що я хочу.


1
В основному ваш синтаксичний аналіз розбирає один документ і видає дерево розбору, потім негайно починає розбір іншого документа і т. Д. Я вважаю, що ця модифікація поведінки є тривіальною порівняно з різноманітністю методів розбору, застосованих до одного документа. Звідси відсутність спеціального терміна для нього.
9000

3
Я здійснив пошук в Google за «Парсером лісом» і виявив, що Earley Parser виробляє їх.
Роберт Харві

7
Ви, можливо, шукаєте монадичні парсерні комбінатори - тобто більший парсер, що складається з кількох менших парсерів. Вони зручні для ситуацій, коли «острів» однієї мови вбудований в іншу. Мій колишній колега з дизайнерської команди C # Люк Хобан має гарну статтю про них: blogs.msdn.com/b/lukeh/archive/2007/08/19/…
Ерік Ліпперт

3
Є деяка плутанина. Ви маєте на увазі, що вам потрібно дерево розбору для кожного документа у вашому потоці та що вони утворюють разом ліс розбору. Це не звичайне значення розбору лісу. Ліс розбору - це набір дерев розбору для одного неоднозначного документа (трохи спрощення), який можна розібрати різними способами. І саме на це всі відповіді. Ваш потік складається з безлічі повних документів, розділених сміттям, чи це єдиний документ, який частково є пошкодженим. Ваш документ повинен бути синтаксично правильним чи ні? Від цього залежить правильна технічна відповідь.
babou

1
Потім забудьте всі відповіді про розбір лісів та похідні Earley, GLR, Marpa. Вони, мабуть, не потрібні, якщо не з’явиться інша причина. Чи ваші документи синтаксично правильні? Деяка техніка розбору може відтворити контекст для частково зібраних документів. Чи є у вас точний синтаксис для цих документів. Це однакова для всіх? Ви дійсно хочете розбирати дерева, або ви б задовольнилися, виділивши документи та, можливо, проаналізуйте їх пізніше, окремо. Я думаю, що я знаю, що може покращити вашу обробку, але я не впевнений, що ви можете це отримати з полиці.
babou

Відповіді:


48

Аналізатор, який повертає (частковий) результат до того, як буде використано весь вхід, називається покроковим аналізатором . Поступовий аналіз може бути складним, якщо в граматиці є місцеві неоднозначності, про які вирішується лише пізніше у введенні. Ще одна складність полягає у формуванні тих частин дерева розбору, які ще не були досягнуті.

Викликається аналізатор, який повертає ліс усіх можливих дерев розбору, тобто повертає дерево розбору для кожного можливого виведення неоднозначної граматики… Я не впевнений, чи мають ці речі ще ім’я. Я знаю, що генератор аналізатора Marpa здатний на це, але будь-який аналізатор на основі Ерлі або GLR повинен бути в змозі зняти це.


Однак ти, схоже, нічого з цього не хочеш. У вас є потік з декількома вбудованими документами, зі сміттям між ними:

 garbagegarbage{key:42}garbagegarbage[1,2,3]{id:0}garbage...

Ви, здається, хочете аналізатор, який пропускає сміття, і (ліниво) дає послідовність ASTs для кожного документа. Це може вважатися поступовим аналізатором у загальному сенсі. Але ви насправді реалізуєте цикл таким чином:

while stream is not empty:
  try:
    yield parse_document(stream at current position)
  except:
    advance position in stream by 1 character or token

Тоді ця parse_docmentфункція буде звичайним неінкрементальним аналізатором. Існує незначна складність у забезпеченні того, що ви прочитали достатньо вхідного потоку для успішного розбору. Як це можна впоратися, залежить від типу аналізатора, який ви використовуєте. Можливості включають збільшення буфера на певних помилках розбору або використання лінивої токенізації.

Ледача токенізація - це, мабуть, найелегантніше рішення завдяки вашому потоку введення. Замість того, щоб фаза лексери створювала фіксований список лексем, аналізатор ліниво запитав наступний маркер із зворотного виклику лексера [1] . Тоді лексер споживає стільки, скільки потрібно. Таким чином, аналізатор може вийти з ладу лише тоді, коли досягнуто реального кінця потоку або коли відбулася реальна помилка розбору (тобто ми почали розбирати, поки ще перебуваєте у смітті).

[1] лексер, керований зворотним викликом, є хорошою ідеєю також і в інших контекстах, оскільки це може уникнути деяких проблем із узгодженням з найдовшим токеном .

Якщо ви знаєте, які саме документи ви шукаєте, ви можете оптимізувати пропуск, щоб зупинитися лише на перспективних місцях. Наприклад, документ JSON завжди починається з символу {або [. Тому сміття - це будь-яка рядок, яка не містить цих символів.


5
Ваш псевдокод - це насправді те, що я робив, але я подумав, що це просто потворний злом. Аналізатор викидає два види винятків ( NO_MATCHі UNDERFLOW), які дозволяють мені розрізнити, чи слід просунути позицію потоку або чекати додаткового введення.
Кевін Крумвіде

5
@Kevin: Я також використовую це, з деякими функціями безпеки, щоб обробляти вхідні дані з мережі у фірмовому форматі. Нічого хакі не з цього приводу!
Гонки легкості з Монікою

5

Не існує жодного конкретного імені для аналізатора, який би це робив. Але я виділю один алгоритм, який робить це: розбір похідних .

Він споживає вхід, один маркер за один раз. Він створить ліс розбору в кінці введення. Крім того, ви також можете отримати весь ліс розбору, перебуваючи в середині розбору ( частковий аналіз).

Розбір з похідними обробляє без контекстних граматик і створить ліс для розбору неоднозначних граматик.

Це насправді елегантна теорія, але вона лише в зародковому стані і не широко розгорнута. У Matt Might є список посилань на різні реалізації у Scala / Racket / тощо.

Теорію легше засвоїти, якщо ви почнете з розпізнавання похідних (тобто, почніть з прийому похідних мов , з метою розпізнати деякий вхід, щоб визначити, чи він дійсний чи ні), а потім змінити програму для розбору з похідними ( тобто змінити його, щоб замість брати похідні мови , він бере похідні аналізаторів і обчислює ліс розбору).


4
Downvoter: Ви можете, будь ласка, пояснити, що було гідно Якщо є щось, що мені потрібно виправити або вдосконалити, це, звичайно, було б добре знати.
Cornstalks

Я не прихильник, і не мріяв би пропустити без коментарів. Але ваш ентузіастичний документ не має посилання на безліч існуючих аналізаторів, які досягають такого ж результату, що стосується складності та розбору лісу. Функціональне програмування чудово, але порівняння результату з існуючою літературою з цього питання також приємно. Наскільки зручний ваш ліс для розбору для подальшого використання?
бабу

@babou: для запису я не є автором цього блогу / статті. Але так, я згоден, я міг би додати більше деталей, порівнюючи цей алгоритм з іншими та детально пояснивши його. У Метта Мойт є ціла лекція з цього приводу , але було б непогано закріпити її у цій відповіді. Якщо я знайду час, я спробую розширити цю відповідь.
Cornstalks

1
Не витрачайте занадто багато часу на її розширення. Наскільки я можу сказати, це не те, що ОП після цього. Його питання вимагає уважного читання. Його використання розбору лісу не ваше. - - Щодо похідних ... це здається, що це повинно бути цікаво, але треба пов'язувати це з попередньою роботою ... і є значна частина цього. Але я маю на увазі не цю відповідь, а статті M Might чи його блог.
babou

2

Далеко від ідеалу, але я бачив це вже не раз: у кожному вхідному рядку спробуйте проаналізувати. якщо не вдалося, збережіть рядок і додайте наступний. У псевдокоді:

buffer = ''
for each line from input:
    buffer = buffer + line
    if can parse buffer:
        emit tree
        buffer = ''

Велика проблема полягає в тому, що в деяких мовах ви не можете знати, чи вираз закінчений перед читанням наступного рядка. У такому випадку вам здається, що ви могли прочитати наступний і перевірити, чи це дійсний початок чи дійсне продовження ... Але для цього вам потрібен точний синтаксис мови

Гірше, що в цих мовах важко створити патологічний випадок, який не можна розібрати до кінця файлу, навіть якщо це не було жодного довгого твердження.


0

Коротко

Здається, що швидке рішення вашої проблеми полягає у визначенні REGEX, або FSA (автомат з обмеженим станом), який розпізнає всі можливі початки документів (помилкові позитиви дозволені, що фактично не відповідало б документу). Потім ви можете запустити його дуже швидко на своєму введенні, щоб визначити наступне місце, де документ може починатися з кількох помилок. Це може спричинити кілька помилкових позицій для запуску документа, але вони будуть розпізнані парсером і залишені.

Тож Finite State Automaton може бути назвою парсера, якого ви шукали. :)

Проблема

Завжди важко зрозуміти практичну проблему, особливо коли словниковий запас може мати багато тлумачень. Слово "розбір лісу" було придумано (afaik) для безтекстового розбору неоднозначних речень, які мають кілька дерев розбору. Це може бути дещо узагальнено для розбору решітки речень або до інших типів граматики. Звідси всі відповіді про Ерлі, GLR, Marpa та похідні аналізатори (є багато інших), які не були актуальними в даному випадку.

Але це, мабуть, не те, що ви маєте на увазі. Ви хочете проаналізувати унікальний рядок, який є послідовністю однозначних документів, і отримати дерево розбору для кожного , або якесь структуроване представлення, оскільки ви насправді не говорите, як визначається синтаксис ваших документів, де він стоїть формальна мовна точка зору. У вас є алгоритм та таблиці, які будуть виконувати завдання розбору при запуску на початку документа. Тож нехай так.

Справжня проблема полягає в тому, що ваш потік документів містить багато сміття, яке розділяє документи. І здається, що ваша складність полягає в тому, щоб сканувати це сміття досить швидко. Ваша поточна техніка полягає в тому, щоб почати спочатку, і спробувати сканувати з першого символу, і переходити до перезавантаження наступного символу, коли він не виходить, поки ви не скануєте весь документ. Потім ви повторюєте заяву від першого символу після того, як документ щойно просканований.

Це також рішення, запропоноване @amon у другій частині своєї відповіді .

Це може бути не дуже швидким рішенням (я не маю можливості перевірити), тому що навряд чи код аналізатора буде оптимізований, щоб дуже ефективно запуститись на початку документа. При звичайному використанні це робиться лише один раз, так що це не гаряча точка з точки зору оптимізації. Отже, ваше помірне щастя з цим рішенням не надто дивно.

Тож вам справді потрібен алгоритм, який дозволяє швидко знайти початок документа, який починається з маси сміття. І вам пощастило: такі алгоритми існують. І я впевнений, що ви це знаєте: це називається пошук REGEX.

Просте рішення

Що вам потрібно зробити, це проаналізувати специфікацію ваших документів, щоб знайти, як починаються ці документи. Я не можу точно сказати вам, як я не впевнений, як їх специфікація синтаксису організована. Можливо, всі вони починаються з якогось слова з кінцевого списку, можливо, змішаного з деякими розділовими знаками чи цифрами. Це вам потрібно перевірити.

Що вам потрібно зробити, це визначити автоматику з кінцевим станом (FSA) або рівнозначно для більшості програмістів регулярний вираз (REGEX), який може розпізнати перші кілька символів документа: чим більше, тим краще, але це не повинно бути дуже великий (тому що це може зайняти час і місце). Це можна зробити порівняно легко із специфікації ваших документів, і, ймовірно, це можна зробити автоматично за допомогою програми, яка зчитує специфікацію ваших документів.

Після того, як ви створили свій регулярний геть, ви можете запустити його у вхідному потоці, щоб дуже швидко перейти до початку першого (або наступного) документа, як описано нижче:

Я припускаю:
- docstartце регулярний вираз, який відповідає початку всіх документів,
- search(regex, stream)це функція, яка шукає streamпідрядку, яка відповідає regex. Коли він повертається, потік зменшується до підпотоку суфіксу, починаючи з початку першого підрядного збігу, або до порожнього потоку не знайдено відповідності.
- parse(stream)намагається проаналізувати документ з початку потоку (те, що від нього залишилося), і поверне дерево розбору в будь-якому форматі або не вдасться. Коли він повертається, потік зменшується до підпотоку суфіксу, починаючи з позиції, безпосередньо після закінчення проаналізованого документа. Він називає виняток, якщо аналіз не вдається.

forest = empty_forest
search(docstart, stream)
while stream is not empty:
  try:
    forest = forest + parse(stream)
  except
    remove first character from stream
  search(docstart, stream)

Зауважте, що видалення першого символу необхідно, щоб наступний пошук знову не знайшов ту саму відповідність.

Звичайно, вкорочення потоку - це зображення. Це може бути просто індекс на потоці.

Остаточне зауваження полягає в тому, що ваш регулярний вираз не повинен бути надто точним, якщо він розпізнає всі початки. Якщо він час від часу розпізнає рядок, який не може бути початком документа (помилково позитивний), то єдиним штрафом є ​​вартість одного марного дзвінка на аналізатор.

Тож це, можливо, допоможе спростити регулярний вираз, якщо це корисно.

Про можливість швидшого вирішення

Вищеописане рішення має працювати досить добре у більшості випадків. Однак якщо у вас дійсно багато сміття та терабайт файлів для обробки, можуть бути інші алгоритми, які працюють швидше.

Ідея походить від алгоритму пошуку рядків Боєра-Мура . Цей алгоритм може надзвичайно швидко шукати потік для однієї струни, оскільки використовує структурний аналіз рядка, щоб пропустити читання більшої частини потоку, перестрибуючи фрагменти, навіть не дивлячись на них. Це найшвидший алгоритм пошуку для однієї струни.

Трудність полягає в тому, що його адаптація до пошукового регулярного виразу, а не однієї струни, здається дуже делікатною і може працювати не так добре, залежно від особливостей регексу, який ви розглядаєте. Це, в свою чергу, може залежати від синтаксису документів, які ви розбираєте. Але не дуже довіряйте мені з цього приводу, оскільки я не встиг уважно прочитати знайдені документи.

Я залишаю вас з одним або двома вказівниками, які я знайшов в Інтернеті, включаючи той, який, мабуть, є реферованим науково-дослідним документом , але ви повинні вважати це більш спекулятивним, можливо, дослідницьким, яке слід розглядати лише у тому випадку, якщо у вас виникли сильні проблеми з роботою. І, мабуть, немає програми полиць, яка б це зробила.


-2

Те, що ви описуєте, можна охарактеризувати як SAX vs. SOM.

SAX - (Simple API для XML) - API аналізатора послідовного доступу до подій, розроблений списком розсилки XML-DEV для документів XML.

SOM - (Об'єктна модель XML-схеми) випадковий доступ до представлення пам'яті XML-файлу в пам'яті

Є реалізація обох типів у C # та Java та, ймовірно, ще багато інших. Зазвичай XSD або DTD не є обов'язковим.

Радість SAX полягає в тому, що він має низькі накладні витрати, що чудово підходить для великих XML-файлів. Компроміс полягає в тому, що випадковий доступ із використанням SAX або не існує, або повільний, а гірший час розробки зазвичай значно більший, ніж для SOM. Очевидною проблемою для SOM є потенційно великі вимоги ОЗУ.

Ця відповідь застосовується не для всіх платформ та всіх мов.


1
Чому, на вашу думку, ОП розбирає XML?
Ден Пішельман

1
Це не дає відповіді на запитання.

@Snowman На сьогодні майже нічого не відповідав на питання, включаючи першу половину прийнятої відповіді. Немає сенсу брати когось. Питання потребує уважного читання.
babou

@babou Я нікого не вибирав, я пояснював свою голову.

@Snowman пояснює мою протилежну заяву . Це справедливо, і я б хотів, щоб більше користувачів зробили це. Я не є носієм мови: підбирати його може бути занадто сильним виразом. Просто всі роблять необґрунтовані припущення. Тож це навіть не варто помічати. Це правда, що це здається трохи більше, ніж інші.
babou
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.