ПЭГ для вдавливания стиля Python
-
25-09-2019 - |
Вопрос
Как бы вы написали Разборная экспрессия грамматика в любом из следующих генераторов парсеров (Peg.js., Цитрусовые, Treetop) который может обрабатывать Python / Haskell / CoffeScript стиль вдавливания:
Примеры неиженадельного языка программирования:
square x =
x * x
cube x =
x * square x
fib n =
if n <= 1
0
else
fib(n - 2) + fib(n - 1) # some cheating allowed here with brackets
Обновлять:Не пытайтесь написать интерпретатор для примеров выше. Я заинтересован только в проблеме отступа. Другой пример может быть расставлен следующим образом:
foo
bar = 1
baz = 2
tap
zap = 3
# should yield (ruby style hashmap):
# {:foo => { :bar => 1, :baz => 2}, :tap => { :zap => 3 } }
Решение
Чистый колыш не может разбирать углубление.
Но peg.js. могу.
Я сделал быстроразъемный эксперимент (вдохновленный комментарию IRA Baxter о обмане) и написал простой токенизатор.
Для более полного решения (полный парсер), пожалуйста, смотрите этот вопрос: Уровень вдавливания разбора с peg.js
/* Initializations */
{
function start(first, tail) {
var done = [first[1]];
for (var i = 0; i < tail.length; i++) {
done = done.concat(tail[i][1][0])
done.push(tail[i][1][1]);
}
return done;
}
var depths = [0];
function indent(s) {
var depth = s.length;
if (depth == depths[0]) return [];
if (depth > depths[0]) {
depths.unshift(depth);
return ["INDENT"];
}
var dents = [];
while (depth < depths[0]) {
depths.shift();
dents.push("DEDENT");
}
if (depth != depths[0]) dents.push("BADDENT");
return dents;
}
}
/* The real grammar */
start = first:line tail:(newline line)* newline? { return start(first, tail) }
line = depth:indent s:text { return [depth, s] }
indent = s:" "* { return indent(s) }
text = c:[^\n]* { return c.join("") }
newline = "\n" {}
depths
это стопка отступов. Отступ () возвращает массив токенов вдавливания и начать () разворачивать массив, чтобы максимальный парсер ведут себя как поток.
peg.js. производит для текста:
alpha
beta
gamma
delta
epsilon
zeta
eta
theta
iota
Эти результаты:
[
"alpha",
"INDENT",
"beta",
"gamma",
"INDENT",
"delta",
"DEDENT",
"DEDENT",
"epsilon",
"INDENT",
"zeta",
"DEDENT",
"BADDENT",
"eta",
"theta",
"INDENT",
"iota",
"DEDENT",
"",
""
]
Этот токенизатор даже ловит плохие отступы.
Другие советы
Я думаю, что чувствительный к отступе язык, чувствительный к контексту. Я верю, что PEG может делать только свободные контекстные лангаги.
Обратите внимание, что, хотя ответ NALPO, безусловно, верна, что peg.js может сделать это через внешнее состояние (то есть страшные глобальные переменные), это может быть опасный путь к прогулке (хуже, чем обычные проблемы с глобальными переменными). Некоторые правила могут изначально соответствовать (а затем запускать свои действия), но родительские правила могут не удавать, что приведет к тому, что действие будет недействительным. Если внешнее состояние изменяется в таком действии, вы можете в конечном итоге с помощью недопустимого состояния. Это супер ужасно, и может привести к треморов, рвоту и смерти. Некоторые проблемы и решения этого находятся в комментариях здесь: https://github.com/dmajda/pegjs/issues/45.
Так что мы действительно делаем здесь с отступом, создают что-то вроде блоков в стиле C, которые часто имеют собственную лексику. Если бы я писал компилятор для такого языка, как, я думаю, я бы попробую, чтобы лексер следил за отступом. Каждый раз, когда отступа увеличивается, что он может вставить токен '{'. Точно так же каждый раз, когда он уменьшается, он может вставлять «}» токен. Затем написание грамматики экспрессии с явными фигурными скобками для представления лексической области становится более прямой вперед.
Вы можете сделать это в Treetop, используя семантические предикаты. В этом случае вам нужен семантический предикат, который обнаруживает закрытие блока с отступом в белосброс из-за возникновения другой линии, которая имеет одинаковую или меньшую отступ. Предикат должен подсчитать отступ от линии открывания и возвращает True (Block Clock), если отступ текущей линии закончил на той же или более короткой длине. Поскольку состояние закрытия является контекстно-зависимым, оно не должно быть изменено. Вот пример кода, который я собираюсь добавить в документацию Treetop. Обратите внимание, что я переопределил метод Syntaxnode Treetop Syntaxnode, чтобы облегчить визуализацию результата.
grammar IndentedBlocks
rule top
# Initialise the indent stack with a sentinel:
&{|s| @indents = [-1] }
nested_blocks
{
def inspect
nested_blocks.inspect
end
}
end
rule nested_blocks
(
# Do not try to extract this semantic predicate into a new rule.
# It will be memo-ized incorrectly because @indents.last will change.
!{|s|
# Peek at the following indentation:
save = index; i = _nt_indentation; index = save
# We're closing if the indentation is less or the same as our enclosing block's:
closing = i.text_value.length <= @indents.last
}
block
)*
{
def inspect
elements.map{|e| e.block.inspect}*"\n"
end
}
end
rule block
indented_line # The block's opening line
&{|s| # Push the indent level to the stack
level = s[0].indentation.text_value.length
@indents << level
true
}
nested_blocks # Parse any nested blocks
&{|s| # Pop the indent stack
# Note that under no circumstances should "nested_blocks" fail, or the stack will be mis-aligned
@indents.pop
true
}
{
def inspect
indented_line.inspect +
(nested_blocks.elements.size > 0 ? (
"\n{\n" +
nested_blocks.elements.map { |content|
content.block.inspect+"\n"
}*'' +
"}"
)
: "")
end
}
end
rule indented_line
indentation text:((!"\n" .)*) "\n"
{
def inspect
text.text_value
end
}
end
rule indentation
' '*
end
end
Вот небольшой тестовый драйвер программы, чтобы вы могли пробовать ее легко:
require 'polyglot'
require 'treetop'
require 'indented_blocks'
parser = IndentedBlocksParser.new
input = <<END
def foo
here is some indented text
here it's further indented
and here the same
but here it's further again
and some more like that
before going back to here
down again
back twice
and start from the beginning again
with only a small block this time
END
parse_tree = parser.parse input
p parse_tree
Я знаю, что это старая нить, но я просто хотел добавить код PEGJS на ответы. Этот код будет проанализировать часть текста и «гнездить» в своем роде «AST-ISH» структуры. Это только один глубокий, и он выглядит уродливым, кроме того, он на самом деле не использует значения возврата для создания правильной структуры, но сохраняет дерево на память вашего синтаксиса, и он вернет это в конце. Это вполне может стать громоздким и вызвать некоторые проблемы с производительностью, но, по крайней мере, это делает то, что он должен.
Примечание. Убедитесь, что у вас есть вкладки вместо пробелов!
{
var indentStack = [],
rootScope = {
value: "PROGRAM",
values: [],
scopes: []
};
function addToRootScope(text) {
// Here we wiggle with the form and append the new
// scope to the rootScope.
if (!text) return;
if (indentStack.length === 0) {
rootScope.scopes.unshift({
text: text,
statements: []
});
}
else {
rootScope.scopes[0].statements.push(text);
}
}
}
/* Add some grammar */
start
= lines: (line EOL+)*
{
return rootScope;
}
line
= line: (samedent t:text { addToRootScope(t); }) &EOL
/ line: (indent t:text { addToRootScope(t); }) &EOL
/ line: (dedent t:text { addToRootScope(t); }) &EOL
/ line: [ \t]* &EOL
/ EOF
samedent
= i:[\t]* &{ return i.length === indentStack.length; }
{
console.log("s:", i.length, " level:", indentStack.length);
}
indent
= i:[\t]+ &{ return i.length > indentStack.length; }
{
indentStack.push("");
console.log("i:", i.length, " level:", indentStack.length);
}
dedent
= i:[\t]* &{ return i.length < indentStack.length; }
{
for (var j = 0; j < i.length + 1; j++) {
indentStack.pop();
}
console.log("d:", i.length + 1, " level:", indentStack.length);
}
text
= numbers: number+ { return numbers.join(""); }
/ txt: character+ { return txt.join(""); }
number
= $[0-9]
character
= $[ a-zA-Z->+]
__
= [ ]+
_
= [ ]*
EOF
= !.
EOL
= "\r\n"
/ "\n"
/ "\r"