Scrittura di un compilatore Compilatore - Analisi dell'uso e delle caratteristiche

10

This is part of a series of questions which focuses on the sister project to the Abstraction Project, which aims to abstract the concepts used in language design in the form of a framework. The sister project is called OILexer, which aims to construct a parser from grammar files, without the use of code injection on matches.

Some other pages associated to these questions, related to structural typing, can be viewed here, and ease of use, found here. The meta-topic associated to an inquiry about the framework and the proper place to post can be found here.

Sto arrivando al punto in cui sto per iniziare a estrarre l'albero di analisi da una determinata grammatica, seguito da un parser di discesa ricorsiva che usa DFA per discernere i percorsi in avanti (simile al LL di ANTLR 4 (*)) , quindi ho pensato di aprirlo per ottenere informazioni.

In un compilatore di parser, quali sono le caratteristiche ideali?

Fin qui ecco una breve panoramica di ciò che è implementato:

  1. Modelli
  2. Previsione anticipata, sapendo cosa è valido in un determinato punto.
  3. Regola "Deliteralizzazione" prendendo i letterali all'interno delle regole e risolvendo da quale token provengono.
  4. Automazione non deterministico
  5. Automatologia deterministica
  6. Semplice macchina a stato lessicale per il riconoscimento di token
  7. Metodi di automazione dei token:
    • Scansione - utile per i commenti: Commento:="/ *" Scansione ("* /");
    • Sottrai - Utile per identificatori: Identificatore: = Sottrai (IdentifierBody, Parole chiave);
      • Assicura che l'identificatore non accetti le parole chiave.
    • Codifica: codifica un'automazione come una serie di conteggi X delle transizioni di base N.
      • UnicodeEscape:="\\ u" BaseEncode (IdentifierCharNoEscape, 16, 4);
        • Effettua una escape unicode in esadecimale, con 4 transizioni esadecimali. La differenza tra questo e: [0-9A-Fa-f] {4} è l'automazione risultante con Encode limita l'insieme consentito di valori esadecimali allo scope di IdentifierCharNoEscape. Quindi se lo dai \ u005c, la versione codificata non accetterà il valore. Cose come questa hanno un avvertimento serio: usare con parsimonia. L'automazione risultante potrebbe essere piuttosto complessa.

Ciò che non è implementato è la generazione di CST, ho bisogno di regolare le automazioni deterministiche per mantenere il contesto appropriato per farlo funzionare.

Per chiunque sia interessato, ho caricato una bella versione stampata del modulo originale del progetto T * y♯ . Ogni file dovrebbe collegarsi a ogni altro file, ho iniziato a collegare le singole regole per seguirli, ma ci sarebbe voluto troppo tempo (sarebbe stato più semplice automatizzare!)

Se è necessario più contesto, si prega di postare di conseguenza.

Modifica 5-14-2013 : Ho scritto codice per creare grafici GraphViz per le macchine di stato in una determinata lingua. Ecco un digraph GraphViz di AssemblyPart . I membri collegati nella descrizione della lingua devono avere un rulename.txt nella relativa cartella con il digraph per quella regola. Alcune delle descrizioni della lingua sono cambiate da quando ho pubblicato l'esempio, questo è dovuto alla semplificazione delle cose sulla grammatica. Ecco un interessante immagine graphviz .

    
posta Alexander Morou 12.04.2017 - 09:31
fonte

3 risposte

5

Questa è una domanda eccellente.

Di recente ho lavorato su parecchie analisi e IMHO alcune delle caratteristiche principali sono:

  • un'API programmatica - quindi può essere utilizzata all'interno di un linguaggio di programmazione, idealmente semplicemente importando una libreria. Può avere un'interfaccia GUI o BNF, ma quella programmatica è la chiave, perché puoi riutilizzare i tuoi strumenti, IDE, analisi statica, test, strutture per l'astrazione del linguaggio, familiarità del programmatore, generatore di documentazione, processo di costruzione, ecc. Inoltre, puoi giocare interattivamente con piccoli parser, che riduce drasticamente la curva di apprendimento. Questi motivi lo collocano nella parte superiore del mio elenco di "funzioni importanti".

  • segnalazione degli errori, come menzionato da @guysherman. Quando viene rilevato un errore, voglio sapere dove si trovava l'errore e cosa stava succedendo quando è successo. Sfortunatamente, non sono stato in grado di trovare buone risorse per spiegare come generare errori decenti quando il backtracking entra in gioco. (Anche se il commento della nota @ Sk-logic sotto).

  • risultati parziali. Quando l'analisi non riesce, voglio essere in grado di vedere ciò che è stato analizzato correttamente dalla parte dell'input che era prima della posizione dell'errore.

  • astrazione. Non puoi mai costruire abbastanza funzioni e l'utente avrà sempre bisogno di più, quindi cercare di capire tutte le possibili funzioni in anticipo è destinato a fallire. È questo che intendi per modelli?

  • Sono d'accordo con il tuo n. 2 (previsione anticipata). Penso che aiuti a generare buoni report di errore. È utile per qualcos'altro?

  • supporto per la costruzione di un albero di analisi in caso di parsing, forse:

    • un albero di sintassi concreto, per il quale la struttura dell'albero corrisponde direttamente alla grammatica e include le informazioni di layout per la segnalazione degli errori delle fasi successive. In questo caso, il client non dovrebbe fare qualsiasi cosa per ottenere la struttura ad albero corretta - dovrebbe dipendere direttamente dalla grammatica.
    • un albero di sintassi astratto. In questo caso, l'utente è in grado di giocherellare con tutti gli alberi di analisi
  • un tipo di registrazione. Non sono sicuro di questo; forse per mostrare una traccia delle regole che il parser ha provato, o per tenere traccia dei token spazzatura come spazi bianchi o commenti, nel caso (per esempio) si vogliono usare i token per generare documentazione HTML.

  • capacità di gestire i linguaggi sensibili al contesto. Non è sicuro quanto sia importante questo, o - in pratica, sembra più pulito analizzare un superset di una lingua con una grammatica context-free, quindi controllare i vincoli sensibili al contesto in ulteriori passaggi successivi.

  • messaggi di errore personalizzati, in modo da poter ottimizzare i rapporti di errore in situazioni specifiche e forse comprendere e risolvere più rapidamente i problemi.

D'altra parte, non ritengo che la correzione degli errori sia particolarmente importante, anche se non sono aggiornato sui progressi attuali. I problemi che ho notato sono che le potenziali correzioni che gli strumenti forniscono sono: 1) troppo numerose, e 2) non corrispondono agli errori reali fatti, e quindi non sono così utili. Speriamo che questa situazione migliorerà (o forse lo ha già fatto).

    
risposta data 17.05.2013 - 23:58
fonte
5

Non ho esperienza nella progettazione della lingua, ma ho provato a scrivere un parser una volta, mentre stavo creando e IDE per un motore di gioco.

Qualcosa che è importante per i tuoi utenti finali, a mio avviso, sono messaggi di errore che hanno senso. Non è un punto particolarmente sconvolgente, lo so, ma dopo averlo fatto indietro, una delle principali implicazioni di questo è che devi essere in grado di evitare i falsi positivi. Da dove vengono i falsi positivi? Vengono dal parser che cade al primo errore e non si riprende mai del tutto. C / C ++ è noto per questo (anche se i nuovi compilatori sono un po 'più intelligenti).

Quindi cosa ti serve invece? Penso che piuttosto che sapere cosa è / non è valido in un punto, è necessario sapere come prendere ciò che non è valido e apportare una modifica minima per renderlo valido - in modo che si possa continuare l'analisi senza generare errori falsi relativi alla tua discesa ricorsiva che si confonde. Essere in grado di creare un parser in grado di generare queste informazioni non solo ti dà un parser molto robusto, ma apre alcune fantastiche funzionalità per il software che utilizza il parser.

Capisco che potrei suggerire qualcosa di veramente difficile, o stupidamente ovvio, mi dispiace se è così. Se questo non è il tipo di cosa che stai cercando, cancellerò felicemente la mia risposta.

    
risposta data 10.05.2013 - 04:32
fonte
0

La grammatica non deve avere restrizioni come "non avere regole ricorsive lasciate". È ridicolo che gli strumenti oggi ampiamente utilizzati abbiano questo e possano capire solo le grammatiche LL di successo - quasi 50 anni dopo che yacc ha funzionato correttamente.

Un esempio per la ricorsione a destra (usando la sintassi yacc):

list: 
      elem                  { $$ = singleton($1); }
    | elem ',' list         { $$ = cons($1, $2);  }
    ;

Un esempio per la ricorsione a sinistra (usando la sintassi yacc):

funapp:
    term                    { $$ = $1; }
    | funapp term           { $$ = application($1, $2); }
    ;

Ora, questo potrebbe forse essere "refactored" a qualcos'altro, ma in entrambi i casi, il tipo specifico di ricorsione è solo il modo "giusto" per scrivere questo, poiché gli elenchi (nella lingua di esempio) sono corretti ricorsivi mentre l'applicazione della funzione è lasciato ricorsivo.

Ci si può aspettare dagli strumenti avanzati che supportano il modo naturale di scrivere cose, invece di richiedere a un "refactoring" di essere tutto ciò che è ricorsivo sinistro / destro su cui lo strumento si impone.

    
risposta data 10.12.2013 - 15:56
fonte