PEG for Python style indentation
comment écririez-vous un Analyse De L'Expression De La Grammaire dans l'un des générateurs D'analyseurs suivants ( PEG.js,Agrumes,la cime des arbres) qui peut supporter L'indentation style Python/Haskell/CoffeScript:
Exemples d'un pas-encore-existant langage de programmation:
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
mise à Jour: N'essayez pas d'écrire un interprète pour les exemples ci-dessus. Je suis seulement intéressé par le problème d'indentation. Un autre exemple pourrait être l'analyse suivante:
foo
bar = 1
baz = 2
tap
zap = 3
# should yield (ruby style hashmap):
# {:foo => { :bar => 1, :baz => 2}, :tap => { :zap => 3 } }
5 réponses
le PEG pur ne peut pas analyser l'indentation.
Mais peg.js peut.
j'ai fait une expérience rapide et sale (en m'inspirant du commentaire d'Ira Baxter sur la tricherie).
/* 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
est une pile d'indentations. indent () renvoie un tableau de signes d'indentation et start () déballe le tableau pour que l'analyseur se comporte un peu comme un flux.
peg.js produit pour le texte:
alpha
beta
gamma
delta
epsilon
zeta
eta
theta
iota
ces résultats:
[
"alpha",
"INDENT",
"beta",
"gamma",
"INDENT",
"delta",
"DEDENT",
"DEDENT",
"epsilon",
"INDENT",
"zeta",
"DEDENT",
"BADDENT",
"eta",
"theta",
"INDENT",
"iota",
"DEDENT",
"",
""
]
cet analyseur prend même les mauvais tirets.
je pense qu'un langage sensible à l'indentation comme celui-là est sensible au contexte. Je crois que PEG ne peut faire que des langages sans contexte.
notez que, tandis que la réponse de nalply est certainement correcte que PEG.js peut le faire via l'état externe (c'est-à-dire les variables globales redoutées), il peut être un chemin dangereux à parcourir (pire que les problèmes habituels avec les variables globales). Certaines règles peuvent initialement correspondre (puis exécuter leurs actions), mais les règles parent peuvent échouer, ce qui fait que l'action s'exécute invalide. Si l'état externe est modifié dans une telle action, vous pouvez vous retrouver avec l'état invalide. C'est super terrible, et pourrait conduire à des tremblements, des vomissements, et la mort. Certains problèmes et solutions se trouvent dans les commentaires ici:https://github.com/dmajda/pegjs/issues/45
donc ce que nous faisons vraiment ici avec l'indentation est de créer quelque chose comme un c-style blocs qui ont souvent leur propre portée lexicale. Si j'écrivais un compilateur pour un langage comme celui-ci, je pense que j'essaierais de faire suivre l'indentation par le lexer. Chaque fois que l'indentation augmente, elle peut insérer un jeton' {'. De même, chaque fois qu'il décroît, il pourrait y avoir un jeton'}'. Ensuite, écrire une grammaire d'expression avec des accolades explicites pour représenter la portée lexicale devient plus tout droit vers l'avant.
vous pouvez faire cela dans Treetop en utilisant des prédicats sémantiques. Dans ce cas, vous avez besoin d'un prédicat sémantique qui détecte la fermeture d'un bloc d'espace blanc indenté en raison de l'occurrence d'une autre ligne qui a la même ou moins indentation. Le prédicat doit compter l'indentation de la ligne d'ouverture, et retourner true (bloc fermé) si l'indentation de la ligne courante a fini à la même longueur ou plus courte. Parce que la condition de fermeture dépend du contexte, elle ne doit pas être mémorisée. Voici l' exemple de code que je suis sur le point d'ajouter à la documentation de Treetop. Notez que J'ai outrepassé la méthode SyntaxNode d'inspection de Treetop pour faciliter la visualisation du résultat.
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
je sais que c'est un vieux fil, mais je voulais juste ajouter du code PEGjs aux réponses. Ce code Analyse un morceau de texte et le "niche" dans une sorte de structure "AST-ish". Il va seulement un profond et il semble laid, en outre il n'utilise pas vraiment les valeurs de retour pour créer la bonne structure mais garde un arbre de mémoire de votre syntaxe et il retournera cela à la fin. Cela pourrait bien devenir lourd et causer des problèmes de performance, mais au moins, il fait ce qu'il est supposé de.
Note: assurez-vous d'avoir des onglets au lieu d'espaces!
{
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"