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 } }
31
demandé sur Nash Bridges 2010-11-17 17:35:46

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.

23
répondu nalply 2017-01-04 10:36:43

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

9
répondu B T 2013-01-06 09:09:23

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.

7
répondu Samsdram 2011-03-09 02:48:04

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
0
répondu cliffordheath 2015-05-06 01:18:59

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"
0
répondu Mr. Baudin 2016-08-10 13:34:56