Qual è la relazione tra linguaggi di programmazione, espressioni regolari e linguaggi formali


25

Ho cercato in rete una risposta a questa domanda e sembra che tutti conoscano implicitamente la risposta tranne me. Presumibilmente questo è perché le uniche persone a cui importa sono quelle che hanno avuto un'istruzione terziaria sull'argomento. Io, d'altra parte, sono stato gettato nel profondo per un incarico di scuola superiore.

La mia domanda è: come sono esattamente i linguaggi di programmazione correlati ai linguaggi formali? Ovunque legga, si dice qualcosa sulla falsariga di "linguaggi formali per definire la grammatica dei linguaggi di programmazione".

Da quello che sono stato in grado di raccogliere, un linguaggio formale è una serie di regole di produzione che si applicano a un insieme specifico di simboli (l'alfabeto del linguaggio). Queste regole di produzione definiscono un insieme di trasformazioni, come:

b -> a

aaa->c

Questo può essere applicato in modo tale che:

abab->aaaa aaaa-> ca

Proprio come una nota a margine, se definiamo che l'alfabeto del nostro linguaggio formale come {a, b, c}, allora aeb sono non terminali e c è terminale in quanto non può essere trasformato (per favore correggimi se sbaglio su quella).

Quindi, dato tutto ciò, come diamine si applica ai linguaggi di programmazione? Spesso si afferma anche che regex viene utilizzato per analizzare una lingua nella sua forma di testo per garantire che la grammatica sia corretta. Questo ha senso. Quindi si afferma che i regex sono definiti da linguaggi formali. Regex restituisce vero o falso (almeno nella mia esperienza) a seconda che gli automi a stati finiti che rappresentano la regex raggiungano il punto obiettivo. Per quanto posso vedere, ciò non ha nulla a che fare con le trasformazioni *.

Per la compilazione del programma stesso, suppongo che un linguaggio formale sarebbe in grado di trasformare il codice in codice consecutivamente di livello inferiore, raggiungendo infine l'assemblaggio tramite un insieme complesso di regole, che l'hardware potrebbe quindi comprendere.

Quindi sono cose dal mio punto di vista confuso. Probabilmente ci sono molte cose fondamentalmente sbagliate in quello che ho detto, ed è per questo che sto chiedendo aiuto.


* A meno che non si consideri qualcosa di simile (a|b)*b*c->truea una regola di produzione, nel qual caso la regola richiede automi a stati finiti (cioè: regex). Questo non ha senso, come abbiamo appena detto


2
Stai confidando grammatiche formali con linguaggi formali . Una grammatica è un insieme di regole di riscrittura che descrive una lingua. La lingua è l'insieme di stringhe descritte dalla grammatica. Quindi una grammatica è un'alternativa a un'espressione regolare: è un modo per descrivere una lingua.
reinierpost,

@reinierpost Hai perfettamente ragione, dopo aver guardato gli appunti delle lezioni universitarie, ho ricevuto alcune di queste informazioni, vedo il mio errore.
Zwander,

Ho condiviso la tua confusione quando ho iniziato. Naturalmente, anche le grammatiche formano una lingua, così come le espressioni regolari. Ma la teoria del linguaggio formale è dedicata allo studio di come può essere descritta la sintassi (forma) delle lingue, quindi di solito usa il termine "linguaggio" per ciò che viene descritto, non per ciò che lo sta descrivendo.
reinierpost,

Risposte:


24

Chiunque ti dicesse che le espressioni regolari sono usate per analizzare il codice stava diffondendo la disinformazione. Classicamente (non so fino a che punto ciò sia vero nei compilatori moderni), l'analisi del codice - la conversione del codice dal testo in un albero di sintassi - è composta da due fasi:

  1. Analisi lessicale: elabora il testo non elaborato in blocchi come parole chiave , costanti numeriche , stringhe , identificatori e così via. Questo è implementato classicamente usando una sorta di macchina a stati finiti, simile nello spirito a un automa deterministico finito (DFA).

  2. Parser: esegui dopo l'analisi lessicale e converte il testo non elaborato in un albero di sintassi con annotazioni. La grammatica dei linguaggi di programmazione è (a prima approssimazione) libera dal contesto (in realtà, è necessario un sottoinsieme ancora più rigoroso) e ciò consente ad alcuni algoritmi efficienti di analizzare il codice lessificato in un albero di sintassi. Questo è simile al problema di riconoscere se una determinata stringa appartiene a una grammatica libera dal contesto, con la differenza che vogliamo anche la prova sotto forma di un albero di sintassi.

Le grammatiche per i linguaggi di programmazione sono scritte come grammatiche senza contesto e questa rappresentazione viene utilizzata dai generatori di parser per costruire parser veloci per loro. Un semplice esempio potrebbe avere alcune DICHIARAZIONI non terminali e quindi le regole del modulo DICHIARAZIONE IF-STATEMENT, dove IF-STATEMENT if CONDITION quindi BLOCK endif o simili (dove BLOCK STATEMENT | BLOCK; STATEMENT, per esempio). Di solito queste grammatiche sono specificate nella forma Backus-Naur (BNF).

Le specifiche effettive dei linguaggi di programmazione non sono prive di contesto. Ad esempio, una variabile non può apparire se non fosse stata dichiarata in molte lingue e le lingue con una tipizzazione rigorosa potrebbero non consentire di assegnare un numero intero a una variabile di stringa. Il compito del parser è solo quello di convertire il codice non elaborato in un modulo più semplice da elaborare.

Dovrei menzionare che ci sono altri approcci come l' analisi ricorsiva della discesa che in realtà non genera un albero di analisi, ma elabora il codice mentre lo analizza. Sebbene non si preoccupi di generare l'albero, sotto tutti gli altri aspetti opera allo stesso livello sopra descritto.


Grazie per la tua risposta, ha sicuramente chiarito alcune cose. Ha anche sollevato molte altre domande. Devo aggiungerli alla mia domanda o farli qui?
Zwander,

5
@Zwander - in realtà, nessuno dei due. Su questo sito, vogliamo che tu scriva una domanda per domanda. Non è un forum di discussione: è un sito di domande e risposte e vogliamo che ogni domanda si trovi in ​​un thread separato. Se questa risposta solleva una nuova domanda, dedica un po 'di tempo alla ricerca di quella domanda di follow-up e se non riesci a trovare una risposta in nessuna delle fonti standard, pubblica una nuova domanda. (Ma assicurati di esaminare prima le risorse standard.)
DW

1
@DW Gotcha, evviva.
Zwander,

3
La prima delle due fasi menzionate viene in genere eseguita utilizzando espressioni regolari. Il formato di ciascun token è di solito dato da un'espressione regolare. Quelle espressioni regolari vengono compilate in un singolo DFA, il DFA viene quindi applicato al codice effettivo.
Kasperd,

2
@Zwander L'analisi ricorsiva della discesa è solo una tecnica di analisi. Può o meno generare un albero di analisi. In generale, l'analisi dell'algoritmo equivale allo sviluppo di una strategia computazionale per esplorare l'albero di sintassi implicito nel testo del programma. Questo albero di sintassi / analisi può o meno essere esplicitato nel processo, a seconda della strategia di compilazione (numero di fasi). Ciò che è necessario, tuttavia, è che alla fine vi sia almeno un'esplorazione dal basso verso l'alto dell'albero di analisi, sia esplicitato che lasciato implicito nella struttura di calcolo.
babou,

12

Queste sono alcune cose pesanti per un incarico di scuola superiore.

La risposta di Yuval Filmus è davvero buona, quindi questa è più una risposta supplementare per chiarire alcuni dei punti che ha sollevato.

Un linguaggio formale è una costruzione matematica. Il loro uso per i linguaggi di programmazione è solo uno dei tanti usi possibili; infatti, il linguista Noam Chomsky ha dato un contributo significativo alla prima teoria dei linguaggi formali. Ha inventato la Gerarchia di Chomsky, che classifica le lingue formali in normali, senza contesto, ecc. Le lingue formali vengono anche applicate in linguistica per descrivere la sintassi delle lingue naturali come l'inglese. Pensalo come i numeri reali: possiamo usare i numeri reali per descrivere sia cose concrete come la distanza da Los Angeles a New York, sia cose astratte come il rapporto tra la circonferenza di un cerchio e il suo diametro. Anche se entrambe queste cose esistono indipendentemente dai numeri reali, i numeri reali sono un sistema utile per descriverli. I linguaggi formali sono un sistema utile per descrivere sia l'inglese che il Python, poiché entrambi hanno un formato strutturato simile.

un'+B+c=dun'+B=d-cun'Bc come importi in dollari, ad esempio, e quindi l'equazione ha un significato.

Classicamente, un linguaggio di programmazione avrà due grammatiche: una grammatica lessicale e una grammatica sintattica. La grammatica lessicale si occupa di caratteri come lettere, punti e virgola, parentesi graffe e parentesi. Di solito è una grammatica regolare, quindi può essere espressa con espressioni regolari o un DFA o NFA. (Esistono prove nella teoria del linguaggio formale che mostrano che i tre sono equivalenti in termini di potere, nel senso che accettano lo stesso insieme di linguaggi.) La fase di lessing del compilatore o dell'interprete è una sorta di mini interprete per la grammatica linguistica regolare. Legge le regole della grammatica e, seguendo tali regole, raggruppa i singoli caratteri in token. Ad esempio, se la lingua ha ifun'affermazione che assomiglia più o meno a quella di C, il lexer potrebbe raggruppare i caratteri ie fnel singolo tokenIF, quindi cerca una parentesi aperta e genera un token OPEN_PAREN, quindi gestisci tutto ciò che è tra parentesi, quindi trova la parentesi chiusa e genera a CLOSE_PAREN. Quando il lexer ha finito di creare token, li consegna al parser, il che determina se i token in realtà formano dichiarazioni valide del linguaggio di programmazione. Quindi, se scrivi ip a == bin Python, il lexer fa del suo meglio per indovinare che tipo di token ipè (probabilmente verrebbe preso come identificatore dalla maggior parte dei lexer), e lo passa al parser, che si lamenta perché non puoi avere un identificatore in quella posizione.

un'B

Diamo un'occhiata alle regole grammaticali per l' ifaffermazione di Python . Questa è la regola:

if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]

Questa regola ci dice come il parser ifcapirà se una stringa di token inviati dal lexer è una dichiarazione. Qualsiasi parola tra virgolette singole deve apparire, proprio così, nel codice sorgente, quindi il parser cercherà la parola semplice if. Il parser tenterà quindi di associare alcuni token alla regola per test:

test: or_test ['if' or_test 'else' test] | lambdef

testè definito in termini di altre regole nella grammatica. Nota come testsi include anche nella sua definizione; si chiama definizione ricorsiva. È il grande potere dei linguaggi senza contesto che i linguaggi normali non hanno e consente di definire cose come i cicli nidificati per la sintassi del linguaggio di programmazione.

Se il parser riesce ad abbinare alcuni token test, proverà ad abbinare i due punti. Se ciò riesce, proverà ad abbinare altri token usando la regola per suite. La sezione ('elif' test ':' suite)*significa che possiamo avere un numero qualsiasi di ripetizioni del testo letterale elif, seguito da qualcosa che corrisponde test, seguito da due punti, seguito da qualcosa che corrisponde suite. Possiamo anche avere zero ripetizioni; l'asterisco alla fine significa "zero o quanti ne vogliamo".

Alla fine è ['else' ':' suite]. Quella parte è racchiusa tra parentesi quadre; ciò significa che possiamo avere zero o uno, ma non di più. Per corrispondere a questo, il parser deve corrispondere al testo letterale else, i due punti e quindi a suite. Ecco la regola per un suite:

suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT

È fondamentalmente un blocco in linguaggi simili al C. Dal momento che Python usa nuove righe e l'indentazione per cose meschine, le uscite lexer NEWLINE, INDENTe DEDENTgettoni a dire il parser in cui una nuova linea iniziata, in cui il codice ha iniziato a essere rientrato, e dove è stato restituito al livello esterno di rientro.

Se uno di questi tentativi di abbinamento fallisce, il parser segnala un errore e si ferma. Se l'analisi dell'intero programma ha esito positivo, il parser di solito avrà creato un albero di analisi come Yuval ha coperto nella sua risposta, e possibilmente una tabella di simboli e altre strutture di dati che memorizzano informazioni semantiche. Se la lingua viene digitata staticamente, il compilatore percorrerà l'albero di analisi e cercherà errori di tipo. Cammina anche l'albero di analisi per generare codice di basso livello (linguaggio assembly, codice byte Java, .Net Intermediate Language o qualcosa di simile) che è ciò che viene effettivamente eseguito.

Come esercizio, consiglierei di prendere la grammatica di alcuni linguaggi di programmazione con cui hai familiarità (di nuovo, Python , Java , ed ecco C # , Javascript , C ) e provare a analizzare a mano qualcosa di semplice, come forse x = a + b;o if (True): print("Yay!"). Se stai cercando qualcosa di più semplice, c'è anche una bella grammatica per JSON , che in pratica copre solo la sintassi dei letterali degli oggetti in Javascript (come {'a': 1, 'b': 2}). Buona fortuna, questa è roba da rompersi il cervello, ma risulta davvero interessante quando non sei in una scadenza folle.


So che non dovrei pubblicare "grazie" qui, ma applausi per aver dedicato del tempo a spiegare tutto questo. "Queste sono alcune cose pesanti per un incarico di scuola superiore." L'intenzione del compito è quella di sfogliare le righe e parlare di espressioni regolari, ma come appassionato studente di informatica volevo ottenere il quadro completo. L'intero argomento è affascinante.
Zwander,

1
@Zwander Mi sono appena laureato, e la maggior parte dei miei elettivi erano roba del genere. Ricordo di essere stato totalmente confuso eppure totalmente assorbito. Potresti anche apprezzare gli articoli sul design del compilatore menzionati in questo blog o i libri Introduzione alla teoria del calcolo , di Michael Sipser, e John C. Martin, Introduzione alle lingue e la teoria del calcolo . Puoi trovare copie usate a basso costo su Amazon. Entrambi rendono la teoria del linguaggio formale tanto semplice quanto sarà.
Tsleyson,

10

In poche parole

I linguaggi di programmazione sono composti da una sintassi che rappresenta il programma come stringhe di caratteri e una semantica che è il significato previsto del programma.

I linguaggi formali sono sintassi senza significato. Ha lo scopo di studiare la struttura di insiemi di stringhe definiti formalmente, senza di solito attribuire significato a tali stringhe.

L'espressione regolare e altri formalismi (come le grammatiche senza contesto) sono usati per definire linguaggi formali, usati come componente sintattica della programmazione e linguaggi naturali, cioè per rappresentare le frasi in modo strutturato. Altri meccanismi sono usati per mettere in relazione quella struttura con la semantica dei linguaggi di programmazione.

Molto qui è notevolmente semplificato, in particolare per quanto riguarda il linguaggio naturale.

Con molti più dettagli

Per rispondere alla tua domanda dovremmo iniziare dall'inizio. Una lingua nel solito senso è, in modo informale, un mezzo per trasmettere informazioni o idee. In una lingua, di solito si distingue tra sintassi e semantica. La semantica è ciò di cui vuoi parlare / scrivere. le informazioni che si desidera trasmettere. La sintassi è il mezzo che usi per trasmetterla, cioè una rappresentazione convenzionale che può essere scambiata tra persone, e ora anche tra persone e dispositivi, o tra dispositivi (computer).

In genere, userai la parola dogper trasmettere l'idea di un cane. La parola dogè composta da tre lettere, o un suono equivalente, e vuole essere la rappresentazione di un tipo di animale. L'idea chiave è che la comunicazione avviene attraverso la rappresentazione di ciò che deve essere comunicato. Le strutture di rappresentazione sono generalmente chiamate sintassi, mentre ciò che viene rappresentato è chiamato semantica. Questo vale più o meno per il linguaggio naturale e per i linguaggi di programmazione.

Le parole sono entità sintattiche per rappresentare concetti semantici più o meno elementari. Ma questi concetti elementari devono essere messi insieme in una varietà di modi per dare un significato più complesso. Scriviamo the dogper comunicare che intendiamo un cane specifico e the dog bites the catper trasmettere un'idea più complessa. Ma il modo in cui le parole sono organizzate deve essere fissato da regole, in modo da poter dire quale cane e gatto sta effettivamente mordendo l'altro.

Quindi abbiamo regole del genere sentence -> subject verb complementche dovrebbero corrispondere a frasi e ci dicono come sono articolate le idee associate a ciascuna parte. Queste regole sono regole sintattiche, poiché ci dicono come deve essere organizzata la rappresentazione del nostro messaggio. La subjectstessa può essere definita da una regola subject -> article noune così via.

2X+1=23X123

equation -> expression "=" expression  
expression -> expression "+" expression 
expression -> number

La struttura dei linguaggi di programmazione è la stessa. I linguaggi di programmazione sono semanticamente specializzati nell'esprimere calcoli da eseguire, piuttosto che esprimere problemi da risolvere, prove di teoremi o relazioni amichevoli tra animali. Ma questa è la differenza principale.

Le rappresentazioni utilizzate nella sintassi sono generalmente stringhe di caratteri o di suoni per le lingue parlate. La semantica di solito appartiene al dominio astratto, o forse alla realtà, ma è ancora astratta nei nostri processi di pensiero, o al dominio comportamentale dei dispositivi. La comunicazione implica la codifica dell'informazione / idea nella sintassi, che viene trasmessa e decodificata dal ricevitore. Il risultato viene quindi interpretato in qualsiasi modo dal destinatario.

Quindi ciò che vediamo del linguaggio è principalmente la sintassi e la sua struttura. L'esempio sopra è solo uno dei modi più comuni per definire le stringhe sintattiche e la loro organizzazione strutturale. Ce ne sono altri Per una determinata lingua, ad alcune stringhe può essere assegnata una struttura e si dice che appartenga alla lingua, mentre altre no.

Lo stesso vale per le parole. Alcune sequenze di lettere (o suoni) sono parole legittime, mentre altre no.

I linguaggi formali sono solo sintassi senza semantica. Definiscono con una serie di regole quali sequenze possono essere costruite, usando gli elementi base di un alfabeto. Quali sono le regole possono essere molto variabili, a volte complesse. Ma i linguaggi formali sono usati per molti scopi matematici oltre la comunicazione linguistica, sia per uso naturale che per linguaggi di programmazione. L'insieme di regole che definiscono le stringhe in una lingua è chiamato grammatica. Ma ci sono molti altri modi per definire le lingue.

In pratica, una lingua è strutturata su due livelli. Il livello lessicale definisce le parole costruite da un alfabeto di caratteri. Il livello sintattico definisce frasi o programmi costruiti da un alfabeto di parole (o più precisamente da famiglie di parole, in modo che rimanga un alfabeto finito). Questo è necessariamente un po 'semplificato.

La struttura delle parole è abbastanza semplice nella maggior parte delle lingue (programmazione o naturale) in modo che siano generalmente definite con quello che è generalmente considerato il tipo più semplice di linguaggio formale: le lingue normali. Possono essere definiti con espressioni regolari (regexp) e sono facilmente identificabili con dispositivi programmati chiamati automi a stati finiti. Nel caso dei linguaggi di programmazione, esempi di una parola sono un identificatore, un numero intero, una stringa, un numero reale, una parola riservata come if o repeat, un simbolo di punteggiatura o una parentesi aperta. Esempi di famiglie di parole sono identificatore, stringa, numero intero.

Il livello sintattico è generalmente definito da un tipo leggermente più complesso di linguaggio formale: i linguaggi senza contesto, usando le parole come alfabeto. Le regole che abbiamo visto sopra sono regole senza contesto per il linguaggio naturale. Nel caso dei linguaggi di programmazione le regole possono essere:

statement -> assignment
statement -> loop
loop ->  "while" expression "do" statement
assignment -> "identifier" "=" expression
expression -> "identifier"
expression -> "integer"
expression -> expression "operator" expression

Con tali regole puoi scrivere:

while aaa /= bbb do aaa = aaa + bbb / 6 che è una dichiarazione.

E il modo in cui è stato prodotto può essere rappresentato da una struttura ad albero chiamata albero di analisi o albero di sintassi (non completo qui):

                          statement
                              |
            _______________  loop _______________
           /      /                 \            \
      "while" expression           "do"       statement
       __________|_________                       |
      /          |         \                  assignment
 expression "operator" expression          _______|_______
     |           |          |             /       |       \
"identifier"   "/="   "identifier" "identifier"  "="   expression
     |                      |            |                 |
    aaa                    bbb          aaa             ... ...

I nomi che appaiono a sinistra di una regola sono chiamati non terminali, mentre le parole sono chiamate anche terminali, in quanto sono nell'alfabeto per la lingua (sopra il livello lessicale). Non terminali rappresentano le diverse strutture sintattiche, che possono essere utilizzate per comporre un programma.

Tali regole sono chiamate senza contesto, perché un non terminale può essere sostituito arbitrariamente usando una delle regole corrispondenti, indipendentemente dal contesto in cui appare. L'insieme di regole che definiscono la lingua è chiamato grammatica senza contesto.

In realtà ci sono delle restrizioni su ciò, quando gli identificatori devono essere dichiarati per la prima volta o quando un'espressione deve soddisfare le restrizioni di tipo. Ma tale restrizione può essere considerata semantica, piuttosto che sintattica. In realtà alcuni professionisti li collocano in quella che chiamano semantica statica .

Dato qualsiasi frase, qualsiasi programma, il significato di quella frase viene estratto analizzando la struttura fornita dall'albero di analisi per questa frase. Quindi è molto importante sviluppare algoritmi, chiamati parser, in grado di recuperare la struttura ad albero corrispondente a un programma, quando viene dato il programma.

Il parser è preceduto dall'analizzatore lessicale che riconosce le parole e determina la famiglia di appartenenza. Quindi la sequenza di parole, o elementi lessicali, viene data al parser che recupera la struttura ad albero sottostante. Da questa struttura il compilatore può quindi determinare come generare il codice, che è la parte semantica dell'elaborazione del programma sul lato del compilatore.

Il parser di un compilatore può effettivamente costruire una struttura di dati corrispondente all'albero di analisi e passarlo alle fasi successive del processo di compilazione, ma non è necessario. L'esecuzione dell'algoritmo di analisi equivale allo sviluppo di una strategia computazionale per esplorare l'albero di sintassi implicito nel testo del programma. Questo albero di sintassi / analisi può o meno essere esplicitato nel processo, a seconda della strategia di compilazione (numero di fasi). Ciò che è necessario, tuttavia, è che alla fine vi sia almeno un'esplorazione dal basso verso l'alto dell'albero di analisi, esplicitato o lasciato implicito nella struttura di calcolo.

La ragione di ciò, intuitivamente, è che un modo formale standard per definire la semantica associata a una struttura ad albero sintattico è per mezzo di quello che viene chiamato omomorfismo. Non temere la parola grossa. L'idea è solo quella di considerare il significato del tutto costruito dal significato delle parti, sulla base dell'operatore che le collega

Ad esempio, la frase the dog bites the catpuò essere analizzata con la regola sentence -> subject verb complement. Conoscere il significato dei 3 sottoalberi subject, verbe complement, la regola che li compone ci dice che il soggetto sta facendo l'azione e che il gatto è colui che viene morso.

Questa è solo una spiegazione intuitiva, ma può essere formalizzata. La semantica è costruita verso l'alto dai costituenti. Ma questo nasconde molta complessità.

Il funzionamento interno di un compilatore può essere scomposto in più fasi. Il compilatore effettivo può funzionare fase per fase, utilizzando rappresentazioni intermedie. Potrebbe anche unire alcune fasi. Ciò dipende dalla tecnologia utilizzata e dalla complessità della compilazione del linguaggio a portata di mano.


Fantastico, molto utile. Comprendo che regex viene utilizzato nel processo di tokenizzazione (ad esempio un letterale stringa può essere definito "[^"]*"nella sua forma più semplice, ignorando i caratteri di escape ecc.), Ma viene anche utilizzato nella creazione dell'albero di sintassi (parlando in termini di linguaggi di programmazione)? Presumo che, per definizione, non siano automi a stati finiti, per definizione finiti. Un albero di sintassi, anche per una singola ifistruzione, può essere teoricamente infinito a causa della nidificazione. Pertanto regex, essendo automi a stati finiti, non può essere utilizzato allo scopo di generare un albero di sintassi.
Zwander,

@Zwander thx 4 editing- Il tuo esempio di regex è corretto (avrei dovuto dare alcuni esempi). A proposito, Regex è anche un linguaggio, con la sua semantica nel mondo degli insiemi di stringhe e con una sintassi senza contesto ( CF ). Viene utilizzato solo per la tokenizzazione della stringa di linguaggio, almeno per i linguaggi di programmazione, in genere non per definire la sintassi più ampia utilizzata per gli alberi di sintassi, tranne che come abbreviazione in Extended BNF (EBNF). L'aggiunta di Regex in qualche forma a formalismi più complessi non cambia il loro potere espressivo nella maggior parte dei casi. Le tue osservazioni sull'infinito non sono del tutto corrette. Vedi il prossimo commento
babou,

@Zwander Tutti i formalismi (linguaggi formali) sono descritti in modo fine. Questa è un'ipotesi fondamentale. Anche se sei interessato, diciamo, alla grammatica CF con un numero infinito di regole, devi dare una descrizione finita di quell'infinito di regole. Anche l'infinito ti gioca brutti scherzi (non c'è spazio per quello). Un'istruzione ifè illimitata (arbitrariamente grande) ma sempre finita. Un infinito finemente definito ifè a while. La differenza tra CF e normale è che la CF controlla / consente la nidificazione (cioè la parentesi) mentre la normale no. Ma entrambi sono descritti in modo fine e consentono stringhe illimitate.
babou,

1
@Zwander Il formalismo deve essere in grado di rappresentare qualsiasi frase (programma) ben formata , ma solo frasi ben formate. Per dirla (troppo) semplicemente, l'FSA non può contare senza limiti. Quindi non possono sapere quante parentesi sono state aperte che dovrebbero essere chiuse o nidificare correttamente due diversi tipi di parentesi. Molte strutture linguistiche hanno parentesi "nascoste". Non si tratta semplicemente di verificare la sintassi, ma implica principalmente che la struttura ad albero appropriata non può essere espressa e costruita, da cui derivare la semantica. Il recupero di una struttura ad albero adeguata richiede il conteggio.
babou,

1
(((UN-B)+3)×C)

2

Ci sono differenze significative. Il principale tra questi, direi, è che l'analisi dei linguaggi di programmazione reali riguarda la gestione degli errori di sintassi. Con un linguaggio formale diresti semplicemente "beh, non è nella lingua", ma un compilatore che dice che non è molto utile - dovrebbe dirti cosa c'è che non va, e se fosse un piccolo errore, idealmente continua ad analizzare così che possa segnala più errori. Un sacco di ricerca (e sforzi di implementazione) lo fa. Quindi davvero non ti interessa nemmeno tanto del risultato vero / falso, vuoi solo analizzare la struttura dell'input. I linguaggi formali sono usati come strumento lì, e c'è molta sovrapposizione, ma stai davvero risolvendo un problema diverso.

Inoltre, nella maggior parte delle lingue è stato scelto di non applicare determinate cose nella grammatica , ad esempio l'esempio che hai citato, "una variabile non può apparire se non fosse stata dichiarata". Questa è in genere una cosa che sarebbe completamente ignorata dal parser e quindi catturata in un'analisi separata (analisi semantica) che guarda quel tipo di cosa e non è influenzata da considerazioni come la mancanza di contesto. Ma non sempre - ad esempio per analizzare C, viene spesso utilizzato l' hack di lexer e C ++ è un famoso esempio di un linguaggio che non può essere analizzato senza eseguire contemporaneamente alcune serie analisi semantiche (in realtà l'analisi di C ++ è indecidibile, poiché i modelli sono Turing completi ). In lingue più semplici tende a essere diviso, tuttavia è più facile in questo modo.


1

Un linguaggio formale è un insieme di parole - in cui una parola è una stringa di simboli di un certo alfabeto.

Ciò significa che il tuo accoppiamento delle regole di produzione e del linguaggio formale è troppo forte. Non è corretto che il linguaggio formale sia le regole di produzione. Piuttosto le regole di produzione definiscono il linguaggio formale. Il linguaggio formale è le parole che possono essere prodotte dalla regola di produzione. (Ciò richiede che il linguaggio formale sia del tipo che può essere definito dalle regole di produzione, ad esempio i linguaggi regolari possono essere definiti da una grammatica libera dal contesto)

Quindi il linguaggio regolare corrispondente all'espressione (a|b)*c*dè definito dalle regole di produzione;

S->ACd
A->
A->aA
A->bA
C->
C->cC

Le parole che queste regole di produzione generano dal simbolo iniziale S sono precisamente quelle stringhe che l'espressione regolare accetta.


0

C'è un'altra relazione tra espressioni regolari e linguaggi di programmazione che ha a che fare con la semantica. I costrutti di controllo di base di un linguaggio imperativo sono la composizione sequenziale (do A e poi B), la scelta (do A o B) e la ripetizione (do A ancora e ancora).

Gli stessi tre modi di combinare comportamenti si trovano nelle espressioni regolari. Lanciare chiamate di subroutine e si ha un'analogia con EBNF.

Quindi c'è molta somiglianza tra l'algebra delle espressioni regolari e l'algebra dei comandi. Questo è esplorato in dettaglio da Dijkstra in "L'unificazione dei tre calcoli". È anche la base del CCS di Milner, che fornisce una risposta alla domanda: e se aggiungessimo il parallelismo?

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.