Pegjs: Admite el análisis de idiomas basados ​​en sangría

Creado en 16 oct. 2013  ·  34Comentarios  ·  Fuente: pegjs/pegjs

Estaba usando un montón de lenguajes basados ​​en sangrías, como CoffeeScript, Jade, y quiero crear DSL por mí mismo.
Encontré algunos trucos para mantener las sangrías en pegjs al buscar, me preguntaba si hay una solución consistente:
http://stackoverflow.com/questions/11659095/parse-indentation-level-with-peg-js
http://stackoverflow.com/questions/4205442/peg-for-python-style-indentation
https://gist.github.com/jakubkulhan/3192844
https://groups.google.com/forum/#!searchin/pegjs/indent/pegjs/RkbAB4rPlfU/xxafrY5wGCEJ
Pero, ¿admitirá pegjs esta función?

feature

Comentario más útil

Es muy peligroso confiar en los efectos secundarios que agrega en los controladores personalizados para analizar las gramáticas basadas en sangría. Simplemente no lo hagas. Pegjs tendría que agregar alguna capacidad para presionar y mostrar el estado condicional para hacer que las sangrías de análisis (y otras gramáticas sensibles al contexto) sean seguras.

Esto es lo que hago por ahora, y le recomiendo que haga esto: preprocese el archivo de entrada e inserte sus propios tokens de sangría / sangría. Yo uso {{{{y}}}} respectivamente. Entonces su gramática está libre de contexto y se puede analizar normalmente. Puede estropear sus valores de línea / columna, pero puede corregirlos en un postprocesador.

Todos 34 comentarios

Es muy peligroso confiar en los efectos secundarios que agrega en los controladores personalizados para analizar las gramáticas basadas en sangría. Simplemente no lo hagas. Pegjs tendría que agregar alguna capacidad para presionar y mostrar el estado condicional para hacer que las sangrías de análisis (y otras gramáticas sensibles al contexto) sean seguras.

Esto es lo que hago por ahora, y le recomiendo que haga esto: preprocese el archivo de entrada e inserte sus propios tokens de sangría / sangría. Yo uso {{{{y}}}} respectivamente. Entonces su gramática está libre de contexto y se puede analizar normalmente. Puede estropear sus valores de línea / columna, pero puede corregirlos en un postprocesador.

Si no necesita apuntar a javascript, Pegasus , mi clon de pegjs para C #, tiene soporte para empujar / hacer estallar el estado. Aquí hay un artículo de wiki sobre cómo hacer exactamente lo que quieres: https://github.com/otac0n/Pegasus/wiki/Significant-Whitespace-Parsing

Me gustaría proponer que los pegjs usen mi sintaxis como punto de partida para el análisis basado en estados.

La capacidad de empujar y hacer estallar de forma segura es agradable. Lo usaría si estuviera basado en Javascript. Simplemente no vale la pena integrar un CLR solo para analizar.

Eso es lo que me imaginé. Creo que, en ese caso, probablemente debería intentar respaldar mis mejoras en pegjs.

Sin embargo, no necesariamente quiero hacer eso sin tener una conversación con @dmajda.

@ otac0n Es bueno. No escribo C #. JavaScript es mucho mejor para mí.

Los lenguajes basados ​​en sangría son importantes. Quiero ver cómo simplificar su análisis después de 1.0.0.

Creo que este problema se resuelve mejor permitiendo el estado en general, tal como lo hace Pegasus y como se sugiere en el # 285. Aquí hay una idea (la siguiente es la gramática de espacios en blanco significativa de Pegasus traducida a pegjs y con mi idea de sintaxis agregada):

{var indentation = 0}

program
  = s:statements eof { return s }

statements
  = line+

line
  = INDENTATION s:statement { return s }

statement
  = s:simpleStatement eol { return s }
  / "if" _ n:name _? ":" eol INDENT !"bar " s:statements UNDENT {
      return { condition: n, statements: s }
    }
  / "def" _ n:name _? ":" eol INDENT s:statements UNDENT {
      return { name: n, statements: s }
    }

simpleStatement
  = a:name _? "=" _? b:name { return { lValue: a, expression: b } }

name
  = [a-zA-Z] [a-zA-Z0-9]* { return text() }

_ = [ \t]+

eol = _? comment? ("\r\n" / "\n\r" / "\r" / "\n" / eof)

comment = "//" [^\r\n]*

eof = !.

INDENTATION
  = spaces:" "* &{ return spaces.length == indentation }

INDENT
  = #STATE{indentation}{ indentation += 4 }

UNDENT
  = #STATE{indentation}{ indentation -= 4 }

Tenga en cuenta los bloques #STATE{indentation} cerca de la parte inferior (obviamente inspirados en Pegasus). Yo llamo a esos bloques estatales. La idea es permitir un bloqueo estatal antes de las acciones. Aquí hay un bloque de estado más complicado:

#STATE{a, b, arr: {arr.slice()}, obj: {shallowCopy(obj)}, c}

Es una abreviatura de:

#STATE{a: {a}, b: {b}, arr: {arr.slice()}, obj: {shallowCopy(obj)}, c: {c}}

En otras palabras, después de que se ha aplicado la expansión taquigráfica, el contenido de un bloque de estado es una lista de identifier ":" "{" code "}" . Agregar un bloque de estado antes de una acción le dice a pegjs que esta acción modificará los identificadores enumerados, y si se retrocede la regla, esos identificadores deben restablecerse al código entre llaves.

Aquí están las funciones compiladas para INDENT y UNDENT de la gramática anterior, con el restablecimiento de la variable indentation agregada:

    function peg$parseINDENT() {
      var s0, s1, t0;

      s0 = peg$currPos;
      t0 = indentation;
      s1 = [];
      if (s1 !== peg$FAILED) {
        peg$reportedPos = s0;
        s1 = peg$c41();
      } else {
        indentation = t0;
      }
      s0 = s1;

      return s0;
    }

    function peg$parseUNDENT() {
      var s0, s1, t0;

      s0 = peg$currPos;
      t0 = indentation;
      s1 = [];
      if (s1 !== peg$FAILED) {
        peg$reportedPos = s0;
        s1 = peg$c42();
      } else {
        indentation = t0;
      }
      s0 = s1;

      return s0;
    }

Y aquí hay un poco de cómo se podría compilar el "bloque de estado complicado" de arriba:

s0 = peg$currPos;
t0 = a;
t1 = b;
t2 = arr.slice();
t3 = shallowCopy(obj);
t4 = c;
// ...
if (s1 !== peg$FAILED) {
  // ...
} else {
  peg$currPos = s0;
  a = t0;
  b = t1;
  arr = t2;
  obj = t3;
  c = t4;
}

¿Qué opinas de esta idea de poder:

  • Indique a pegjs qué variables con estado serán modificadas por una acción.
  • Proporcione el código necesario para almacenar esas variables si es necesario restablecerlas. (Incluyendo sintaxis abreviada para el caso simple donde la variable es un valor primitivo).

¿Y qué opinas de la sintaxis?

Editar: Aquí está la gramática de sintaxis propuesta (solo por diversión):

diff --git a/src/parser.pegjs b/src/parser.pegjs
index 08f6c4f..09e079f 100644
--- a/src/parser.pegjs
+++ b/src/parser.pegjs
@@ -116,12 +116,31 @@ ChoiceExpression
     }

 ActionExpression
-  = expression:SequenceExpression code:(__ CodeBlock)? {
+  = expression:SequenceExpression code:((__ StateBlock)? __ CodeBlock)? {
       return code !== null
-        ? { type: "action", expression: expression, code: code[1] }
+        ? {
+            type:       "action",
+            expression: expression,
+            code:       code[2],
+            stateVars:  (code[0] !== null ? code[0][1] : [])
+          }
         : expression;
     }

+StateBlock "state block"
+  = "#STATE{" __ first:StateBlockItem rest:(__ "," __ StateBlockItem)* __ "}" {
+      return buildList(first, rest, 3);
+    }
+
+StateBlockItem
+  = varName:Identifier expression:(__ ":" __ CodeBlock)? {
+      return {
+        type:       "stateVar",
+        name:       varName,
+        expression: expression !== null ? expression[3] : varName
+      };
+    }
+
 SequenceExpression
   = first:LabeledExpression rest:(__ LabeledExpression)* {
       return rest.length > 0

Hola tios,
Solo para aclarar, ¿tengo razón en que es mejor no usar PEG.js (con soluciones alternativas desde la parte superior de este problema) con lenguajes basados ​​en sangría hasta que se resuelva este problema?
Gracias.

@hoho , no entiendo lo que quieres decir ... Pero luego encontré otra solución para analizar las sangrías con el combinador de analizador como soluciones y funcionó. Y creo que mi sangría original para analizar sangrías con PEG.js desapareció.

Quiero decir que hay soluciones para analizar la sangría, pero los comentarios dicen que estas soluciones fallarán en algunos casos determinados.

Permítanme aclarar la situación: es posible analizar los lenguajes basados ​​en sangría en PEG.js. Hay varias soluciones mencionadas anteriormente y acabo de crear otra mientras trataba de "sentir" esto (es una gramática de un lenguaje simple con dos declaraciones, una de las cuales puede contener sub-declaraciones con sangría, similar a, por ejemplo, if en Python).

Una cosa común a todas las soluciones es que necesitan rastrear el estado de sangría manualmente (porque PEG.js no puede hacer eso). Esto significa que hay dos limitaciones:

  1. No puede compilar la gramática con el almacenamiento en caché de forma segura (porque el analizador podría usar resultados almacenados en caché en lugar de ejecutar código de manipulación de estado).
  2. No puede retroceder a través de los niveles de sangría (porque actualmente no hay forma de desenrollar el estado al retroceder). En otras palabras, no puede analizar un idioma en el que hay dos construcciones válidas que pueden eliminarse de la ambigüedad solo después de un cambio de línea nueva y de nivel de sangría.

La limitación 1 puede causar problemas de rendimiento en algunos casos, pero no creo que haya muchos idiomas para los que la limitación 2 sea un problema.

Estoy de acuerdo con este estado hasta la 1.0.0 y planeo volver a este tema en algún momento posterior. El primer nivel de mejora podría ser deshacerse de la limitación 2 utilizando un seguimiento de estado más explícito (como se sugirió anteriormente) o proporcionando un gancho de retroceso (para que uno pueda desenrollar el estado correctamente). El segundo nivel podría deshacerse de la necesidad de rastrear el estado de sangría manualmente proporcionando alguna forma declarativa de hacerlo. Esto podría ayudar con la limitación 1.

H, escribí un parche (pequeño, hacky) para PEG.js que admite el retroceso adecuado, como expliqué aquí: https://github.com/pegjs/pegjs/issues/45

perdón por el golpe 😜

Solo estaba buscando crear analizadores CSON y YAML para un lenguaje que estoy diseñando, y mientras buscaba formas de crear un analizador basado en sangría con PEG.js, se me ocurrió un método simple que:

1) no depende de los estados push / pop
2) afirmación de niveles de sangría a través de código dentro de acciones

Se me había ocurrido que cualquiera de las 2 soluciones anteriores en realidad agrega problemas de rendimiento a los analizadores generados. Además en mi opinión:

1) confiar en los estados no solo agrega una sintaxis de PEG.js desagradable, sino que también puede afectar el tipo de analizadores que se pueden generar, ya que necesitarían admitir la gestión de estados basada en acciones.
2) a veces, agregar algo de código en las acciones da como resultado una regla dependiente del idioma, y ​​para algunos desarrolladores eso significa que no pueden usar complementos para generar analizadores para otros lenguajes como C o PHP sin recurrir a más complementos para manejar acciones sobre reglas, que simplemente significa un sistema de compilación más grande solo para admitir 1 o 2 cambios.

Después de un tiempo comencé a crear mi propia variante del analizador PEG.js y pensé: ¿por qué no usar simplemente operadores de prefijo de incremento ("++") y decremento ("-") (__ ++ expresión__ y __-- expresión__ ) para manejar los resultados de las expresiones coincidentes (__expression * __ o __expression + __).

La siguiente es una gramática de ejemplo basada en el lenguaje basado en simples de @dmajda , reescrito para usar la nueva __ ++ expresión__ y __-- expresión__ en lugar de __ & {predicado} __:

Start
  = Statements

Statements
  = Statement*

Statement
  = Indent* statement:(S / I) { return statement; }

S
  = "S" EOS {
      return "S";
    }

I
  = "I" EOL ++Indent statements:Statements --Indent { return statements; }
  / "I" EOS { return []; }

Indent "indent"
  = "\t"
 / !__ "  "

__ "white space"
 = " \t"
 / " "

EOS
  = EOL
  / EOF

EOL
  = "\n"

EOF
  = !.

Mucho más agradable a la vista, ¿no? También es más fácil de entender, tanto para los humanos como para el software.

¿Como funciona? sencillo:

1) Indent* le dice al analizador que queremos 0 o más de lo que devuelve Indent
2) ++Indent le dice al analizador que aumente la cantidad mínima de coincidencias requeridas para Indent
3) Ahora, cada vez que el analizador está a punto de devolver las coincidencias para Indent , primero espera que sea __1 más__ coincidencia que antes, de lo contrario, se lanza _peg $ SyntaxError_.
4) --Indent le dice al analizador que disminuya la cantidad mínima de coincidencias requeridas para Indent
5) Ahora, cada vez que el analizador busca Indent y devuelve las coincidencias que espera __1 menos__ coincidencia que antes, de lo contrario, se lanza _peg $ SyntaxError_.

Esta solución es la mejor manera de agregar soporte para 'Análisis significativo de espacios en blanco' sin agregar una sintaxis desagradable a las gramáticas PEG.js o bloquear generadores de terceros.

Aquí están las reglas modificadas para agregar soporte para analizar esto en _src / parser.pegjs_:

{
  const OPS_TO_PREFIXED_TYPES = {
    "$": "text",
    "&": "simple_and",
    "!": "simple_not",
    "++": "increment_match",
    "--": "decrement_match"
  };
}

PrefixedOperator
  = "$"
  / "&"
  / "!"
  / "++"
  / "--"

SuffixedOperator
  = "?"
  / "*"
  / "+" !"+"

¿Estoy en lo cierto al suponer que para admitirlo en el lado del compilador / generador tendremos que:

1) agregue un pase de compilador que asegure que __ ++ expresión__ o __-- expresión__ solo se usen en __ expresión * __ o __ expresión + __, donde __ expresión__ debe ser de los tipos: elección, secuencia o regla_ref
2) agregue una verificación basada en caché en el analizador generado para __expression * __ o __expression + __ que afirme que se cumple la coincidencia mínima requerida antes de devolver las coincidencias
3) opcionalmente, agregue un método auxiliar para que los analizadores generados lo implementen que devuelva el número de coincidencias requeridas para una regla determinada, por ejemplo. nMatches( name: String ): Number

@futagoza , esto es limpio e inteligente. Me gusta. Estoy trabajando en un analizador que maneja el estado, pero el único estado que realmente necesitamos son los niveles de sangría. Puedo usar esta idea y darle crédito por ello. El seguimiento del nivel de sangría aún requiere de manera efectiva empujar / hacer estallar el estado y, por lo tanto, aún puede evitar algunas optimizaciones, pero la semántica de esto es muy agradable.

Si está agregando operadores a una gramática, le recomiendo agregar el operador de prefijo @ también. Su propósito es simplemente extraer un resultado de una sola regla de una secuencia. Usando eso, la gramática de muestra se vuelve aún más limpia. No más acciones {return x} triviales.

Start
  = Statements

Statements
  = Statement*

Statement
  = Indent* @(S / I)

S
  = "S" EOS {
      return "S";
    }

I
  = "I" EOL ++Indent <strong i="8">@Statements</strong> --Indent
  / "I" EOS { return []; }

Indent "indent"
  = "\t"
 / !__ "  "

__ "white space"
 = " \t"
 / " "

EOS
  = EOL
  / EOF

EOL
  = "\n"

EOF
  = !.

@kodyjking ¿qué opinas de esto?

@futagoza ¿Tiene una bifurcación / rama con el parche de sangría habilitado y una pequeña muestra de gramática?

Estoy trabajando en esta sangría de bifurcación / rama

@krinye "Recomiendo agregar el operador de prefijo @ también. Su propósito es simplemente extraer un resultado de una sola regla de una secuencia"

¿Podría alguno de ustedes echar un vistazo y hacer un comentario o hacer relaciones públicas con la solución? Gracias :)

Léame: cambios de horquilla

Ah, no noté la advertencia:

¿Estoy en lo cierto al suponer que para admitirlo en el lado del compilador / generador tendremos que:

  • agregue un pase de compilador que asegure que ++ expresión o - expresión solo se usen en la expresión * o expresión +, donde la expresión debe ser de los tipos: elección, secuencia o regla_ref
  • agregue una verificación basada en caché en el analizador generado para la expresión * o expresión + que afirme que se cumple la coincidencia mínima requerida antes de devolver las coincidencias
  • opcionalmente, agregue un método auxiliar para que los analizadores generados lo implementen que devuelva el número de coincidencias requeridas para una regla determinada, por ejemplo. nMatches (nombre: Cadena): Número

Solo por diversión, intenté agregar esto en visitor.js

      increment_match: visitExpression,
      decrement_match: visitExpression,

Ahora obtengo Invalid opcode: undefined.

@kristianmandrup con respecto al operador @ para extraer valores únicos de secuencias, tengo una bifurcación con solo esa característica agregada a PegJS disponible aquí:

https://github.com/krisnye/pegjs

Es una adición bastante simple.

@krisnye +1 para la implementación basada en gramática, agradable y simple. Si no le importa, agregaré esto a mi variante de PEG.js 😄

@kristianmandrup nos vemos comprometidos con mi sugerencia

@futagoza Por favor, hazlo.

Estaba discutiendo la lógica de la sangría con un compañero de trabajo y sugerimos los siguientes elementos sintácticos

// incrementa la variable de estado nombrada
identificador ++
// disminuye la variable de estado nombrada
identificador

// repite la cantidad constante o la variable de estado (con cero por defecto si la variable aún no se ha incrementado)
regla {entero | identificador}
// repite min / max usando constantes o variables de estado
regla {entero | identificador, entero | identificador}

El analizador en el que estamos trabajando puede manejar estados arbitrarios, pero honestamente, lo anterior es todo lo que se necesita para el análisis de sangría.

¡Muchas gracias chicos! Si es tan fácil de hacer, ¿por qué no crear una bifurcación "dedicada" donde las cosas que mencionas "simplemente funcionan";)
¡Salud!

@krisnye

  1. El uso de __identifier ++ __ puede causar fácilmente un error complicado si el desarrollador quiso decir __identifier + __, por eso elegí __ ++ identifier__ y, por coherencia, __-- identifier__
  2. Como se mencionó en un número diferente sobre rangos, rule{ STATE_REPEAT / RANGE } se puede confundir con rule{ ACTION } , especialmente si está creando un resaltador de sintaxis para PEG.js, por lo que @dmajda ha rechazado este enfoque

@kristianmandrup _ (OFF TOPIC) _ Soy bueno diseñando características y, a veces, haciendo implementaciones, pero soy horrible para probarlas y compararlas, por lo que generalmente hago variantes de trabajo (sin pruebas ni puntos de referencia) en directorios privados que no son de repositorio en mi PC , y luego olvídate de ellos 🤣. Para mi variante actual de PEG.js (llamada ePEG.js 😝, una reescritura extendida de PEG.js), estoy agregando las cosas mencionadas aquí, así como otras características (rangos, importaciones, plantillas, etc.), así que agrego pruebas y puntos de referencia, pero actualmente también estoy trabajando en un proyecto de C ++ que está tomando mi tiempo, por lo que no hay ETA en eso.

@futagoza Gracias amigo :) Mirando las extensiones de funciones, pero no menciona el soporte de sangría. ¿Eso está incluido pero indocumentado o en camino?

Alguien más de esta lista me señaló otras soluciones de generador / generador de analizadores sintácticos que también podría considerar. ¡Mantenme informado! ¡Salud!

@kristianmandrup No está incluido, por lo que puedo decir, pero @dmajda dijo hace 3 años que lo investigará después de lanzar PEG.js v1, pero por lo que puedo decir, no lo estará por otros 2 años, a menos que planee hacerlo. lanzar más versiones menores de PEG.js v0 (_0.12_, _0.13_, _etc_)

Me refiero a si ya había incluido el soporte de sangría en ePEG o en la hoja de ruta.

@kristianmandrup oh 😆, está en la hoja de ruta. No he actualizado el repositorio ePEG.js durante un tiempo, y recientemente decidí convertirlo en una reescritura completa de PEG.js en lugar de un complemento.

@futagoza estuvo de acuerdo en ++ / - como operaciones previas, y me olvidé de la sintaxis de acción, la cambiamos a [min, max]

Entonces

++identifier
--identifier
rule[integer | identifier]
rule[integer | identifier, integer | identifier]

@krisnye [ ... ] se usa para clases de caracteres, consulte https://github.com/pegjs/pegjs#characters

Lo que estoy haciendo en ePEG.js es agregar rangos (también en la hoja de ruta) para lograr lo que creo que estás describiendo:

space = [ \t]*
rule = range|expression
  • __expression__ puede ser cualquiera de estos: __ ++ space__, __-- space__ o __space__
  • __range__ puede ser __ min.. __, __ min..max __, __ ..max __ o __ exact __
  • __min__, __max__ o __exact__ solo pueden ser un __ entero sin signo__
  • el uso de un __range__ con una __expression__ (por ejemplo, 2 | expresión) puede permitirnos establecer la cantidad total de __expression__ requerida para que __rule__ analice correctamente.
  • usar el rango __exact__ con __ ++ expresión__ o __-- expresión__ (por ejemplo, 3 | ++ expresión) puede permitirnos establecer la cantidad __integer__ para __ ++ __ o __ - __, que es __1__ por defecto
  • el uso del rango __min__ o __max__ con __ ++ expresión__ o __-- expresión__ arroja un error de sintaxis.

No estoy usando variables de estado, ya que eso sería confuso con los identificadores de reglas.

Usando una combinación de __range__, __ ++ __, __ - __ o __ @__ , espero crear archivos de gramática PEG que se basen menos en una __acción__ de reglas para su resultado, lo que debería aumentar el tiempo de desarrollo de los espacios en blanco (por ejemplo, sangría -basados, arte ASCII, etc.) ya que los diseñadores y / o implementadores del lenguaje no tienen que preocuparse por tratar de confirmar si se ha analizado la cantidad correcta de espacios en blanco.
Esto también debería permitir a los desarrolladores de complementos crear generadores de analizadores que puedan optimizar sin temor a cuál es el lenguaje de nuestra __acción __ (de forma predeterminada, JavaScript, pero con los complementos se puede cambiar a CoffeeScript, PHP, etc.).

Entonces, parece que todavía no es posible analizar Python con PEG.js listo para usar, ¿verdad?

Si no es así, ¿es esto algo que llegará pronto? ¿Existe un conjunto de tareas necesarias para que este trabajo funcione y que la gente pueda contribuir?

Tengo un proyecto en el que me encantaría poder obtener un AST de Python en JS, modificarlo y luego convertirlo de nuevo al código fuente con el mismo formato, así que estaría interesado en hacer que esto suceda si hay un claro mapa vial.

@mindjuice Aún no, está previsto para la publicación 1.0.

Si no puede esperar, mi sobrino y yo creamos un analizador escrito en TypeScript que usa la misma sintaxis y maneja la sangría. Aún no está documentado, pero es bastante simple. Todavía hay trabajo en curso, ya que lo estamos usando como analizador para un nuevo diseño de lenguaje.

https://github.com/krisnye/pegs

También puede probar otro generador de analizador con soporte como chevrotain

Ejemplo de sangría de Python

Creo que es muy posible analizar la sangría similar a Python con PEGjs.
El siguiente ejemplo usa solo sangría basada en cuatro espacios, pero puede extenderse para cubrir tabulaciones espaciadas arbitrariamente y otros caracteres de espacio en blanco.
De hecho, el lenguaje en el que estoy trabajando tiene una historia de sangría un poco más complicada que Python y esta gramática funciona bien para él.

{
    let prevIndentCount = 0;
    function print(...s) { console.log(...s); }
}

Indent 'indent'
    = i:("    "+) { 
        let currentIndentCount = i.toString().replace(/,/g, "").length;
        if (currentIndentCount === prevIndentCount + 4) { 
            // DEBUG //
            print("=== Indent ===");
            print("    current:"+currentIndentCount); 
            print("    previous:"+prevIndentCount);
            print("    lineNumber:"+location().start.line); 
            // DEBUG //
            prevIndentCount += 4;
            return "[indent]";
        }
        error("error: expected a 4-space indentation here!")
    } // 4 spaces 

Samedent 'samedent'
    = s:("    "+ / "") &{
        let currentIndentCount = s.toString().replace(/,/g, "").length;
        if (currentIndentCount === prevIndentCount) {
            print("=== Samedent ===");
            return true;
        }
        return false;
    }

Dedent 'dedent'
    = d:("    "+ / "") {
        let currentIndentCount = d.toString().replace(/,/g, "").length;
        if (currentIndentCount < prevIndentCount) {
            // DEBUG //
            print("=== Dedent ===");
            print("    current:"+currentIndentCount); 
            print("    previous:"+prevIndentCount);
            print("    lineNumber:"+location().start.line); 
            // DEBUG //
            prevIndentCount -= 4;
            return "[dedent]";
        }
        error("error: expected a 4-space dedentation here!");
    }

Con la gramática anterior, puede crear una regla de bloque con sangría como esta:

FunctionDeclaration 
    = 'function' _ Identifier _ FunctionParameterSection _ ":" _ FunctionBody

FunctionBody
    = Newline Indent FunctionSourceCode (Newline Samedent FunctionSourceCode)* Dedent 
¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

emmenko picture emmenko  ·  15Comentarios

brettz9 picture brettz9  ·  8Comentarios

futagoza picture futagoza  ·  13Comentarios

dmajda picture dmajda  ·  15Comentarios

Coffee2CodeNL picture Coffee2CodeNL  ·  13Comentarios