-
Notifications
You must be signed in to change notification settings - Fork 14
/
pl2cpp.doc
4547 lines (3877 loc) · 190 KB
/
pl2cpp.doc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\documentclass[11pt,a4paper]{report}
\usepackage{times}
\usepackage{pl}
\usepackage{plpage}
%\usepackage{xpce}
\usepackage{html}
\sloppy
\onefile
\htmloutput{.} % Output directory
\htmlmainfile{pl2cpp} % Main document file
\bodycolor{white} % Page colour
\renewcommand{\runningtitle}{A C++ interface to SWI-Prolog}
\makeindex
\begin{document}
\title{A C++ interface to SWI-Prolog}
\author{Jan Wielemaker \& Peter Ludemann \\
SWI-Prolog Solutions b.v. \\
E-mail: \email{[email protected]}}
\maketitle
\begin{abstract}
This document describes a C++ interface to SWI-Prolog. SWI-Prolog could
be used with C++ for a very long time, but only by calling the extern
"C" functions of the C-interface. The interface described here
provides a true C++ layer around the C-interface for much more concise
and natural programming from C++. The interface deals with automatic
type-conversion to and from native C data-types, transparent mapping of
exceptions, making queries to Prolog and registering foreign predicates.
This document describes version~2 of the C++ interface. Version~1 is
considered \textit{deprecated}. Version 2 is implemented by
\file{SWI-cpp2.h} and \file{SWI-cpp2.cpp}. This is a much more mature
C++ interface has been designed and implemented by Peter Ludemann.
\end{abstract}
\vfill
\vfill
\vfill
\newpage
\tableofcontents
\newpage
\chapter{A C++ interface to SWI-Prolog}
\label{sec:cpp2}
\section{Summary of changes between Versions 1 and 2}
\label{sec:summary-cpp2-changes}
Version~1 is in \file{SWI-cpp.h}; version~2 is in \file{SWI-cpp2.h},
\file{SWI-cpp2.cpp}, \file{SWI-cpp2-plx.h}, and \file{SWI-cpp2-atommap.h}.
The overall structure of the API has been retained - that is, it is a
thin layer of lightweight classes on top of the interface provided by
\file{SWI-Prolog.h}. Based on experience with the API, most of the
conversion operators and some of the comparison operators have been
removed or deprecated, and replaced by ``getter'' methods; the
overloaded constructors have been replaced by subclasses for the
various types. Some changes were also made to ensure that the
\const{[]} operator for \ctype{PlTerm} and \ctype{PlTermv} doesn't
cause unexpected implicit conversions.\footnote{If
there is an implicit conversion operator from \ctype{PlTerm} to
\ctype{term_t} and also to \ctype{char*}, then the \const{[]} operator
is ambiguous if \exam{f} is overloaded to accept a \ctype{term_t} or
\ctype{char*} in the code \exam{PlTerm t=...; f(t[0])}. }
Prolog errors are now converted to C++ exceptions (which contain
the exception term rather being a subclass of \ctype{PlTerm} as in
version 1), where they can be caught and thrown using the usual C++
mechanisms; and the subclasses that create exceptions have been
changed to functions. In addition, an exception type \ctype{PlFail}
has been added, together with \ctype{PlCheckFail}, to allow more compact
code by ``short circuit'' return to Prolog on failure.
A convenience class for creating blobs has been added, so that an
existing structure can be converted to a blob with only a few lines
of code. More specifically:
\begin{itemize}
\item \file{SWI-cpp2.cpp} has been added, containing the
implementation of some functions. This is included by default
from \file{SWI-cpp2.h} or can be compiled separately.
\item
The constructor PlTerm() is restricted to a few
unambiguous cases - instead, you should use the appropriate
subclass constructors that specify the type (PlTerm_var(),
PlTerm_atom(), etc.).
\item
Wrapper functions have been provided for almost all the PL_*()
functions in \file{SWI-Prolog.h}, and have the same names with
the ``PL'' replaced by ``Plx''.\footnote{``Pl'' is used
throughout the \file{SWI-cpp2.h} interface, and the ``x'' is
for ``eXtended with eXception handling.''}
Where appropriate, these check return codes and throw a C++
exception (created from the Prolog error).
See \secref{cpp2-wrapper-functions}.
Many of these wrapper functions are also methods in the \ctype{PlAtom}
and \ctype{PlTerm} classes, with the arguments changed from
\ctype{atom_t} and \ctype{term_t} to \ctype{PlAtom} and \ctype{PlTerm}
and in some cases \ctype{char*} and \ctype{wchar_t*}
changed to \ctype{std::string} and \ctype{std::wstring}.
These wrappers are available if you include \file{SWI-cpp2.h}
(they are in a separate \file{SWI-cpp2-plx.h} file for ease
of maintenance).
\item
Instead of returning \const{false} from a foreign predicate to
indicate failure, you can throw PlFail(). The
convenience function PlCheckFail(rc) can be used to
throw PlFail() if \const{false} is returned from a function in
\file{SWI-Prolog.h}. If the wrapper functions or class methods
are used, Prolog errors result in a C++ \ctype{PlException}
exception.\footnote{If a ``Plx_'' wrapper is used to call a
\file{SWI-Prolog.h} function, a Prolog error will have already
resulted in throwing \ctype{PlException}};
PlCheckFail(rc) is used to additionally throw
\ctype{PlFail}, similar to returning \const{false} from the
top-level of a foreign predicate - Prolog will check for an error
and call throw/1 if appropriate.
\item
The \ctype{PlException} class is now a subclass of \ctype{std::exception}
and encapsulates a Prolog error.
Prolog errors are converted into \exam{throw PlException(...)}.
If the user code does not catch the \ctype{PlException}, the PREDICATE()
macro converts the error to a Prolog error upon return to the
Prolog caller.
\item
The C++ constructors, functions, and methods use the wrapper
functions to throw a C++ exception on error (this C++ exception is
converted to a Prolog exception when control returns to
Prolog).
\item
The ``cast'' operators (e.g., \exam{(char*)t}, \exam{(int64_t)t},
\exam{static_cast<char*>(t)})
have been deprecated, replaced by ``getters'' (e.g.,
\exam{t.as_string()}, \exam{t.as_int64_t()}).
\item
The overloaded assignment operator for unification is deprecated,
replaced by \cfuncref{PlTerm::unify_term}{}, \cfuncref{PlTerm::unify_atom}{},
etc., and the helper \cfuncref{PlCheckFail}{}.
\item
Many of the equality and inequality operators are deprecated;
replaced by the PlTerm::as_string() or PlTerm::get_nchars() methods
and the associated
\ctype{std::string}, comparison operators. The \cfuncref{PlTerm::as_string}{} method
allows specifying the encoding to use whereas the \exam{==} and
similar operators do not allow for this.
\item
Methods that return \ctype{char*} have been replaced by methods
that return \ctype{std::string} to ensure that lifetime issues
don't cause subtle bugs.\footnote{If you want to
return a \ctype{char*} from a function, you should not do
\exam{return t.as_string().c_str()} because that will return
a pointer to local or stack memory. Instead, you should
change your interface to return a \ctype{std::string} and apply
the \exam{c_str()} method to it. These lifetime errors can
\emph{sometimes} be caught by specifying the Gnu C++ or Clang
options \exam{-Wreturn-stack-address} or
\exam{-Wreturn-local-addr} - as of 2023-04, Clang seems to do a
better analysis.}
\item
Most constructors, methods, and functions that accept \ctype{char*}
or \ctype{wchar_t*}
arguments also accept \ctype{std::string} or \ctype{std::wstring}
arguments. Where possible, encoding information can also be
specified.
\item
Type-checking methods have been added: PlTerm::type(),
PlTerm::is_variable(), PlTerm::is_atom(), etc.
\item
\ctype{PlString} has been renamed to \ctype{PlTerm_string} to make it clear
that it's a term that contains a Prolog string.
\item
More \exam{PL_...(term_t, ...)} methods have been added to \ctype{PlTerm},
and \exam{PL_...(atom_t, ...)} methods have been added to \ctype{PlAtom}.
Where appropriate, the arguments use \ctype{PlTerm}, \ctype{PlAtom}, etc.
instead of \ctype{term_t}, \ctype{atom_t}, etc.
\item
Most functions/methods that return an \ctype{int} for true/false now
return a C++ \ctype{bool}.
\item
The wrapped C types fields (\ctype{term_t}, \ctype{atom_t}, etc.)
have been renamed from \exam{handle}, \exam{ref}, etc. to
\exam{C_}.\footnote{This is done by subclassing from
\ctype{Wrapped<term_t>}, \ctype{Wrapped<atom_t>}, etc., which
define the field \exam{C_}, standard constructors, the methods
is_null(), not_null(), reset(), reset(v), reset_wrapped(v),
plus the constant \const{null}.} This value can be accessed by
the unwrap() and unwrap_as_ptr() methods.
There is also a ``friend'' function PlUnwrapAsPtr().
\item
A convenience function \exam{PlControl::context_unique_ptr<ContextType>()}
has been added, to simplify dynamic memory allocation in
non-deterministic predicates.
\item
A convenience function PlRewindOnFail() has been added,
to simplify non-deterministic code that does backtracking by
checking unification results.
\item
\ctype{PlStringBuffers} provides a simpler interface for allocating
strings on the stack than PL_STRINGS_MARK() and PL_STRINGS_RELEASE().
However, this is mostly not needed because most functions now
use \ctype{std::string}: see \secref{cpp2-strings}.
\item
\ctype{PlStream} provides a simpler interface for streams than
PL_get_stream(), PL_acquire_stream(), and PL_release_stream().
See \secref{cpp2-stream-io}.
\item
Wrapper classes for \ctype{record_t} have been added. The
\ctype{PlRecordExternalCopy} class contains the opaque handle,
as a convenience.
\item
Wrapper class for \ctype{control_t} has been added and the
PREDICATE_NONDET() has been modified to use it.
\end{itemize}
More details on the rationale and how to port from version 1 to
version 1 are given in \secref{cpp2-rationale} and
\secref{cpp2-porting-1-2}.
\section{A simple example}
\label{sec:cpp2-foreign-example}
Here is the ``simple example'' in the
\href{https://www.swi-prolog.org/pldoc/man?section=foreign-example}{Foreign Language Interface},
rewritten in C++. As before, it is compiled by
\begin{code}
swipl-ld -o calc -goal true calc.cpp calc.pl
\end{code}
\begin{code}
#include <string>
#include <SWI-cpp2.h>
int main(int argc, char **argv) {
PlEngine e(argv[0]);
// combine all the arguments in a single string
std::string expression;
for (int n = 1; n < argc; n++) {
if (n != 1) {
expression.append(" ");
}
expression.append(argv[n]);
}
// Lookup calc/1 and make the arguments and call
PlPredicate pred("calc", 1, "user");
PlTerm_string h0(expression);
PlQuery q(pred, PlTermv(h0), PL_Q_NORMAL);
return q.next_solution() ? 0 : 1;
}
\end{code}
\section{Sample code}
\label{sec:cpp2-sample-code}
The file
\href{https://github.com/SWI-Prolog/packages-cpp/blob/master/test_cpp.cpp}{test_cpp.cpp}
contains examples of Prolog predicates written in C++. This file is
used for testing (called from
\href{https://github.com/SWI-Prolog/packages-cpp/blob/master/test_cpp.pl}{test_cpp.pl}).
Notable examples:
\begin{itemize}
\item add_num/3 - same as \exam{A3 is A1+A2}, converting the sum
to an integer if possible.
\item name_arity/3 - C++ implementation of functor/3.
\item average/3 - computes the average of all the solutions to \arg{Goal}
\item can_unify/2 - tests whether the two arguments can unify with each
other, without instantiating anything (similar to unifiable/3).
\item eq1/1, eq2/2, eq3/2 - three different ways of implementing =/2.
\item write_list/1 - outputs the elements of a list, each on a new line.
\item cappend/3 - appends two lists (requires that the two lists are
instantiated).
\item square_roots/2 - same as \verb$bagof(Sqrt, X^(between(0,4,X), Sqrt is sqrt(X)), A2)$.
\item range_cpp/3 - on backtracking, generates all integers starting
at \arg{A1} and less than \arg{A2} (that is, one less than between/3).
\item int_info/2 - on backtracking generates all the integral types with their
minimum and maximum values.
\end{itemize}
The file
\href{https://github.com/SWI-Prolog/packages-cpp/blob/master/test_cpp.cpp}{likes.cpp}
contains a simple program that calls the Prolog predicate likes/2 and
happy/1 (these predicates are defined in
\href{https://github.com/SWI-Prolog/packages-cpp/blob/master/test_cpp.pl}{likes.pl}.
The usage and how to compile the code are in comments in \file{likes.cpp}
\section{Introduction}
\label{sec:cpp2-intro}
C++ provides a number of features that make it possible to define a
more natural and concise interface to dynamically typed languages than
plain C does. Using type-conversion (\jargon{casting}) and
overloading, native data-types can be easily translated into
appropriate Prolog types, automatic destructors can be used to deal
with most of the cleanup required and C++ exception handling can be
used to map Prolog exceptions and interface conversion errors to C++
exceptions, which are automatically mapped to Prolog exceptions as
control is turned back to Prolog.
However, there are subtle differences between Prolog and C++ that can
lead to confusion; in particular, the lifetime of terms do not fit
well with the C++ notion of constructor/destructor. It might be
possible to handle this with ``smart pointers'', but that would lead to
other complications, so the decision was made to provide a thin layer
between the underlying C functions and the C++ classes/methods/functions.
More information on the SWI-Prolog native types is given in
\href{https://www.swi-prolog.org/pldoc/man?section=foreigntypes}{Interface
Data Types}.
It would be tempting to use C++ implicit conversion operators and
method overloading to automatically convert between C++ types such as
\ctype{std::string} and \ctype{int64_t} and Prolog foreign language
interface types such as \ctype{term_t} and \ctype{atom_t}. However,
types such as \ctype{term_t} are unsigned integers, so many of the
automatic type conversions can inadvertently do something other than
what the programmer intended, resulting in subtle bugs that are
difficult to find. Therefore Version 2 of this interface reduces the
amount of automatic conversion and introduces some redundancy, to
avoid these subtle bugs, by using ``getter'' methods rather than
conversion operators, and using naming conventions for explicitly
specifying constructors.
\subsection{Acknowledgements}
\label{sec:cpp2-acknowledgements}
I would like to thank Anjo Anjewierden for comments on the definition,
implementation and documentation of the original C++ interface. Peter
Ludemann implemented the current version (2) of the interface (see
\secref{summary-cpp2-changes}).
\section{The life of a PREDICATE}
\label{sec:cpp2-life-of-a-predicate}
A foreign predicate is defined using the PREDICATE()
macro, plus a few variations on this, such as
PREDICATE_NONDET(), NAMED_PREDICATE(), and
NAMED_PREDICATE_NONDET(). These define an internal name for
the function, register it with the SWI-Prolog runtime (where it will
be picked up by the use_foreign_library/1 directive), and define the
names \exam{A1}, \exam{A2}, etc. for the arguments.\footnote{You can
define your own names for the arguments, for example:
\exam{auto dir=A1, db=A2;} or \exam{PlTerm options(A3);}.}
If a non-deterministic predicate is being
defined, an additional parameter \exam{handle} is defined (of type
\ctype{PlControl}).
The foreign predicate returns a value:
\begin{itemize}
\item \const{true} - success
\item \const{false} - failure or an error (see \secref{cpp2-exceptions}
and \href{https://www.swi-prolog.org/pldoc/man?section=foreign-exceptions}{Prolog exceptions in foreign code}).
\item ``retry'' - for non-deterministic predicates, gives a ``context''
for backtracking / redoing the call for the next solution.
\end{itemize}
If a predicate fails, it
could be simple failure (the equivalent of calling the builtin fail/0
predicate) or an error (the equivalent of calling the throw/1
predicate). When a Prolog exception is raised, it is important that a
return be made to the calling environment as soon as possible. In C
code, this requires checking every call for failure, which can become
cumbersome; with the C++ API, most errors are thrown as exceptions to
the enclosing PREDICATE() wrapper, and turned back into Prolog errors.
The C++ API provides Plx_*() functions that are the same as the PL_*()
functions except that where appropriate they check for exceptions and
thrown a PlException().
Addditionally, the function PlCheckFail() can be used to
check for failure and throw a \ctype{PlFail} exception that
is handled before returning to Prolog with failure.
The following three snippets do essentially the same thing (for
implementing the equivalent of =/2); however the first version (with
PlTerm::unify_term()) and second version (with Plx_unify()) throw a
C++ \ctype{PlExceptionFail} exception if there's an error and
otherwise return \const{true} or \const{false}; the third version
(with \cfuncref{PlCheckFail}{}) throws a \ctype{PlFail} exception for
failure (and \ctype{PlExceptionFail} for an error) and otherwise
returns \const{true} - the PREDICATE() wrapper handles all of these
appropriately and reports the same result back to Prolog; but you
might wish to distinguish the two situations in more complex code.
\begin{code}
PREDICATE(eq, 2)
{ return A1.unify_term(A2);
}
\end{code}
\begin{code}
PREDICATE(eq, 2)
{ return Plx_unify(A1.unwrap(), A2.unwrap()));
}
\end{code}
\begin{code}
PREDICATE(eq, 2)
{ PlCheckFail(A1.unify_term(A2));
return true;
}
\end{code}
\section{Overview}
\label{sec:cpp2-overview}
One useful area for exploiting C++ features is type-conversion.
Prolog variables are dynamically typed and all information is passed
around using the C-interface type \ctype{term_t}. In C++,
\ctype{term_t} is embedded in the \jargon{lightweight} class
\ctype{PlTerm}. Other lightweight classes, such as \ctype{PlAtom} for
\ctype{atom_t} are also provided. Constructors and operator
definitions provide flexible operations and integration with
important C-types (\ctype{char*}, \ctype{wchar_t*}, \ctype{long} and
\ctype{double}), plus the C++-types (\ctype{std::string},
\ctype{std::wstring}). (\ctype{char*} and \ctype{wchar_t*} are
deprecated in the C++ API; \ctype{std::string} and
\ctype{std::wstring} are safer and should be used instead.)
Another useful area is in handling errors and cleanup. Prolog errors
can be modeled using C++ exceptions; and C++'s destructors can be used
to clean up error situations, to prevent memory and other resource
leaks.
\subsection{Design philosophy of the classes}
\label{sec:cpp2-philosophy}
See also \secref{cpp2-naming} for more on naming conventions and
standard methods.
The general philosophy for C++ classes is that a ``half-created'' object
should not be possible - that is, the constructor should either
succeed with a completely usable object or it should throw an
exception. This API tries to follow that philosophy, but there are
some important exceptions and caveats. (For more on how the C++ and
Prolog exceptions interrelate, see \secref{cpp2-exceptions}.)
Most of the PL_*() functions have corresponding wrapper methods. For
example, PlTerm::get_atom() calls Plx_get_atom(), which calls
PL_get_atom(). If the PL_get_atom() has an error, it creates a Prolog
error; the Plx_get_atom() wrapper checks for this and converts the
error to a C++ exception, which is thrown; upon return to Prolog, the
exception is turned back into a Prolog error. Therfore, code typically
does not need to check for errors.
Some functions return \const{false} to indicate either failure or an
error, for example PlTerm::unify_term(); for such methods, a check is
made for an error and an exception is thrown, so the return value of
\const{false} only means failure. (The whole thing can be wrapped in
PlCheckFail(), in which case a \ctype{PlFail} exception is thrown,
which is converted to failure in Prolog.) For more on this, see
\secref{cpp2-wrapper-functions}, and for handling failure, see
\secref{cpp2-plframe}.
For PL_*() functions that take or return \ctype{char*} or
\ctype{wchar_t*} values, there are also wrapper functions and methods
that use \ctype{std::string} or \ctype{std::wstring}. Because these
copy the values, there is usually no need to enclose the calls with
\ctype{PlStringBuffers} (which wraps PL_STRING_MARK() and
PL_STRING_RELEASE()). See also the rationale for string:
\secref{cpp2-rationale-strings}.
Many of the classes (\ctype{PlAtom}, \ctype{PlTerm}, etc.) are thin
wrappers around the C interface's types (\ctype{atom_t},
\ctype{term_t}, etc.). As such, they inherit the concept of ``null''
from these types (which is abstracted as \ctype{PlAtom::null},
\ctype{PlTerm::null}, etc., which typically is equivalent to
\const{0}). Normally, you shouldn't need to check whether the object
is ``fully created'', for the rare situations where a check is needed,
the methods is_null() and not_null() are provided.
Most of the classes have constructors that create a
``complete'' object. For example,
\begin{code}
PlAtom foo("foo");
\end{code}
will ensure that the object \exam{foo} is useable and will throw an
exception if the atom can't be created. However, if you choose
to create a \ctype{PlAtom} object from an \ctype{atom_t} value,
no checking is done (similarly, no checking is done if you
create a \ctype{PlTerm} object from a \ctype{term_t}
value).
In many situations, you will be using a term; for these, there are
special constructors. For example:
\begin{code}
PlTerm_atom foo("foo"); // Same as PlTerm(PlAtom("foo"))
PlTerm_string str("a string");
\end{code}
To help avoid programming errors, some of the classes do not have a
default ``empty'' constructor. For example, if you with to create a
\ctype{PlAtom} that is uninitialized, you must explicitly use
\exam{PlAtom(PlAtom::null)}. This make some code a bit more cumbersome
because you can't omit the default constructors in struct initalizers.
Many of the classes have an as_string() method\footnote{This might be changed
in future to to_string(), to be consistent with
\exam{std::to_string()}}, which is useful for debugging.
The method names such as
as_int32_t() were chosen itnstead of to_int32_t() because they imply
that the representation is already an \ctype{int32_t}, and not that
the value is converted to a \ctype{int32_t}. That is, if the value is
a float, \ctype{int32_t} will fail with an error rather than (for example)
truncating the floating point value to fit into a 32-bit integer.
Many of the classes wrap long-lived items, such as atoms, functors,
predicates, or modules. For these, it's often a good idea to define
them as \ctype{static} variables that get created at load time, so
that a lookup for each use isn't needed (atoms are unique, so
\exam{PlAtom("foo")} requires a lookup for an atom \exam{foo} and
creates one if it isn't found).
C code sometimes creates objects ``lazily'' on first use:
\begin{code}
void my_function(...)
{ static atom_t ATOM_foo = 0;
...
if ( ! foo )
foo = PL_new_atom("foo");
...
}
\end{code}
For C++, this can be done in a simpler way, because C++
will call a local ``\ctype{static}'' constructor on
first use.
\begin{code}
void my_function(...)
{ static PlAtom ATOM_foo("foo");
}
\end{code}
The class \ctype{PlTerm} (which wraps \ctype{term_t}) is the most
used. Although a \ctype{PlTerm} object can be created
from a \ctype{term_t} value, it is intended to be used with a
constructor that gives it an initial value. The default constructor
calls PL_new_term_ref() and throws an exception if this fails. The
various constructors are described in
\secref{cpp2-plterm}. Note that the default constructor
is not public; to create a ``variable'' term, you should use the
subclass constructor PlTerm_var().
\subsection{Summary of files}
\label{sec:cpp2-files-summary}
The following files are provided:
\begin{itemize}
\item
\file{SWI-cpp2.h}
- Include this file to get the C++ API. It automatically includes
\file{SWI-cpp2-plx.h} and \file{SWI-cpp2.cpp}, unless the
macro \const{_SWI_CPP2_CPP_SEPARATE} is defined, in which case
you must compile \file{SWI-cpp2.cpp} separately.
\item
\file{SWI-cpp2.cpp}
- Contains the implementations of some methods and functions.
If you wish to compile this separately, you must define
the macro \const{_SWI_CPP2_CPP_SEPARATE} before your
include for \file{SWI-cpp2.h}.
\item
\file{SWI-cpp2-plx.h}
- Contains the wrapper functions for the most of the functions in
\file{SWI-Prolog.h}. This file is not intended to be used by
itself, but is \exam{\#include}d by \file{SWI-cpp2.h}.
\item
\file{SWI-cpp2-atommap.h}
- Contains a utility class for mapping atom-to-atom or atom-to-term,
which is useful for naming long-lived blobs instead of having to
pass them around as arguments.
\item
\file{test_cpp.cpp}, \file{test_cpp.pl}
- Contains various tests, including some longer sequences of
code that can help in understanding how the C++ API
is intended to be used.
In addition, there are \file{test_ffi.cpp}, \file{test_ffi.pl}, which
often have the same tests written in C, without the C++ API.
\end{itemize}
\subsection{Summary of classes}
\label{sec:cpp2-class-summary}
The list below summarises the classes defined in the C++ interface.
\begin{description}
\classitem{PlTerm}
Generic Prolog term that wraps \ctype{term_t} (for more details on \ctype{term_t}, see
\href{https://www.swi-prolog.org/pldoc/man?section=foreigntypes}{Interface Data Types}).
This is a ``base class'' whose constructor is
protected; subclasses specify the actual contents. Additional methods
allow checking the Prolog type, unification, comparison, conversion to
native C++-data types, etc. See \secref{cpp2-plterm-casting}.
For more details about \ctype{PlTerm}, see \secref{cpp2-plterm}
\classitem{PlCompound}
Subclass of \ctype{PlTerm} with constructors for building compound
terms. If there is a single string argument, then PL_chars_to_term()
or PL_wchars_to_term() is used to parse the string and create the
term. If the constructor has two arguments, the first is name of
a functor and the second is a \ctype{PlTermv} with the arguments.
\classitem{PlTermv}
Vector of Prolog terms. See PL_new_term_refs(). The \const{[]} operator
is overloaded to access elements in this vector. \ctype{PlTermv} is used
to build complex terms and provide argument-lists to Prolog goals.
\classitem{PlAtom}
Wraps \ctype{atom_t} in their internal Prolog
representation for fast comparison. (For more details on
\ctype{atom_t}, see
\href{https://www.swi-prolog.org/pldoc/man?section=foreigntypes}{Interface
Data Types}).
For more details of \ctype{PlAtom}, see \secref{cpp2-extraction-comparison-char-star}.
\classitem{PlFunctor}
A wrapper for \ctype{functor_t}, which maps to the internal
representation of a name/arity pair.
\classitem{PlPredicate}
A wrapper for \ctype{predicate_t}, which maps to the internal
representation of a Prolog predicate.
\classitem{PlModule}
A wrapper for \ctype{module_t}, which maps to the internal
representation of a Prolog module.
\classitem{PlQuery}
Represents opening and enumerating the solutions to a Prolog query.
\classitem{PlException}
If a call to Prolog results in an error, the C++ interface converts
the error into a \ctype{PlException} object and throws it. If the
enclosing code doesn't intercept the exception, the \ctype{PlException}
object is turned back into a Prolog error when control returns to Prolog
from the PREDICATE() macros. This is a subclass of \ctype{PlExceptionBase},
which is a subclass of \ctype{std::exception}.
\classitem{PlFrame}
This utility-class can be used to discard unused term-references as well
as to do \jargon{data-backtracking}.
\classitem{PlEngine}
This class is used in \jargon{embedded} applications (applications
where the main control is held in C++). It provides creation and
destruction of the Prolog environment.
\classitem{PlRegister}
Encapsulates PL_register_foreign() to allow using C++ global
constructors for registering foreign predicates.
\classitem{PlFail}
Can be thrown to short-circuit processing and return failure to
Prolog. Performance-critical code should use \exam{return false}
instead if failure is expected. An error can be signaled by calling
Plx_raise_exception() or one of the PL_*_error() functions and then
throwing \ctype{PlFail}; but it's better style to create the error
throwing one of the subclasses of \ctype{PlException} e.g.,
\exam{throw PlTypeError("int", t)}.
Subclass of \ctype{PlExceptionFailBase}.
\classitem{PlExceptionFail}
In some situations, a Prolog error cannot be turned into a
\ctype{PlException} object, so a \ctype{PlExceptionFail} object
is thrown. This is turned into failure by the PREDICATE()
macro, resulting in normal Prolog error handling.
Subclass of \ctype{PlExceptionFailBase}.
\classitem{PlExceptionBase}
A ``do nothing'' subclass of \ctype{std::exception}, to allow catching
\ctype{PlException}, \ctype{PlExceptionFail} or \ctype{PlFail}
in a single ``catch'' clause.
\classitem{PlExceptionFailBase}
A ``do nothing'' subclass of \ctype{PlExceptionBase}, to allow catching
\ctype{PlExceptionFail} or \ctype{PlFail}
in a single ``catch'' clause, excluding \ctype{PlException}.
\end{description}
\subsection{Wrapper functions}
\label{sec:cpp2-wrapper-functions}
The various PL_*() functions in \file{SWI-Prolog.h} have corresponding
Plx_*() functions, defined in \file{SWI-cpp2-plx.h}, which is always
included by \file{SWI-cpp2.h}. There are three kinds of wrappers, with
the appropriate one being chosen according to the semantics of
the wrapped function:
\begin{itemize}
\item
``as-is'' - the PL_*() function cannot cause an error. If it has a
return value, the caller will want to use it.
\item
``exception wrapper'' - the PL_*() function can return \const{false},
indicating an error. The Plx_*() function checks for this and
throws a \ctype{PlException} object containing the error. The
wrapper uses \exam{template<typename C_t> C_t PlEx(C_t rc)},
where \exam{C_t} is the return type of the PL_*() function.
\item
``success, failure, or error'' - the PL_*() function can return
\const{true} if it succeeds and \const{false} if it fails or has a
runtime error. If it fails, the wrapper checks for a Prolog error
and throws a \ctype{PlException} object containing the error. The
wrapper uses \exam{template<typename C_t> C_t PlWrap(C_t rc)},
where \exam{C_t} is the return type of the PL_*() function.
\end{itemize}
A few PL_*() functions do not have a corresponding Plx*() function
because they do not fit into one of these categories. For example,
PL_next_solution() has multiple return values (\const{PL_S_EXCEPTION},
\const{PL_S_LAST}, etc.) if the query was opened with the
\const{PL_Q_EXT_STATUS} flag.
Most of the PL_*() functions whose first argument is of type
\ctype{term_t}, \ctype{atom_t}, etc. have corresponding methods
in classes \ctype{PlTerm}, \ctype{PlAtom}, etc.
\emph{Important}: You should use the Plx_*() wrappers only in the
context of a PREDICATE() call, which will handle any C++ exceptions.
Some blob callbacks can also handle an exception (see
\secref{cpp2-blobs}). Everywhere else, the result of calling a
Plx_*() function is unpredicatable - probably a crash.
\subsection{Naming conventions, utility functions and methods}
\label{sec:cpp2-naming}
See also the discussion on design philosophy in \secref{cpp2-philosophy}.
The classes all have names starting with ``Pl'', using CamelCase;
this contrasts with the C functions that start with ``PL_'' and
use underscores.
The wrapper classes (\ctype{PlFunctor}, \ctype{PlAtom},
\ctype{PlTerm}), etc. all contain a field \exam{C_} that contains the
wrapped value (\ctype{functor_t}, \ctype{atom_t}, \ctype{term_t}
respectively). If this wrapped value is needed, it should be accessed
using the unwrap() or unwrap_as_ptr() methods.
In some cases, it's natural to use a pointer to a wrapper class.
For those, the function PlUnwrapAsPtr() returns \ctype{nullptr} if
the pointer is null; otherwise it returns the wrapped value (which
itself might be some kind of ``null'').
The wrapper classes, which subclass \ctype{WrappedC$<$\ldots$>$},
all define the following methods and constants:
\begin{itemize}
\item
Default constructor (sets the wrapped value to \exam{null}).
Some classes do not have a default constructor because it
can lead to subtle bugs - instead, they either have a different
way of creating the object or can use the ``null'' value for
the class.
\item
Constructor that takes the wrapped value (e.g.,
for \ctype{PlAtom}, the constructor takes an \ctype{atom_t}
value).
\item
\exam{C_} - the wrapped value.
This can be used directly when calling C functions,
for example, if \exam{t} and \exam{a} are of type \ctype{PlTerm}
and \ctype{PlAtom}: \verb$PlEx(PL_put_atom(t.unwrap(),a.unwrap()))$
(although it's better to do \verb$Plx_put_atom(t.unwrap(),a.unwrap())$,
which does the check).
\item
\exam{null} - the null value (typically \exam{0}, but
code should not rely on this).
\item
\exam{is_null()}, \exam{not_null()} - test
for the wrapped value being \exam{null}.
\item
\exam{reset()} - set the wrapped value to \exam{null}
\item
\exam{reset(new_value)} - set the wrapped value from the wrapped type
(e.g., PlTerm::reset(term_t new_value))
\item
\exam{reset_wrapped(new_value)} - set the wrapped value from the
same type (e.g., PlTerm::reset_wrapped(PlTerm new_value))
\item
The \ctype{bool} operator is disabled - you should
use not_null() instead.\footnote{The reason: a
\ctype{bool} conversion causes ambiguity with \exam{PlAtom(PlTterm)}
and \exam{PlAtom(atom_t)}.}
\end{itemize}
The method unwrap() can be used to access the \exam{C_} field, and can
be used wherever a \ctype{atom_t} or \ctype{term_t} is used. For
example, the PL_scan_options() example code can be written as follows.
Note the use of \exam{\&callback.unwrap()} to pass a pointer to the
wrapped \ctype{term_t} value.
\begin{code}
PREDICATE(mypred, 2)
{ auto options = A2;
int quoted = false;
size_t length = 10;
PlTerm_var callback;
PlCheckFail(PL_scan_options(options, 0, "mypred_options", mypred_options,
"ed, &length, &callback.unwrap()));
callback.record(); // Needed if callback is put in a blob that Prolog doesn't know about.
// If it were an atom (OPT_ATOM): register_ref().
<implement mypred>
}
\end{code}
For functions in \file{SWI-Prolog.h} that don't have a C++ equivalent
in \file{SWI-cpp2.h}, \cfuncref{PlCheckFail}{} is a convenience
function that checks the return code and throws a \ctype{PlFail}
exception on failure or \ctype{PlException} if there was an
exception. The enclosing PREDICATE() code catches \ctype{PlFail}
exceptions and converts them to the \ctype{foreign_t} return code for
failure. If the failure from the C function was due to an exception
(e.g., unification failed because of an out-of-memory condition), the
foreign function caller will detect that situation and convert the
failure to an exception.
The ``getter'' methods for \ctype{PlTerm} all throw an exception if the
term isn't of the expected Prolog type. The ``getter'' methods typically
start with ``as'', e.g. PlTerm::as_string(). There are also other ``getter''
methods, such as PlTerm::get_float_ex() that wrap PL_*() functions.
``getters'' for integers have an additional problem, in that C++
doesn't define the sizes of \ctype{int}, \ctype{long}, or
\ctype{size_t}. It seems to be impossible to make an overloaded method
that works for all the various combinations of integer types on all
compilers, so there are specific methods for \ctype{int64_t},
\ctype{uint64_t}, \ctype{size_t}.
In some cases,it is possible to overload methods; for example, this
allows the following code without knowing the exact definition of
\ctype{size_t}:
\begin{code}
PREDICATE(p, 1)
{ size_t sz;
A1.integer(&sz);
...
}
\end{code}
\emph{It is strongly recommended that you enable conversion checking.}
For example, with GNU C++, use these options (possibly with \exam{-Werror}):
\exam{-Wconversion -Warith-conversion -Wsign-conversion -Wfloat-conversion}.
There is an additional problem with characters - C promotes
them to \ctype{int} but C++ doesn't. In general, this shouldn't
cause any problems, but care must be used with the various
getters for integers.
\subsection{PlTerm class}
\label{sec:cpp2-plterm}
As we have seen from the examples, the \ctype{PlTerm} class plays a
central role in conversion and operating on Prolog data. This section
provides complete documentation of this class.
There are a number of subclasses that exist only to provide a safe way
of constructing at term.
There is also a subclass (\ctype{PlTermScoped}) that helps reclaim
terms.
Most of the \ctype{PlTerm} constructors are defined as subclasses of
\ctype{PlTerm}, with a name that reflects the Prolog type of what is
being created (e.g., \ctype{PlTerm_atom} creates a term from an atom;
\ctype{PlTerm_string} creates a term from a Prolog string). This is
done to ensure that the there is no ambiguity in the constructors -
for example, there is no way to distinguish between \ctype{term_t},
\ctype{atom_t}, and ordinary integers, so there are constructors
PlTerm(), PlTerm_atom(), and PlTerm_integer. All of the constructors
are ``explicit'' because implicit creation of \ctype{PlTerm} objects
can lead to subtle and difficult to debug errors.
If a constructor fails (e.g., out of memory), a \ctype{PlException} is
thrown. The class and subclass constructors are as follows.
\begin{description}
\constructor{PlTerm}{PlAtom a}
Creates a term reference containing an atom, using PL_put_atom().
It is the same as PlTerm_atom().
\constructor{PlTerm}{term_t t}
Creates a term reference from a C term (by wrapping it).
As this is a lightweight class, this is a no-op in
the generated code.
\constructor{PlTerm}{PlRecord r}
Creates a term reference from a record, using PL_recorded().
\constructor{PlTerm_atom}{atom_t a}
Creates a term reference containing an atom.
\constructor{PlTerm_atom}{PlAtom a}
Creates a term reference containing an atom.
\constructor{PlTerm_atom}{const char *text}
Creates a term reference containing an atom, after creating the atom from the \arg{text}.
\constructor{PlTerm_atom}{const wchar_t *text}
Creates a term reference containing an atom, after creating the atom from the \arg{text}.
\constructor{PlTerm_atom}{const std::string\& text}
Creates a term reference containing an atom, after creating the atom from the \arg{text}.
\constructor{PlTerm_atom}{const std::wstring\& text}
Creates a term reference containing an atom, after creating the atom from the \arg{text}.
\constructor{PlTerm_var}{}
Creates a term reference for an uninstantiated variable.
Typically this term is then unified with another object.
\constructor{PlTerm_term_t}{}
Creates a term reference from a C \ctype{term_t}.
This is a lightweight class, so no code is generated.
\constructor{PlTerm_integer}{}
Subclass of \ctype{PlTerm} with constructors for building a term that
contains a Prolog integer from a
\ctype{long}.\footnote{PL_put_integer() takes a \ctype{long} argument.}
\constructor{PlTerm_int64}{}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog integer from a \ctype{int64_t}.
\constructor{PlTerm_uint64}{}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog integer from a \ctype{uint64_t}.
\constructor{PlTerm_size_t}{}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog integer from a \ctype{size_t}.
\constructor{PlTerm_float}{}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog float.
\constructor{PlTerm_pointer}{}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a raw pointer. This is mainly for
backwards compatibility; new code should use \jargon{blobs}.
A pointer is
represented in Prolog as a mangled integer. The mangling is designed
to make most pointers fit into a \jargon{tagged-integer}. Any valid
pointer can be represented. This mechanism can be used to represent
pointers to C++ objects in Prolog. Please note that \ctype{MyClass}
should define conversion to and from \ctype{void *}.
\begin{code}
PREDICATE(make_my_object, 1)
{ auto myobj = new MyClass();
return A1.unify_pointer(myobj);
}
PREDICATE(my_object_contents, 2)
{ auto myobj = static_cast<MyClass*>(A1.as_pointer());
return A2.unify_string(myobj->contents);
}
PREDICATE(free_my_object, 1)
{ auto myobj = static_cast<MyClass*>(A1.as_pointer());
delete myobj;
return true;
}
\end{code}
\constructor{PlTerm_string}{}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog string object.
For constructing a term from the text form, see
\ctype{PlCompound}.
\constructor{PlTerm_list_codes}{}
Subclass of \ctype{PlTerm} with constructors for building
Prolog lists of character integer values.
\constructor{PlTerm_chars}{}
Subclass of \ctype{PlTerm} with constructors for building
Prolog lists of one-character atoms (as atom_chars/2).
\constructor{PlTerm_tail}{}
SubClass of \ctype{PlTerm} for building and analysing Prolog lists.
\end{description}
The methods are:
\begin{description}
\cfunction{bool}{PlTerm::get_atom}{PlAtom* a} Wrapper of PL_get_atom(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_bool}{int* value} Wrapper of PL_get_bool(), throwing an exception on Prolog error.
\cfunction{chars}{PlTerm::get_chars}{char**s, unsigned int flags} Wrapper of PL_get_chars(), throwing an exception on Prolog error.
\cfunction{chars}{PlTerm::get_list_chars}{char**s, unsigned int flags} Wrapper of PL_get_list_chars(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_list_chars}{char **s, unsigned int flags} Wrappper of PL_get_list_chars(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_atom_nchars}{size_t *len, char **a} Wrappper of PL_get_atom_nchars(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_list_nchars}{size_t *len, char **s, unsigned int flags} Wrappper of PL_get_list_nchars(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_nchars}{size_t *len, char **s, unsigned int flags} Wrappper of PL_get_nchars(), throwing an exception on Prolog error. Deprecated: see PlTerm::get_nchars(flags) that returns a \ctype{std::string}. If you use this, be sure to wrap it with \ctype{PlStringBuffers}, and if you use the \const{BUF_MALLOC} flag, you can use \ctype{std::unique_ptr<char, decltype(\&PL_free)>} to manage the pointer.
\cfunction{bool}{PlTerm::get_wchars}{size_t *length, pl_wchar_t **s, unsigned flags} Wrappper of PL_get_wchars(), throwing an exception on Prolog error. Deprecated: see PlTerm::getwchars(flags) that returns a \ctype{std::wstring}. If you use this, be sure to wrap it with \ctype{PlStringBuffers}, and if you use the \const{BUF_MALLOC} flag, you can use \ctype{std::unique_ptr<char, decltype(\&PL_close)>} to manage the pointer.
\cfunction{bool}{PlTerm::get_integer}{int *i} Wrappper of PL_get_integer(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_long}{long *i} Wrappper of PL_get_long(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_intptr}{intptr_t *i} Wrappper of PL_get_intptr(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_pointer}{void **ptr} Wrappper of PL_get_pointer(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_float}{double *f} Wrapper Plx_get_float(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_functor}{PlFunctor *f} Wrappper of PL_get_functor(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_name_arity}{PlAtom *name, size_t *arity} Wrappper of PL_get_name_arity(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_compound_name_arity}{PlAtom *name, size_t *arity} Wrappper of PL_get_compound_name_arity(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_module}{PlModule *module} Wrappper of PL_get_module(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_arg}{size_t index, PlTerm a} Wrappper of PL_get_arg(index, ), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_dict_key}{PlAtom key, PlTerm dict, PlTerm value} Wrappper of PL_get_dict_key(key.), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_list}{PlTerm h, PlTerm t} Wrappper of PL_get_list(), throwing an exception on Prolog error.
\cfunction{bool}{PlTerm::get_head}{PlTerm h} Wrappper of PL_get_head(), throwing an exception on Prolog error.