Pegjs: Предоставьте краткий способ указать одно возвращаемое значение из последовательности

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

Для меня это довольно обычное дело. Я хочу вернуть только соответствующую часть последовательности в метке инициализации. В этом случае просто AssignmentExpression.

pattern:Pattern init:(_ "=" _ a:AssignmentExpression {return a})?

Я рекомендую вам добавить выражение @, которое вернет только следующее выражение из последовательности. Таким образом, приведенный выше пример будет выглядеть так:

pattern:Pattern init:(_ "=" _ @AssignmentExpression)?

Связанные вопросы:

  • # 427 Разрешить возвращать результат совпадения определенного выражения в правиле без действия
  • # 545 Простые расширения синтаксиса для сокращения грамматик

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

Было ли это уже выпущено в тег разработчика на npm?

https://www.npmjs.com/package/pegjs/v/0.11.0-dev.325

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

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

Я оставлю этот вопрос открытым и помечу его для рассмотрения после версии 1.0.0.

Согласовано. Очень хотелось бы увидеть это в 1.0. Однако я не в восторге от синтаксиса @. ИМХО, лучше было бы сделать это: если есть только одна метка «верхнего уровня», то неявно вернуть эту метку. Так что вместо:

rule = space* a:(text space+ otherText)+ newLine* { return a; }

Ты получаешь:

rule = space* a:(text space+ otherText)+ newLine*

И если на этикетке нет ничего особенно значимого, также разрешите это:

rule = space* :(text space+ otherText)+ newLine*

Так что вообще пропустите название ярлыка.

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

+1 за @ - это часто повторяется в моем коде.

+1 за краткий способ сделать это.

Похоже, эта боль похожа на многословный синтаксис function в JS, который ES6 решает с помощью стрелочных функций . Может быть, здесь можно было бы использовать что-то подобное? Что-то вроде:

rule = (space* a:(text space+ otherText) newLine*) => a

Мне кажется, что это довольно гибкий, все же явный (а ля @wildeyes вопрос ), и меньше похоже на добавление сложности, поскольку как в синтаксисе, так и в реализации он подчиняется базовому JS ...

Я представлял себе что-то вроде:

additive = left:multiplicative "+" right:additive {= left + right; }

Где = (не стесняйтесь обсуждать выбор символа) в качестве первого непробельного символа блока превращается в return .

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

Любые новости? Почему не additive = left:multiplicative "+" right:additive { => left + right } ?

Это, безусловно, было бы интуитивно понятно, учитывая, как сейчас работают стрелочные функции ( (left, right) => left + right ).

На самом деле в вашем файле parser.pegjs есть несколько примеров мест, которые можно было бы улучшить с помощью этой функции.

Например:

  = head:ActionExpression tail:(__ "/" __ ActionExpression)* {
      return tail.length > 0
        ? {
            type: "choice",
            alternatives: buildList(head, tail, 3),
            location: location()
          }
        : head;
    }

Это хрупкое из-за магического числа 3 в вызове buildList, которое не интуитивно связано с позицией вашего ActionExpression в последовательности. Сама функция buildList усложняется объединением двух разных операций. Используя выражение @ и синтаксис распространения es6, это становится чище:

  = head:ActionExpression tail:(__ "/" __ @ActionExpression)* {
      return tail.length > 0
        ? {
            type: "choice",
            alternatives: [head, ...tail],
            location: location()
          }
        : head;
    }

Поскольку это в значительной степени синтаксический сахар, я смог добавить эту функцию в ваш синтаксический анализатор, просто изменив ActionExpression в parser.pegjs

  = ExtractSequenceExpression
  / expression:SequenceExpression code:(__ CodeBlock)? {
      return code !== null
        ? {
            type: "action",
            expression: expression,
            code: code[1],
            location: location()
          }
        : expression;
    }

ExtractExpression
  = "@" __ expression:PrefixedExpression {
      return {
        type: "labeled",
        label: "value",
        expression: expression,
        location: location()
      };
    }

ExtractSequenceExpression
  = head:(__ PrefixedExpression)* _ extract:ExtractExpression tail:(__ PrefixedExpression)* {
      return {
        type: "action",
        expression: {
          type: "sequence",
          elements: extractList(head, 1).concat(extract, extractList(tail, 1)),
          location: location()
        },
        code: "return value;",
        location: location()
      }
    }

Я проверил суть, которая показывает, как можно упростить parser.pegjs, используя нотацию @.

Функции extractOptional, extractList и buildList были полностью удалены, поскольку нотация @ упрощает извлечение желаемых значений из последовательности.

https://gist.github.com/krisnye/a6c2aac94ffc0e222754c52d69e44b83

@krisnye Вот как это выглядело бы, если бы синтаксического сахара было еще больше:

https://github.com/polkovnikov-ph/newpeg/blob/master/parse.np

Я думаю об использовании комбинации :: и # для этой синтаксической функции (см. Мое объяснение / причину в # 545 ):

  • :: оператор привязки
  • # оператор раскрытия
// this is imported into grammar
class List extends Array {
  constructor() { this.isList = true; }
}

// grammar
number = _ ::value+ _
value = ::int #(_ "," _ ::int)* { return new List(); }
int = $[0-9]+
_ = [ \t]*
  • В корневой последовательности правила :: вернет результат выражения в качестве результата правила.
  • Во вложенной последовательности :: вернет результат вложенного выражения в качестве результата последовательности.
  • Если используется более одного :: , результат отмеченных выражений будет возвращен в виде массива
  • Если # используется во вложенной последовательности, содержащей :: , результаты будут помещены в родительский массив.
  • Если в правиле есть блок кода вместе с :: / # , сначала выполните его, а затем используйте метод push

Следуя этим правилам, передача 09 , 55, 7 в сгенерированный синтаксический анализатор приведенного выше примера даст:

result = [
    isList: true
    0: "09"
    1: "55"
    2: "7"
]

result instanceof Array # true in ES2015+ enviroments
result instanceof List # true

В корневой последовательности правила :: вернет результат выражения как результат правила
Во вложенной последовательности :: вернет результат вложенного выражения в качестве результата последовательности.

Почему они разделены? Почему бы просто не " :: в последовательности сделать результат аргумента результатом последовательности"?

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

Это плохая идея. В этом случае лучше бы выручить. Нет смысла объединять значения нескольких типов в массив. (Это будет кортеж, но в JS они практически бесполезны.)

Если # используется в последовательности, результаты будут объединены в массив # с родительским массивом.

Это ужасная идея. Очевидно, это кладж для избавления от посторонних { return xs.push(x), xs } . Создание такого особого случая не имеет никакого смысла, потому что его можно решить обычным образом с помощью параметризованных правил. Для операторов не так много последовательностей, состоящих из одного символа, и мы не должны тратить их зря.

Если в правиле есть блок кода вместе с :: / #, сначала выполните его, а затем используйте метод push results.

Итак, f = ::"a" { return "b" } должно иметь результат ["a", "b"] ?

прохождение 09. 55: 7 для сгенерированного парсера из приведенного выше примера выдаст:

Нет, не упоминается ни . ни : . Я также не понимаю, как описанное поведение дало бы результат.

число = _ :: значение + _

Также не следует использовать _ . Он идет либо справа, либо слева от токена, сторона, выбранная один раз для грамматики. В случае, если он справа, главное правило также должно начинаться с _ и наоборот.

start = _ value
value = ::int #("," _ ::int)* { return new List(); }
number = ::$[0-9]+ _
_ = [ \t]*

Пример классов, реализованных в примере JavaScript (не предназначенный для реальной реализации):

ClassMethod
  = head:FunctionHead __ params:FunctionParameters __ body:FunctionBody {
      return {
        type: "method",
        name: head[ 1 ],
        modifiers: head[ 0 ],
        params: params,
        body: body
      };
    }

// `::` inside the zero_or_more "( ... )*" builds an array as we want,
// so this rule returns `[FunctionModifier[], Identifier]` as expected
MethodHead = (::MethodModifier __)* ("function" __)? ::Identifier

// https://github.com/tc39/proposal-class-fields#private-fields
MethodModifier
  = "#"
  / "static"
  / "async"

FunctionParameters
  = "(" __ head:FunctionParam tail:(__ "," __ ::FunctionParam)* __ ")" {
      // due to `::`, tail is `FunctionParam[]` instead of `[__, "", __, FunctionParam][]`
      return [ head ].concat( tail );
    }
    / "(" __ ")" { return []; }

FunctionParam
  = name:Identifier value:(__ "=" __ ::Expression)? {
      return { name, value };
    }

FunctionBody = "{" __ ::SourceElements? __ "}"

Почему они разделены? Почему бы просто :: в последовательности не сделать результат аргумента результатом последовательности "?

Разве это не одно и то же? Я только прояснил это, чтобы можно было легко понять результат различных вариантов использования, таких как MethodHead , FunctionParam и FunctionBody .

Нет смысла объединять значения нескольких типов в массив. (Это будет кортеж, но в JS они практически бесполезны.)

На мой взгляд, при создании AST (наиболее распространенный результат сгенерированного синтаксического анализатора) он упростил бы варианты использования, такие как MethodHead , вместо того, чтобы писать:

MethodHead
  = modifiers:(::MethodModifier __)* ("function" __)? name:Identifier {
      return [ modifiers, name ];
    }

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

Если # используется в последовательности, результаты будут объединены в массив # с родительским массивом.

Это ужасная идея. Очевидно, это кладж для избавления от посторонних {return xs.push (x), xs}

Это также помогает в более распространенных случаях использования, таких как FunctionParameters где было бы лучше написать:

// should always return `FunctionParam[]`
FunctionParameters
  = "(" __ ::FunctionParam #(__ "," __ ::FunctionParam)* __ ")"
  / "(" __ ")" { return []; }

это может быть решено общим способом с помощью параметризованных правил

Я думаю, что мне следует реализовать одиночные возвращаемые значения перед параметризованными правилами, поскольку я все еще не уверен, как действовать в дальнейшем (мне нравится использовать шаблоны, но использование rule < .., .. > = .. похоже, добавляет много шума в грамматики PEG.js, поэтому я пытаюсь подумать о небольшом изменении синтаксиса, чтобы он лучше подходил), но это отдельная проблема.

Для операторов не так много последовательностей, состоящих из одного символа, и мы не должны тратить их зря.

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

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

Итак, f = ::"a" { return "b" } должно иметь результат ["a", "b"] ?

Нет, поскольку синтаксический анализатор ожидает, что блок кода вернет объект, подобный массиву, который содержит метод push (так что в результате будет f = ::"a" { return [ "b" ] } возвращающее [ "b", "a" ] ), таким образом, позволяя передавать данные не только в массивы, но и в пользовательские узлы, в которых реализован один и тот же метод, для работы аналогичным образом. Учитывая, что это был ваш первый ход мыслей после прочтения этого, было бы лучше понять, стоит ли это за вариантом вроде pushSingleReturns ? Если этот параметр - false (по умолчанию), наличие блока кода после последовательности, содержащей одиночные возвращаемые значения, вызовет ошибку.

прохождение 09. 55: 7 для сгенерированного парсера из приведенного выше примера выдаст:

Нет, не упоминается ни . ни : .

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

Также не следует использовать _ . Он идет либо справа, либо слева от токена, сторона, выбранная один раз для грамматики. В случае, если он справа, главное правило также должно начинаться с _ и наоборот.

Я думаю, что на самом деле это просто вопрос предпочтений: smile:, хотя я думаю, что было бы легче понять этот пример:

number = _ ::value+ EOS
...
EOS = !.

Я также не понимаю, как описанное поведение дало бы результат.

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

Я согласен с @ polkovnikov-ph, что предлагаемые изменения местами крайне неочевидны и будут лишь источником дополнительных ошибок.

Если # используется в последовательности, результаты будут объединены в массив # с родительским массивом.

Что нужно вернуть в грамматике start = #('a') ? Насколько я понимаю, он думал, как синтаксис сахар для сглаживания массивов? Не думаю, что этот оператор нужен. Для наиболее очевидного использования - выражения списков элементов с разделителями - лучше использовать специальный синтаксис (см. № 30 и мою вилку, https://github.com/Mingun/pegjs/commit/db4b2b102982a53dbed1f579477c85c06f8b92e6).

Если в правиле есть блок кода вместе с :: / #, сначала выполните его, а затем используйте метод push results.

Крайне неочевидное поведение. Действия в источнике грамматики располагаются после выражения и обычно вызываются после синтаксического анализа выражения. И вдруг как-то они начинают вызываться перед синтаксическим анализом. Как должны вести себя ярлыки?

Оставшиеся 3 пункта вам удалось описать так, что их ясный смысл начал ускользать. Насколько правильно заметил @ polkovnikov-ph, почему в описании было разделено первый и второй случай? Должны быть выполнены только два простых правила:

  1. :: (честно говоря, мне не нравится выбор этого символа, слишком шумно) перед выражениями приводит к тому, что из sequence узловых элементов, отмеченных этим символом, возвращаются
  2. Если в последовательности только один такой элемент, возвращается его результат, иначе возвращается массив результатов

Примеры:

start =   'a' 'b'   'c'; // => ['a', 'b', 'c']
start = ::'a' 'b'   'c'; // => 'a'
start = ::'a' 'b' ::'c'; // => ['a', 'c']

Большой пример описывает именно то, что я ожидал увидеть от :: и не описывает сомнительные варианты использования ( # , несколько :: ).

Разве это не одно и то же?

Это то, о чем я спрашивал. Описание в общем виде обычно более полезно, потому что оно дает читателю уверенность в том, что это действительно одно и то же. Спасибо за разъяснения. :)

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

Но почему бы вместо этого не создать объект? В нотации есть modifiers: и name: , оставьте их как есть в результирующем объекте JS, и это будет круто.

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

Изначально я собирался об этом написать, но потом решил, что у меня недостаточно веских аргументов. Я бы предпочел вообще не допускать нескольких :: на одном уровне последовательности (даже с флагом).

Это также помогает в более распространенных случаях использования, таких как FunctionParameters, где было бы лучше написать:

Но это то же самое. Действительно хороший синтаксис был бы inter(FunctionParam, "," __) , с

inter a b = x:a xs:(b ::a)* { return xs.unshift(x), xs; }

rule <.., ..> = .. кажется, добавляет много шума в грамматику PEG.js

Люди ожидают, что <...> будет использоваться для типов, тогда как в этом случае аргументы не являются типами. Лучший способ - это способ Haskell вообще без лишних символов (см. inter выше). Я не уверен, как это взаимодействует в грамматике PEG.js с опущенным ; . Возможен случай, когда f a b = ... (частично) включается в предыдущую строку.

используется в качестве оператора раскрытия на некоторых языках, реализующих директивы препроцессора.

Да, но я бы предпочел использовать его с умом. Вместо предлагаемого действия push для массивов я бы использовал его как действие Object.assign для объектов или даже как вещь, которая соответствует пустой строке ( eps ), но возвращает свой аргумент. Так что, например,

f = type:#"ident" name:$([a-z]i [a-z0-9_]i+)

вернет {type: "ident", name: "abc"} для ввода "abc" .

Нет, поскольку синтаксический анализатор ожидает, что блок кода вернет объект, подобный массиву, который содержит метод push (так что это будет f = :: "a" {return ["b"]}, возвращающее ["b", " a "] как результат),

О_О

Я думаю, что это просто вопрос предпочтений.

Не только это, но и производительность. Если каждый токен имеет _ с обеих сторон, последовательности пробельных символов соответствуют только завершающим, а предшествующие _ соответствуют ничему. Дополнительные звонки на parse$_ занимают немного времени. Также код длиннее, потому что в нем вдвое больше _ s.

@Mingun Я думаю, что @futagoza исследует пространство дизайна. Это хорошо, особенно если это публично, и у нас есть шанс не согласиться :)

Главное не спрашивать «почему», а сказать «нет! Не так!»

image

f = type:#"ident" name:$([a-z]i [a-z0-9_]i+) вернет {type: "ident", name: "abc"} для ввода "abc" .

Пожалуйста, нет, ха-ха. Этот синтаксис _wayyy_ слишком волшебный.

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

sequence
    = first:element rest:(whitespace next:element {return next;})*
    {
        return [first].concat(rest);
    }
    ;

_Такая_ боль печатать снова и снова, особенно когда они более сложные, чем это.

Однако с оператором @ приведенное выше становится просто:

sequence = first:element rest:(whitespace @element)* { return [first].concat(rest); };

и либо https://github.com/pegjs/pegjs/issues/235#issuecomment -66915879, либо https://github.com/pegjs/pegjs/issues/235#issuecomment -67544080, который дополнительно сокращается до:

sequence = first:element rest:(whitespace @element)* => [first].concat(rest);
/* or */
sequence = first:element rest:(whitespace @element)* {=[first].concat(rest)};

... первый из которых я очень неравнодушен.

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

На самом деле, если я не ошибаюсь, это может быть просто незначительный удар. Может быть, есть над чем подумать для 0.11.0 @futagoza.

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

Я начал реализовывать это самостоятельно некоторое время назад, но отказался от него до сих пор (когда я должен был сделать # 579 вместо этого 😆) и основал генератор байт-кода на реализации Mingun (https://github.com/Mingun/pegjs/commit/1c1c852bae91868eaa90d9bd9f7e4f722aa6435e )

Вы можете попробовать здесь: https://pegjs.org/development/try (онлайн-редактор, но с использованием PEG 0.11.0-dev)

Святое время, денщик. Отличная работа dev на npm? Я бы с удовольствием начал с него тестирование.

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

foo
    = '"' @$bar '"'
    ;

bar
    = [abcd]*
    ;

Было ли это уже выпущено в тег разработчика на npm?

https://www.npmjs.com/package/pegjs/v/0.11.0-dev.325

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

Этот билет объединен

pattern:Pattern init:(_ "=" _ <strong i="7">@a</strong>:AssignmentExpression)?

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

pattern:Pattern init:(_ "=" _ a:AssignmentExpression)? => a

Проблематично, что при тестировании эта реализация pluck кажется ошибочной, и, конечно же, она помечена как закрытая, потому что это исправлено в ветке, которая никогда не будет выпущена.

Пожалуйста, повторно откройте эту проблему, @futagoza , пока она не будет исправлена ​​в выпущенной версии.

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