Pegjs: Возможность игнорировать определенные постановки

Созданный на 8 окт. 2010  ·  29Комментарии  ·  Источник: pegjs/pegjs

Было бы неплохо иметь возможность указать лексеру / синтаксическому анализатору игнорировать определенные продукты (например, пробелы и комментарии), чтобы не было необходимости засорять все другие продукты допусками на комментарии / пробелы. Однако это может быть невозможно из-за того, что лексирование встроено в синтаксический анализ?

Спасибо

Самый полезный комментарий

@atesgoral - Я выручил. Мне не нужен был «настоящий парсер» - мне нужно было только изолировать определенные именованные элементы в целевом файле.

Так что я сделал то, что сделал бы любой слабак, - использовал регулярные выражения. (А потом у меня было две проблемы :-)

Но это помогло, и я смог перейти к следующему испытанию. Удачи в вашем проекте!

Все 29 Комментарий

Согласовано. Есть ли чистый способ сделать это на данный момент?

@benekastah : На данный момент нет чистого пути.

Это было бы сложно сделать, не изменив принцип работы PEG.js. Возможные решения включают:

  1. Разрешить добавить лексер перед сгенерированным парсером.
  2. Вставьте информацию об исходных правилах где-нибудь в грамматике. Это, вероятно, также означало бы различие между лексическим и синтаксическим уровнями грамматики - чего я бы хотел избежать.

Я не буду сейчас над этим работать, но в этой функции есть над чем подумать.

Мне нужна эта функция.

Может быть, вы могли бы ввести «пропускающий» токен. Поэтому, если правило возвращает этот токен, он будет проигнорирован и не получит узла в AST (он же запись в массиве).

Я тоже ищу способ сделать это.

У меня есть большой файл грамматики (он анализирует формат ASN.1 для файлов SNMP MIB). Я не писал его, но я тривиально преобразовал его из исходной формы, чтобы создать синтаксический анализатор в PEG.js. (Это хорошо. На самом деле, это очень здорово, что мне потребовалось менее 15 минут, чтобы настроить его так, чтобы PEG.js его принял.)

К сожалению, грамматика была написана с возможностью просто игнорировать пробелы и комментарии, когда они встречаются. Следовательно, никакие настоящие файлы MIB не могут быть обработаны, потому что синтаксический анализатор останавливается при первом появлении пробела.

Я не стремлюсь разбираться в грамматике, чтобы можно было вставить все правильные пробелы во все правила (их около 126 ...). Есть ли другой способ сделать это?

NB: В случае, если мне придется изменить грамматику вручную, я попросил помощи с некоторыми вопросами в тикете в списке групп Google. http://groups.google.com/group/pegjs/browse_thread/thread/568b629f093983b7

Большое спасибо!

Спасибо ребятам из групп Google. Думаю, у меня достаточно информации, чтобы делать то, что я хочу.

Но я действительно с нетерпением жду возможности в PEG.js отмечать пробелы / комментарии как что-то, что нужно полностью игнорировать, чтобы мне не пришлось тратить несколько часов на изменение чистой грамматики ... Спасибо!

Богатый

Я согласен с утверждением, что pegjs нужна возможность пропускать токены. Я могу изучить это, поскольку, если вы хотите написать серьезную грамматику, вы сойдете с ума, если поставите ws между каждым токеном.

Поскольку сгенерированные парсеры являются модульными. В качестве обходного пути создайте упрощенный лексер и используйте его вывод в качестве входных данных для реального, например:

elideWS.pegjs:

s = ввод: (whitespaceCharacter / textCharacter) *
{
var result = "";

for (var i = 0; i <input.length; i ++) result + = input [i];
вернуть результат;
}

whitespaceCharacter = [nt] {возврат ""; }
textCharacter = c: [^ nt] {return c; }

но это вызывает проблемы, когда пробел является разделителем - например, для идентификаторов

С этой проблемой сталкиваюсь довольно часто.
Но написать хороший лексер непросто (вы можете в конечном итоге продублировать хороший фрагмент исходной грамматики, чтобы получить согласованный лексер).

Я думал о том, чтобы определить правила пропуска, которые можно использовать в качестве альтернативы, когда нет совпадений. Однако это вводит необходимость в неразрывном классе. Пример с arithmetics.pegjs с использованием чисел с плавающей запятой

  = Term (("+" / "-") Term)*

Term
  = Factor (("*" / "/") Factor)*

Factor
  = "(" Expression ")"
  / Float

Float "float"
  = "-"? # [0-9]+ # ("." # [0-9]+) // # means that skip rules cannot match

// skip rule marked by "!="
// skip rules cannot match the empty string
_ "whitespace"
  != [ \t\n\r]+

Все еще перевариваю это. Есть отзывы? Может быть, это очень глупая идея.

Так что разница в том, что вы хотите различать, когда двигатель в целом
работает в режиме лексера (пробелы значимы), а когда нет (пробелы
игнорируется).

Есть ли случай, когда вы хотите не игнорировать пробелы в режиме лексера?
В качестве опции? Или, наоборот, когда не внутри регулярного выражения? Я думаю нет.

Будет ли следующее эквивалентно?

Плавать
"-? [0-9] + (". "[0-9] +)"

или иным образом расширить привязку для обработки типичного регулярного выражения напрямую и вне
пробел в строке в кавычках (которая включает регулярные выражения) игнорируется.

19 апреля 2014 г. в 15:22 Андрей Некулау [email protected] написал:

С этой проблемой сталкиваюсь довольно часто.
Но написать хороший лексер непросто (вы можете в конечном итоге продублировать хороший фрагмент исходной грамматики, чтобы получить согласованный лексер).

Я думал о том, чтобы определить правила пропуска, которые можно использовать в качестве альтернативы, когда нет совпадений. Однако это вводит необходимость в неразрывном классе. Пример с arithmetics.pegjs с использованием Floats

Выражение
= Срок (("+" / "-") Срок) *

Срок
= Фактор (("_" / "/") Фактор) _

Фактор
= "(" Выражение ")"
/ Плавать

Поплавок «поплавок»
знак равно # [0-9] + # ("." # [0-9] +) // # означает, что правила пропуска не могут соответствовать

// пропустить правило, отмеченное "! ="
// правила пропуска не могут соответствовать пустой строке
_ "пробел"
! = [tnr] +
Все еще перевариваю это. Есть отзывы? Может быть, это очень глупая идея.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

@waTeim Собственно нет.

Традиционно процесс синтаксического анализа делится на лексирование и синтаксический анализ. Во время лексирования важен каждый символ, включая пробелы. Но затем они превращаются в токен «сброса». Анализатор, переходя к следующему токену, затем отбрасывает все токены сброса. Важная часть состоит в том, что вы можете отбросить все, что угодно, а не только пробелы. Это именно то, что описывает @andreineculau .

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

23 апреля 2014 г. в 14:54 Шон Фаррелл [email protected] написал:

@waTeim Собственно нет.

Так что согласны. Достаточно традиционного подхода. Нет необходимости иметь
часть строго синтаксического анализатора распознает существование отброшенных токенов, и есть
нет причин заставлять часть лексера вести себя условно (контекстно-зависимым образом)
по распознаванию токенов.

Следовательно, нет необходимости иметь элементы склеивания (например, '#') в языке.
потому что достаточно, чтобы

1) токены могут быть созданы исключительно из регулярного выражения и не зависят от контекста.
2) жетоны могут быть помечены как выбрасываемые без исключения.

Традиционно процесс синтаксического анализа делится на лексирование и синтаксический анализ. Во время лексирования важен каждый символ, включая пробелы. Но затем они превращаются в токен «сброса». Анализатор, переходя к следующему токену, затем отбрасывает все токены сброса. Важная часть состоит в том, что вы можете отбросить все, что угодно, а не только пробелы. Это именно то, что описывает @andreineculau .

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

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

Хорошо, тогда я тебя неправильно понял. Могут быть случаи для состояний лексера, но это совершенно другое требование, и IMHO выходит за рамки peg.js.

@waTeim @rioki Забудьте немного о моем предложении.

Руки, возьми это правило . Если вы хотите упростить грамматику правила, убрав *WS , то как бы вы проинструктировали PEGjs не разрешать *WS между field_name и : ?

@andreineculau Поскольку ваша грамматика чувствительна к expr = WS lit WS op WS expr WS ";" для каждого правила. Вы только представьте себе грамматику, подобную грамматике для C, с обработкой whitepsace?

Я понимаю, что перенести правила сброса в pegjs непросто, но это не значит, что это не достойная похвалы цель.

О боже, раздел бесплатных ответов! Мне есть что сказать, извините за длину.

1) Для людей TL; DR, если бы я мог добавить какие-либо элементы привязки, которые я хотел, я бы написал это так

header_field
= имя_поля ":" значение_поля

пробел (ИГНОРИРОВАТЬ)
= [t] +

Дополнением, которое я бы сделал, является раздел опций, который может быть включен в любую постановку.

Язык http-bis не будет ограничен этой перезаписью (см. Приложение а).

2) Моя проблема с предложенным #

Такое ощущение, что вы обмениваетесь, требуя, чтобы пользователь заполнил определение парсера кучей
отбрасывать нетерминалы (обычно пробелы / разделители), требуя от пользователя заполнения
определение парсера с набором метасимволов «здесь символы не отбрасываются»
без надобности. По общему признанию, таких случаев было бы меньше. Это тот редкий случай, когда
люди действительно потребляют разделители и что-то с ними делают, и, как я комментирую в

Приложение a, HTTP-bis не является одним из таких случаев, просто плохо задокументирован.

3) Пользовательские состояния парсера

Но я вижу, как было бы проще определителю парсера просто вырезать и вставить
спецификация языка из определения, поэтому, если у вас должно быть что-то подобное, тогда
это может быть сделано с помощью лексических состояний, на которые ранее ссылался Шон. Я думаю, что сделаю это
следующим образом.

production1 (состояние == 1)
= вещи

production2 (состояние == 2)
= вещи

производство3
= материал {состояние = 1}

производство4
= материал {состояние = 2}

Другими словами, подобно тому, как lex / yacc делает возможным, чтобы продукция была доступна только

если система находится в определенном состоянии, и позволить пользователю установить это значение состояния.

4) Дополнительные параметры

Или вы можете упростить задачу для пользователя и сделать ее более очевидной для читателя с помощью другого
вариант

производство (DONTIGNORE)
= вещи

Это позволит синтаксическому анализатору переопределить действие по умолчанию - отбрасывать маркеры, отмеченные
как сброс, но только для этого производства. Это действительно то же самое, что и 3, только проще
читать. Это менее гибко, чем предложение #, потому что либо производство полностью игнорируется

или не игнорировать, но я не думаю, что нужна дополнительная гибкость.

5) Добавление параметра в getNextToken () позволяет учитывать контекстную чувствительность

Я думаю, все сводится к следующему (я делаю здесь некоторые предположения) в настоящее время
часть парсера вызывает getNextToken (input), и вместо этого нужно добавить

параметр к нему getNextToken (ввод, параметры).

Приложение а) Спецификация HTTP-bis

Хорошо, я прочитал кое-что, но еще не прочитал все это

Протокол передачи гипертекста (HTTP / 1.1): синтаксис сообщений и маршрутизация
черновик-ietf-httpbis-p1-сообщения-26

Мне не нравится, как они определили свою грамматику. Я не предлагаю менять ввод
принимает, но я бы не определил это так, как они. В частности, мне не нравится, почему у них
определены OWS, RWS и BWS, которые все приравниваются к одной и той же строке символов
но в разных контекстах. Они определили

OWS :: == (SP | HTAB) *
RWS :: == (SP | HTAB) +
BWS :: == OWS

это просто повторение табуляции и пробелов

Без уважительной причины. Они усложнили синтаксический анализ языка - требуется лексический анализатор
чтобы отследить его контекст - и им не нужно было этого делать.

Они определили OWS как «необязательный пробел», BWS как «недопустимый пробел» или иным образом необязательный.
пробел, но в «плохом» контексте - где это не обязательно - и RWS требовал пробелов, где он
необходимо для разграничения токенов. Этот пробел нигде не используется, за исключением, возможно, парсера.
предупреждение, если он соответствует BWS («обнаружены ненужные конечные пробелы» или что-то в этом роде), что является всем
разделители все равно делать.

В их спецификации единственное место, где используется RWS, - это здесь

Via = 1 # (получено - протокол RWS получен [комментарий RWS])

 received-protocol = [ protocol-name "/" ] protocol-version
                     ; see Section 6.7
 received-by       = ( uri-host [ ":" port ] ) / pseudonym
 pseudonym         = token

но «протокол-версия» - это числа и, возможно, буквы, а «полученный» - это цифры и буквы. Другими словами,
лексический анализатор не сможет правильно распознать эти две части, если они не разделены пробелами
и это будет синтаксическая ошибка с явным указанием RWS или без него, если нет хотя бы 1
пробельный символ. Так что просто удалите RWS из производства и обработайте пробелы
везде в качестве разделителя, и это не меняет язык, а только то, как это задокументировано.

24 апреля 2014 г. в 13:23 Андрей Некулау [email protected] написал:

@waTeim @rioki Забудьте немного о моем предложении.

Руки, возьми это правило. Если вы хотите упростить грамматику правила, убрав OWS, то как бы вы проинструктировали PEGjs не разрешать OWS между field_name и:?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

@waTeim Я думаю, вы

Я никогда не видел правильного использования состояний лексера, вытекающих из синтаксического анализатора. Основная проблема здесь в том, что при одном взгляде вперед, когда синтаксический анализатор видит токен для переключения состояний, лексер уже ошибочно лексирует следующий токен. То, что вы предлагаете, практически невозможно реализовать без обратного отслеживания, и это никогда не бывает хорошей функцией в парсере.

При написании грамматики вы в основном определяете, какие произведения считаются проанализированными, а какие можно проглотить. В примере @andreineculau есть два варианта: либо вы обрабатываете пробелы в анализаторе, либо определяете завершающую часть токена ":". ( [a-zA-Z0-9!#$%&'+-.^_|~]+ ":" ).

Я мог бы предложить превратить проблему в определение белого списка - какие части я хочу захватить и преобразовать - вместо черного списка. Хотя пробелы - одна из проблем текущей системы захвата, вложенность правил - другая. Как я писал в выпуске №66, система LPeg, указывающая, что вы хотите захватить напрямую, с помощью преобразований или захвата строк, кажется мне более полезной, чем указание нескольких постановок, которые нужно пропустить и по-прежнему иметь дело с вложением любого другого продукта.

См. Мой комментарий в выпуске №66, где приведен простой пример сравнения LPeg с PEG.js в отношении захвата. Несмотря на то, что имена немного загадочные см Позволяют снимать раздел документации LPeg для различных способов , которыми вы можете захватить или преобразующие данную продукцию (или ее часть).

Здравствуйте, я создал сниппет, чтобы игнорировать некоторые общие случаи: null , undefined и строки только с пробелами.
Это может потребоваться в заголовке файла грамматики, например:

{
  var strip = require('./strip-ast');
}

Два способа улучшить это:

  • Настраиваемый фильтр для терминов - чтобы игнорировать конкретные термины, требующие определенной грамматики.
  • Пропускать вложенные пустые массивы - это можно сделать на втором этапе после strip , это удалит «пирамиды» вложенных пустых массивов.
    Если кому интересно, мы можем обновить его до пакета.

@ richb-hanover Куда привели ваши усилия по синтаксическому анализатору определений ASN.1?

@atesgoral - Я выручил. Мне не нужен был «настоящий парсер» - мне нужно было только изолировать определенные именованные элементы в целевом файле.

Так что я сделал то, что сделал бы любой слабак, - использовал регулярные выражения. (А потом у меня было две проблемы :-)

Но это помогло, и я смог перейти к следующему испытанию. Удачи в вашем проекте!

Посмотрев на шевротейн и его опцию пропуска , очень хочется чего-то подобного.

Слишком часто мы пишем что-то вроде этого:

Pattern = head:PatternPart tail:( WS "," WS PatternPart )*
{
  return {
    type: 'pattern',
    elements: buildList( head, tail, 3 )
  };
}

Было бы здорово, если бы мы могли написать это вместо этого:

WS "whitespace" = [ \t\n\r] { return '@<strong i="11">@skipped</strong>' }

IgnoredComma = "," { return '@<strong i="12">@skipped</strong>' }

Pattern = head:PatternPart tail:( WS IgnoredComma WS PatternPart )*
{
  return {
    type: 'pattern',
    elements: [head].concat(tail)
  };
}

@ richb-hanover и всех, кто попал сюда в поисках аналогичной потребности, я тоже написал свои собственные парсеры: https://www.npmjs.com/package/asn1exp и https: //www.npmjs. com / пакет / asn1-tree

Пропуск будет относительно легко реализовать, используя es6 symbol , или, может быть, более надежно, передав синтаксическому анализатору предикат во время синтаксического анализа (я предпочитаю последний вариант)

Просто на это тоже наткнулся.
Ничего не зная о внутренностях PEG.js, позвольте мне бросить туда кость ...

Когда мы пишем правило, в конце мы можем добавить блок возврата.
В этом блоке мы можем вызывать такие вещи, как text() и location() . Это внутренние функции.

Где-то в коде возвращаемое значение этого блока попадает в выходной поток.

Итак, что нужно изменить в PEG.js, если я хочу пропустить значение, возвращаемое правилом, если это значение является результатом вызова локальной функции skip ?

например, comment = "//" space ([^\n])* newline { return skip() }

Как упоминалось выше, skip () может вернуть символ, который затем где-то проверяется кодом и удаляется.
Что-то вроде того, что сказал лжаки, но внутри библиотеки

Я не понимаю вашего вопроса. Вы ищете способ нарушить правило при определенных обстоятельствах? Используйте &{...} или !{...} . В противном случае просто не используйте возвращаемое значение правила comment :

seq = comment r:another_rule { return r; };
choice = (comment / another_rule) { <you need to decide what to return instead of "comment" result> };

Если это кому-то помогает, я игнорирую пробелы, так как правило верхнего уровня фильтрует массив результатов.

Пример:

    = prog:expression+ {return prog.filter(a => a)}

expression
    = float
    / number
    / whitespace

float
    = digits:(number"."number) {return parseFloat(digits.join(""),10)}

number 
    = digits:digit+ {return parseInt(digits.join(""),10)}

digit 
    = [0-9]

whitespace
    = [ \t\r\n] {return undefined}

Это с радостью проанализирует ввод, сохраняя пробелы вне массива результатов.
Это также будет работать для таких вещей, как комментарии, просто пусть правило возвращает undefined, а правило верхнего уровня отфильтрует его.

Это работает только для постановок высшего уровня. Вам нужно вручную отфильтровать каждого родителя, который может содержать фильтруемого дочернего элемента.

@StoneCypher Верно, это требует некоторой работы на высшем уровне, но у меня это работает, и я думаю, что пока гамма не слишком сложна, можно обойтись фильтром верхнего уровня.

Помимо этого, все, о чем я могу думать, - это иметь функцию верхнего уровня, которая фильтрует пробелы из ввода и пропускает через нее каждое совпадение. Конечно, медленнее и требует намного больше вызовов, но легко, если вы (как и я) передаете все в генератор токенов. Вы можете вызвать функцию фильтрации из того места, где вы генерируете токены, и вам нужно только беспокоиться о создании ваших токенов, а пробелы более или менее автоматически фильтруются

Одна из вещей, которые мне понравились в текущем HEAD pegjs, - это его (недокументированная) поддержка выбора полей без необходимости создавать метки и выполнять операторы возврата. Это выглядит так:

foo = <strong i="6">@bar</strong> _ <strong i="7">@baz</strong>
bar = $"bar"i
baz = $"baz"i
_ = " "*
parse('barbaz') // returns [ 'bar', 'baz' ]

Я чувствую, что это дает хороший, чистый, явный синтаксис для этого варианта использования и множества других.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги