Compare commits

...

3 commits

Author SHA1 Message Date
5726f33d34 Sync 2025-05-12 15:30:50 +02:00
a5110f5a9d
Sync 2025-05-12 12:19:02 +02:00
e5ef4244e3 Sync 2025-05-11 21:44:25 +02:00
3 changed files with 237 additions and 49 deletions

View file

@ -1,11 +0,0 @@
@book{russell2016,
author = {Russell, Stuart and Norvig, Peter},
title = {{Artificial Intelligence A Modern Approach, Global Edition}},
abstract = {{For one or two-semester, undergraduate or graduate-level courses in Artificial Intelligence. The long-anticipated revision of this best-selling text offers the most comprehensive, up-to-date introduction to the theory and practice of artificial intelligence. }},
pages = {1152},
publisher = {Pearson Deutschland},
year = {2016},
isbn = {9781292153964},
doi = {},
url = {https://elibrary.pearson.de/book/99.150005/9781292153971}
}

View file

@ -0,0 +1,39 @@
@book{boizumault-1993,
author = {given-i=P, given=Patrice, family=Boizumault},
date = {1993-12-31},
doi = {10.1515/9781400863440},
publisher = {Princeton University Press},
title = {The Implementation of Prolog},
url = {https://doi.org/10.1515/9781400863440},
}
@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},
date = {1996-01-01},
doi = {10.1007/978-3-642-61411-8},
title = {Prolog: The Standard},
url = {https://doi.org/10.1007/978-3-642-61411-8},
}
@misc{enwiki:1274527056,
author = "{Wikipedia contributors}",
title = "Comparison of Prolog implementations --- {Wikipedia}{,} The Free Encyclopedia",
year = "2025",
howpublished = "\url{https://en.wikipedia.org/w/index.php?title=Comparison_of_Prolog_implementations&oldid=1274527056}",
note = "[Online; accessed 12-May-2025]"
}
@book{russell2016,
author = {Russell, Stuart and Norvig, Peter},
booktitle = {{Artificial Intelligence A Modern Approach, Global Edition}},
title = {Chapter 9. Inference in First-Order Logic},
abstract = {{For one or two-semester, undergraduate or graduate-level courses in Artificial Intelligence.
The long-anticipated revision of this best-selling text offers the most comprehensive, up-to-date introduction to the theory and practice of artificial intelligence.
}},
pages = {325--345},
publisher = {Pearson Deutschland},
year = {2016},
isbn = {9781292153964},
doi = {},
url = {https://elibrary.pearson.de/book/99.150005/9781292153971}
}

View file

@ -5,59 +5,179 @@
\usepackage{amsmath} \usepackage{amsmath}
\usepackage[dutch]{babel} % Nederlands taal \usepackage[dutch]{babel} % Nederlands taal
\usepackage[style=apa]{biblatex} % Bronnen
\usepackage{enumitem} % Aanpasbare lijsten \usepackage{enumitem} % Aanpasbare lijsten
\usepackage[margin=1in]{geometry} % Sane marges \usepackage[margin=1in]{geometry} % Sane marges
\usepackage{hyperref} % Hyperlinks
\usepackage{minted} % Syntax highlighting
\usepackage{multicol} % Meerdere kolommen \usepackage{multicol} % Meerdere kolommen
\usepackage{url} % Beter geformatteerde URLs
\addbibresource{bibliography.bib}
\title{Ghent Prolog} \title{Ghent Prolog}
\author{Tibo De Peuter} \author{Tibo De Peuter}
\date{\today} \date{\today}
% Document
\begin{document} \begin{document}
\maketitle \maketitle
\section{Inleiding}\label{sec:inleiding} \abstract{
% KERN: Wat is Ghent Prolog?
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.
}
% Uitvoeren van testen ref appendix
\section{Architectuur}\label{sec:architectuur} \section{Overzicht}\label{sec:overzicht}
% Overzicht van programma-loop % KERN: Programmeertaal, libraries, etc.
Ghent Prolog werd geschreven in Kotlin en maakt gebruik van zowel object-gerichte als functionele concepten.
\begin{enumerate} Er werden twee bibliotheken gebruikt:
\item Lexing and parsing \href{https://github.com/h0tk3y/better-parse}{\texttt{better-parse}}, voor het parsen van een gegeven grammatica, en
\item Preprocessor \href{https://github.com/xenomachina/kotlin-argparser}{\texttt{kotlin-argparser}}, voor het gebruik van command-line argumenten.
\item Programma en Repl
\end{enumerate}
% Parser maakt gebruik van welk Grammar??? % KERN: Hoe ziet Ghent Prolog er abstract uit?
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.
% Preprocessor om parser eenvoudig te houden (eventueel later eigen implementatie
% Interfaces: satisfiable, resolvent \section{Lexing, parsing en preprocessing}\label{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}
% KERN: Datastructuur
Ghent Prolog maakt gebruik van functie-oproepen streams om het backtracking mechanisme te implementeren.
Bijzondere logica met betrekking tot \textit{choicepoints} en foutenafhandeling maakt gebruik van de Kotlin \texttt{Result} klasse.
Om de resultaten van unificatie en evaluatie lazy te verwerken, wordt er gebruik gemaakt van Kotlin \texttt{Sequence}s (\textit{lazy streams}).
Meer specifiek wordt er gebruik gemaakt van de volgende types:
\begin{minted}{kotlin}
typealias Substitutions = Map<Term, Term>
typealias Answer = Result<Substitutions>
typealias Answers = Sequence<Answer>
\end{minted}
Een lege \mintinline{kotlin}{Sequence} betekent dat er geen oplossingen zijn gevonden.
Een \mintinline{kotlin}{Result.success} met \mintinline{kotlin}{Substitutions} representeert een oplossing met de resulterende substituties (die leeg kan zijn).
Een \texttt{Result.failure} betekent een fout of bijzondere logica die afgehandeld moet worden.
Er werden twee interfaces gedefinieerd:
\begin{minted}{kotlin}
interface Resolvent {
fun solve(goal: Goal, substitutions: Substitutions): Answers
}
interface Satisfiable {
fun satisfy(substitutions: Substitutions): Answers
}
\end{minted}
Klassen die \mintinline{kotlin}{Resolvent} implementeren kunnen een \textit{goal} oplossen, bijvoorbeeld een database of een regel (\textit{rule}).
Klassen die \mintinline{kotlin}{Satisfiable} implementeren kunnen worden geëvalueerd, bijvoorbeeld operatoren.
De verschillende Prolog termen werden voorgesteld als klassen op basis van
\href{https://www.swi-prolog.org/pldoc/man?section=glossary}{SWI-Prolog's Glossary of Terms},
met bijkomende inspiratie uit~\cite{deransart-1996}.
\subsection{Evaluatiestrategie}\label{subsec:evaluatiestrategie}
% KERN: Evaluatiestrategie
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.
Daarna wordt de body van de clause 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.
Vervolgens zal het programma 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.
% TODO Verklarende figuur met substitutie toevoegen
% Overzicht van geïmplementeerde predicaten in appendix.
Operator-specifieke logica bevindt zich in de klassen van de operatoren zelf.
\subsection{Unificatie}\label{subsec:unificatie} \subsection{Unificatie}\label{subsec:unificatie}
Zoals beschreven in Artificial Intelligence a Modern Approach. Algoritme is geïnspireerd door Het unificatie-algoritme is gebaseerd op Robinsons Unificatie-algoritme, zoals beschreven door~\cite{boizumault-1993}, met inspiratie van~\cite{russell2016}.
Merk op dat er gebruik gemaakt wordt van de \textit{occurs check}.
\section{Implementatie}\label{sec:implementatie} \subsection{Cut}\label{subsec:cut}
% Kotlin, libraries 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.
% Data representatie: klassen, satisfiable, resolvent \subsection{Meta abstracties}\label{subsec:meta-abstractions}
%% Evaluatie strategie
% Overzicht van geïmplementeerde predicaten in appendix.
% Belangrijkste delen zijn Clause.satisfy
\section{Resultaat}\label{sec:resultaat} \section{Resultaat}\label{sec:resultaat}
% Code wordt lelijk en overzichtelijk. Door de geneste datatypes veel boilerplate. De implementatie van Ghent Prolog ondersteunt de gevraagde functionaliteit, waaronder database operaties, meta abstracties.
De architecturele verschillen met SWI-Prolog zijn echter groot, wat zowel positieve als negatieve gevolgen heeft.
\subsection{Afwijkingen van SWI-Prolog}\label{subsec:afwijkingen} \subsection{Voordelen}\label{subsec:voordelen}
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.
Een volledig overzicht van de geïmplementeerde predicaten kan teruggevonden worden in sectie~\ref{sec:predicaten}.
\subsection{Uitdagingen}\label{subsec:uitdagingen}
% KERN: Nesting probleem
\textbf{Code wordt snel onoverzichtelijk} door het gebruik van \texttt{Sequence} en \texttt{Result}.
Belangrijke logica zit genest in onduidelijke boilerplate.
Volgende code komt bijvoorbeeld in de meeste termen voor, weliswaar in verschillende vorm:
\begin{minted}{kotlin}
/* Function entry logic */
unifyLazy(a, b, subs).forEach { firstResult ->
firstResult.map { firstSubs ->
/* First stage logic, e.g. preparing the body etc. */
c.satsify(firstSubs).forEach { secondResult ->
secondResult.fold(
onSuccess = { secondSubs ->
/* End result logic */
yield(Result.success(firstSubs + secondSubs))
}
onFailure = { failure ->
when (failure) {
is AppliedCut -> /* Cut logic */
is AppliedShift -> /* Shift logic */
} } ) } } }
\end{minted}
% KERN: Overerving zorgt voor boilerplate
\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.
Hoewel nuttig voor de representatie van de AST en generieke functies, moeten de meeste klassen de \texttt{satsify} en \texttt{solve} methoden overschrijven.
Dit zorgt voor boilerplate code en verpreid de logica over verschillende klassen.
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}
% TODO Maak appendix?
\begin{itemize}
\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.
\item ISO Prolog en SWI-Prolog maken gebruik van SLD-resolutie
% TODO Bron
\end{itemize}
% Occurs check % Occurs check
@ -67,60 +187,78 @@
% TODO Bronnen voor ISO-Prolog maakt gebruik van SLD resolutie % TODO Bronnen voor ISO-Prolog maakt gebruik van SLD resolutie
% TODO Bronnen voor SWIPL 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}
% Stack gebruiken \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.
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}.
\end{itemize}
\section{Conclusie}\label{sec:conclusie} \section{Conclusie}\label{sec:conclusie}
\printbibliography
\appendix \appendix
\newpage \newpage
\section{Aanvullende opmerkingen}\label{sec:aanvullende-opmerkingen} \section{Aanvullende opmerkingen}\label{sec:aanvullende-opmerkingen}
\subsection{Operator precedence en associativiteit}\label{subsec:operator-precedence} \subsection{Operator precedentie en associativiteit}\label{subsec:operator-precedence}
Mijn implementatie heeft volledig ondersteuning voor operator precendentie en associativiteit. Ghent Prolog ondersteunt operator precedentie en associativiteit.
% TODO Link naar prolog tabel 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}.
\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 door eerst een klasse te definiëren met Kotlin TODO() implementaties, vervolgens testen te schrijven die falen en pas daarna mijn functies te implementeren todat de testen slagen. 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} \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 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 % TODO reference sectie over de parser
te gebruiken. te gebruiken.
De implementatie was gebaseerd op 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}.
% TODO Crafting interpreters
% TODO Dmitri Soshnikov
De voorlopige implementatie van de lexer en parser kunnen hier teruggevonden worden. De voorlopige implementatie van de lexer en parser kunnen hier teruggevonden worden.
% TODO Link naar commit met voorlopige implementatie % 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`. De nodige stappen, waaronder het bouwen van een JAR, worden dan automatisch uitgevoerd. 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`.
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 word ook een Docker omgeving voorzien waarin Ghent Prolog opgeroepen kan worden met \texttt{gpl}. Er wordt ook een Docker omgeving voorzien waarin Ghent Prolog opgeroepen kan worden met \texttt{gpl}.
Het programma ondersteunt de volgende vlaggen: Het programma ondersteunt de volgende vlaggen:
% TODO gpl --help % TODO gpl --help
\subsection{Testen}\label{subsection: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}. Resultaten worden naar \texttt{stdout} geschreven of kunnen bekeken worden met Alternatief kunnen de testen uitgevoerd worden met \texttt{./gradlew test}.
Resultaten worden naar \texttt{stdout} geschreven of kunnen bekeken worden met
% TODO HTML rapporten. % TODO HTML rapporten.
\section{Overzicht van geïmplementeerde predicaten}\label{sec:predicaten} \section{Overzicht van geïmplementeerde predicaten}\label{sec:predicaten}
Deze sectie geeft een overzicht van de geïmplementeerde predicaten in Ghent Prolog, gesorteerd volgens de categorieën die in
\href{https://www.swi-prolog.org/pldoc/man?section=builtin}{de SWI-Prolog documentatie}
gebruikt worden.
\begin{multicols}{2} \begin{multicols}{2}
\begin{itemize}[label={}] \begin{itemize}[label={}]
\item \textbf{Analysing and Constructing Terms} \item \textbf{Analysing and Constructing Terms}
@ -220,8 +358,30 @@
\end{itemize} \end{itemize}
\end{multicols} \end{multicols}
\section{Bestaande Prolog implementaties}\label{sec:prolog-implementaties}
Tijdens mijn onderzoek naar Prolog implementaties, kwam ik verschillende bestaande Prolog implementaties tegen die niet vermeld worden in~\cite{enwiki:1274527056}.
Daarom zou ik durven stellen dat er geen gebrek is aan Prolog implementaties, maar dat er een gebrek is aan \emph{goede} Prolog implementaties.
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.
\begin{itemize}
\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://github.com/benjamin-hodgson/Amateurlog}{benjamin-hodgson/Amateurlog}, in C\#
\end{itemize}
Van de implementaties die wel vermeld worden in~\cite{enwiki:1274527056}, zijn
\href{https://www.swi-prolog.org/}{SWI-Prolog},
\href{https://sicstus.sics.se/index.html}{SICStus Prolog} en
\href{https://dobrev.com/}{Strawberry Prolog} de meest interessante.
\section{Dankwoord}\label{sec:dankwoord} \section{Dankwoord}\label{sec:dankwoord}
Bedankt aan LogicalCaptain om steeds nuttige informatie en voorbeelden te geven bij SWI-Prolog documentatie die iets minder duidelijk is. Uw nota's waren verhelderend en zorgden voor een beter begrip van en voor de nuances van SWI-Prolog. Bedankt, \href{https://www.swi-prolog.org/user/view_profile?user=c86f61d8-c201-11e6-84af-00163e986a2a}{LogicalCaptain}
om steeds nuttige informatie en voorbeelden te geven bij SWI-Prolog documentatie die iets minder duidelijk is.
Uw nota's waren verhelderend en zorgden voor een beter begrip van en voor de nuances van SWI-Prolog.
\end{document} \end{document}