Для меня это довольно обычное дело. Я хочу вернуть только соответствующую часть последовательности в метке инициализации. В этом случае просто AssignmentExpression.
pattern:Pattern init:(_ "=" _ a:AssignmentExpression {return a})?
Я рекомендую вам добавить выражение @, которое вернет только следующее выражение из последовательности. Таким образом, приведенный выше пример будет выглядеть так:
pattern:Pattern init:(_ "=" _ @AssignmentExpression)?
Связанные вопросы:
Согласен, это обычная проблема. Но я не уверен, стоит ли это решать. Другими словами, я не уверен, происходит ли это достаточно часто и причиняет ли это достаточно боли, чтобы оправдать добавление сложности 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, почему в описании было разделено первый и второй случай? Должны быть выполнены только два простых правила:
::
(честно говоря, мне не нравится выбор этого символа, слишком шумно) перед выражениями приводит к тому, что из sequence
узловых элементов, отмеченных этим символом, возвращаютсяПримеры:
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 исследует пространство дизайна. Это хорошо, особенно если это публично, и у нас есть шанс не согласиться :)
Главное не спрашивать «почему», а сказать «нет! Не так!»
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?
Привет, это еще одна из тех проблем, которые просто исчезают, если у нас есть es6, и нам нужны эти символы для других вещей, которые были добавлены в es6 с тех пор. Добавление операторов к тому, что вы уже можете делать, очень контрпродуктивно.
Этот билет объединен
pattern:Pattern init:(_ "=" _ <strong i="7">@a</strong>:AssignmentExpression)?
То же самое в es6, которое каждый поймет по своей сути и которое предоставляется бесплатно, когда другие части синтаксического анализатора завершены, это
pattern:Pattern init:(_ "=" _ a:AssignmentExpression)? => a
Проблематично, что при тестировании эта реализация pluck кажется ошибочной, и, конечно же, она помечена как закрытая, потому что это исправлено в ветке, которая никогда не будет выпущена.
Пожалуйста, повторно откройте эту проблему, @futagoza , пока она не будет исправлена в выпущенной версии.
Самый полезный комментарий
https://www.npmjs.com/package/pegjs/v/0.11.0-dev.325