Scrivere un compilatore del compilatore: informazioni sull'uso e sulle funzionalità


10

Questo fa parte di una serie di domande che si concentrano sul progetto gemello del Progetto Abstraction, che mira a sottrarre i concetti utilizzati nella progettazione del linguaggio sotto forma di un quadro. Il progetto gemello si chiama OILexer, che mira a costruire un parser da file grammaticali, senza l'uso dell'iniezione di codice sulle partite.

Alcune altre pagine associate a queste domande, relative alla tipizzazione strutturale, possono essere visualizzate qui e facilità d'uso, trovate qui . Il meta-argomento associato a una richiesta di informazioni sul framework e il luogo adatto per la pubblicazione è disponibile qui .

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 utilizza DFA per discernere i percorsi in avanti (simile a LL (*) di ANTLR 4, quindi I ho pensato di aprirlo per ottenere informazioni.

In un compilatore parser, quali tipi di funzionalità sono ideali?

Finora ecco una breve panoramica di ciò che è implementato:

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

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

Per chiunque sia interessato, ho caricato una bella stampa della forma originale del progetto T * y♯ . Ogni file dovrebbe collegarsi ad 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 pubblicare di conseguenza.

Modifica 5-14-2013 : ho scritto codice per creare grafici GraphViz per le macchine a stati in una determinata lingua. Ecco un digraph GraphViz di AssemblyPart . I membri collegati nella descrizione della lingua dovrebbero avere un ruleename.txt nella loro cartella relativa con il digraph per quella regola. Parte della descrizione della lingua è cambiata da quando ho pubblicato l'esempio, ciò è dovuto alla semplificazione delle cose sulla grammatica. Ecco un'interessante immagine di graphviz .


8
Wall o 'text. Non prenderla nel modo sbagliato, apprezzo un problema completamente spiegato. In questo caso è semplicemente un po 'troppo prolisso. Da quello che ho raccolto, stai chiedendo quali funzionalità dovrebbero essere incluse in un parser grammaticale o come crearne una senza ricominciare da zero? Modifica per rispondere alle seguenti domande (non è necessario riscrivere, aggiungi semplicemente alla fine in sintesi): Qual è il tuo problema? Quali sono i vincoli a cui sei legato in possibili soluzioni al tuo problema (deve essere veloce, deve essere LL *, ecc.)?
Neil

1
Chiedo informazioni dettagliate sul set di funzionalità. L'attenzione è rivolta alla facilità d'uso. La difficoltà sta nel trovare qualcuno che non conosce il progetto, approfondire il progetto in modo da essere informato sulla sua attenzione. Non sto chiedendo "come fare", sto chiedendo in relazione all'usabilità. Sono apprezzati suggerimenti su come tagliare la domanda.
Allen Clark Copeland Jr

1
Per me, non è ovvio di cosa tratta il progetto. Ad esempio, sin dai tempi di Yacc, abbiamo visto molti generatori di parser. Cosa c'è di diverso nel tuo OILexer? Cosa c è di nuovo?
Ingo,

1
L'obiettivo di questo progetto è semplificare la generazione di parser. Simile sì, a YACC / Bison e FLEX / LEX. La differenza principale è evitare la complessità implicata in tali programmi. Mantenere le cose semplici e al punto è l'obiettivo principale. Questo è il motivo per cui ho creato un formato privo di sezioni dispari, ma l'obiettivo è renderlo simile alla normale programmazione: specifico solo per lo sviluppo del linguaggio. I token sono definiti usando ': =' dopo il loro nome, le regole sono definite usando :: = dopo il loro nome. I modelli usano '<' e '>' per i loro argomenti seguiti da ":: =" poiché condividono la sintassi delle regole.
Allen Clark Copeland Jr

3
Questa infernale attenzione all'analisi sembra fuori luogo; è un problema ben risolto, e difficilmente dona ciò di cui hai bisogno per elaborare i linguaggi di programmazione. Google per il mio saggio sulla "vita dopo l'analisi".
Ira Baxter,

Risposte:


5

Questa è un'ottima domanda

Ho lavorato su un sacco di analisi di recente 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 anche una GUI o un'interfaccia simile a BNF, ma quella programmatica è la chiave, perché puoi riutilizzare i tuoi strumenti, IDE, analisi statica, test, strutture di astrazione del linguaggio, familiarità del programmatore, generatore di documentazione, processo di compilazione, ecc. Inoltre, puoi giocare interattivamente con piccoli parser, il che riduce drasticamente la curva di apprendimento. Questi motivi lo collocano in cima al mio elenco di "caratteristiche importanti".

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

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

  • astrazione. Non puoi mai integrare 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 la tua # 2 (previsione del futuro). Penso che aiuti a generare buoni rapporti sugli errori. È utile per qualcos'altro?

  • supporto per la creazione di un albero di analisi quando si verifica l'analisi, forse:

    • un albero di sintassi concreto, per il quale la struttura dell'albero corrisponde direttamente alla grammatica e include informazioni di layout per la segnalazione degli errori delle fasi successive. In questo caso, il client non dovrebbe fare nulla per ottenere la giusta struttura ad albero - dovrebbe dipendere direttamente dalla grammatica.
    • un albero di sintassi astratto. In questo caso, l'utente è in grado di giocherellare con qualsiasi e tutti gli alberi di analisi
  • una specie di registrazione. Non ne sono sicuro; magari 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 vogliano usare i token per generare documentazione HTML.

  • capacità di trattare lingue sensibili al contesto. Non sono sicuro di quanto sia importante questo - in pratica, sembra più pulito analizzare un superset di una lingua con una grammatica senza contesto, quindi controllare i vincoli sensibili al contesto in ulteriori passaggi successivi.

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

D'altra parte, non trovo la correzione degli errori particolarmente importante, anche se non sono aggiornato sui progressi attuali. I problemi che ho notato sono che le potenziali correzioni fornite dagli strumenti sono: 1) troppo numerose e 2) non corrispondono agli errori effettivi commessi, quindi non sono poi così utili. Speriamo che questa situazione migliorerà (o forse lo abbia già fatto).


Ho modificato il corpo della domanda per includere un collegamento a PrecedenceHelper nel punto elenco che legge "Modelli". Consente tuple di array di parametri, quindi se si dispone di quattro parametri, ogni array di parametri, il modello deve essere utilizzato in set di argomenti di quattro.
Allen Clark Copeland Jr

Il motivo principale per cui si dovrebbe costruire il CST è il layout generale del file analizzato. Se si desidera stampare piuttosto il documento, la soluzione migliore è utilizzare un CST perché gli AST come il loro nome implica mancanza di informazioni per gestire lo spazio dispari che un CST catturerebbe. Trasformare un CST è di solito abbastanza semplice se è un buon CST.
Allen Clark Copeland Jr

Ho modificato di nuovo la domanda sull'argomento delle funzioni integrate disponibili per l'uso.
Allen Clark Copeland Jr

Penso di non aver fatto un ottimo lavoro esprimendo il mio punto di vista su modelli / funzioni: intendevo dire che, poiché non si può mai avere abbastanza, un sistema non dovrebbe cercare di capirli in anticipo: l'utente deve essere in grado di creare il suo stesso.

1
Ho trovato un approccio particolarmente utile per la segnalazione degli errori con backtracking infinito (Packrat): ogni regola di produzione è annotata con messaggi di errore (formulati come "blah-blah-blah previsti") e in caso di errore tale messaggio viene archiviato nello stream allo stesso modo come token memorizzati. Se tutti gli errori non sono recuperabili (l'analisi è terminata prima di raggiungere la fine dello stream), il messaggio di errore più a destra (o una raccolta di tali messaggi) è il più appropriato. Questa è la cosa più semplice da fare, ovviamente ci sono modi per perfezionarla ulteriormente con più annotazioni.
SK-logic,

5

Non ho esperienza nella progettazione del linguaggio, ma una volta ho provato a scrivere un parser, mentre creavo e IDE per un motore di gioco.

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

Allora cosa ti serve? Penso che piuttosto che sapere cosa è / non è valido in un determinato momento, devi sapere come prendere ciò che non è valido e apportare una modifica minima per renderlo valido - in modo da poter continuare ad analizzare senza generare falsi errori relativi alla tua discesa ricorsiva confondendoti. Essere in grado di costruire 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 consuma il parser.

Mi rendo conto che potrei suggerire qualcosa di veramente difficile, o stupidamente ovvio, scusami se è così. Se questo non è il genere di cose che stai cercando, eliminerò felicemente la mia risposta.


Questa è una delle cose che ho intenzione di fare. Per aiutarmi con la mia conoscenza del dominio, un mio amico mi ha suggerito di scrivere a mano un vero parser per capire come automatizzarlo. Una cosa che ho capito abbastanza rapidamente: i parser sono complessi e ci sono cose che facciamo a mano che semplificano il processo. Regole e modelli condividono la stessa sintassi; tuttavia, ci sono elementi del linguaggio che sono validi nei modelli ma non nelle regole, ci sono stati interni che gestiscono questo compito. Il che mi ha fatto venire in mente un'idea: le regole dovrebbero essere in grado di specificare gli aiuti al percorso per facilitare la condivisione delle sotto-regole.
Allen Clark Copeland Jr

... questo dovrebbe essere abbastanza semplice da riportare nell'automazione, ma richiederebbe che le automazioni abbiano condizioni basate sullo stato. Ci lavorerò un po 'e tornerò da te. ANTLR usa automazioni a stati finiti per gestire cicli di dire: "T" *, dove come lo userò per gestire la maggior parte del processo di analisi poiché le riduzioni dovrebbero essere più pulite come stati quando ci sono più di 800 variazioni in una regola (questo si gonfia rapidamente come codice spaghetti in forma if / else standard.)
Allen Clark Copeland Jr

0

La grammatica non deve avere restrizioni come "non hanno lasciato regole ricorsive". È ridicolo che gli strumenti ampiamente utilizzati oggi abbiano questo e possano capire solo succhiare le grammatiche LL - quasi 50 anni dopo che yacc ha fatto bene.

Un esempio per la corretta ricorsione (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 "rifattorizzato" a qualcos'altro, ma in entrambi i casi, il tipo specifico di ricorsione è solo il modo "giusto" per scriverlo, poiché gli elenchi (nella lingua di esempio) sono ricorsivi a destra mentre l'applicazione delle funzioni viene lasciata ricorsiva .

Ci si può aspettare da strumenti avanzati che supportano il modo naturale di scrivere le cose, invece di richiedere di "rifattorizzare" tutto ciò che deve essere ricorsivo sinistro / destro dallo strumento su cui si impone.


Sì, è bello non avere restrizioni arbitrarie del genere. Tuttavia, qual è un esempio di ricorsione a sinistra che non può essere sostituito con un operatore di ripetizione (come regex *o +quantificatori)? Ammetto liberamente che le mie conoscenze in quest'area sono limitate, ma non ho mai incontrato un uso della ricorsione di sinistra che non potesse essere riformulato in ripetizione. E ho trovato anche la versione ripetitiva più chiara (anche se questa è solo una preferenza personale).

1
@MattFenwick Vedi la mia modifica. Nota come la sintassi direttamente porta ad azioni semantiche semplici e naturali (ad es.) Per la creazione di un albero di sintassi. Mentre con la ripetizione, (che non è disponibile in Yacc, a proposito), immagino che spesso devi controllare se hai un elenco vuoto, un singleton, ecc.
Ingo,

Grazie per la risposta. Penso di capire meglio ora - Preferirei scrivere quegli esempi come list = sepBy1(',', elem)e funapp = term{+}(e ovviamente sepBy1e +verrebbero implementati in termini di ricorsione sinistra / destra e produrre alberi di sintassi standard). Quindi non è che penso che la ricorsione a sinistra e a destra siano cattive, è solo che sento che sono di basso livello e vorrei usare un'astrazione di livello superiore ove possibile per rendere le cose più chiare. Grazie ancora!

1
Prego @MattFenwick. Ma allora è forse una questione di gusti. Per me, la ricorsione è (almeno nel contesto delle lingue, che sono tutte intrinsecamente ricorsive o totalmente poco interessanti) il modo più naturale di pensarci. Inoltre, un albero è una struttura dati ricorsiva, quindi non vedo la necessità di ricorrere a iterazione ricorsione simulazione. Ma, naturalmente, le preferenze sono diverse.
Ingo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.