In formele grammatica's worden syntactische domeinen gedefinieerd door regels die de structuur van zinnen binnen die domeinen beschrijven. Elk domein wordt gedefinieerd door een reeks alternatieven die bestaan uit symbolen en variabelen die elkaar volgens specifieke grammaticale regels kunnen vervangen. Het begrijpen van hoe abstracte syntaxis werkt, vereist een goed inzicht in de manier waarop deze syntactische regels werken, en hoe we daarmee syntactische zinnen kunnen opbouwen. Het volgende beschrijft hoe syntactische zinnen binnen de domeinen Numeral en Expression worden gecreëerd aan de hand van de regels die voor deze domeinen gelden.
In het geval van het domein Numeral kunnen we vier alternatieven identificeren, te beginnen met 'z', 'o', 'nz', en 'no'. Deze alternatieven vormen de bouwstenen van getallen in dit formele systeem. De elementen van de domeinen Expression en Numeral zijn syntactische zinnen, die we 'uitdrukkingen' en 'nummers' zullen noemen. Deze zinnen worden systematisch opgebouwd door elke variabele in de zin te herschrijven volgens de grammaticale regels, te beginnen met een zin die slechts bestaat uit de getypte variabele van het bijbehorende domein, totdat er geen verdere herschrijvingsstappen mogelijk zijn.
Bijvoorbeeld, volgens de regels voor het domein Numeral, kunnen we de volgende herschrijvingsstappen zien (waarbij 𝑁 → 𝑁 ′ gelezen moet worden als "𝑁 wordt in één stap herschreven naar 𝑁 ′"):
-
𝑁 → o()
-
𝑁 → nz(𝑁) → nz(o())
-
𝑁 → no(𝑁) → no(o())
Daarom zijn de zinnen o(), nz(o()), en no(o()) elementen van het domein Numeral. Wanneer we de regels voor het domein Expression in overweging nemen, zien we de volgende herschrijvingsstappen (waarbij 𝐸 →∗ 𝐸 ′ gelezen moet worden als "𝐸 wordt in meerdere stappen herschreven naar 𝐸 ′"):
-
𝐸 → s(𝐸, 𝐸)
-
𝐸 → s(𝐸, p(𝐸, 𝐸)) →∗ s(n(𝑁), p(n(𝑁), n(𝑁))) →∗ s(n(o()), p(n(nz(o())), n(no(o()))))
De zin s(n(o()), p(n(nz(o())), n(no(o())))) is dus een element van het domein Expression. Dit proces van herschrijving volgt de grammaticale regels en resulteert in een abstracte syntaxisboom, die de structuur van de uitdrukking visueel weergeeft.
De formele rechtvaardiging van bovenstaande voorbeelden wordt gegeven door de definitie van abstracte syntaxis en de grammaticale regels die de structuur van de uitdrukkingen bepalen. De grammatica van abstracte syntaxis bevat een reeks verklaringen die syntactische domeinen definiëren, evenals de variabelen die deze domeinen vertegenwoordigen. Elke verklaring introduceert een uniek naam voor een syntactisch domein en een variabele die elementen van dit domein aanduidt. Vervolgens bevat de grammatica een reeks alternatieven voor elke variabele, die de mogelijke vormen van een syntactisch element binnen dat domein bepalen.
Wat belangrijk is om te begrijpen, is dat deze abstracte syntaxis beschrijft hoe zinnen worden opgebouwd in termen van hun structurele elementen, zonder zich zorgen te maken over de specifieke lineaire representatie van deze zinnen. Dit betekent dat we ons niet hoeven te bekommeren over de specifieke volgorde van de symbolen in de zin, zolang de syntactische structuur (de abstracte syntaxisboom) correct is. In het geval van de uitdrukking 1 + (10 × 11) kan de syntaxboom visueel worden weergegeven als een abstracte structuur, die de onderliggende grammaticale regels volgt, ongeacht de volgorde van de symbolen in de zin zelf.
Dit leidt ons naar een ander belangrijk concept binnen de formele grammatica, namelijk structurele inductie. Structurele inductie is een bewijsprincipe dat kan worden gebruikt om eigenschappen van syntactische domeinen te bewijzen. Het idee is dat we een statement over een syntactische zin kunnen bewijzen door te laten zien dat het waar is voor alle alternatieven van de grammaticale regels van dat domein, onder de veronderstelling dat de stelling waar is voor alle niet-terminale variabelen in de alternatieven.
Een voorbeeld van het gebruik van structurele inductie zou kunnen zijn het bewijs dat een bepaald symbool, zoals het cijfer '2', niet voorkomt in een bepaald numeriek domein. Dit kan worden bewezen door de vier alternatieven van de grammatica voor het domein Numeral te onderzoeken en voor elk alternatief te verifiëren dat het cijfer '2' niet voorkomt. Dit bewijs is triviaal, omdat het in elk geval duidelijk waar is.
Wat verder van belang is, is dat de grammatica van een abstracte syntaxis geen concrete volgorde van symbolen voorschrijft. In plaats daarvan gaat het om de abstracte structuur die de relaties tussen de verschillende syntactische elementen aangeeft. Dit biedt een krachtig kader voor het werken met formele systemen, waarbij de focus ligt op de onderliggende structuur van de zinnen in plaats van de specifieke symbolen die worden gebruikt om die zinnen uit te drukken.
De toepassing van abstracte syntaxis en structurele inductie in formele grammatica's is essentieel voor het begrijpen van de logica en de structuur van formele talen. Dit biedt een fundamentele basis voor het ontwikkelen van formules en algoritmen die de regels van een formele grammatica volgen, en die later kunnen worden toegepast op diverse computertalen, wiskundige systemen of logische systemen.
Hoe "refinement" van systemen de veiligheid en prestaties van een systeem waarborgt
Het concept van "refinement" in systeemtheorie gaat over het geleidelijk transformeren van abstracte modellen naar meer concrete implementaties zonder de fundamentele eigenschappen van het systeem te verliezen. Het centrale doel van refinement is dat het systeem zijn essentiële eigenschappen behoudt, zelfs als het in de loop van de tijd steeds gedetailleerder en specifieker wordt. Dit is cruciaal in de formele verificatie van systemen, waar het van belang is dat een verfijnd model voldoet aan de veiligheids- en prestatie-eisen die in de abstracte specificatie zijn vastgelegd.
De theorie van systeemverfijning kan wiskundig worden gepresenteerd door middel van een reeks formules en regels die de overdracht van eigenschappen van een abstracte naar een concrete voorstelling beschrijven. In het geval van een verfijning van systemen, kan deze formaliteit als volgt worden samengevat: Als een abstract systeem voldoet aan een bepaalde veiligheidsvoorwaarde (gedefinieerd door een LTL-formule zonder de "next-time" of "release" operatoren), dan zal elke uitvoer van het verfijnde systeem, gemapt door een functie van de toestand van het verfijnde systeem naar de toestand van het abstracte systeem, ook voldoen aan diezelfde veiligheidsvoorwaarde. Dit impliceert dat veiligheidseigenschappen van een systeem behouden blijven, zelfs als het systeem complexer wordt.
Bijvoorbeeld, het systeem "HourClock" kan een veiligheidsformule zoals □(ℎ = 23 ⇒ ◇(ℎ = 0)) vervullen, wat aangeeft dat als de klok 23 uur aangeeft, de klok uiteindelijk 0 uur zal aangeven. Dit geldt ook voor het verfijnde systeem "MinuteClock", waar de overgang van een toestand met ℎ = 23 naar ℎ = 0 wordt geïmplementeerd, maar niet noodzakelijk op een manier die voldoet aan sterkere formules zoals □(ℎ = 23 ⇒ ○(ℎ = 0)), omdat de abstractie van de verfijning de onmiddellijke opvolger van een toestand negeert.
Verfijning speelt een cruciale rol bij het ontwikkelen van systemen die correct functioneren in de praktijk. Het idee is dat een systeem in abstracte vorm, dat bijvoorbeeld een bepaalde veiligheidsvoorwaarde voldoet, kan worden omgezet naar een concreet systeem dat dezezelfde voorwaarde blijft waarmaken, zelfs als de implementatie steeds gedetailleerder wordt. De verfijning van een systeem kan plaatsvinden in meerdere stadia, waarbij een abstract model wordt geconcretiseerd via verschillende tussenmodellen, totdat het uiteindelijke model een implementatie is die in de praktijk kan worden uitgevoerd. Dit proces garandeert dat de veiligheidseigenschappen die in de abstracte specificatie zijn vastgelegd, behouden blijven.
Echter, verfijning heeft zijn grenzen. Het waarborgt alleen de "veiligheids"eigenschappen van een systeem, dat wil zeggen eigenschappen die geen betrekking hebben op liveness—zoals de garantie dat een systeem uiteindelijk tot een oplossing komt of dat het systeem nooit in een eindeloze lus vastloopt. De verfijning kan leiden tot een situatie waarin de implementatie geen vooruitgang boekt, bijvoorbeeld als een systeem geen transities heeft om uit te voeren vanwege ongeldige initiële voorwaarden, of als het systeem eindigt in een staat waarin geen verdere overgangen mogelijk zijn. Daarom is het essentieel om niet alleen veiligheid te waarborgen, maar ook te verifiëren of het verfijnde systeem adequaat voortgang maakt, wat een complexere analyse vereist dan wat de verfijning zelf biedt.
Bijvoorbeeld, een systeem dat voldoet aan een formule die uitsluitend veiligheid beschrijft, zoals een klok die de tijd correct weergeeft, hoeft niet noodzakelijkerwijs alle vereisten voor liveness te vervullen, zoals de garantie dat de klok altijd een stap vooruit maakt, zonder vast te lopen in een eindeloze herhaling van dezelfde staat.
De verfijning van systemen is dan ook niet alleen een kwestie van veiligheid, maar vereist ook een gedegen aanpak voor het bewaken van de algehele voortgang en prestaties van het systeem, zoals geïllustreerd in de meer complexe bewijsprincipes die worden besproken in secties over de verifiëren van liveness-eigenschappen.
Deze balans tussen het verfijnen van systemen op basis van veiligheid en het waarborgen van de voortgang is van groot belang voor de ontwerpers van betrouwbare en robuuste systemen. Zonder een degelijk begrip van zowel de verfijning als de implicaties voor liveness, kan een systeem dat op papier voldoet aan de veiligheidsvereisten in de praktijk faalt.
Wat is de rol van denotationale en operationele semantiek in het begrijpen van programmacorrectheid?
In de context van formele logica en semantiek, wordt de betekenis van programma's vaak beschreven aan de hand van twee hoofdconcepten: denotationale semantiek en operationele semantiek. Beide benaderingen hebben als doel om de werking van programma's nauwkeurig vast te leggen, maar doen dit op verschillende manieren, wat van belang is bij de formalisering van bijvoorbeeld programmacorrectheid of optimalisatie zoals loop unrolling.
De denotationale semantiek beschrijft de betekenis van een programma door het te relateren aan een wiskundige functie die de staat van de machine bepaalt na uitvoering van het programma. Dit wordt vaak gedaan via een zogenaamde 'relatiedefinitie', waarin de werking van een programma als een functie van de staat van het systeem wordt gepresenteerd. Bijvoorbeeld, voor een programma met een while-lus, kunnen we formeel bewijzen dat de lus correct is als deze voldoet aan de verwachte eindstaat, gebruikmakend van de gedefinieerde denotationale semantiek.
Aan de andere kant staat de operationele semantiek, die probeert om de uitvoering van een programma in kleine stappen te modelleren. Deze semantiek richt zich op de transities die plaatsvinden van de ene toestand naar de andere tijdens de uitvoering van een programma. De operationele semantiek biedt een meer 'uitvoerbare' benadering van programma's, waarbij elke stap van de uitvoering expliciet wordt beschreven. Dit biedt een meer praktische, maar minder abstracte, benadering van de betekenis van een programma, waarbij de focus ligt op het traceren van de individuele bewerkingen.
Bijvoorbeeld, wanneer we kijken naar de optimalisatie van een programma door middel van 'loop unrolling', is de rol van de denotationale semantiek om aan te tonen dat het geoptimaliseerde programma dezelfde eindtoestand bereikt als het oorspronkelijke programma. In dit geval wordt de while-lus ontrolt en vervangen door een if-then-else structuur die de prestatie van het programma verbetert door het vermijden van "back-jumps" in de machinecode. Dit kan formeel bewezen worden door de gelijkheid van de semantiek van beide constructies, zoals weergegeven in de voorbeelden van de gelijkheidsbewijzen voor de while-lus en de if-then-else structuur.
In de formele afleiding van semantische gelijkheden zoals de bovenstaande, wordt niet alleen de functionele betekenis van de programma's gecontroleerd, maar wordt ook de correctheid van de uitvoering geanalyseerd. Dit stelt ons in staat om de effecten van specifieke programmaoptimalisaties met zekerheid te begrijpen, zoals de correctheid van het 'unrollen' van lussen. De betekenis van een programma wordt dus zowel bepaald door de structuur van de regels die de semantiek definiëren als door de manier waarop deze regels de voortgang van de uitvoering beschrijven.
Bij de afleiding van semantische gelijkheden zoals voor loop unrolling kunnen we zien dat de rol van de semantiek niet alleen het begrijpen van de specifieke werking van een programma is, maar ook het formaliseren van de relaties tussen verschillende programmatische constructies en hun geoptimaliseerde vormen. Dit biedt inzicht in de onderliggende logica van programma's en stelt ons in staat om robuuste en geoptimaliseerde software te creëren.
Naast het begrijpen van de semantische correctheid van programma's, moeten we ook de praktische implicaties van dergelijke formalisaties begrijpen. In de praktijk kunnen kleine veranderingen in de manier waarop een programma wordt gepresenteerd, zoals het aanpassen van de structuur van lussen, aanzienlijke invloed hebben op de prestaties zonder de functionele correctheid in gevaar te brengen. Daarom is het niet alleen belangrijk om de formalisering van programmacorrectheid te begrijpen, maar ook om te weten hoe dergelijke optimalisaties invloed kunnen hebben op de efficiëntie van de uitvoering. De uiteindelijke uitdaging ligt dus in het combineren van formele en praktische benaderingen om een balans te vinden tussen de leesbaarheid, het onderhoud van de code en de uitvoeringsefficiëntie.
Hoe worden programma's vertaald naar machinetaal en wat betekent dit voor de correctheid van de vertalingen?
In de vertaling van programma's naar machinetaal zijn er specifieke regels die bepalen hoe elke commando-instructie wordt omgezet in een reeks machinetaal-instructies. Dit proces is essentieel voor de correcte uitvoering van een programma op een machine. De vertaling gebeurt op basis van de structuur van het programma, waarbij de controleflow, zoals conditionele en iteratieve statements, nauwkeurig in de machine-instructies wordt omgezet.
Wanneer we bijvoorbeeld een programma hebben met een conditie, zoals "if F then C1 else C2", vertaalt dit naar een sequentie van machine-instructies die de waarde van de voorwaarde F evalueert, een tak kiest op basis van de waarheid van F, en vervolgens doorloopt naar de juiste sectie van het programma. In deze vertaling wordt de waarheid van F eerst geëvalueerd en bewaard op de stack. Als de voorwaarde niet waar is, springt het programma naar het begin van de andere tak. Anders wordt de eerste tak uitgevoerd en daarna doorgestroomd naar het einde van de instructies.
Een vergelijkbaar mechanisme werkt voor lussen. Wanneer een programma een "while"-lus bevat, zoals "while F then C", worden de instructies van de lus vertaald naar een reeks die continu de voorwaarde F evalueert. De lus zelf wordt herhaald totdat F niet meer waar is. De machine-instructies volgen de structuur van de lus: een "jump"-instructie herstart de evaluatie van F na elke iteratie, terwijl de instructies voor het uitvoeren van de lus zelf tussen de evaluaties worden uitgevoerd.
Bij de vertaling van commando's naar machinecode wordt er ook rekening gehouden met het opslaan en laden van waarden van variabelen. In sommige gevallen worden variabelen eerst van de stack gepusht of van het geheugen geladen, waarna ze bewerkt worden en de resultaten weer opgeslagen in de geheugenlocaties.
De structuur van een machineprogramma kan verder complexer worden naarmate de logica van het originele programma ook complexer is. In het geval van een variabele toewijzing, zoals "a := a + 1", wordt elke bewerking omgezet in een reeks van laden van de waarde van a, het toevoegen van 1, en het opnieuw opslaan van de waarde van a. Deze instructies werken door de waarde van de variabele eerst van de stack of het geheugen te laden, de bewerking uit te voeren, en daarna het resultaat terug op te slaan.
In de voorbeelden van machinecode die in het oorspronkelijke programma worden weergegeven, zien we de specifieke transities van de beginstatus naar de eindstatus van het programma. Deze transities beschrijven de volgorde van de instructies die op de machine moeten worden uitgevoerd, waarbij de stack en het geheugen continu worden aangepast op basis van de waarden van de variabelen en de uitvoering van de commando's.
Het is van cruciaal belang om te begrijpen dat deze vertalingen correct moeten zijn om de logica van het originele programma te behouden. De juiste werking van de machine-instructies moet overeenkomen met de beoogde werking van het oorspronkelijke programma. Dit wordt bewezen door middel van de correctheidstheorie, die stelt dat als een programma correct is geanalyseerd en vertaald, de uitvoer van het machineprogramma overeenkomt met de uitvoer van het oorspronkelijke programma voor alle mogelijke invoeren en omstandigheden.
Bijvoorbeeld, voor een eenvoudig programma waarin twee variabelen worden opgeteld, zoals "sum(a, b, c) { c := a + b }", zal de vertaalde machinecode correct de waarden van de variabelen a en b optellen en het resultaat in c opslaan. De transitie van de beginstatus van de variabelen naar de eindstatus, waarin de waarde van c correct is berekend, is een voorbeeld van hoe deze vertaling plaatsvindt.
Naast de technologische vertalingen van commando's en variabelen, moet de lezer ook begrijpen dat de juistheid van dergelijke vertalingen niet alleen afhangt van de formele regels voor het omzetten van de programmacommandos, maar ook van de betrouwbaarheid van de machinemethoden die deze vertalingen uitvoeren. Dit betekent dat de vertaling van een hoger niveau naar machinetaal altijd een zekere mate van abstractie en precisie vereist, vooral wanneer de complexiteit van het programma toeneemt. Het gebruik van de juiste vertaalstrategieën is essentieel voor het behouden van de correcte werking van het programma in verschillende omgevingen, met verschillende waardes van de invoerparameters.

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский