2013-04-09 7 views
11

Modifica: Ho fatto una prima versione, che Eike mi ha aiutato ad avanzare un po 'su di esso. Ora sono bloccato a un problema più specifico, che descriverò qui sotto. Si può avere uno sguardo alla domanda originale nella historyMigliorare i messaggi di errore con pyparsing


sto usando pyparsing di analizzare un piccolo linguaggio utilizzato per richiedere dati specifici da un database. Dispone di numerose parole chiave, operatori e tipi di dati, oltre alla logica booleana.

Sto cercando di migliorare il messaggio di errore inviato all'utente quando fa un errore di sintassi, poiché quello corrente non è molto utile. Ho progettato un piccolo esempio, simile a quello che sto facendo con la lingua di cui sopra, ma molto più piccolo:

#!/usr/bin/env python        

from pyparsing import * 

def validate_number(s, loc, tokens): 
    if int(tokens[0]) != 0: 
     raise ParseFatalException(s, loc, "number musth be 0") 

def fail(s, loc, tokens): 
    raise ParseFatalException(s, loc, "Unknown token %s" % tokens[0]) 

def fail_value(s, loc, expr, err): 
    raise ParseFatalException(s, loc, "Wrong value") 

number = Word(nums).setParseAction(validate_number).setFailAction(fail_value) 
operator = Literal("=") 

error = Word(alphas).setParseAction(fail) 
rules = MatchFirst([ 
    Literal('x') + operator + number, 
]) 

rules = operatorPrecedence(rules | error , [ 
    (Literal("and"), 2, opAssoc.RIGHT), 
]) 

def try_parse(expression): 
    try: 
     rules.parseString(expression, parseAll=True) 
    except Exception as e: 
     msg = str(e) 
     print("%s: %s" % (msg, expression)) 
     print(" " * (len("%s: " % msg) + (e.loc)) + "^^^") 

Quindi, in pratica, le uniche cose che possiamo fare con questo linguaggio, sta scrivendo una serie di x = 0, uniti con and e parentesi.

Ora, ci sono casi in cui vengono utilizzati and e parentesi, in cui la segnalazione degli errori non è molto buona. Considerate i seguenti esempi:

>>> try_parse("x = a and x = 0") # This one is actually good! 
Wrong value (at char 4), (line:1, col:5): x = a and x = 0 
               ^^^ 
>>> try_parse("x = 0 and x = a") 
Expected end of text (at char 6), (line:1, col:1): x = 0 and x = a 
                 ^^^ 
>>> try_parse("x = 0 and (x = 0 and (x = 0 and (x = a)))") 
Expected end of text (at char 6), (line:1, col:1): x = 0 and (x = 0 and (x = 0 and (x = a))) 
                 ^^^ 
>>> try_parse("x = 0 and (x = 0 and (x = 0 and (x = 0)))") 
Expected end of text (at char 6), (line:1, col:1): x = 0 and (x = 0 and (x = 0 and (xxxxxxxx = 0))) 
                 ^^^ 

In realtà, sembra che se il parser non è in grado di analizzare (e parse qui è importante) qualcosa dopo un and, non produce più buoni messaggi di errore: (

E intendo parse, in quanto se è in grado di analizzare 5, ma la "validazione" fallisce nell'azione parse, produce ancora un buon messaggio di errore. ma, se non in grado di analizzare un numero valido (come a) o una parola chiave valida (come xxxxxx), arresta il prodotto cingendo i giusti messaggi di errore.

Qualche idea?

+0

avere un'azione convalida parse per i nomi delle variabili troppo. Oppure prendi un nome con tutte le variabili come "Word (alphas)" e applica un'azione di analisi, che genera sempre un'eccezione. – Eike

+0

In alternativa è possibile eseguire la convalida di un livello. Avere un parser 'Word (alphas) ​​-" == "- Word (nums)' e mettere su di esso un'azione di analisi più complessa, che cerca nomi di variabili legali e garantisce la correttezza dei numeri. – Eike

+0

Al momento, che sarebbe una soluzione di ultima resort :) –

risposta

9

Pyparsing avrà sempre messaggi di errore un po 'cattivi, perché fa il backtrack. Il messaggio di errore viene generato nell'ultima regola che tenta il parser. Il parser non può sapere dove si trova realmente l'errore, sa solo che non esiste una regola corrispondente.

Per i messaggi di errore corretti è necessario un parser che si arrenda presto. Questi parser sono meno flessibili del Pyparsing, ma la maggior parte dei linguaggi di programmazione convenzionali può essere analizzata con tali parser. (C++ e Scala IMHO non possono.)

Per migliorare i messaggi di errore in Pyparsing utilizzare l'operatore -, funziona come l'operatore +, ma non esegue il backtrack. Si potrebbe utilizzare in questo modo:

assignment = Literal("let") - varname - "=" - expression 

Ecco un piccolo articolo sul miglioramento della segnalazione degli errori, per autore di Pyparsing:

http://pyparsing.wikispaces.com/message/view/home/30875955#30901387

Modifica

Si potrebbe anche generare buona errore messaggi per i numeri non validi nelle azioni di analisi che eseguono la convalida. Se il numero non è valido, si genera un'eccezione che non viene rilevata da Pyparsing. Questa eccezione può contenere un buon messaggio di errore.

azioni

Parse possono avere tre argomenti [1]:

  • s = la stringa originale di essere analizzati (vedi nota sotto)
  • loc = la posizione della sottostringa corrispondente
  • toks = una lista dei gettoni abbinate, confezionato come ParseResults oggetto

Esistono inoltre tre metodi di supporto utili per creare i più messaggi di errore [2]:

0.123.
  • lineno(loc, string) - funzione per fornire il numero di riga della posizione all'interno della stringa; la prima riga è la linea 1, i newline iniziano nuove righe.
  • col(loc, string) - funzione per fornire il numero di colonna della posizione all'interno della stringa; la prima colonna è la colonna 1, le nuove righe riportano il numero di colonna a 1.
  • line(loc, string) - funzione per recuperare la riga di testo che rappresenta lineno(loc, string). Utile quando si stampano messaggi diagnostici per eccezioni.

La vostra azione di convalida parse sarebbe allora come questo:

def validate_odd_number(s, loc, toks): 
    value = toks[0] 
    value = int(value) 
    if value % 2 == 0: 
     raise MyFatalParseException(
      "not an odd number. Line {l}, column {c}.".format(l=lineno(loc, s), 
                   c=col(loc, s))) 

[1] http://pythonhosted.org/pyparsing/pyparsing.pyparsing.ParserElement-class.html#setParseAction

[2] http://pyparsing.wikispaces.com/HowToUsePyparsing

Modifica

Qui [3] è una versione migliorata della domanda corrente (2013 -4-10) script. Ottiene correttamente gli errori di esempio, ma altri errori sono indicati nella posizione sbagliata. Credo che ci siano errori nella mia versione di Pyparsing ('1.5.7'), ma forse non capisco come funziona Pyparsing. I problemi sono:

  • ParseFatalException sembra non essere sempre fatale. Lo script funziona come previsto quando utilizzo la mia eccezione.
  • Il - operatore sembra non funzionare.

[3] http://pastebin.com/7E4kSnkm

+0

Aiuta solo leggermente: non un numero pari (a char 0), (riga: 1, colonna: 1): x == 1 ey == 1 (mentre l'errore è su "y") –

+0

Sì, è una zona difficile, sto lottando per ottenere buoni messaggi di errore anche – Eike

+0

Un problema IMHO è 'operatorPrecedence' riscrive' rules' e restituisce un parser complicato, che può realmente analizzare l'expr ESSIONE. La qualità dei messaggi di errore dipende principalmente dall'implementazione di 'operatorPrecedence' e meno dal codice. – Eike