387 lines
18 KiB
TeX
387 lines
18 KiB
TeX
%! Author = tdpeuter
|
|
%! Date = 27/03/2025
|
|
|
|
\documentclass[11pt,a4paper]{article}
|
|
|
|
\usepackage{amsmath}
|
|
\usepackage[dutch]{babel} % Nederlands taal
|
|
\usepackage[style=apa]{biblatex} % Bronnen
|
|
\usepackage{enumitem} % Aanpasbare lijsten
|
|
\usepackage[margin=1in]{geometry} % Sane marges
|
|
\usepackage{hyperref} % Hyperlinks
|
|
\usepackage{minted} % Syntax highlighting
|
|
\usepackage{multicol} % Meerdere kolommen
|
|
\usepackage{url} % Beter geformatteerde URLs
|
|
|
|
\addbibresource{bibliography.bib}
|
|
|
|
\title{Ghent Prolog}
|
|
\author{Tibo De Peuter}
|
|
\date{\today}
|
|
|
|
\begin{document}
|
|
\maketitle
|
|
|
|
\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.
|
|
}
|
|
|
|
|
|
\section{Overzicht}\label{sec:overzicht}
|
|
|
|
% KERN: Programmeertaal, libraries, etc.
|
|
Ghent Prolog werd geschreven in Kotlin en maakt gebruik van zowel object-gerichte als functionele concepten.
|
|
|
|
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/xenomachina/kotlin-argparser}{\texttt{kotlin-argparser}}, voor het gebruik van command-line argumenten.
|
|
|
|
% 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.
|
|
|
|
|
|
\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}
|
|
|
|
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}.
|
|
|
|
\subsection{Cut}\label{subsec:cut}
|
|
|
|
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.
|
|
|
|
\subsection{Meta abstracties}\label{subsec:meta-abstractions}
|
|
|
|
|
|
\section{Resultaat}\label{sec:resultaat}
|
|
|
|
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{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
|
|
|
|
% 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}
|
|
|
|
\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}
|
|
|
|
\printbibliography
|
|
|
|
\appendix
|
|
\newpage
|
|
|
|
|
|
\section{Aanvullende opmerkingen}\label{sec:aanvullende-opmerkingen}
|
|
|
|
\subsection{Operator precedentie en associativiteit}\label{subsec:operator-precedence}
|
|
|
|
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.
|
|
|
|
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}
|
|
|
|
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}
|
|
|
|
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}.
|
|
|
|
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}
|
|
|
|
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
|
|
% TODO HTML rapporten.
|
|
|
|
|
|
\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{itemize}[label={}]
|
|
\item \textbf{Analysing and Constructing Terms}
|
|
\begin{itemize}
|
|
\item \texttt{functor/3}
|
|
\item \texttt{arg/3}
|
|
\item \texttt{=..}
|
|
\item \texttt{numbervars/1}
|
|
\item \texttt{numbervars/3}
|
|
\end{itemize}
|
|
\item \textbf{Arithmetic}
|
|
\begin{itemize}
|
|
\item \texttt{between/3}
|
|
\item \texttt{succ/2}
|
|
\item \texttt{plus/3}
|
|
\item \texttt{=\textbackslash=/2}
|
|
\item \texttt{=:=/2}
|
|
\item \texttt{is/2}
|
|
\item \texttt{-/1}
|
|
\item \texttt{+/1}
|
|
\item \texttt{+/2}
|
|
\item \texttt{*/2}
|
|
\item \texttt{//2}
|
|
\item \texttt{inf/0}
|
|
\end{itemize}
|
|
\item \textbf{Comparison and Unification of Terms}
|
|
\begin{itemize}
|
|
\item \texttt{=/2}
|
|
\item \texttt{\textbackslash=/2}
|
|
\item \texttt{==/2}
|
|
\item \texttt{\textbackslash==/2}
|
|
\end{itemize}
|
|
\item \textbf{Control Predicates}
|
|
\begin{itemize}
|
|
\item \texttt{fail/0}
|
|
\item \texttt{false/0}
|
|
\item \texttt{true/0}
|
|
\item \texttt{!/0}
|
|
\item \texttt{,/2}
|
|
\item \texttt{;/2}
|
|
\item \texttt{|/2}
|
|
\item \texttt{\textbackslash+/1}
|
|
\end{itemize}
|
|
\item \textbf{Database}
|
|
\begin{itemize}
|
|
\item \texttt{retract/1}
|
|
\item \texttt{retractall/1}
|
|
\item \texttt{asserta/1}
|
|
\item \texttt{assertz/1}
|
|
\item \texttt{assert/1}
|
|
\end{itemize}
|
|
\item \textbf{Declaring predicate properties}
|
|
\begin{itemize}
|
|
\item \texttt{dynamic/1}
|
|
\end{itemize}
|
|
\item \textbf{Delimited continuations}
|
|
\begin{itemize}
|
|
\item \texttt{reset/3}
|
|
\item \texttt{shift/1}
|
|
\end{itemize}
|
|
\item \textbf{Examining the program}
|
|
\begin{itemize}
|
|
\item \texttt{clause/2}
|
|
\end{itemize}
|
|
\item \textbf{Forall}
|
|
\begin{itemize}
|
|
\item \texttt{forall/2}
|
|
\end{itemize}
|
|
\item \textbf{Loading Prolog source files}
|
|
\begin{itemize}
|
|
\item \texttt{consult/1}
|
|
\item \texttt{initialization/1}
|
|
\end{itemize}
|
|
\item \textbf{Meta-Call Predicates}
|
|
\begin{itemize}
|
|
\item \texttt{call/1}
|
|
\item \texttt{once/1}
|
|
\item \texttt{ignore/1}
|
|
\end{itemize}
|
|
\item \textbf{Primitive character I/O}
|
|
\begin{itemize}
|
|
\item \texttt{nl/0}
|
|
\end{itemize}
|
|
\item \textbf{Term reading and writing}
|
|
\begin{itemize}
|
|
\item \texttt{write/1}
|
|
\item \texttt{writeln/1}
|
|
\item \texttt{read/1}
|
|
\end{itemize}
|
|
\item \textbf{Verify Type of a Term}
|
|
\begin{itemize}
|
|
\item \texttt{var/1}
|
|
\item \texttt{nonvar/1}
|
|
\item \texttt{atom/1}
|
|
\item \texttt{compound/1}
|
|
\end{itemize}
|
|
\end{itemize}
|
|
\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}
|
|
|
|
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}
|