2010-03-04 9 views
16

Sto creando un DSL e utilizzando la libreria combinatore di parser di Scala per analizzare il DSL. La DSL segue una sintassi semplice, simile a Ruby. Un file sorgente può contenere una serie di blocchi che assomigliano a questo:Come posso creare un combinatore di parser in cui le terminazioni di linea sono significative?

create_model do 
    at 0,0,0 
end 

fine riga sono significativi nel DSL, in quanto sono effettivamente utilizzati come terminatori dichiarazione.

ho scritto un parser Scala che assomiglia a questo:

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 
} 

Da fine riga importa, io overrode whiteSpace in modo che ti trattano solo spazi e tabulazioni come spazi bianchi (invece di trattare nuove linee come spazi bianchi, e quindi ignorandoli).

Questo funziona, ad eccezione dell'istruzione "fine" per commandBlock. Poiché il mio file sorgente contiene una nuova riga finale, il parser si lamenta che si aspettava solo un end ma ha ottenuto una nuova riga dopo la parola chiave end.

Così ho cambiato la definizione commandBlock s' a questo:

def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"~opt(eol) 

(Cioè, ho aggiunto una nuova linea opzionale dopo "fine").

Ma ora, durante l'analisi del file di origine, ottengo il seguente errore:

[4.1] failure: `end' expected but `' found 

ho che questo è perché, dopo che succhia la nuova linea di uscita, il parser incontra una stringa vuota che pensa non sia valido, ma non sono sicuro del motivo per cui lo sta facendo.

Qualche consiglio su come risolvere questo problema? Potrei estendere il parser errato dalla libreria del parser combinator di Scala, quindi qualsiasi suggerimento su come creare una definizione di lingua con significativi nuovi caratteri di riga è anch'esso benvenuto.

risposta

9

Ottengo lo stesso errore in entrambi i modi, ma penso che si stia interpretando male. Quello che sta dicendo è che si aspetta un end, ma ha già raggiunto la fine dell'input.

E il motivo che sta accadendo è che end viene letto come una dichiarazione. Ora, sono sicuro che c'è un buon modo per risolvere questo problema, ma non ho esperienza sufficiente con i parser di Scala. Sembra che la strada da seguire sarebbe quella di utilizzare parser di token con una parte di scansione, ma non riuscivo a capire come rendere il parser di token standard non trattare le newline come spazi bianchi.

Quindi, ecco un'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 
} 
+0

Mi piace la tua interpretazione del messaggio di errore. Mi chiedo se c'è un modo per far sì che il parser stampi ciò che sta cercando di abbinare mentre procede. Ciò renderebbe la risoluzione dei problemi più facile. – huynhjl

+4

Puoi racchiudere qualsiasi riferimento a una produzione che appare nel lato destro di un'altra produzione in 'log (...)' e otterrai l'output di traccia ogni volta che l'analisi tenta di far corrispondere quel non-terminale. Ad esempio, per registrare un particolare tentativo di abbinamento di 'model' sostituire quel riferimento non terminale in una regola con' log (model) '. –

+0

Ah, sì, ora vedo il problema - 'end' veniva letto in' functionName', poiché * era * un nome di funzione valido. Ho implementato le tue modifiche e ora funziona bene, grazie mille. – mipadi

0

È possibile override il protected val whiteSpace (un Regex) la cui impostazione predefinita è """\s+""".r o override il metodo protected def handleWhiteSpace(...) se avete bisogno di un maggiore controllo rispetto si ottiene facilmente con un'espressione regolare. Entrambi questi membri si trovano in RegexParsers, che è la classe base per JavaTokenParsers.

+0

sto sovrascrivendo 'whiteSpace' (vedere il codice di cui sopra), ma che si traduce ancora in un errore. – mipadi

+0

Sì, capisco. Prova a cambiare 'opt (eol)' in 'eol *' (o, ugualmente, 'rep (eol)'). –

+0

Non ha funzionato. Ha provocato lo stesso errore. – mipadi

Problemi correlati