Compare commits

..

5 commits

Author SHA1 Message Date
e5c61c89da Sync 2025-05-15 21:29:02 +02:00
28168cb0f1
test: Summer example 2025-05-15 21:25:58 +02:00
2c6dfb77a0
Finale indiening 2025-05-12 19:52:41 +02:00
f4d96d4143
Voorlopige indiening 2 2025-05-12 19:39:30 +02:00
6f1b252f52
Voorlopige indiening 2025-05-12 19:33:51 +02:00
12 changed files with 236 additions and 98 deletions

BIN
build/gpl.jar Normal file

Binary file not shown.

View file

@ -7,6 +7,16 @@
url = {https://doi.org/10.1515/9781400863440}, url = {https://doi.org/10.1515/9781400863440},
} }
@book{toman-2008,
author = {Toman, David},
booktitle = {{Computational Logic}},
title = {Chapter 9. SLD-Resolution And Logic Programming (PROLOG)},
pages = {410--475},
publisher = {University of Waterloo},
year = {2008},
url = {https://cs.uwaterloo.ca/~david/cl/}
}
@book{deransart-1996, @book{deransart-1996,
author = {given-i=P, given=Pierre, family=Deransart and given-i=A, given=AbdelAli, family=Ed-Dbali and given-i=L, given=Laurent, family=Cervoni}, author = {given-i=P, given=Pierre, family=Deransart and given-i=A, given=AbdelAli, family=Ed-Dbali and given-i=L, given=Laurent, family=Cervoni},
date = {1996-01-01}, date = {1996-01-01},
@ -23,6 +33,14 @@
note = "[Online; accessed 12-May-2025]" note = "[Online; accessed 12-May-2025]"
} }
@article{iso1995iec,
title={IEC 13211-1: 1995: Information Technology—Programming Languages—Prolog—Part 1: General Core},
author={ISO, ISO and ISO, IEC},
journal={ISO: Geneva, Switzerland},
pages={1--199},
year={1995}
}
@book{russell2016, @book{russell2016,
author = {Russell, Stuart and Norvig, Peter}, author = {Russell, Stuart and Norvig, Peter},
booktitle = {{Artificial Intelligence A Modern Approach, Global Edition}}, booktitle = {{Artificial Intelligence A Modern Approach, Global Edition}},
@ -37,3 +55,11 @@ The long-anticipated revision of this best-selling text offers the most comprehe
doi = {}, doi = {},
url = {https://elibrary.pearson.de/book/99.150005/9781292153971} url = {https://elibrary.pearson.de/book/99.150005/9781292153971}
} }
@software{unknown-author-2025,
date = {2025-04-25},
title = {SWI-Prolog},
type = {software},
url = {https://www.swi-prolog.org/},
version = {9.3.24},
}

BIN
documentatie/renaming.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

View file

@ -7,7 +7,9 @@
\usepackage[dutch]{babel} % Nederlands taal \usepackage[dutch]{babel} % Nederlands taal
\usepackage[style=apa]{biblatex} % Bronnen \usepackage[style=apa]{biblatex} % Bronnen
\usepackage{enumitem} % Aanpasbare lijsten \usepackage{enumitem} % Aanpasbare lijsten
\usepackage[margin=1in]{geometry} % Sane marges \usepackage{float} % Figures
\usepackage[margin=2cm]{geometry} % Sane marges
\usepackage{graphicx} % Figures
\usepackage{hyperref} % Hyperlinks \usepackage{hyperref} % Hyperlinks
\usepackage{minted} % Syntax highlighting \usepackage{minted} % Syntax highlighting
\usepackage{multicol} % Meerdere kolommen \usepackage{multicol} % Meerdere kolommen
@ -26,14 +28,18 @@
% KERN: Wat is Ghent Prolog? % KERN: Wat is Ghent Prolog?
Ghent Prolog is een command-line Prolog interpreter geschreven in Kotlin. Ghent Prolog is een command-line Prolog interpreter geschreven in Kotlin.
Het biedt een subset van de functionaliteit van SWI-Prolog, waaronder database operaties en meta abstracties. Het biedt een subset van de functionaliteit van SWI-Prolog, waaronder database operaties en meta abstracties.
Hoewel het programma nog niet als equivalent van SWI-Prolog kan worden beschouwd, is Ghent Prolog een nuttig leerproject.
} }
\section{Overzicht}\label{sec:overzicht} \section{Overzicht}\label{sec:overzicht}
% KERN: Insteek
Ghent Prolog is een proof of concept van een Prolog interpreter, als alternatief voor SWI-Prolog.
Het volgt grotendeels ~\cite[de ISO Prolog standaard]{iso1995iec}, maar is niet volledig compatibel.
% KERN: Programmeertaal, libraries, etc. % KERN: Programmeertaal, libraries, etc.
Ghent Prolog werd geschreven in Kotlin en maakt gebruik van zowel object-gerichte als functionele concepten. Ghent Prolog werd geschreven in Kotlin en maakt gebruik van zowel object-gerichte als functionele concepten.
Er werden twee bibliotheken gebruikt: Er werden twee bibliotheken gebruikt:
\href{https://github.com/h0tk3y/better-parse}{\texttt{better-parse}}, voor het parsen van een gegeven grammatica, en \href{https://github.com/h0tk3y/better-parse}{\texttt{better-parse}}, voor het parsen van een gegeven grammatica, en
\href{https://github.com/xenomachina/kotlin-argparser}{\texttt{kotlin-argparser}}, voor het gebruik van command-line argumenten. \href{https://github.com/xenomachina/kotlin-argparser}{\texttt{kotlin-argparser}}, voor het gebruik van command-line argumenten.
@ -42,27 +48,19 @@
De verschillende fases van een interpreter (lexing, parsing, evaluatie, etc.) zijn in Ghent Prolog zo goed mogelijk gescheiden. De verschillende fases van een interpreter (lexing, parsing, evaluatie, etc.) zijn in Ghent Prolog zo goed mogelijk gescheiden.
Op die manier blijft de implementatie modulair en uitbreidbaar. Op die manier blijft de implementatie modulair en uitbreidbaar.
De volgende secties concentreren zich op de evaluatie-fase van de interpreter.
\section{Lexing, parsing en preprocessing}\label{sec:lexing-parsing-preprocessing} Voor meer informatie over de lexing en parsing, zie sectie~\ref{sec:lexing-parsing-preprocessing}.
Traditioneel zijn de lexer en parser aparte componenten.
Om tijd te besparen werd echter voor het lexen en parsen van Prolog broncode en REPL-invoer gebruik gemaakt van de \texttt{better-parse} bibliotheek.
De parser maakt gebruik van een vereenvoudigde Prolog grammatica, gebaseerd op
\href{https://github.com/antlr/grammars-v4/blob/master/prolog/prolog.g4}{de ANTLR Prolog grammatica},
\href{https://sicstus.sics.se/sicstus/docs/3.7.1/html/sicstus_45.html#SEC370}{SICStus Prolog Full Prolog Syntax} en
\href{https://github.com/simonkrenger/ch.bfh.bti7064.w2013.PrologParser/blob/2c06e5a221c1cc51ba766304250749a7f0caed8c/doc/prolog-bnf-grammar.txt}{simonkrenger/PrologParser's BNF}.
Omdat de lexer en parser niet op maat gemaakt zijn, worden ze bewust zo eenvoudig mogelijk gehouden.
Hun verantwoordelijkheid is het omzetten van inputtekst naar een basis Abstract Syntax Tree (AST), die enkel uit generieke Prolog termen bestaat.
Daarna worden de termen in de AST omgezet naar specifieke klassen die de ingebouwde operatoren voorstellen.
\section{Programma en REPL}\label{sec:programma-repl} \section{Programma en REPL}\label{sec:programma-repl}
% KERN: Methode
Ghent Prolog's evaluatiemethode kan gezien worden als een vorm van \textit{backward chaining} (\cite{russell2016}), maar komt niet exact overeen met de definitie.
% KERN: Datastructuur % KERN: Datastructuur
Ghent Prolog maakt gebruik van functie-oproepen streams om het backtracking mechanisme te implementeren. Ghent Prolog maakt gebruik van functie-oproepen en streams om een backtracking mechanisme te implementeren.
Bijzondere logica met betrekking tot \textit{choicepoints} en foutenafhandeling maakt gebruik van de Kotlin \texttt{Result} klasse. Bijzondere logica met betrekking tot \textit{choicepoints} en foutenafhandeling maakt gebruik van de Kotlin \mintinline{kotlin}{Result} klasse.
Om de resultaten van unificatie en evaluatie lazy te verwerken, wordt er gebruik gemaakt van Kotlin \texttt{Sequence}s (\textit{lazy streams}). Om de resultaten van unificatie en evaluatie \textit{lazy} te verwerken, wordt er gebruik gemaakt van Kotlin \mintinline{kotlin}{Sequence}'s (\textit{lazy streams}).
Meer specifiek wordt er gebruik gemaakt van de volgende types: Meer specifiek wordt er gebruik gemaakt van de volgende types:
@ -95,22 +93,39 @@
\href{https://www.swi-prolog.org/pldoc/man?section=glossary}{SWI-Prolog's Glossary of Terms}, \href{https://www.swi-prolog.org/pldoc/man?section=glossary}{SWI-Prolog's Glossary of Terms},
met bijkomende inspiratie uit~\cite{deransart-1996}. met bijkomende inspiratie uit~\cite{deransart-1996}.
\subsection{Evaluatiestrategie}\label{subsec:evaluatiestrategie}
\section{Evaluatiestrategie}\label{sec:evaluatiestrategie}
% KERN: Evaluatiestrategie % KERN: Evaluatiestrategie
Ghent Prolog maakt gebruik van een depth-first zoekstrategie. Ghent Prolog maakt gebruik van een depth-first zoekstrategie.
Op het moment dat de databank een goal gevraagd wordt (\textit{query}), geeft die de goal beurtelings door aan de overeenkomende clauses, aan de hand van \mintinline{kotlin}{solve}.
Eerst wordt de goal met de head van de clause geünificeerd, wat nieuwe substituties kan introduceren. Op het moment dat de databank een goal gevraagd wordt (\textit{query}), worden de volgende stappen doorlopen:
Daarna wordt de body van de clause geëvalueerd (\mintinline{kotlin}{Body.satisfy}), die zo de nieuwe goal wordt.
\begin{enumerate}
\item De databank geeft de goal beurtelings door aan de overeenkomende clauses, aan de hand van \mintinline{kotlin}{solve}.
\item Voor elke clause wordt de goal met de head geünificeerd, wat nieuwe substituties kan introduceren.
\item De body van de clause wordt geëvalueerd (\mintinline{kotlin}{Body.satisfy}), die zo de nieuwe goal wordt.
Deze procedure wordt herhaald totdat de goal \mintinline{prolog}{true} of \mintinline{prolog}{false} is. Deze procedure wordt herhaald totdat de goal \mintinline{prolog}{true} of \mintinline{prolog}{false} is.
Vervolgens zal het programma backtracken naar de functie die de laatste stap uitvoerde, en de pas geïntroduceerde substituties omhoog doorgeven. \item Het programma zal backtracken naar de functie die de laatste stap uitvoerde, en de pas geïntroduceerde substituties omhoog doorgeven.
Daar kan dan logica uitgevoerd worden, of het resultaat verder doorgegeven worden aan de volgende stap. Daar wordt de juiste logica uitgevoerd, of het resultaat wordt verder doorgegeven aan de volgende stap.
\end{enumerate}
Doorheen dit proces kunnen variabelen hernoemd worden aan de hand van \mintinline{prolog}{numbervars/1}.
Een schematisch overzicht van dit proces is te zien in figuur~\ref{fig:renaming}.
% TODO Verklarende figuur met substitutie toevoegen % TODO Verklarende figuur met substitutie toevoegen
% Overzicht van geïmplementeerde predicaten in appendix. % Overzicht van geïmplementeerde predicaten in appendix.
Operator-specifieke logica bevindt zich in de klassen van de operatoren zelf. Operator-specifieke logica bevindt zich in de klassen van de operatoren zelf.
\begin{figure}[H]
\centering
\includegraphics[width=0.4\textwidth]{renaming}
\caption{Voorbeeld van een substitutie met variable renaming.}\label{fig:renaming}
\end{figure}
In de volgende subsecties worden de belangrijkste onderdelen van de evaluatiestrategie in meer detail besproken.
\subsection{Unificatie}\label{subsec:unificatie} \subsection{Unificatie}\label{subsec:unificatie}
Het unificatie-algoritme is gebaseerd op Robinsons Unificatie-algoritme, zoals beschreven door~\cite{boizumault-1993}, met inspiratie van~\cite{russell2016}. Het unificatie-algoritme is gebaseerd op Robinsons Unificatie-algoritme, zoals beschreven door~\cite{boizumault-1993}, met inspiratie van~\cite{russell2016}.
@ -119,30 +134,39 @@
\subsection{Cut}\label{subsec:cut} \subsection{Cut}\label{subsec:cut}
De cut operator geeft altijd een \mintinline{kotlin}{Result.failure(AppliedCut)} terug wanneer \mintinline{kotlin}{satisfy} wordt opgeroepen. De cut operator geeft altijd een \mintinline{kotlin}{Result.failure(AppliedCut)} terug wanneer \mintinline{kotlin}{satisfy} wordt opgeroepen.
Deze uitzondering moet dan afgehandeld worden door de aanroepende functie. Deze uitzondering moet dan afgehandeld worden door de aanroepende functie, wat meestal resulteert in het teruggeven van de eerstvolgende oplossing en het onderbreken van de logica.
\subsection{Meta abstracties}\label{subsec:meta-abstractions} \subsection{Meta abstracties}\label{subsec:meta-abstractions}
% KERN: Wat wordt ondersteund?
Ghent Prolog ondersteunt meta abstracts zoals \mintinline{prolog}{functor/3}, \mintinline{prolog}{arg/3}, \mintinline{prolog}{=../2} en \mintinline{prolog}{clause/2}.
Daarnaast werden ook de operatoren \mintinline{prolog}{reset/3} en \mintinline{prolog}{shift/1} ingebouwd.
Die laten toe om \textit{delimited continuations} te gebruiken.
% KERN: Hoe wordt dat ondersteund?
De ingebouwde ondersteuning van delimited continuations maakt opnieuw gebruik vaan een uitzondering.
Deze keer heet die \mintinline{kotlin}{Result.failure(AppliedShift)}, die de nieuwe substituties, doorgespeeld term (\textit{ball}) en de nieuwe \textit{continuatie}.
Net zoals bij de cut operator, moet deze uitzondering afgehandeld worden door de aanroepende functie.
In dit geval zal de \mintinline{kotlin}{AppliedShift} steeds doorgespeeld worden, totdat de \mintlinline{kotlin}{Reset} de uitzondering opvangt.
Daarna gaat het programma verder met de nieuwe substituties en continuatie.
\section{Resultaat}\label{sec:resultaat} \section{Resultaat}\label{sec:resultaat}
De implementatie van Ghent Prolog ondersteunt de gevraagde functionaliteit, waaronder database operaties, meta abstracties. De implementatie van Ghent Prolog ondersteunt de gevraagde functionaliteit, waaronder database operaties en meta abstracties.
De architecturele verschillen met SWI-Prolog zijn echter groot, wat zowel positieve als negatieve gevolgen heeft. De architecturele verschillen met SWI-Prolog zijn echter groot, wat zowel positieve als negatieve gevolgen heeft.
\subsection{Voordelen}\label{subsec:voordelen} % KERN: Uitbreidbaarheid
\textbf{De architectuur van Ghent Prolog is modulair en uitbreidbaar.}
De architectuur van Ghent Prolog is modulair en uitbreidbaar.
Eens de evaluatiestrategie en datastructuren zijn gedefinieerd, is het eenvoudig om extra ingebouwde operatoren toe te voegen. Eens de evaluatiestrategie en datastructuren zijn gedefinieerd, is het eenvoudig om extra ingebouwde operatoren toe te voegen.
Een volledig overzicht van de geïmplementeerde predicaten, waaronder extra's en uitbreidingen, kan teruggevonden worden in sectie~\ref{sec:predicaten}.
Een volledig overzicht van de geïmplementeerde predicaten kan teruggevonden worden in sectie~\ref{sec:predicaten}.
\subsection{Uitdagingen}\label{subsec:uitdagingen}
% KERN: Nesting probleem % KERN: Nesting probleem
\textbf{Code wordt snel onoverzichtelijk} door het gebruik van \texttt{Sequence} en \texttt{Result}. \textbf{Code wordt snel onoverzichtelijk} door het gebruik van \mintinline{kotlin}{Sequence} en \mintinline{kotlin}{Result}.
Belangrijke logica zit genest in onduidelijke boilerplate. Belangrijke logica zit genest in onduidelijke boilerplate.
Volgende code komt bijvoorbeeld in de meeste termen voor, weliswaar in verschillende vorm: De code in~\ref{lst:nesting} komt bijvoorbeeld in de meeste termen voor, weliswaar in verschillende vorm.
\begin{listing}[H]
\begin{minted}{kotlin} \begin{minted}{kotlin}
/* Function entry logic */ /* Function entry logic */
unifyLazy(a, b, subs).forEach { firstResult -> unifyLazy(a, b, subs).forEach { firstResult ->
@ -153,58 +177,112 @@
onSuccess = { secondSubs -> onSuccess = { secondSubs ->
/* End result logic */ /* End result logic */
yield(Result.success(firstSubs + secondSubs)) yield(Result.success(firstSubs + secondSubs))
} },
onFailure = { failure -> onFailure = { failure ->
when (failure) { when (failure) {
is AppliedCut -> /* Cut logic */ is AppliedCut -> /* Cut logic */
is AppliedShift -> /* Shift logic */ is AppliedShift -> /* Shift logic */
} } ) } } } } } ) } } }
\end{minted} \end{minted}
\caption{Voorbeeld van geneste boilerplate code}\label{lst:nesting}
\end{listing}
% KERN: Overerving zorgt voor boilerplate % KERN: Overerving zorgt voor boilerplate
\textbf{De implementatie bevat boilerplate code} door het gebruik van overerving en interfaces in de klassenrepresentatie van termen. \textbf{De implementatie bevat boilerplate code} door het gebruik van overerving en interfaces in de klassenrepresentatie van termen.
Deze methode wordt gebruikt om de onderlinge relaties tussen de verschillende termen te beschrijven. Deze methode wordt gebruikt om de onderlinge relaties tussen de verschillende termen te beschrijven.
Hoewel nuttig voor de representatie van de AST en generieke functies, moeten de meeste klassen de \texttt{satsify} en \texttt{solve} methoden overschrijven. Hoewel nuttig voor de representatie van de AST en generieke functies, moeten de meeste klassen de \mintinline{kotlin}{satsify} en \mintinline{kotlin}{solve} methoden overschrijven.
Dit zorgt voor boilerplate code en verpreid de logica over verschillende klassen. Dit zorgt voor boilerplate code en verspreidt de logica over verschillende klassen.
Daarnaast kan het leiden tot verlies van type-informatie wanneer generieke functies en type-specifieke functies worden gemengd. Daarnaast kan het leiden tot verlies van type-informatie wanneer generieke functies en type-specifieke functies worden gemengd.
\subsection{Functionele afwijkingen van SWI-Prolog}\label{subsec:afwijkingen} \subsection{Functionele afwijkingen van SWI-Prolog}\label{subsec:afwijkingen}
% TODO Maak appendix? % TODO Maak appendix?
% KERN: Functionele afwijkingen
De huidige versie van Ghent Prolog heeft functionele afwijkingen van SWI-Prolog:
\begin{itemize} \begin{itemize}
\item Door het gebruik van de \textit{occurs check} is het niet mogelijk om een oplossing te vinden voor recursieve unificatie. \item Door het gebruik van de \textit{occurs check} is het niet mogelijk om een oplossing te vinden voor recursieve unificatie.
Voor \mintinline{prolog}{?- X = f(X).} vindt SWI-Prolog de oplossing \mintinline{prolog}{X = f(f(X))}, maar Ghent Prolog geeft \texttt{false} terug. Voor \mintinline{prolog}{?- X = f(X).} vindt SWI-Prolog de oplossing \mintinline{prolog}{X = f(f(X))}, maar Ghent Prolog geeft \texttt{false} terug.
\item ISO Prolog en SWI-Prolog maken gebruik van SLD-resolutie \item Ghent Prolog maakt geen gebruik van SLD-resolutie, in tegenstelling tot ISO Prolog en SWI-Prolog (\cite{toman-2008}).
% TODO Bron \item In de REPL wordt er niet meteen teruggekeerd naar de prompt als een query maar één oplossing heeft.
Dit is een artifact om oplossingen die de database aanpassen, bijvoorbeeld \mintinline{prolog}{retract/1}, pas uit te voeren wanneer de gebruiker dat expliciet vraagt.
Het gebruik van een iterator en \mintinline{kotlin}{it.hasNext()} zou al de volgende oplossing genereren.
Verder wordt ook de puntkomma (\texttt{;}) pas herkend na een newline.
\item Voorlopig is het niet mogelijk om ingebouwde operatoren te overschrijven met \mintinline{prolog}{assert/1} of \mintinline{prolog}{retract/1}.
Dit is een gevolg van het gebruik van de preprocessor.
\end{itemize} \end{itemize}
% Occurs check
% SLD Resolutie
In tegenstelling tot ISO Prolog en SWI-Prolog, maak ik geen gebruik van SLD-resolutie.
% TODO Bronnen voor SLD-resolutie
% TODO Bronnen voor ISO-Prolog maakt gebruik van SLD resolutie
% TODO Bronnen voor SWIPL maakt gebruik van SLD resolutie
\section{Toekomstig werk}\label{sec:toekomstig-werk} \section{Toekomstig werk}\label{sec:toekomstig-werk}
% KERN: Wat moet volgende keer anders?
Bij de ontwikkeling van een variant van Ghent Prolog, wordt er best aandacht besteed aan de volgende zaken:
\begin{itemize} \begin{itemize}
\item Het gebruik van een stack voor choicepoints en andere informatie om het gebruik van exceptions weg te werken en eenvoudiger choicepoints te kunnen manipuleren. \item Het gebruik van een stack voor choicepoints en andere informatie om het gebruik van exceptions weg te werken en eenvoudiger choicepoints te kunnen manipuleren.
Zo kan bijvoorbeeld de cut operator transparanter \textit{committen} tot de huidige oplossing door in één stap de juiste choicepoints te verwijderen, zonder dat elke operator een \texttt{AppliedCut} moet afhandelen. Zo kan bijvoorbeeld de cut operator transparanter \textit{committen} tot de huidige oplossing door in één stap de juiste choicepoints te verwijderen, zonder dat elke operator een \texttt{AppliedCut} moet afhandelen.
\item Het gebruik van een visitor-patroon in plaats van overerving, om boilerplate te verminderen, zoals aangegeven in sectie~\ref{subsec:uitdagingen}. \item Het gebruik van een visitor-patroon in plaats van overerving, om boilerplate te verminderen, zoals aangegeven in sectie~\ref{sec:resultaat}.
\end{itemize} \end{itemize}
\section{Conclusie}\label{sec:conclusie} \section{Conclusie}\label{sec:conclusie}
% KERN: Niet equivalent.
Ghent Prolog is duidelijk een proof of concept project.
Het kan nog lang niet als equivalent van SWI-Prolog beschouwd worden.
% KERN: Wat heb ik eruit gehaald?
Het implementeren ervan en het onderzoeken van de (voorlopig) ingebouwde functionaliteit biedt echter een goed inzicht in de werking van Prolog.
Het geeft een beter begrip van de ingebouwde operatoren en hoe die interageren.
Persoonlijk vond ik de moeilijkste stap het opzetten van de evaluatie-strategie.
Daarna ging het toevoegen van extra operatoren en functionaliteit vrij vlot.
Ik ben ondertussen bezig met het zoeken van een manier om een bronbestand te schrijven waarmee de som van de getallen in een interval berekend wordt, aan de hand van \mintinline{prolog}{read/1}, \mintinline{prolog}{between/3} en delimited continuations.
\printbibliography \printbibliography
\appendix \appendix
\newpage \newpage
\section{Lexing, parsing en preprocessing}\label{sec:lexing-parsing-preprocessing}
% KERN: Overzicht
Traditioneel zijn de lexer en parser aparte componenten.
Om tijd te besparen werd echter voor het lexen en parsen van Prolog broncode en REPL-invoer gebruik gemaakt van de \texttt{better-parse} bibliotheek.
Een gecompartimentaliseerde, onafgewerkt variant wordt besproken in sectie~\ref{subsec:onafgewerkte-lexer-parser}.
% KERN: Grammatica
De parser maakt gebruik van een vereenvoudigde Prolog grammatica, gebaseerd op
\href{https://github.com/antlr/grammars-v4/blob/master/prolog/prolog.g4}{de ANTLR Prolog grammatica},
\href{https://sicstus.sics.se/sicstus/docs/3.7.1/html/sicstus_45.html#SEC370}{SICStus Prolog Full Prolog Syntax} en
\href{https://github.com/simonkrenger/ch.bfh.bti7064.w2013.PrologParser/blob/2c06e5a221c1cc51ba766304250749a7f0caed8c/doc/prolog-bnf-grammar.txt}{simonkrenger/PrologParser's BNF}.
De uiteindelijk gebruikte grammatica kan teruggevonden worden in de \texttt{parser/grammars}.
% KERN: Simpliciteit en preprocessor
Omdat de lexer en parser niet op maat gemaakt zijn, worden ze bewust zo eenvoudig mogelijk gehouden.
Hun verantwoordelijkheid is het omzetten van inputtekst naar een basis Abstract Syntax Tree (AST), die enkel uit generieke Prolog termen bestaat.
Merk op dat de parser ook verantwoordelijk is voor operator precedentie en associativiteit, zoals besproken wordt in sectie~\ref{subsec:operator-precedence}.
Daarna worden de termen in de AST door de preprocessor omgezet naar specifieke klassen die de ingebouwde operatoren voorstellen.
Termen worden herkend aan de hand van hun functor en ariteit.
Na de preprocessor is de AST klaar om gebruikt te worden door de evaluatie-fase.
\subsection{Onafgewerkte Lexer en Parser implementatie}\label{subsec:onafgewerkte-lexer-parser}
% KERN: Introductie vorige implementatie
Origineel werden de lexer en parser op maat gemaakt, zonder het gebruik van een parser library.
De lexer en parser waren gebaseerd op
\href{https://craftinginterpreters.com/contents.html}{Crafting Interpreters, Robert Nystrom} en
\href{https://www.youtube.com/playlist?list=PLGNbPb3dQJ_5FTPfFIg28UxuMpu7k0eT4}{Building a Parser from scratch, Dmitry Soshnikov}.
De parser was een recursive descent parser.
De voorlopige implementatie kan nog steeds teruggevonden worden op commit
\href{https://github.com/Logisch-Programmeren/project2425-tdpeuter/tree/d5632e92173abf443251d2445de31f03b696b1d0/src}{d5632e9}.
\section{Aanvullende opmerkingen}\label{sec:aanvullende-opmerkingen} \section{Aanvullende opmerkingen}\label{sec:aanvullende-opmerkingen}
\subsection{Operator precedentie en associativiteit}\label{subsec:operator-precedence} \subsection{Operator precedentie en associativiteit}\label{subsec:operator-precedence}
@ -212,45 +290,32 @@
Ghent Prolog ondersteunt operator precedentie en associativiteit. Ghent Prolog ondersteunt operator precedentie en associativiteit.
Deze functionaliteit bevindt zich in de parser, omdat de argumenten van een operator steeds rechtstreeks als parameters in de constructor van de klasse worden meegegeven. Deze functionaliteit bevindt zich in de parser, omdat de argumenten van een operator steeds rechtstreeks als parameters in de constructor van de klasse worden meegegeven.
Operator precedentie en associativiteit werd geïmplementeerd volgens de \href{https://www.swi-prolog.org/pldoc/man?section=operators}{SWI-Prolog documentatie}. Operator precedentie en associativiteit werd geïmplementeerd volgens de~\href{https://www.swi-prolog.org/pldoc/man?section=operators}{SWI-Prolog documentatie}.
\subsection{Database bewerkingen}\label{subsec:database}
Ghent Prolog laat toe om tijdens de programma-uitvoering database bestanden te laden met behulp van de \mintinline{prolog}{consult/1} predicaat.
De ingeladen predicaten worden als \mintinline{prolog}{static} gemarkeerd, maar kunnen \mintinline{prolog}{dynamic} worden met behulp van het \mintinline{prolog}{dynamic/1} predicaat.
Dit is analoog aan SWI-Prolog.
\subsection{Test driven development}\label{subsec:ttd} \subsection{Test driven development}\label{subsec:ttd}
Doorheen de ontwikkeling van grote delen van mijn implementatie heb ik gebruik gemaakt van Test Driven Development, onder andere met behulp van \href{https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-t-o-d-o.html}{Kotlin \texttt{TODO}}. Doorheen de ontwikkeling van grote delen van mijn implementatie heb ik gebruik gemaakt van Test Driven Development, onder andere met behulp van \href{https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-t-o-d-o.html}{Kotlin \texttt{TODO}}.
\subsection{Onafgewerkte Lexer en Parser implementatie}\label{subsec:lexer-parser}
Bij de start van het project was ik begonnen met het schrijven van mijn eigen lexer en parser.
Uit gebruik omdat het eenvoudiger was om de parser library
% TODO reference sectie over de parser
te gebruiken.
De implementatie was gebaseerd op \href{https://craftinginterpreters.com/contents.html}{Crafting Interpreters, Robert Nystrom} en \href{https://www.youtube.com/playlist?list=PLGNbPb3dQJ_5FTPfFIg28UxuMpu7k0eT4}{Building a Parser from scratch, Dmitry Soshnikov}.
De voorlopige implementatie van de lexer en parser kunnen hier teruggevonden worden.
% TODO Link naar commit met voorlopige implementatie
\section{Uitvoeren en testen}\label{sec:uitvoeren-en-testen} \section{Uitvoeren en testen}\label{sec:uitvoeren-en-testen}
Om Ghent Prolog op een Windows, Linux of MacOS uit te voeren is het voldoende om Java te installeren en Ghent Prolog op te roepen met `./src/gpl`. Om Ghent Prolog op een Windows, Linux of MacOS uit te voeren is het voldoende om Java te installeren en Ghent Prolog op te roepen met \texttt{./src/gpl}.
De nodige stappen, waaronder het bouwen van een JAR, worden dan automatisch uitgevoerd. De nodige stappen, waaronder het bouwen van een JAR, worden dan automatisch uitgevoerd.
De ingediende JAR kan ook handmatig opgeroepen worden met \texttt{java -jar ./build/gpl.jar}. De ingediende JAR kan ook handmatig opgeroepen worden met \texttt{java -jar ./build/gpl.jar}.
Er wordt ook een Docker omgeving voorzien waarin Ghent Prolog opgeroepen kan worden met \texttt{gpl}.
Het programma ondersteunt de volgende vlaggen:
% TODO gpl --help
\subsection{Testen}\label{subsec:testen} \subsection{Testen}\label{subsec:testen}
De testen kunnen uitgevoerd worden door de meeste IDE's. De testen kunnen uitgevoerd worden door de meeste IDE's.
Alternatief kunnen de testen uitgevoerd worden met \texttt{./gradlew test}. Alternatief kunnen de testen uitgevoerd worden met \texttt{./gradlew test}.
Resultaten worden naar \texttt{stdout} geschreven of kunnen bekeken worden met
% TODO HTML rapporten.
\section{Overzicht van geïmplementeerde predicaten}\label{sec:predicaten} \section{Overzicht van geïmplementeerde predicaten}\label{sec:predicaten}
@ -265,7 +330,7 @@
\begin{itemize} \begin{itemize}
\item \texttt{functor/3} \item \texttt{functor/3}
\item \texttt{arg/3} \item \texttt{arg/3}
\item \texttt{=..} \item \texttt{=../2}
\item \texttt{numbervars/1} \item \texttt{numbervars/1}
\item \texttt{numbervars/3} \item \texttt{numbervars/3}
\end{itemize} \end{itemize}
@ -366,10 +431,14 @@
Vaak zijn deze implementaties niet meer dan een proof of concept. Vaak zijn deze implementaties niet meer dan een proof of concept.
Ze waren enkel nuttig als inspiratie om specifieke problemen die zich tijdens de ontwikkeling van Ghent Prolog voordeden op te lossen. Ze waren enkel nuttig als inspiratie om specifieke problemen die zich tijdens de ontwikkeling van Ghent Prolog voordeden op te lossen.
Voorbeelden van zulke implementaties zijn:
\begin{itemize} \begin{itemize}
\item \href{https://github.com/adamjstewart/prolog}{adamjstewart/prolog}, in OCaml \item \href{https://github.com/adamjstewart/prolog}{adamjstewart/prolog}, in OCaml
\item \href{https://plzoo.andrej.com/language/miniprolog.html}{miniprolog}, in OCaml \item \href{https://plzoo.andrej.com/language/miniprolog.html}{miniprolog}, in OCaml
\item \href{https://github.com/benjamin-hodgson/Amateurlog}{benjamin-hodgson/Amateurlog}, in C\# \item \href{https://github.com/benjamin-hodgson/Amateurlog}{benjamin-hodgson/Amateurlog}, in C\#
\item \href{https://github.com/fmidue/prolog}{fmidue/prolog}, in Haskell
\item \href{https://github.com/patirasam/Prolog-Interpreter}{patirasam/Prolog-Interpreter}, in Python
\end{itemize} \end{itemize}
Van de implementaties die wel vermeld worden in~\cite{enwiki:1274527056}, zijn Van de implementaties die wel vermeld worden in~\cite{enwiki:1274527056}, zijn

22
examples/basics/summer.pl Normal file
View file

@ -0,0 +1,22 @@
summer(Start, End, 0) :- Start == End.
summer(Start, End, Sum) :-
Start \== End,
Next is Start + 1,
summer(Next, End, Rest),
writeln(rest(Rest)),
Sum is Start + Rest,
writeln(sum(Sum)).
my_sum :-
write('Enter start: '),
read(Start),
write('Enter end: '),
read(End),
summer(Start, End, Sum),
write('The sum is: '),
write(Sum), nl.
main :-
summer(1, 5, Sum).
:- initialization(main).

15
examples/meta/ceremony.pl Normal file
View file

@ -0,0 +1,15 @@
printer :-
writeln("Good evening everyone!"),
write("Thanks for coming "),
shift(Name),
write(Name), write(", ").
main :-
writeln("/Ceremony starts/"),
reset(printer, Name, Cont),
\+ \+ ( Name = "John", call(Cont) ),
\+ \+ ( Name = "Mary", call(Cont) ),
writeln("and my parents!"),
writeln("/Ceremony ends/").
:- initialization(main).

View file

@ -3,8 +3,9 @@ package prolog.ast.arithmetic
import prolog.Answers import prolog.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.logic.LogicOperand import prolog.ast.logic.LogicOperand
import prolog.ast.terms.Body
data class Integer(override val value: Int) : Number, LogicOperand() { data class Integer(override val value: Int) : Number, Body, LogicOperand() {
// Integers are already evaluated // Integers are already evaluated
override fun simplify(subs: Substitutions): Simplification = Simplification(this, this) override fun simplify(subs: Substitutions): Simplification = Simplification(this, this)

View file

@ -1,5 +1,8 @@
package prolog.ast.terms package prolog.ast.terms
import prolog.Substitutions
import prolog.ast.logic.Satisfiable import prolog.ast.logic.Satisfiable
interface Body : Term, Satisfiable interface Body : Term, Satisfiable {
override fun applySubstitution(subs: Substitutions): Body
}

View file

@ -40,8 +40,8 @@ class EvaluatesToDifferent(private val left: Expression, private val right: Expr
} }
override fun applySubstitution(subs: Substitutions): EvaluatesToDifferent = EvaluatesToDifferent( override fun applySubstitution(subs: Substitutions): EvaluatesToDifferent = EvaluatesToDifferent(
left.applySubstitution(subs) as Expression, applySubstitution(left, subs),
right.applySubstitution(subs) as Expression applySubstitution(right, subs)
) )
} }
@ -63,8 +63,8 @@ class EvaluatesTo(private val left: Expression, private val right: Expression) :
} }
override fun applySubstitution(subs: Substitutions): EvaluatesTo = EvaluatesTo( override fun applySubstitution(subs: Substitutions): EvaluatesTo = EvaluatesTo(
left.applySubstitution(subs) as Expression, applySubstitution(left, subs),
right.applySubstitution(subs) as Expression applySubstitution(right, subs)
) )
} }
@ -89,8 +89,8 @@ class Is(val number: Expression, val expr: Expression) :
} }
override fun applySubstitution(subs: Substitutions): Is = Is( override fun applySubstitution(subs: Substitutions): Is = Is(
number.applySubstitution(subs) as Expression, applySubstitution(number, subs),
expr.applySubstitution(subs) as Expression applySubstitution(expr, subs)
) )
} }
@ -117,8 +117,8 @@ open class Add(private val expr1: Expression, private val expr2: Expression) :
} }
override fun applySubstitution(subs: Substitutions): Add = Add( override fun applySubstitution(subs: Substitutions): Add = Add(
expr1.applySubstitution(subs) as Expression, applySubstitution(expr1, subs),
expr2.applySubstitution(subs) as Expression applySubstitution(expr2, subs)
) )
} }
@ -140,8 +140,8 @@ open class Subtract(private val expr1: Expression, private val expr2: Expression
} }
override fun applySubstitution(subs: Substitutions): Subtract = Subtract( override fun applySubstitution(subs: Substitutions): Subtract = Subtract(
expr1.applySubstitution(subs) as Expression, applySubstitution(expr1, subs),
expr2.applySubstitution(subs) as Expression applySubstitution(expr2, subs)
) )
} }
@ -158,8 +158,8 @@ class Multiply(val expr1: Expression, val expr2: Expression) :
} }
override fun applySubstitution(subs: Substitutions): Multiply = Multiply( override fun applySubstitution(subs: Substitutions): Multiply = Multiply(
expr1.applySubstitution(subs) as Expression, applySubstitution(expr1, subs),
expr2.applySubstitution(subs) as Expression applySubstitution(expr2, subs)
) )
} }
@ -173,8 +173,8 @@ class Divide(private val expr1: Expression, private val expr2: Expression) :
} }
override fun applySubstitution(subs: Substitutions): Divide = Divide( override fun applySubstitution(subs: Substitutions): Divide = Divide(
expr1.applySubstitution(subs) as Expression, applySubstitution(expr1, subs),
expr2.applySubstitution(subs) as Expression applySubstitution(expr2, subs)
) )
} }
@ -206,9 +206,9 @@ class Between(private val expr1: Expression, private val expr2: Expression, priv
} }
override fun applySubstitution(subs: Substitutions): Between = Between( override fun applySubstitution(subs: Substitutions): Between = Between(
expr1.applySubstitution(subs) as Expression, applySubstitution(expr1, subs),
expr2.applySubstitution(subs) as Expression, applySubstitution(expr2, subs),
expr3.applySubstitution(subs) as Expression applySubstitution(expr3, subs)
) )
override fun toString(): String = "$expr1..$expr3..$expr2" override fun toString(): String = "$expr1..$expr3..$expr2"

View file

@ -55,7 +55,7 @@ open class Conjunction(val left: LogicOperand, private val right: LogicOperand)
right.fold( right.fold(
// If the right part succeeds, yield the result with the left substitutions // If the right part succeeds, yield the result with the left substitutions
onSuccess = { rightSubs -> onSuccess = { rightSubs ->
yield(Result.success(leftSubs + rightSubs)) yield(Result.success(rightSubs + leftSubs))
}, },
onFailure = { exception -> onFailure = { exception ->
// If the right part fails, check if it's a cut // If the right part fails, check if it's a cut
@ -74,8 +74,8 @@ open class Conjunction(val left: LogicOperand, private val right: LogicOperand)
} }
fun findNextCutSolution(appliedCut: AppliedCut): Answers = sequence { fun findNextCutSolution(appliedCut: AppliedCut): Answers = sequence {
val leftSubs = appliedCut.subs val leftSubs = appliedCut.subs ?: emptyMap()
right.satisfy(subs + (appliedCut.subs!!)).firstOrNull()?.map { rightSubs -> right.satisfy(subs + leftSubs).firstOrNull()?.map { rightSubs ->
// If the right part succeeds, yield the result with the left substitutions // If the right part succeeds, yield the result with the left substitutions
yield(Result.success(leftSubs + rightSubs)) yield(Result.success(leftSubs + rightSubs))
return@sequence return@sequence

View file

@ -27,7 +27,7 @@ class Examples {
@Test @Test
fun debugHelper() { fun debugHelper() {
loader.load("examples/meta/continuations.pl") loader.load("examples/basics/summer.pl")
} }
@ParameterizedTest @ParameterizedTest
@ -60,11 +60,13 @@ class Examples {
Arguments.of("forall.pl", "Only alice likes pizza.\n"), Arguments.of("forall.pl", "Only alice likes pizza.\n"),
Arguments.of("fraternity.pl", "Citizen robespierre is eligible for the event.\nCitizen danton is eligible for the event.\nCitizen camus is eligible for the event.\n"), Arguments.of("fraternity.pl", "Citizen robespierre is eligible for the event.\nCitizen danton is eligible for the event.\nCitizen camus is eligible for the event.\n"),
Arguments.of("liberty.pl", "Give me Liberty, or give me Death!\nI disapprove of what you say, but I will defend to the death your right to say it.\nThe revolution devours its own children.\nSo this is how liberty dies, with thunderous applause.\n"), Arguments.of("liberty.pl", "Give me Liberty, or give me Death!\nI disapprove of what you say, but I will defend to the death your right to say it.\nThe revolution devours its own children.\nSo this is how liberty dies, with thunderous applause.\n"),
Arguments.of("summer.pl", "rest(0)\nsum(4)\nrest(4)\nsum(7)\nrest(7)\nsum(9)\nrest(9)\nsum(10)\n"),
Arguments.of("unification.pl", "While alice got an A, carol got an A, but bob did not get an A, dave did not get an A, unfortunately.\n"), Arguments.of("unification.pl", "While alice got an A, carol got an A, but bob did not get an A, dave did not get an A, unfortunately.\n"),
Arguments.of("write.pl", "gpl zegt: dag(wereld)\n"), Arguments.of("write.pl", "gpl zegt: dag(wereld)\n"),
) )
fun meta() = listOf( fun meta() = listOf(
Arguments.of("ceremony.pl", "/Ceremony starts/\nGood evening everyone!\nThanks for coming John, Mary, and my parents!\n/Ceremony ends/\n"),
Arguments.of("continuations.pl", "Inside test\nEntering reset\nAfter reset\nCalling Cont(2)\nIn test X = 5; done\nCalling Cont(4)\nIn test X = 9; done\n"), Arguments.of("continuations.pl", "Inside test\nEntering reset\nAfter reset\nCalling Cont(2)\nIn test X = 5; done\nCalling Cont(4)\nIn test X = 9; done\n"),
Arguments.of("mib_voorbeelden.pl", "b\nf(b)\nf(g(a,a),h(c,d),i(e,f))\nf(g(a,a),h(c,c),i(e,f))\nf(g(a,a),h(c,c),i(e,e))\n") Arguments.of("mib_voorbeelden.pl", "b\nf(b)\nf(g(a,a),h(c,d),i(e,f))\nf(g(a,a),h(c,c),i(e,f))\nf(g(a,a),h(c,c),i(e,e))\n")
) )