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 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192
|
\chapter{A C++ interface to SWI-Prolog (Version 2)}
\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}.
The overall structure of the API has been retained - that is,
it is a thin layer on top of the interface provided by
\file{SWI-Prolog.h}. Based on experience with the API,
most of the conversion 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 in
\exam{PlTerm t=...; f(t[0])}
if \exam{f} is overloaded to accept a \ctype{term_t} or \ctype{char*}.
}
More specifically:
\begin{itemize}
\item
The constructor \cfuncref{PlTerm}{} is not available - instead,
you should use the appropriate subclass' constructor
(\cfuncref{PlTerm_var}{}, \cfuncref{PlTerm_atom}{a},
\cfuncref{PlTerm_term_t}{t}, \cfuncref{PlTerm_integer}{i},
\cfuncref{PlTerm_int64}{i}, \cfuncref{PlTerm_uint64}{i},
\cfuncref{PlTerm_size_t}{i},
\cfuncref{PlTerm_float}{v}, or \cfuncref{PlTerm_pointer}{p}).
\item
Instead of returning \const{false} from a predicate to indicate
failure, you can use \exam{throw PlFail()}. The convenience
function \cfuncref{PlCheck}{rc} can be used to throw \exam{PlFail()},
if a \const{false} is returned from a function in \file{SWI-Prolog.h}
\item
The "cast" operators (e.g., \exam{(char*)t}, \exam{(int64_t)t})
have been deprecated, replaced by "getters" (e.g.,
\exam{t.as_string()}, \exam{t.as_int64_t()}).\footnote{The form
\exam{(char*)t} is a C-style cast; C++'s preferred form is
more verbose: \exam{static_cast<char*>(t)}.}
\item
The overloaded assignment operator for unification is deprecated;
replaced by \cfuncref{unify_term}{}, \cfuncref{unify_atom}{}, etc., and the helper
\cfuncref{PlCheck}{}.
\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 will need to
change your interface to return a \ctype{std::string} and apply
the \exam{c_str()} method to it. These errors can \emph{sometimes} be caught by
specifying the Gnu C++ or Clang options \exam{-Wreturn-stack-address}
or \exam{-Wreturn-local-addr} - Clang seems to do a better
analysis.}
\item
Type-checking methods have been added: \cfuncref{type}{}, \cfuncref{is_variable}{},
\cfuncref{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}.
\item
\ctype{std::string} and \ctype{std::wstring} are now supported in most
places where \ctype{char*} or \ctype{wchar_t*} are allowed.
\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
\cfuncref{is_null}{}, \cfuncref{not_null}{}, \cfuncref{reset}{},
and \cfuncref{reset}{v}, plus the constant \const{null}.}
\item
A convenience class \ctype{PlForeignContextPtr<\emph{ContextType}>}
has been added, to simplify dynamic memory allocation in
non-deterministic predicates.
\item
A convenience function \cfuncref{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().
\end{itemize}
More details are given in \secref{cpp2-rationale} and
\secref{cpp2-porting-1-2}.
\section{Introduction (version 2)}
\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 programmable type-conversion (\jargon{casting})
and overloading,
native data-types can be translated automatically 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.
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++ 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 easily 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 (version 2)}
\label{sec:cpp2-acknowledgements}
I would like to thank Anjo Anjewierden for comments on the definition,
implementation and documentation of this package. Peter Ludemann
modified the interface to remove some pitfalls, and also added
some convenience functions (see \secref{summary-cpp2-changes}).
\section{The life of a PREDICATE (version 2)}
\label{sec:cpp2-life-of-a-predicate}
A foreign predicate is defined using the \cfuncref{PREDICATE}{}
macro.\footnote{Plus a few variations on this, such as
\cfuncref{PREDICATE_NONDET}{}, \cfuncref{NAMED_PREDICATE}{}, and
\cfuncref{NAMED_PREDICATE_NONDET}{}.} This defines an internal name for
the function, registers it with the SWI-Prolog runtime (where it will
be picked up by the use_foreign_library/1 directive), and defines the
names \exam{A1}, \exam{A2}, etc. for the arguments.\footnote{You can
define your own names for the arguments, for example: \exam{auto x=A1,
y=A2, result=A3;}.} If a non-deterministic predicate is being
defined, an additional parameter \exam{handle} is defined (of type
\ctype{control_t}).
The foreign predicate returns a value of \exam{true} or \exam{false}
to indicate whether it succeeded or failed.\footnote{Non-deterministic
predicates can also return a "retry" value.} If a predicate fails, it
could be simple failure (the equivalent of calling the builtin fail/0)
or an error (the equivalent of calling throw/1). When an 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 to check for failure, which can become cumbersome. C++ has
exceptions, so instead the code can wrap calls to \cfuncref{PL_*}{} functions with
\cfuncref{PlCheck}{}, which will do \exam{throw PlFail()} to exit from the top
level of the foreign predicate, and handle the failure or exception
appropriately.
The following three snippets do the same thing (for implementing the
equivalent of =/2):
\begin{code}
PREDICATE(eq, 2)
{ PlCheck(A1.unify_term(A2));
return true;
}
\end{code}
\begin{code}
PREDICATE(eq, 2)
{ return A1.unify_term(A2);
}
\end{code}
\begin{code}
PREDICATE(eq, 2)
{ PlCheck(PL_unify(A1.C_, A2.C_));
return true;
}
\end{code}
\section{Overview (version 2)}
\label{sec:cpp2-overview}
The most 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}.
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}).
\subsection{Design philosophy of the classes}
\label{sec:cpp2-philosophy}
See also \secref{cpp2-naming}.
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}.)
The various 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}). You can check whether the object is "fully created" by
using the \cfuncref{verify}{} method - it will throw an exception if the
object is \const{null}.
However, 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.
To help avoid programming errors, most 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 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). Sometimes, it's desirable to create
them "lazily", such as:
\begin{code}
static PlAtom foo(PlAtom::null};
...
if ( foo.is_null() )
foo = PlAtom("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-constructurs}. Note that the default constructor
is not public; to create a "variable" term, you should use the
subclass constructor \cfuncref{PlTerm_var}{}.
\subsection{Summary of classes}
\label{sec: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}.
The subclass constructors are as follows. If a constructor fails
(e.g., out of memory), a \ctype{PlException} is thrown.
\begin{description}
\classitem{PlTerm_atom}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains an atom.
\classitem{PlTerm_var}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains an uninstantiated variable. Typically
this term is then unified with another object.
\classitem{PlTerm_term_t}
Subclass of \ctype{PlTerm} with constructors for building
a term from a C \ctype{term_t}.
\classitem{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.}
\classitem{PlTerm_int64}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog integer from a \ctype{int64_t}.
\classitem{PlTerm_uint64}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog integer from a \ctype{uint64_t}.
\classitem{PlTerm_size_t}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog integer from a \ctype{size_t}.
\classitem{PlTerm_float}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog float.
\classitem{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}.
\classitem{PlTerm_string}
Subclass of \ctype{PlTerm} with constructors for building
a term that contains a Prolog string object.
\classitem{PlTerm_list_codes}
Subclass of \ctype{PlTerm} with constructors for building
Prolog lists of character integer values.
\classitem{PlTerm_chars}
Subclass of \ctype{PlTerm} with constructors for building
Prolog lists of one-character atoms (as atom_chars/2).
\classitem{PlTerm_tail}
SubClass of \ctype{PlTerm} for building and analysing Prolog lists.
\end{description}
Additional subclasses of \ctype{PlTerm} are:
\begin{description}
\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{PlException}
Subclass of \ctype{PlTerm} representing a Prolog exception. Provides
methods for the Prolog communication and mapping to human-readable text
representation.
\classitem{PlTypeError}
Subclass of \ctype{PlException} for representing a Prolog
\except{type_error} exception.
\classitem{PlDomainError}
Subclass of \ctype{PlException} for representing a Prolog
\except{domain_error} exception.
\classitem{PlExistenceError}
Subclass of \ctype{PlException} for representing a Prolog
\except{existence_error} exception.
\classitem{PlPermissionError}
Subclass of \ctype{PlException} for representing a Prolog
\except{permission_error} exception.
\end{description}
\classitem{PlAtom}
Allow for manipulating atoms (\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}).
\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{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.
\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}
The encapsulation of PL_register_foreign() is defined to be able to
use C++ global constructors for registering foreign predicates.
\end{description}
The required C++ function header and registration of a predicate
is arranged through a macro called \cfuncref{PREDICATE}{}.
\subsection{Naming conventions, utility functions and methods (version 2)}
\label{sec:cpp2-naming}
See also \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})
all contain a field \exam{C_} that contains the wrapped value
(\ctype{functor_t}, \ctype{atom_t}, \ctype{term_t} respectively).
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})
\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$Plcheck(PL_put_atom(t.C_,a.C_))$.
\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
\item
\exam{verify()} - if the wrapped value (\exam{C_})
is \exam{null}, throw a \cfuncref{PlFail}{} exception. Typically, this check
is done after an allocation function such as Plnew_term_ref()
returns a null value, so the \cfuncref{PlFail}{} is turned into a a resource
error. However, if there is no pending exception, this results in
simple failure (see \secref{cpp2-exceptions-notes}).
\item
The \ctype{bool} operator is turned off - 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 \exam{C_} field 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.C_}
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;
PlCheck(PL_scan_options(options, 0, "mypred_options", mypred_options,
"ed, &length, &callback.C_));
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{PlCheck}{} is a convenience function that checks
the return code and throws a \ctype{PlFail} exception on failure. The
\cfuncref{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. Where possible, the "getters"
have the same name as the underlying type; but this isn't possible for
types such as \ctype{int} or \ctype{float}, so for these the name is
prepended with "as_".
"Getters" for integers have an additionnal problem, in that C++
doesn't define the sizes of \ctype{int} and \ctype{long}, nor for
\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++, 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.
\section{Examples (version 2)}
\label{sec:cpp2-examples}
Before going into a detailed description of the C++ classes we present
a few examples illustrating the `feel' of the interface.
\subsection{Hello(World) (version 2)}
\label{sec:cpp2-hello-world}
This simple example shows the basic definition of the predicate hello/1
and how a Prolog argument is converted to C-data:
\begin{code}
PREDICATE(hello, 1)
{ cout << "Hello " << A1.as_string() << endl;
return true;
}
\end{code}
The arguments to \cfuncref{PREDICATE}{} are the name and arity of the predicate.
The macros A<n> provide access to the predicate arguments by position
and are of the type \ctype{PlTerm}. The C or C++ string for a \ctype{PlTerm}
can be extracted using \cfuncref{as_string}{}, or \cfuncref{as_wstring}{} methods;\footnote{The C-string
values can be extracted from \ctype{std::string} by using \cfuncref{c_str}{}, but you
must be careful to not return a pointer to a local/stack value.}
and similar access methods provide an easy type-conversion
for most Prolog data-types, using the output of write/1 otherwise:
\begin{code}
?- hello(world).
Hello world
Yes
?- hello(X)
Hello _G170
X = _G170
\end{code}
\subsection{Adding numbers (version 2)}
\label{sec:cpp2-ex-adding-numbers}
This example shows arithmetic using the C++ interface, including
unification, type-checking, and conversion. The predicate add/3 adds
the two first arguments and unifies the last with the result.
\begin{code}
PREDICATE(add, 3)
{ return A3.unify_integer(A1.as_long() + A2.as_long());
}
\end{code}
You can use your own variable names instead of \exam{A1},
\exam{A2}, etc.:
\begin{code}
PREDICATE(add, 3) // add(+X, +Y, +Result)
{ PlTerm x(A1);
PlTerm y(A2);
PlTerm result(A3);
return result.unify_integer(x.as_long() + y.as_long());
}
\end{code}
The \cfuncref{as_long}{} method for a \ctype{PlTerm} performs a PL_get_long_ex()
and throws a C++ exception if the Prolog argument is not a Prolog
integer or float that can be converted without loss to a
\ctype{long}. The \cfuncref{unify_integer}{} method of \ctype{PlTerm} is defined
to perform unification and returns \const{true} or \const{false}
depending on the result.
\begin{code}
?- add(1, 2, X).
X = 3.
?- add(a, 2, X).
[ERROR: Type error: `integer' expected, found `a']
Exception: ( 7) add(a, 2, _G197) ?
\end{code}
\subsection{Average of solutions (version 2)}
\label{sec:cpp2-ex-average}
This example is a bit harder. The predicate average/3 is defined to take
the template \mbox{average(+Var, :Goal, -Average)}, where \arg{Goal}
binds \arg{Var} and will unify \arg{Average} with average of the
(integer) results.
\ctype{PlQuery} takes the name of a predicate and the goal-argument
vector as arguments. From this information it deduces the arity and
locates the predicate. The method \cfuncref{next_solution}{} yields
\const{true} if there was a solution and \const{false} otherwise. If
the goal yields a Prolog exception, it is mapped into a C++ exception.
A return to Prolog does an implicit "cut" (PL_cut_query()); this
can also be done explicitly by the \cfuncref{PlQuery::cut}{} method.
\begin{code}
PREDICATE(average, 3) /* average(+Templ, :Goal, -Average) */
{ long sum = 0;
long n = 0;
PlQuery q("call", PlTermv(A2));
while( q.next_solution() )
{ sum += A1.as_long();
n++;
}
return A3.unify_float(double(sum) / double(n));
}
\end{code}
\begin{code}
?- [user].
|: p(1).
|: p(10).
|: p(20).
|:
% user://1 compiled 0.00 sec, 3 clauses
true.
?- average(X, p(X), Average).
Average = 10.333333333333334.
\end{code}
\section{Rational for changes from version 1 (version 2)}
\label{sec:cpp2-rationale}
\subsection{Implicit constructors and conversion operators}
\label{sec:cpp2-rationale-ctors}
The original version of the C++ interface heavily used implicit
constructors and conversion operators. This allowed, for example:
\begin{code}
PREDICATE(hello, 1)
{ cout << "Hello " << A1.as_string() << endl;
return true;
}
PREDICATE(add, 3)
{ return A3 = (long)A1 + (long)A2;
}
\end{code}
Version 2 is a bit more verbose:
\begin{code}
PREDICATE(hello, 1)
{ cout << "Hello " << A1.as_string() << endl;
return true;
}
PREDICATE(add, 3)
{ return A3.unify_int(A1.as_long() + A2.as_long());
}
\end{code}
There are a few reasons for this:
\begin{itemize}
\item
The C-style of casts is deprecated in C++, so the expression
\exam{(char *)A1} becomes the more verbose
\exam{static_cast<std::string>(A1)}, which is longer than
\exam{A1.as_string()}. Also, the string casts don't allow for
specifying encoding.
\item
The implicit constructors and conversion operators allowed
directly calling the foreign language interface functions, for example:
\begin{code}
PlTerm t;
Pl_put_atom_chars(t, "someName");
\end{code}
whereas this is now required:
\begin{code}
PlTerm t;
Pl_put_atom_chars(t.as_term_t(), "someName");
\end{code}
However, this is mostly avoided by methods and constructors that
wrap the foreign language functions:
\begin{code}
PlTerm_atom t("someName");
\end{code}
or
\begin{code}
auto t = PlTerm_atom("someName");
\end{code}
\item
The implicit constructors and conversion operators, combined with
the C++ conversion rules for integers and floats, could sometimes
lead to subtle bugs that were difficult to find -- in one case, a
typo resulted in terms being unified with floating point values when
the code intended them to be atoms. This was mainly because the
underlying C types for terms, atoms, etc. are unsigned integers,
leading to confusion between numeric values and Prolog terms and
atoms.
\item
The overloaded assignment operator for unification changed the
usual C++ semantics for assignments from returning a reference
to the left-hand-side to returning a ctype{bool}. In addition,
the result of unification should always be checked (e.g., an
"always succeed" unification could fail due to an out-of-memory
error); the \cfuncref{unify_XXX}{} methods return
a \ctype{bool} and they can be wrapped inside a \cfuncref{PlCheck}{}
to raise an exception on unification failure.
\end{itemize}
Over time, it is expected that some of these restrictions will be
eased, to allow a more compact coding style that was the intent of the
original API. However, too much use of overloaded
methods/constructors, implicit conversions and constructors can result
in code that's difficult to understand, so a balance needs to be
struck between compactness of code and understandability.
For backwards compatibility, some of the version 1 interface is still
available (except for the implicit constructors and operators), but
marked as "deprecated"; code that depends on the parts that have been
removed can be easily changed to use the new interface.
\subsection{Strings}
\label{sec:cpp2-rationale-strings}
The version API often used \ctype{char*} for both setting and setting
string values. This is not a problem for setting (although encodings
can be an issue), but can introduce subtle bugs in the lifetimes of
pointers if the buffer stack isn't used properly. The buffer stack is
abstracted into \ctype{PlStringBuffers}, but it would be preferable to
avoid its use altogether. C++, unlike C, has a standard string that
allows easily keeping a copy rather than dealing with a pointer that
might become invalid. (Also, C++ strings can contain null characters.)
C++ has default conversion operators from \ctype{char*} to
\ctype{std::string}, so some of the API support only
\ctype{std::string}, even though this can cause a small
inefficiency. If this proves to be a problem, additional overloaded
functions and methods can be provided in future (note that some
compilers have optimizations that reduce the overheads of using
\ctype{std::string}); but for performance-critical code, the C
functions can still be used.
There still remains the problems of Unicode and encodings.
\ctype{std::wstring} is one way of dealing with this. And for
interfaces that use \ctype{std::string}, an encoding can be
specified.\footnote{As of 2022-11, this had only been partially
implemented}. Some of the details for this - such as the
default encoding - may change slightly in the future.
\section{Porting from version 1 to version 2}
\label{sec:cpp2-porting-1-2}
The easiest way of porting from \file{SWI-cpp.h} to \file{SWI-cpp2.h}
is to change the \exam{\#include "SWI-cpp.h"} to \exam{\#include "SWI-cpp2.h"}
and look at the warning and error messages. Where possible, version 2
keeps old interfaces with a "deprecated" flag if there is a better way
of doing things with version 2.
Here is a list of typical changes:
\begin{itemize}
\item
Replace \cfuncref{PlTerm}{} constructor with
\cfuncref{PlTerm_var}{} for uninstantiated variables,
\cfuncref{PlTerm_atom}{a} for atoms, \cfuncref{PlTerm_term_t}{t}
for the raw \ctype{term_t}, \cfuncref{PlTerm_integer}{i},
\cfuncref{PlTerm_float}{v}, or \cfuncref{PlTerm_pointer}{p}.
\item
Examine uses of \ctype{char*} or \ctype{wchar_t} and replace them by
\ctype{std::string} or \ctype{std::wstring} if appropriate.
For example, \exam{cout << "Hello " << A1.as_string().c_str()() << endl}
can be replaced by \exam{cout << "Hello " << A1.as_string() << endl}.
In general, \ctype{std::string} is safer than \ctype{char*} because
the latter can potentially point to freed memory.
\item
Instead of returning \const{false} from a predicate for failure,
you can do \exam{throw \cfuncref{PlFail}{}}. This mechanism is also used by
\cfuncref{PlCheck}{rc}. Note that throwing an exception is
significantly slower than returning \const{false}, so
performance-critical code should avoid \cfuncref{PlCheck}{rc}.
\item
You can use the \cfuncref{PlCheck}{rc} to check the return code
from a function in \file{SWI-Prolog} and throw a \cfuncref{PlFail}{}
exception to short-circuit execution and return failure (\const{false})
to Prolog.
\item
\exam{PlAtom::handle} has been replaced by \exam{PlAtom::C_}.
\item
\exam{PlTerm::ref} has been replaced by \exam{PlAtom::C_}.
\item
\exam{PlFunctor::functor} has been replaced by \exam{PlAtom::C_}.
\item
The operator \exam{=} for unification has been deprecated,
replaced by various \exam{unify_XXX}` methods
(\cfuncref{PlTerm::unify_term}{t2},
\cfuncref{PlTerm::unify_atom}{a}, etc.).
\item
The various "cast" operators have been deprecated or deleted;
you should use the various "getter" methods. For example,
\exam{static_cast<char*>(t)} is replaced by \exam{t.as_string().c_str()};
\exam{static_cast<int32_t>(t)} is replaced by \exam{t.as_int32_t()}.
\item
It is recommended that you do not use \ctype{int} or
\ctype{long} because of problems porting between Unix and Windows
platforms; instead, use \ctype{int32_t}, \ctype{int64_t},
\ctype{uint32_t}, \ctype{uint64_t}, etc.
\end{itemize}
\section{The class PlFail (version 2)}
\label{sec:cpp2-plfail}
The \ctype{PlFail} class is used for short-circuiting a function when
failure or an exception occurs and any errors will be handled in the
code generated by the \cfuncref{PREDICATE}{} macro. See also
\secref{cpp2-exceptions-notes}).
For example, this code:
\begin{code}
PREDICATE(unify_zero, 1)
{ if ( !PL_unify_integer(A1.C_, 0) )
return false;
return true;
}
\end{code}
can instead be written this way:
\begin{code}
void
PREDICATE(unify_zero, 1)
{ if ( !PL_unify_integer(A1.C_, 0) )
throw PlFail();
return true;
}
\end{code}
or:
\begin{code}
PREDICATE(unify_zero, 1)
{ PlCheck(PL_unify_integer(t.C_, 0));
return true;
}
\end{code}
or:
\begin{code}
PREDICATE(unify_zero, 1)
{ PlCheck(A1.unify_integer(0));
return true;
}
\end{code}
or:
\begin{code}
PREDICATE(unify_zero, 1)
{ return A1.unify_integer(0);
}
\end{code}
Using \exam{throw PlFail()} in performance-critical code can cause a
signficant slowdown. A simple benchmark showed a 15x to 20x slowdown
using \exam{throw PlFail()} compared to \exam{return false} (comparing
the first code sample above with the second and third samples; the
speed difference seems to have been because in the second sample, the
compiler did a better job of inlining). However, for most code, this
difference will be barely noticeable.
There was no significant performance difference between the C++
version and this C version:
\begin{code}
static foreign_t
unify_zero(term_t a1)
{ return PL_unify_integer(a1, 0);
}
\end{code}
\subsection{\cfuncref{PlCheck}{} convenience function}
\label{sec:cpp2-plcheck}
In general, wherever there is a method that wraps a C "PL_"
function, \cfuncref{PlCheck}{} can be used to return failure
to Prolog from the "PL_" function.
The code for \cfuncref{PlCheck}{} is very simple - it checks the
return code and throws \ctype{PlFail} if the return code isn't
"true". If the return code is from a Prolog function (that is,
a function starting with "PL_"), the return code can be "false"
either because of failure or because an exception happened.
If the cause is an exception, then the only sensible thing is to
return to Prolog immediately; throwing \ctype{PlFail} will do this.
See also \secref{cpp2-exceptions-notes}.
\section{The class PlTerm (version 2)}
\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.
\subsection{Constructors (version 2)}
\label{sec:cpp2-plterm-constructurs}
The 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 an atom; \ctype{PlTerm_string}
creates a Prolog string).
All of the constructors are
"explicit" because implicit creation of \ctype{PlTerm} objects can lead
to subtle and difficult to debug errors.
\begin{description}
\constructor{PlTerm}{}
Creates a new initialised "null" term (holding a Prolog variable).
\constructor{PlTerm_term_t}{term_t t}
Converts between the C-interface and the C++ interface by turning the
term-reference into an instance of \ctype{PlTerm}. Note that, being a
lightweight class, this is a no-op at the machine-level!
\constructor{PlTerm_atom}{const char *text}
Creates a term-references holding a Prolog atom representing \arg{text}.
\constructor{PlTerm_atom}{const wchar_t *text}
Creates a term-references holding a Prolog atom representing \arg{text}.
\constructor{PlTerm_atom}{const PlAtom \&atom}
Creates a term-references holding a Prolog atom from an atom-handle.
\constructor{PlTerm_int}{long n}
Creates a term-references holding a Prolog integer representing \arg{n}.
\constructor{PlTerm_int}{int64_t n}
Creates a term-references holding a Prolog integer representing \arg{n} (up to 64 bits signed).
\constructor{PlTerm_int}{uint64_t n}
Creates a term-references holding a Prolog integer representing \arg{n} (up to 64 bits unsigned).
\constructor{PlTerm_float}{double f}
Creates a term-references holding a Prolog float representing \arg{f}.
\constructor{PlTerm_pointer}{void *ptr}
Creates a term-references holding a Prolog pointer. 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 `MyClass' should
define conversion to and from \ctype{void *}.
Also note that in general \jargon{blobs} are a better way of doing this
(see the section on \jargon{blobs} in the Foreign Language Interface
part of the SWI-Prolog manual).
\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.pointer());
return A2.unify_string(myobj->contents);
}
PREDICATE(free_my_object, 1)
{ auto myobj = static_cast<MyClass*>(A1.pointer());
delete myobj;
return true;
}
\end{code}
\end{description}
\subsection{Overview of accessing and changing values (version 2)}
\label{sec:cpp2-plterm-get-put-unify}
The \file{SWI-Prolog.h} header provides various functions for
accessing, setting, and unifying terms, atoms and other types.
Typically, these functions return a \const{0} (\const{false}) or
\const{1} (\const{true}) value for whether they succeeded or not. For
failure, there might also be an exception created - this can be tested
by calling PL_excpetion(0).
There are three major groups of methods:
\begin{itemize}
\item Put (set) a value, corresponding to the PL_put_*() functions.
\item Get a value, corresponding to the PL_get_*() and PL_get_*_ex() functions.
\item Unify a value, corresponding to the PL_unify_*() and PL_unify_*_ex() functions.
\end{itemize}
The "put" operations are typically done on an uninstantiated term (see
the PlTerm_var() constructor). These are expected to succeed, and
typically raise an exception failure (e.g., resource exception) - for
details, see the corresponding PL_put_*() functions in
\href{https://www.swi-prolog.org/pldoc/man?section=foreign-term-construct}{Constructing
Terms}.
For the "get" and "unify" operations, there are three possible failures:
\begin{itemize}
\item \const{false} return code
\item unification failure
\item exception (value of unexpected type or out of resources)
\end{itemize}
Each of these is communicated to Prolog by returning \const{false}
from the top level; exceptions also set a "global" exception term
(using PL_raise_exception()). The C++ programmer usually doesn't have
to worry about this; instead they can \exam{throw PlFail()} for
failure or \exam{throw PlException()} (or one of \ctype{PlException}'s
subclasses) and the C++ API will take care of everything.
\subsection{Converting PlTerm to native C and C++ types (version 2)}
\label{sec:cpp2-plterm-casting}
These are \emph{deprecated} and replaced by the various \exam{as_*()} methods.
\ctype{PlTerm} can be converted to the following types:
\begin{description}
\cppcast{PlTerm}{term_t}
This cast is used for integration with the C-interface primitives.
\cppcast{PlTerm}{long}
Yields a \ctype{long} if the \ctype{PlTerm} is a Prolog integer or
float that can be converted without loss to a long. throws a
\except{type_error} exception otherwise.
\cppcast{PlTerm}{int}
Same as for \ctype{long}, but might represent fewer bits.
\cppcast{PlTerm}{double}
Yields the value as a C double if \ctype{PlTerm} represents a
Prolog integer or float.
\cppcast{PlTerm}{wchar_t *}
\nodescription
\cppcast{PlTerm}{char *}
Converts the Prolog argument using PL_get_chars() using the flags
\const{CVT_ALL|CVT_WRITE|BUF_RING}, which implies Prolog atoms and
strings are converted to the represented text. All other data is
handed to write/1. If the text is static in Prolog, a direct pointer
to the string is returned. Otherwise the text is saved in a ring of
16 buffers and must be copied to avoid overwriting.
\cppcast{PlTerm}{void *}
Extracts pointer value from a term. The term should have been created
by PlTerm::PlTerm(void*).
\end{description}
In addition, the Prolog type (`PL_VARIABLE`, `PL_ATOM`, ... `PL_DICT`)
can be determined using the type() method. There are also boolean
methods that check the type:
\begin{description}
\cfunction{int}{type}{} See PL_term_type()
\cfunction{bool}{is_variable}{} See PL_is_variable()
\cfunction{bool}{is_ground}{} See PL_is_ground()
\cfunction{bool}{is_atom} See PL_is_atom()
\cfunction{bool}{is_integer} See PL_is_integer()
\cfunction{bool}{is_string} See PL_is_string()
\cfunction{bool}{is_float} See PL_is_float()
\cfunction{bool}{is_rational} See PL_is_rational()
\cfunction{bool}{is_compound} See PL_is_compound()
\cfunction{bool}{is_callable} See PL_is_callable()
\cfunction{bool}{is_list} See PL_is_list()
\cfunction{bool}{is_dict} See PL_is_dict()
\cfunction{bool}{is_pair} See PL_is_pair()
\cfunction{bool}{is_atomic} See PL_is_atomic()
\cfunction{bool}{is_number} See PL_is_number()
\cfunction{bool}{is_acyclic} See PL_is_acyclic()
\cfunction{bool}{is_functor}{PlFunctor} See PL_is_functor()
\end{description}
\subsection{Unification (version 2)}
\label{sec:cpp2-plterm-unification}
See also \secref{cpp2-foreign-frame}.
\begin{description}
\cfunction{bool}{PlTerm::unify_term}{PlTerm}
\nodescription
\cfunction{bool}{PlTerm::unify_atom}{PlAtom}
\nodescription
\cfunction{bool}{PlTerm::unify_atom}{string}
\nodescription
\cfunction{bool}{PlTerm::unify_list_codes}{string}
\nodescription
\cfunction{bool}{PlTerm::unify_list_chars}{string}
\nodescription
\cfunction{bool}{PlTerm::unify_integer}{int}
\nodescription
\cfunction{bool}{PlTerm::unify_float}{double}
\nodescription
\cfunction{bool}{PlTerm::unify_string}{string}
\nodescription
\cfunction{bool}{PlTerm::unify_functor}{PlFunctor}
\nodescription
\cfunction{bool}{PlTerm::unify_pointer}{void *}
\nodescription
\cfunction{bool}{PlTerm::unify_nil}{}
\nodescription
\cfunction{bool}{PlTerm::unify_blob}{void *blob, size_t len, PL_blob_t *type}
\nodescription
\cfunction{bool}{PlTerm::unify_chars}{int flags, size_t len, const char *s}
A family of unification methods are defined for the various Prolog types and
C++ types. Wherever \ctype{string} is shown, you can use:
\begin{itemize}
\item \ctype{char*}
\item \ctype{whar_t*}
\item \ctype{std::string}
\item \ctype{std::wstring}
\end{itemize}
\end{description}
Here is an example:
\begin{code}
PREDICATE(hostname, 1)
{ char buf[256];
if ( gethostname(buf, sizeof buf) == 0 )
return A1.unify_atom(buf);
return false;
}
\end{code}
An alternative way of writing this would use the \cfuncref{PlCheck}{}
to raise an exception if the unification fails.
\begin{code}
PREDICATE(hostname2, 1)
{ char buf[256];
PlCheck(gethostname(buf, sizeof buf) == 0);
PlCheck(A1.unify_atom(buf));
return true;
}
\end{code}
Of course, in a real program, the failure of
\cfuncref{gethostname}{buf}{sizeof buf} should create an error term
than contains information from \const{errno}.
\subsection{Comparison (version 2)}
\label{sec:cpp2-plterm-comparison}
\begin{description}
\cfunction{int}{PlTerm::compare}{const PlTerm \&t2}
\nodescription
\cfunction{bool}{PlTerm::operator ==}{const PlTerm \&t}
\nodescription
\cfunction{bool}{PlTerm::operator !=}{const PlTerm \&t}
\nodescription
\cfunction{bool}{PlTerm::operator $<$}{const PlTerm \&t}
\nodescription
\cfunction{bool}{PlTerm::operator $>$}{const PlTerm \&t}
\nodescription
\cfunction{bool}{PlTerm::operator $<=$}{const PlTerm \&t}
\nodescription
\cfunction{bool}{PlTerm::operator $>=$}{const PlTerm \&t}
Compare the instance with \arg{t} and return the result according to
the Prolog defined \jargon{standard order of terms}.
\cfunction{bool}{PlTerm::operator ==}{long num}
\nodescription
\cfunction{bool}{PlTerm::operator !=}{long num}
\nodescription
\cfunction{bool}{PlTerm::operator $<$}{long num}
\nodescription
\cfunction{bool}{PlTerm::operator $>$}{long num}
\nodescription
\cfunction{bool}{PlTerm::operator $<=$}{long num}
\nodescription
\cfunction{bool}{PlTerm::operator $>=$}{long num}
Convert \ctype{PlTerm} to a \ctype{long} and perform standard
C-comparison between the two long integers. If \ctype{PlTerm} cannot be
converted a \except{type_error} is raised.
\cfunction{bool}{PlTerm::operator ==}{const wchar_t *}
\nodescription
\cfunction{bool}{PlTerm::operator ==}{const char *}
\nodescription
\cfunction{bool}{PlTerm::operator ==}{std::wstring}
\nodescription
\cfunction{bool}{PlTerm::operator ==}{std::string}
Yields \const{true} if the \ctype{PlTerm} is an atom or string
representing the same text as the argument, \const{false} if the
conversion was successful, but the strings are not equal and an
\except{type_error} exception if the conversion failed.
\end{description}
Below are some typical examples. See \secref{cpp2-dirplatom} for direct
manipulation of atoms in their internal representation.
\begin{center}
\begin{tabularlp}{\tt A1 == PlCompound("a(1)")}
\hline
\tt A1 $<$ 0 & Test \arg{A1} to hold a Prolog integer or float
that can be transformed lossless to an integer
less than zero. \\
\tt A1 $<$ PlTerm(0) &
\arg{A1} is before the term `0' in the `standard
order of terms'. This means that if \arg{A1}
represents an atom, this test yields \const{true}. \\
\tt A1 == PlCompound("a(1)") &
Test \arg{A1} to represent the term
\exam{a(1)}. \\
\tt A1 == "now" &
Test \arg{A1} to be an atom or string holding the
text ``now''. \\
\hline
\end{tabularlp}
\end{center}
\subsection{Analysing compound terms (version 2)}
\label{sec:cpp2-plterm-compound}
Compound terms can be viewed as an array of terms with a name and arity
(length). This view is expressed by overloading the \const{[]} operator.
A \except{type_error} is raised if the argument is not compound and a
\except{domain_error} if the index is out of range.
In addition, the following functions are defined:
\begin{description}
\cfunction{PlTerm}{PlTerm::operator []}{int arg}
If the \ctype{PlTerm} is a compound term and \arg{arg} is between 1 and
the arity of the term, return a new \ctype{PlTerm} representing the
arg-th argument of the term. If \ctype{PlTerm} is not compound, a
\except{type_error} is raised. Id \arg{arg} is out of range, a
\except{domain_error} is raised. Please note the counting from 1 which
is consistent to Prolog's arg/3 predicate, but inconsistent to C's
normal view on an array. See also class \ctype{PlCompound}. The
following example tests \arg{x} to represent a term with first-argument
an atom or string equal to \exam{gnat}.
\begin{code}
...,
if ( x[1] == "gnat" )
...
\end{code}
\cfunction{const char *}{PlTerm::name}{}
Return a \ctype{const char *} holding the name of the functor of the
compound term. Raises a \except{type_error} if the argument is not
compound.
\cfunction{size_t}{PlTerm::arity}{}
Returns the arity of the compound term. Raises a \except{type_error} if
the argument is not compound.
\end{description}
\subsection{Miscellaneous (version 2)}
\label{sec:cpp2-plterm-misc}
\begin{description}
\cfunction{bool}{is_null}{}
\exam{t.is_null()} is the same as \exam{t.C_ == PlTerm::null}
\cfunction{bool}{not_null}{}
\exam{t.not_null()} is the same as \exam{t.C_ != PlTerm::null}
\cfunction{bool}{reset}{}
\exam{t.reset()} is the same as \exam{t.C_ = PlTerm::null}
\cfunction{bool}{reset}{term_t}
\exam{t.reset(x)} is the same as \exam{t.C_ = x}
\cfunction{int}{PlTerm::type}{}
Yields the actual type of the term as PL_term_type(). Return values are
\const{PL_VARIABLE}, \const{PL_FLOAT}, \const{PL_INTEGER},
\const{PL_ATOM}, \const{PL_STRING} or \const{PL_TERM}
\end{description}
To avoid very confusing combinations of constructors and therefore
possible undesirable effects a number of subclasses of \ctype{PlTerm}
have been defined that provide constructors for creating special Prolog
terms. These subclasses are defined below.
\subsection{The class PlTermString (version 2)}
\label{sec:cpp2-plstring}
A SWI-Prolog string represents a byte-string on the global stack. Its
lifetime is the same as for compound terms and other data living on
the global stack. Strings are not only a compound representation of
text that is garbage-collected, but as they can contain 0-bytes, they
can be used to contain arbitrary C-data structures. However, it is
generally preferred to use blobs for storing arbitrary C-data structures
(see also \exam{PlTerm_pointer(void *ptr)}).
\begin{description}
\constructor{PlString}{const wchar_t *text}
\nodescription
\constructor{PlString}{const char *text}
Create a SWI-Prolog string object from a 0-terminated C-string. The
\arg{text} is copied.
\constructor{PlString}{const wchar_t *text, size_t len}
\nodescription
\constructor{PlString}{const char *text, size_t len}
Create a SWI-Prolog string object from a C-string with specified length.
The \arg{text} may contain 0-characters and is copied.
\end{description}
\subsection{The class PlCodeList (version 2)}
\label{sec:cpp2-codelist}
\begin{description}
\constructor{PlCodeList}{const wchar_t *text}
\nodescription
\constructor{PlCodeList}{const char *text}
Create a Prolog list of ASCII codes from a 0-terminated C-string.
\end{description}
\subsection{The class PlCharList (version 2)}
\label{sec:cpp2-plcharlist}
Character lists are compliant to Prolog's atom_chars/2 predicate.
\begin{description}
\constructor{PlCharList}{const wchar_t *text}
\nodescription
\constructor{PlCharList}{const char *text}
Create a Prolog list of one-character atoms from a 0-terminated
C-string.
\end{description}
\subsection{The class PlCompound (version 2)}
\label{sec:cpp2-plcompound}
\begin{description}
\constructor{PlCompound}{const wchar_t *text}
\nodescription
\constructor{PlCompound}{const char *text}
Create a term by parsing (as read/1) the \arg{text}. If the \arg{text}
is not valid Prolog syntax, a \except{syntax_error} exception is raised.
Otherwise a new term-reference holding the parsed text is created.
\constructor{PlCompound}{const wchar_t *functor, PlTermv args}
\nodescription
\constructor{PlCompound}{const char *functor, PlTermv args}
Create a compound term with the given name from the given vector of
arguments. See \ctype{PlTermv} for details. The example below
creates the Prolog term \exam{hello(world)}.
\begin{code}
PlCompound("hello", PlTermv("world"))
\end{code}
\end{description}
\subsection{The class PlTail (version 2)}
\label{sec:cpp2-pltail}
The class \ctype{PlTail} is both for analysing and constructing lists.
It is called \ctype{PlTail} as enumeration-steps make the term-reference
follow the `tail' of the list.
\begin{description}
\constructor{PlTail}{PlTerm list}
A \ctype{PlTail} is created by making a new term-reference pointing to
the same object. As \ctype{PlTail} is used to enumerate or build a
Prolog list, the initial \arg{list} term-reference keeps pointing to
the head of the list.
\cfunction{int}{PlTail::append}{const PlTerm \&element}
Appends \arg{element} to the list and make the \ctype{PlTail} reference
point to the new variable tail. If \arg{A} is a variable, and this
function is called on it using the argument \exam{"gnat"}, a list of
the form \exam{[gnat|B]} is created and the \ctype{PlTail} object
now points to the new variable \arg{B}.
This function returns \const{true} if the unification succeeded and
\const{false} otherwise. No exceptions are generated.
The example below translates the \cfuncref{main}{} argument vector to Prolog and
calls the prolog predicate entry/1 with it.
\begin{code}
int
main(int argc, char **argv)
{ PlEngine e(argv[0]);
PlTermv av(1);
PlTail l(av[0]);
for(int i=0; i<argc; i++)
PlCheck(l.append(argv[i]));
PlCheck(l.close());
PlQuery q("entry", av);
return q.next_solution() ? 0 : 1;
}
\end{code}
\cfunction{int}{PlTail::close}{}
Unifies the term with \const{[]} and returns the result of the
unification.
\cfunction{int}{PlTail::next}{PlTerm \&t}
Bind \arg{t} to the next element of the list \ctype{PlTail} and advance
\ctype{PlTail}. Returns \const{true} on success and \const{false} if
\ctype{PlTail} represents the empty list. If \ctype{PlTail} is neither a
list nor the empty list, a \except{type_error} is thrown. The example
below prints the elements of a list.
\begin{code}
PREDICATE(write_list, 1)
{ PlTail tail(A1);
PlTerm e;
while(tail.next(e))
cout << e.as_string() << endl;
return true;
}
\end{code}
\end{description}
\section{The class PlTermv (version 2)}
\label{sec:cpp2-pltermv}
The class \ctype{PlTermv} represents an array of term-references. This
type is used to pass the arguments to a foreignly defined predicate,
construct compound terms (see \cfuncref{PlTerm::PlTerm}{const char *name,
PlTermv arguments}) and to create queries (see \ctype{PlQuery}).
The only useful member function is the overloading of \const{[]},
providing (0-based) access to the elements. Range checking is performed
and raises a \except{domain_error} exception.
The constructors for this class are below.
\begin{description}
\constructor{PlTermv}{int size}
Create a new array of term-references, all holding variables.
\constructor{PlTermv}{int size, term_t t0}
Convert a C-interface defined term-array into an instance.
\constructor{PlTermv}{PlTerm ...}
Create a vector from 1 to 5 initialising arguments. For example:
\begin{code}
load_file(const char *file)
{ return PlCall("compile", PlTermv(file));
}
\end{code}
If the vector has to contain more than 5 elements, the following
construction should be used:
\begin{code}
{ PlTermv av(10);
av[0] = "hello";
...
}
\end{code}
\end{description}
\section{The class PlAtom - Supporting Prolog constants (version 2)}
\label{sec:cpp2-prolog-constants}
Both for quick comparison as for quick building of lists of atoms, it
is desirable to provide access to Prolog's atom-table, mapping handles
to unique string-constants. If the handles of two atoms are different
it is guaranteed they represent different text strings.
Suppose we want to test whether a term represents a certain atom, this
interface presents a large number of alternatives:
\subsection{Direct comparision to char *}
\label{sec:cpp2-direct-commparison-to-char-star}
Example:
\begin{code}
PREDICATE(test, 1)
{ if ( A1 == "read" )
...;
}
\end{code}
This writes easily and is the preferred method is performance is not
critical and only a few comparisons have to be made. It validates
\arg{A1} to be a term-reference representing text (atom, string, integer
or float) extracts the represented text and uses strcmp() to match the
strings.
\subsection{Direct comparision to PlAtom} \label{sec:cpp2-dirplatom}
Example:
\begin{code}
static PlAtom ATOM_read("read");
PREDICATE(test, 1)
{ if ( A1 == ATOM_read )
...;
}
\end{code}
This case raises a \except{type_error} if \arg{A1} is not an atom.
Otherwise it extacts the atom-handle and compares it to the atom-handle
of the global \ctype{PlAtom} object. This approach is faster and
provides more strict type-checking.
\subsection{Extraction of the atom and comparison to PlAtom}
\label{sec:cpp2-extraction-comparison-atoms}
Example:
\begin{code}
static PlAtom ATOM_read("read");
PREDICATE(test, 1)
{ PlAtom a1(A1);
if ( a1 == ATOM_read )
...;
}
\end{code}
This approach is basically the same as \secref{cpp2-dirplatom}, but in
nested if-then-else the extraction of the atom from the term is
done only once.
\subsection{Extraction of the atom and comparison to char *}
\label{sec:cpp2-extraction-comparison-char-star}
Example:
\begin{code}
PREDICATE(test, 1)
{ PlAtom a1(A1);
if ( a1 == "read" )
...;
}
\end{code}
This approach extracts the atom once and for each test extracts
the represented string from the atom and compares it. It avoids
the need for global atom constructors.
\begin{description}
\constructor{PlAtom}{atom_t handle}
Create from C-interface atom handle (\ctype{atom_t}). Used internally and for
integration with the C-interface.
\constructor{PlAtom}{const char_t *text}
\nodescription
\constructor{PlAtom}{const wchar *text}
\nodescription
\constructor{PlAtom}{const std::string\& text}
\nodescription
\constructor{PlAtom}{const std::wstring\& text}
Create an atom from a string. The \arg{text} is copied if a new atom
is created. See PL_new_atom(), PL_new_atom_wchars(),
PL_new_atom_nchars(), PL_new_atom_wchars().
\constructor{PlAtom}{const PlTerm \&t}
If \arg{t} represents an atom, the new instance represents this
atom. Otherwise a \except{type_error} is thrown.
\cfunction{int}{PlAtom::operator ==}{const wchar_t *text}
\nodescription
\cfunction{int}{PlAtom::operator ==}{const char *text}
\nodescription
\cfunction{int}{PlAtom::operator ==}{const std::string\& text}
\nodescription
\cfunction{int}{PlAtom::operator ==}{const std::wstring\& text}
Yields \const{true} if the atom represents \arg{text}, \const{false}
otherwise. Performs a \cfuncref{strcmp}{} or similar for this.
\cfunction{int}{PlAtom::operator ==}{const PlAtom \&a}
Compares the two atom-handles, returning \const{true} or
\const{false}. Because atoms are unique, there is no need
to use strcmp() for this.
\cfunction{int}{PlAtom::operator !=}{const wchar_t *text}
\nodescription
\cfunction{int}{PlAtom::operator !=}{const char *text}
\nodescription
\cfunction{int}{PlAtom::operator !=}{const std::string\& text}
\nodescription
\cfunction{int}{PlAtom::operator !=}{const std::wstring\& text}
\nodescription
\cfunction{int}{PlAtom::operator !=}{const PlAtom \&a}
The inverse of the \exam{==} operator.
\cfunction{bool}{is_valid}{}
Verifies that the handle is valid. This can be used after calling
a function that returns an atom handle, to check that a new atom
was created.
\cfunction{void}{reset}{}
Sets the handle to an invalid valid - a subsequent call to \cfuncref{is_null}{}
will return \const{true}.
\cfunction{const std::string}{as_string}{PlEncoding enc=EncLocale}
Returns the string representation of the atom.\footnote{If you wish
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
into the stack (Gnu C++ or Clang options \exam{-Wreturn-stack-address}
or \exam{-Wreturn-local-addr}) can \emph{sometimes} catch this, as can
the runtime address sanitizer when run with
\exam{detect_stack_use_after_return=1}.}
This does not
quote or escape any characters that would need to be escaped
if the atom were to be input to the Prolog parser. The possible values
for \exam{enc} are:
\begin{itemize}
\item \exam{EncLatin1} - throws an exception if cannot be represented in ASCII.
\item \exam{EncUTF8}
\item \exam{EncLocale} - uses the locale to determine the representation.
\end{itemize}
\cfunction{const std:wstring}{as_wstring}{}
Returns the string representation of the atom. This does not
quote or escape any characters that would need to be escaped
if the atom were to be input to the Prolog parser.
\cfunction{void}{register_atom}{}
See PL_register_atom().
\cfunction{void}{unregister_atom}{}
See PL_unregister_atom().
\cfunction{void*}{blob_data}{size_t *len, struct PL_blob_t **type}
See PL_blob_data().
\end{description}
\section{Unification and foreign frames (version 2)}
\label{sec:cpp2-foreign-frame}
As documented with PL_unify(), if a unification call fails and control isn't
made immediately to Prolog, any changes made by unification must be undone.
The functions PL_open_foreign_frame(), PL_rewind_foreign_frame(), and
PL_close_foreign_frame() are encapsulated in the class \ctype{PlFrame},
whose destructor calls PL_close_foreign_frame(). Using this, the example
code with PL_unify() can be written:
\begin{code}
{ PlFrame frame;
...
if ( !t1.unify_term(t2) )
frame.rewind();
...
}
\end{code}
Note that \cfuncref{PlTerm::unify_term}{} checks for an exception and
throws an exception to Prolog; if you with to handle exceptions, you
must call \exam{PL_unify_term(t1.C_,t2.C_)}.
\section{The class PlRegister (version 2)}
\label{sec:cpp2-plregister}
This class encapsulates PL_register_foreign(). It is defined as a class
rather then a function to exploit the C++ \jargon{global constructor}
feature. This class provides a constructor to deal with the \cfuncref{PREDICATE}{}
way of defining foreign predicates as well as constructors to deal with
more conventional foreign predicate definitions.
\begin{description}
\constructor{PlRegister}{const char *module,
const char *name,
int arity,
foreign_t (f)(term_t t0, int a, control_t ctx)}
Register \arg{f} as a the implementation of the foreign predicate
<name>/<arity>. This interface uses the \const{PL_FA_VARARGS} calling
convention, where the argument list of the predicate is passed using an
array of \ctype{term_t} objects as returned by PL_new_term_refs(). This
interface poses no limits on the arity of the predicate and is faster,
especially for a large number of arguments.
\constructor{PlRegister}{const char *module,
const char *name,
foreign_t (*f)(PlTerm a0, \ldots)}
Registers functions for use with the traditional calling conventional,
where each positional argument to the predicate is passed as an argument
to the function \arg{f}. This can be used to define functions as
predicates similar to what is used in the C-interface:
\begin{code}
static foreign_t
pl_hello(PlTerm a1)
{ ...
}
PlRegister x_hello_1(NULL, "hello", 1, pl_hello);
\end{code}
This construct is currently supported upto 3 arguments.
\end{description}
\section{The class PlQuery (version 2)}
\label{sec:cpp2-plquery}
This class encapsulates the call-backs onto Prolog.
\begin{description}
\constructor{PlQuery}{const char *name, const PlTermv \&av}
Create a query where \arg{name} defines the name of the predicate and
\arg{av} the argument vector. The arity is deduced from \arg{av}. The
predicate is located in the Prolog module \module{user}.
\constructor{PlQuery}{const char *module, const char *name,
const PlTermv \&av}
Same, but performs the predicate lookup in the indicated module.
\cfunction{int}{PlQuery::next_solution}{}
Provide the next solution to the query. Yields \const{true} if
successful and \const{false} if there are no (more) solutions.
Prolog exceptions are mapped to C++ exceptions.
\cfunction{void}{PlQuery::cut()}{}
Discards the query, but does not delete an of the data created
by the query. If there is any pending Prolog exception, it is
mapped to a C++ exception and thrown.
The call to \cfuncref{PlQuery::cut}{} is done implicitly by \ctype{PlQuery}'s destructor.
Below is an example listing the currently defined Prolog modules
to the terminal.
\begin{code}
PREDICATE(list_modules, 0)
{ PlTermv av(1);
PlQuery q("current_module", av);
while( q.next_solution() )
cout << av[0].as_string() << endl;
return true;
}
\end{code}
\end{description}
In addition to the above, the following functions have been defined.
\begin{description}
\cfunction{int}{PlCall}{const char *predicate, const PlTermv \&av}
Creates a \ctype{PlQuery} from the arguments generates the
first \cfuncref{next_solution}{} and destroys the query. Returns the
result of \cfuncref{next_solution}{} or an exception.
\cfunction{int}{PlCall}{const char *module, const char *predicate,
const PlTermv \&av}
Same, locating the predicate in the named module.
\cfunction{int}{PlCall}{const wchar_t *goal}
\nodescription
\cfunction{int}{PlCall}{const char *goal}
Translates \arg{goal} into a term and calls this term as the other
\cfuncref{PlCall}{} variations. Especially suitable for simple goals such as making
Prolog load a file.
\end{description}
\subsection{The class PlFrame (version 2)}
\label{sec:cpp2-plframe}
The class \ctype{PlFrame} provides an interface to discard unused
term-references as well as rewinding unifications
(\jargon{data-backtracking}). Reclaiming unused term-references is
automatically performed after a call to a C++-defined predicate has
finished and returns control to Prolog. In this scenario \ctype{PlFrame}
is rarely of any use. This class comes into play if the toplevel program
is defined in C++ and calls Prolog multiple times. Setting up arguments
to a query requires term-references and using \ctype{PlFrame} is the
only way to reclaim them.
\begin{description}
\constructor{PlFrame}{}
Creating an instance of this class marks all term-references created
afterwards to be valid only in the scope of this instance.
\destructor{PlFrame}
Reclaims all term-references created after constructing the instance.
\cfunction{void}{PlFrame::rewind}{}
Discards all term-references {\bf and} global-stack data created as well
as undoing all unifications after the instance was created.
\end{description}
\index{assert}%
A typical use for \ctype{PlFrame} is the definition of C++ functions
that call Prolog and may be called repeatedly from C++. Consider the
definition of assertWord(), adding a fact to word/1:
\begin{code}
void
assertWord(const char *word)
{ PlFrame fr;
PlTermv av(1);
av[0] = PlCompound("word", PlTermv(word));
PlQuery q("assert", av);
PlCheck(q.next_solution());
}
\end{code}
This example shows the most sensible use of \ctype{PlFrame} if it is
used in the context of a foreign predicate. The predicate's thruth-value
is the same as for the Prolog unification (=/2), but has no
side effects. In Prolog one would use double negation to achieve this.
\begin{code}
PREDICATE(can_unify, 2)
{ PlFrame fr;
int rval = (A1=A2);
fr.rewind();
return rval;
}
\end{code}
\cfuncref{PlRewindOnFail}{f} is a convenience function that does a frame rewind if
unification fails. Here is an example, where \exam{name_to_term}
contains a map from names to terms (which are made global by using the
PL_record() function):
\begin{code}
static const std::map<const std::string, record_t> name_to_term =
{ {"a", PlTerm(...).record()}, ...};
bool lookup_term(const std::string name, PlTerm result)
{ const auto it = name_to_term.find(name);
if ( it == name_to_term.cend() )
return false;
PlTerm t = PlTerm_recorded(it->second);
return PlRewindOnFail([result,t]() -> bool { return result.unify_term(t); });
}
\end{code}
\section{The PREDICATE and PREDICATE_NONDET macros (version 2)}
\label{sec:cpp2-predicate-macro}
The PREDICATE macro is there to make your code look nice, taking care of
the interface to the C-defined SWI-Prolog kernel as well as mapping
exceptions. Using the macro
\begin{code}
PREDICATE(hello, 1)
\end{code}
is the same as writing:\footnote{There are a few more details,
such as catching \exam{std::bad_alloc}.}:
\begin{code}
static foreign_t pl_hello__1(PlTermv PL_av);
static foreign_t
_pl_hello__1(term_t t0, int arity, control_t ctx)
{ (void)arity; (void)ctx;
try
{ return pl_hello__1(PlTermv(1, t0));
} catch( PlFail& )
{ return false;
} catch ( PlException& ex )
{ return ex.plThrow();
}
}
static PlRegister _x_hello__1("hello", 1, _pl_hello__1);
static foreign_t
pl_hello__1(PlTermv PL_av)
\end{code}
The first function converts the parameters passed from the Prolog
kernel to a \ctype{PlTermv} instance and maps exceptions raised in the
body to simple failure or Prolog exceptions. The \ctype{PlRegister}
global constructor registers the predicate. Finally, the function
header for the implementation is created.
\subsection{Variations of the PREDICATE macro (version 2)}
\label{sec:cpp2-predicate-macro-variations}
The \cfuncref{PREDICATE}{} macros have a number of variations that deal with
special cases.
\begin{description}
\cmacro{}{PREDICATE0}{name}
This is the same as PREDICATE(name, 0). It avoids a compiler warning
about that \const{PL_av} is not used.
\cmacro{}{NAMED_PREDICATE}{plname, cname, arity}
This version can be used to create predicates whose name is not a valid
C++ identifier. Here is a ---hypothetical--- example, which unifies the
second argument with a stringified version of the first. The `cname' is
used to create a name for the functions. The concrete name does not
matter, but must be unique. Typically it is a descriptive name using the
limitations imposed by C++ indentifiers.
\begin{code}
NAMED_PREDICATE("#", hash, 2)
{ A2 = (wchar_t*)A1;
}
\end{code}
\cmacro{}{PREDICATE_NONDET}{name, arity}
Define a non-deterministic Prolog predicate in C++. See also
\secref{cpp2-nondet}.
\cmacro{}{NAMED_PREDICATE_NONDET}{plname, cname, arity}
Define a non-deterministic Prolog predicate in C++, whose name
is not a valid C++ identifier. See also \secref{cpp2-nondet}.
\end{description}
\subsection{Non-deterministic predicates (version 2)}
\label{sec:cpp2-nondet}
Non-deterministic predicates are defined using
\cfuncref{PREDICATE_NONDET}{plname, cname, arity} or
\cfuncref{NAMED_PREDICATE_NONDET}{plname, cname, arity}.
A non-deterministic predicate returns a "context", which is passed to a
a subsequent retry. Typically, this context is allocated on the first
call to the predicate and freed when the predicate either fails or
does its last successful return. To simplify this, a template helper class
\ctype{PlForeignContextPtr<\emph{ContextType}>} provides a "smart
pointer" that frees the context on normal return or an exception;
if \cfuncref{PlForeignContextPtr$<$ContextType$>$::keep}{}
is called, the pointer isn't freed on return or exception.
The skeleton for a typical non-deterministic predicate is:
\begin{code}
struct PredContext { ... }; // The "context" for retries
PREDICATE_NONDET(pred, <arity>)
{ PlForeignContextPtr<PredContext> ctxt(handle);
switch( PL_foreign_control(handle) )
{ case PL_FIRST_CALL:
ctxt.set(new PredContext(...));
...
break;
case PL_REDO:
break;
case PL_PRUNED:
return true;
}
if ( ... )
return false; // Failure (and no more solutions)
// or throw PlFail();
if ( ... )
return true; // Success (and no more solutions)
...
ctxt.keep();
PL_retry_address(ctxt.get()); // Succeed with a choice point
}
\end{code}
\subsection{Controlling the Prolog destination module (version 2)}
\label{sec:cpp2-module}
With no special precautions, the predicates are defined into the
module from which load_foreign_library/1 was called, or in the module
\const{user} if there is no Prolog context from which to deduce the
module such as while linking the extension statically with the Prolog
kernel.
Alternatively, {\em before} loading the SWI-Prolog include file, the
macro PROLOG_MODULE may be defined to a string containing the name of
the destination module. A module name may only contain alpha-numerical
characters (letters, digits, _). See the example below:
\begin{code}
#define PROLOG_MODULE "math"
#include <SWI-Prolog.h>
#include <math.h>
PREDICATE(pi, 1)
{ A1 = M_PI;
}
\end{code}
\begin{code}
?- math:pi(X).
X = 3.14159
\end{code}
\section{Exceptions (version 2)}
\label{sec:cpp2-exceptions}
Prolog exceptions are mapped to C++ exceptions using the subclass
\ctype{PlException} of \ctype{PlTerm} to represent the Prolog exception
term. All type-conversion functions of the interface raise
Prolog-compliant exceptions, providing decent error-handling support at
no extra work for the programmer.
For some commonly used exceptions, subclasses of \ctype{PlException}
have been created to exploit both their constructors for easy creation
of these exceptions as well as selective trapping in C++. Currently,
these are \ctype{PlTypeEror} and \ctype{PlDomainError},
\ctype{PlTermvDomainError}, \ctype{PlInstantiationError},
\ctype{PlExistenceError}, \ctype{PermissionError}, \ctype{PlResourceError},
and \ctype{PlException_qid}.
To throw an exception, create an instance of \ctype{PlException} and
use \exam{throw}. This is intercepted by the PREDICATE macro and
turned into a Prolog exception. See \secref{cpp2-exceptions-notes}.
\begin{code}
char *data = "users";
throw PlException(PlCompound("no_database", PlTerm(data)));
\end{code}
\subsection{The class PlException (version 2)}
\label{sec:cpp2-plexception}
This subclass of \ctype{PlTerm} is used to represent exceptions.
Currently defined methods are:
\begin{description}
\constructor{PlException}{const PlTerm \&t}
Create an exception from a general Prolog term. This provides the
interface for throwing any Prolog terms as an exception.
\cfunction{std::string}{as_string}{}
The exception is translated into a message as produced by
print_message/2. The character data is stored in a ring. Example:
\begin{code}
...;
try
{ PlCall("consult(load)");
} catch ( PlException& ex )
{ cerr << ex.as_string() << endl;
}
\end{code}
\cfunction{int}{plThrow}{}
Used in the \cfuncref{PREDICATE}{} wrapper to pass the exception to Prolog. See
PL_raise_exeption().
\end{description}
\subsection{The class PlTypeError (version 2)}
\label{sec:cpp2-pl-type-error}
A \jargon{type error} expresses that a term does not satisfy the
expected basic Prolog type.
\begin{description}
\constructor{PlTypeError}{const char *expected, const PlTerm \&actual}
Creates an ISO standard Prolog error term expressing the
\arg{expected} type and \arg{actual} term that does not satisfy this
type.
\end{description}
\subsection{The class PlDomainError (version 2)}
\label{sec:cpp2-pl-domain-error}
A \jargon{domain error} expresses that a term satisfies the basic
Prolog type expected, but is unacceptable to the restricted domain
expected by some operation. For example, the standard Prolog open/3
call expect an \const{io_mode} (read, write, append, ...). If an integer
is provided, this is a \jargon{type error}, if an atom other than one
of the defined io-modes is provided it is a \jargon{domain error}.
\begin{description}
\constructor{PlDomainError}{const char *expected, const PlTerm \&actual}
Creates an ISO standard Prolog error term expressing a the
\arg{expected} domain and the \arg{actual} term found.
\end{description}
\section{Embedded applications (version 2)}
\label{sec:cpp2-embedding}
Most of the above assumes Prolog is `in charge' of the application and
C++ is used to add functionality to Prolog, either for accessing
external resources or for performance reasons. In some applications,
there is a \jargon{main-program} and we want to use Prolog as a
\jargon{logic server}. For these applications, the class
\ctype{PlEngine} has been defined.
Only a single instance of this class can exist in a process. When used
in a multi-threading application, only one thread at a time may have
a running query on this engine. Applications should ensure this using
proper locking techniques.%
\footnote{For Unix, there is a multi-threaded version of SWI-Prolog.
In this version each thread can create and destroy a
thread-engine. There is currently no C++ interface defined
to access this functionality, though ---of course--- you
can use the C-functions.}
\begin{description}
\constructor{PlEngine}{int argc, char **argv}
Initialises the Prolog engine. The application should make sure to
pass \exam{argv[0]} from its main function, which is needed in the
Unix version to find the running executable. See PL_initialise()
for details.
\constructor{PlEngine}{char *argv0}
Simple constructure using the main constructor with the specified
argument for \exam{argv[0]}.
\destructor{PlEngine}
Calls PL_cleanup() to destroy all data created by the Prolog engine.
\end{description}
\Secref{pltail} has a simple example using this class.
\section{Considerations (version 2)}
\label{sec:cpp2-considerations}
\subsection{The C++ versus the C interface (version 2)}
\label{sec:cpp2-vs-c}
Not all functionality of the C-interface is provided, but as
\ctype{PlTerm} and \ctype{term_t} are essentially the same thing with
type-conversion between the two (using the \exam{C_} field), this interface
can be freely mixed with the functions defined for plain C.
For checking return codes from C functions, it is recommended to
use \cfuncref{PlCheck}{}.
Using this interface rather than the plain C-interface requires a little
more resources. More term-references are wasted (but reclaimed on return
to Prolog or using \ctype{PlFrame}). Use of some intermediate types
(\ctype{functor_t} etc.) is not supported in the current interface,
causing more hash-table lookups. This could be fixed, at the price of
slighly complicating the interface.
\subsection{Notes on exceptions}
\label{sec:cpp2-exceptions-notes}
Exceptions are normal Prolog terms that are handled specially by the
PREDICATE macro when they are used by a C++ \exam{throw}, and
converted into Prolog exceptions. The exception term may not be
unbound; that is, throw(_) must raise an error. The C++ code and
underlying C code do not explicitly check for the term being a
variable, and behaviour of raising an exception that is an unbound
term is undefined, including the possibility of causing a crash or
corrupting data.
The Prolog exception term error(Formal, _) is special. If the 2nd
argument of error/2 is undefined, and the term is thrown, the system
finds the catcher (if any), and calls the hooks in
library(prolog_stack) to add the context and stack trace information
when appropriate. That is, \exam{throw PlDomainError(Domain,Culprit)}
ends up doing the same thing as calling
\exam{PL_domain_error(Domain,Culprit)} which internally calls
PL_raise_exception() and returns control back to Prolog.
The VM handling of calling to C finds the \const{FALSE} return code,
checks for the pending exception and propagates the exception into the
Prolog environment. As the term references (\ctype{term_t}) used to
create the exception are lost while returning from the foreign
function we need some way to protect them. That is done using a
global \ctype{term_t} handle that is allocated at the epoch of Prolog.
PL_raise_exception() sets this to the term using PL_put_term().
PL_exception(0) returns the global exception \ctype{term_t} if it is
bound and 0 otherwise.
Special care needs to be taken with data backtracking using
PL_discard_foreign_frame() or PL_close_query() because that will
invalidate the exception term. So, between raising the exception and
returning control back to Prolog we must make sure not to do anything
that invalidates the exception term. If you suspect something like
that to happen, use the debugger with a breakpoint on
\cfuncref{__do_undo__LD}{} defined in \file{pl-wam.c}.
In order to always preserve Prolog exceptions and return as quickly as
possible to Prolog on an exception, some of the C++ classes can throw
an exception in their destructor. This is theoretically a dangerous
thing to do, and can lead to a crash or program termination if the
destructor is envoked as part of handling another exception.
\subsection{Static linking and embedding (version 2)}
\label{sec:cpp2-linking}
The mechanisms outlined in this document can be used for static linking
with the SWI-Prolog kernel using \manref{swipl-ld}{1}. In general the
C++ linker should be used to deal with the C++ runtime libraries and
global constructors.
\subsection{Status and compiler versions (version 2)}
\label{sec:cpp2-status}
The current interface is entirely defined in the \fileext{h} file using
inlined code. This approach has a few advantages: as no C++ code is in
the Prolog kernel, different C++ compilers with different name-mangling
schemas can cooperate smoothly.
Also, changes to the header file have no consequences to binary
compatibility with the SWI-Prolog kernel. This makes it possible to have
different versions of the header file with few compatibility
consequences.
As of 2022-11, some details remain to be decided, mostly to do
with encodings. A few methods have a \ctype{PlEncoding} optional
parameter (e.g., \cfuncref{PlTerm::as_string}{}), but this hasn't yet been
extended to all methods that take or return a string. Also, the
details of how the default encoding is set have not yet been decided.
As of 2022-11, the various error convenience classes do not fully
match what the equivalent C functions do. That is,
\exam{throw PlInstantiationError(A1)} does not result in the same
context and traceback information that calling that would happen
from \exam{PL_instantiation_error(A1.C_); throw PlFail()}.
See \secref{cpp2-exceptions-notes}.
\section{Conclusions (version 2)}
\label{sec:cpp2-conclusions}
In this document, we presented a high-level interface to Prolog
exploiting automatic type-conversion and exception-handling defined in
C++.
Programming using this interface is much more natural and requires only
little extra resources in terms of time and memory.
Especially the smooth integration between C++ and Prolog exceptions
reduce the coding effort for type checking and reporting in foreign
predicates.
|