standard online nomenclatura español parsing syntax language-design treetop peg

parsing - nomenclatura - pep8 online



PEG para sangría de estilo Python (5)

Cómo escribiría una gramática de expresión de análisis en cualquiera de los siguientes generadores de analizador ( PEG.js , Citrus , Treetop ) que pueden manejar la sangría de estilo de Python / Haskell / CoffeScript:

Ejemplos de un lenguaje de programación aún no existente:

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

Actualización: No intente escribir un intérprete para los ejemplos anteriores. Solo estoy interesado en el problema de sangría. Otro ejemplo podría ser analizar lo siguiente:

foo bar = 1 baz = 2 tap zap = 3 # should yield (ruby style hashmap): # {:foo => { :bar => 1, :baz => 2}, :tap => { :zap => 3 } }


Creo que un lenguaje sensible a las indentaciones es sensible al contexto. Creo que PEG solo puede hacer lenguajes sin contexto.

Tenga en cuenta que, aunque la respuesta de nalply es ciertamente correcta, que PEG.js puede hacerlo a través de un estado externo (es decir, las temidas variables globales), puede ser una ruta peligrosa para caminar (peor que los problemas habituales con variables globales). Algunas reglas pueden coincidir inicialmente (y luego ejecutar sus acciones) pero las reglas primarias pueden fallar, lo que hace que la ejecución de la acción no sea válida. Si se modifica el estado externo en dicha acción, puede terminar con un estado no válido. Esto es súper horrible y podría provocar temblores, vómitos y la muerte. Algunos problemas y soluciones están en los comentarios aquí: https://github.com/dmajda/pegjs/issues/45


Entonces, lo que estamos haciendo aquí con sangría es crear algo así como un bloque de estilo C que a menudo tiene su propio alcance léxico. Si estuviera escribiendo un compilador para un lenguaje como ese, creo que trataría de hacer que el lexer no pierda de vista la sangría. Cada vez que la sangría aumenta, podría insertar un token ''{''. Del mismo modo, cada vez que disminuye puede insertar un token ''}''. Luego, escribir una expresión gramatical con llaves explícitas para representar el alcance léxico se vuelve más directo.


Puedes hacer esto en Treetop usando predicados semánticos. En este caso, necesita un predicado semántico que detecte el cierre de un bloque indentado de espacio en blanco debido a la aparición de otra línea que tiene la misma sangría o menos. El predicado debe contar la sangría de la línea de apertura, y devolver verdadero (bloque cerrado) si la sangría de la línea actual ha terminado en la misma longitud o más corta. Debido a que la condición de cierre depende del contexto, no debe ser memorada. Aquí está el código de ejemplo que estoy a punto de agregar a la documentación de Treetop. Tenga en cuenta que he anulado el método de inspección SyntaxNode de Treetop para que sea más fácil visualizar el resultado.

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

Aquí hay un pequeño programa de prueba de controlador para que pueda probarlo fácilmente:

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


Pure PEG no puede analizar la sangría.

Pero peg.js puede.

Hice un experimento rápido y sucio (inspirándome en el comentario de Ira Baxter sobre hacer trampa).

/* 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 es una pila de indentaciones. indent () devuelve una matriz de tokens de sangría y start () desenvuelve la matriz para hacer que el analizador se comporte de forma similar a una secuencia.

peg.js produce para el texto:

alpha beta gamma delta epsilon zeta eta theta iota

estos resultados:

[ "alpha", "INDENT", "beta", "gamma", "INDENT", "delta", "DEDENT", "DEDENT", "epsilon", "INDENT", "zeta", "DEDENT", "BADDENT", "eta", "theta", "INDENT", "iota", "DEDENT", "", "" ]

Este analizador incluso detecta malas sangrías.


Sé que este es un hilo viejo, pero solo quería agregar un código PEGjs a las respuestas. Este código analizará un fragmento de texto y lo "anidará" en una especie de estructura "AST-ish". Solo va uno profundo y se ve feo, además, realmente no usa los valores de retorno para crear la estructura correcta pero mantiene un árbol en memoria de tu sintaxis y lo devolverá al final. Esto podría volverse difícil de manejar y causar algunos problemas de rendimiento, pero al menos hace lo que se supone que debe hacer.

Nota: ¡Asegúrate de tener pestañas en lugar de espacios!

{ 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"