scala - ¿Cómo puedo crear un combinador de analizador en el que los finales de línea sean significativos?
parsing parser-combinators (2)
Puede override
el protected val whiteSpace
(un Regex) cuyo valor predeterminado es """/s+""".r
o override
el método protected def handleWhiteSpace(...)
si necesita más control del que se obtiene fácilmente con una expresión regular . Ambos miembros se originan en RegexParsers, que es la clase base para JavaTokenParsers.
Estoy creando un DSL, y uso la biblioteca del combinador de analizador de Scala para analizar el DSL. El DSL sigue una sintaxis similar a Ruby. Un archivo fuente puede contener una serie de bloques que se ven así:
create_model do
at 0,0,0
end
Los finales de línea son significativos en el DSL, ya que se utilizan efectivamente como terminadores de instrucciones.
Escribí un analizador de Scala que se parece a esto:
class ML3D extends JavaTokenParsers {
override val whiteSpace = """[ /t]+""".r
def model: Parser[Any] = commandList
def commandList: Parser[Any] = rep(commandBlock)
def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"
def eol: Parser[Any] = """(/r?/n)+""".r
def command: Parser[Any] = commandName~opt(commandLabel)
def commandName: Parser[Any] = ident
def commandLabel: Parser[Any] = stringLiteral
def statementList: Parser[Any] = rep(statement)
def statement: Parser[Any] = functionName~argumentList~eol
def functionName: Parser[Any] = ident
def argumentList: Parser[Any] = repsep(argument, ",")
def argument: Parser[Any] = stringLiteral | constant
def constant: Parser[Any] = wholeNumber | floatingPointNumber
}
Como los finales de línea son importantes, whiteSpace
para que solo trate los espacios y las pestañas como espacios en blanco (en lugar de tratar las nuevas líneas como espacios en blanco y, por lo tanto, las ignore).
Esto funciona, a excepción de la declaración "final" para commandBlock
. Como mi archivo de origen contiene una nueva línea final, el analizador se queja de que estaba esperando solo un end
pero obtuvo una nueva línea después de la palabra clave end
.
Así que cambié la definición de commandBlock
a esto:
def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"~opt(eol)
(Es decir, agregué una nueva línea opcional después de "finalizar").
Pero ahora, al analizar el archivo de origen, aparece el siguiente error:
[4.1] failure: `end'' expected but `'' found
Creo que esto se debe a que, después de que succiona la nueva línea, el analizador se encuentra con una cadena vacía que cree que no es válida, pero no estoy seguro de por qué está haciendo esto.
¿Algún consejo sobre cómo solucionar esto? Podría extender el analizador incorrecto de la biblioteca del combinador de analizadores de Scala, por lo que cualquier sugerencia sobre cómo crear una definición de idioma con caracteres de línea nuevos significativos también es bienvenida.
Recibo el mismo error en ambos sentidos, pero creo que lo estás malinterpretando. Lo que está diciendo es que está esperando un end
, pero ya llegó al final de la entrada.
Y la razón por la que está sucediendo es que el end
se lee como una declaración. Ahora, estoy seguro de que hay una buena manera de resolver esto, pero no tengo experiencia suficiente con los analizadores de Scala. Parece que el camino a seguir sería utilizar analizadores de token con una parte de escaneo, pero no pude encontrar una manera de hacer que el analizador de token estándar no trate las nuevas líneas como espacios en blanco.
Entonces, aquí hay una alternativa:
import scala.util.parsing.combinator.JavaTokenParsers
class ML3D extends JavaTokenParsers {
override val whiteSpace = """[ /t]+""".r
def keywords: Parser[Any] = "do" | "end"
def identifier: Parser[Any] = not(keywords)~ident
def model: Parser[Any] = commandList
def commandList: Parser[Any] = rep(commandBlock)
def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"~opt(eol)
def eol: Parser[Any] = """(/r?/n)+""".r
def command: Parser[Any] = commandName~opt(commandLabel)
def commandName: Parser[Any] = identifier
def commandLabel: Parser[Any] = stringLiteral
def statementList: Parser[Any] = rep(statement)
def statement: Parser[Any] = functionName~argumentList~eol
def functionName: Parser[Any] = identifier
def argumentList: Parser[Any] = repsep(argument, ",")
def argument: Parser[Any] = stringLiteral | constant
def constant: Parser[Any] = wholeNumber | floatingPointNumber
}