Mercurial > hg > plml
changeset 0:0dd31a8c66bd
Initial check in to Mercurial, V.1
author | samer |
---|---|
date | Fri, 13 Jan 2012 15:29:02 +0000 |
parents | |
children | 4d183f2855c2 |
files | COPYING INSTALL LICENSE Makefile Makefile.lnx Makefile.maci64 Makefile.osx NOTES README cpp/Makefile cpp/plml.cpp matlab/db/contents.m matlab/db/dbdrop.m matlab/db/dbload.m matlab/db/dbpath.m matlab/db/dbread.m matlab/db/dbroot.m matlab/db/dbsave.m matlab/db/dbsaveas.m matlab/db/dbtmp.m matlab/db/typecode.m matlab/db/uniquefile.m matlab/db/uniquevar.m matlab/db/unknown.m matlab/general/cellmap.m prolog/Makefile prolog/dcgu.pl prolog/ops.pl prolog/plml.pl prolog/update prolog/utils.pl |
diffstat | 31 files changed, 5161 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/COPYING Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/INSTALL Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,93 @@ +*** Prolog Matlab interface +*** +*** Authors: +*** Samer Abdallah +*** Centre for Digital Music, +*** Queen Mary, University of London +*** +*** Christophe Rhodes +*** Centre for Computational Creativity +*** Goldsmiths College, University of London +*** +*** Dec 2004--November 2008 + + +------------------------------------------------------------------------- +BUILDING/INSTALLATION + + +Before you start, you need a working SWI Prolog installation and +a Matlab installation. The compilation is done using the plld utility +that comes with SWI. This only has a hope of working on Unix systems, +and I've only tried with Mac OS X and Linux. + + +*** Binary and Prolog code *** + +There are several possible make files: + + Makefile.osx - Makefile for OS X + Makefile.lnx - Makefile for Linux + Makefile.maci64 - Makefile for OS X 64 bit Intel + +The top of each make file contains configuration variables that +you may need to adjust: + + MATLAB - Top Matlab installation directory + MLARCH - Matlab name for system architecture (mac,glnx86) + SO - Extension for shared objects (dylib,so) + INCLUDES - extra include options + PLLD - command to run SWI Prolog compiler/linker plld + MLLIBS - Matlab libraries to link with + INSTALL_LIB_TO - directory to install shared object to + INSTALL_PL_TO - directroy to install Prolog files to + +See one of the make files for more information. +Copy of one the make files to Makefile and +modify as necessary. +Then run + $ make + $ make install + +This should copy the binary object and Prolog libraries to the target +installation directory. SWI Prolog needs to be able to find them, +eg, with INSTALL_PL_TO=~/lib/prolog I put + + file_search_path(foreign,'/Users/samer/lib/prolog'). + +into my ~/.plrc +Since ~/lib/prolog is automatically in the file_search_path(library,_), +nothing else needs to be done. If you install the libraries somewhere +else, you will need to add a file_search_path(library,PathToLibraries) +clause. + + + +** Matlab code ** + +The subdirectories of the matlab directory need to be in your matlab +path, or the contents copied to somewhere already in you Matlab search +path. + + + + +------------------------------------------------------------------------- +SANITY CHECK + +If the installation is ok, then the following should work: + + $ swipl + ?- use_module(library(plml)). + ... + Yes. + + ?- ml_open(ml). % ml is the name assigned to the Matlab engine instance + Matlab engine (ml) open. + + Yes + + ?- float(A)===2*pi. + A = 6.28319 + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,14 @@ + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,43 @@ +# ---------------- configuration ---------------------- + +# Point this at your Matlab installation +export MATLAB=/usr/local/matlab7 + +# this needs to match the correct subdirectory of $(MATLAB)/bin +export MLARCH=maci64 + +# I arrived at these by trial and error so it may need adjusting. +export MLLIBS=-leng +#export MLLIBS=-leng -lmx -lmat -licuuc -licudata -licui18n -lz -lreadline + +# target is plml.dylib for OSX, plml.so under Linux +export SO=dylib + +# if you have multiple SWI Prolog installations or an installation +# in a non-standard place, set PLLD to the appropriate plld invokation, eg +# PLLD=/usr/local/bin/plld -p /usr/local/bin/swipl +export PLLD=swipl-ld + +# install directories +export INSTALL_LIB_TO=~/lib/prolog/x86_64 +export INSTALL_PL_TO=~/lib/prolog/source + +# flags for install - BSD install seems to be different from GNU install +export INSTALL_FLAGS='-bCS' + +VER=1.0 +# ---------------- end of configuration --------------- + +main: + make -C cpp + +clean: + make -C cpp clean + +install: main + make -C cpp install + make -C prolog install + +tarball: + mkdirhier release + (cd .. && tar czf plml/release/plml-$(VER).tar.gz --exclude CVS --exclude "*.gz" --exclude release plml)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.lnx Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,45 @@ +# ---------------- configuration ---------------------- + +# Point this at your Matlab installation +export MATLAB=/usr/local/matlab7 + +# this needs to match the correct subdirectory of $(MATLAB)/bin +export MLARCH=glnx86 + +# I arrived at these by trial and error so it may need adjusting. +export MLLIBS=-leng -lmx -lmat -licuuc -licudata -licui18n -lz -lreadline + +# target is plml.dylib for OSX, plml.so under Linux +export SO=so + +# Any extra includes go here - I use Fink, hence /sw/include +export INCLUDES= + +# if you have multiple SWI Prolog installations or an installation +# in a non-standard place, set PLLD to the appropriate plld invokation, eg +# PLLD=/usr/local/bin/plld -p /usr/local/bin/swipl +export PLLD=swipl-ld + +# install directories +export INSTALL_LIB_TO=~/lib/prolog +export INSTALL_PL_TO=~/lib/prolog + +# flags for install - BSD install seems to be different from GNU install +export INSTALL_FLAGS='-bp' + +VER=0.91 +# ---------------- end of configuration --------------- + +main: + make -C cpp + +clean: + make -C cpp clean + +install: main + make -C cpp install + make -C prolog install + +tarball: + mkdirhier release + (cd .. && tar czf plml/release/plml-$(VER).tar.gz --exclude CVS --exclude "*.gz" --exclude release plml)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.maci64 Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,43 @@ +# ---------------- configuration ---------------------- + +# Point this at your Matlab installation +export MATLAB=/usr/local/matlab7 + +# this needs to match the correct subdirectory of $(MATLAB)/bin +export MLARCH=maci64 + +# I arrived at these by trial and error so it may need adjusting. +export MLLIBS=-leng +#export MLLIBS=-leng -lmx -lmat -licuuc -licudata -licui18n -lz -lreadline + +# target is plml.dylib for OSX, plml.so under Linux +export SO=dylib + +# if you have multiple SWI Prolog installations or an installation +# in a non-standard place, set PLLD to the appropriate plld invokation, eg +# PLLD=/usr/local/bin/plld -p /usr/local/bin/swipl +export PLLD=swipl-ld + +# install directories +export INSTALL_LIB_TO=~/lib/prolog +export INSTALL_PL_TO=~/lib/prolog + +# flags for install - BSD install seems to be different from GNU install +export INSTALL_FLAGS='-bCS' + +VER=0.92 +# ---------------- end of configuration --------------- + +main: + make -C cpp + +clean: + make -C cpp clean + +install: main + make -C cpp install + make -C prolog install + +tarball: + mkdirhier release + (cd .. && tar czf plml/release/plml-$(VER).tar.gz --exclude CVS --exclude "*.gz" --exclude release plml)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.osx Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,45 @@ +# ---------------- configuration ---------------------- + +# Point this at your Matlab installation +export MATLAB=/usr/local/matlab7 + +# this needs to match the correct subdirectory of $(MATLAB)/bin +export MLARCH=mac + +# I arrived at these by trial and error so it may need adjusting. +export MLLIBS=-leng -lmx -lmat -licuuc -licudata -licui18n -lz -lreadline + +# target is plml.dylib for OSX, plml.so under Linux +export SO=dylib + +# Any extra includes go here - I use Fink, hence /sw/include +export INCLUDES=-I/sw/include + +# if you have multiple SWI Prolog installations or an installation +# in a non-standard place, set PLLD to the appropriate plld invokation, eg +# PLLD=/usr/local/bin/plld -p /usr/local/bin/swipl +export PLLD=swipl-ld + +# install directories +export INSTALL_LIB_TO=~/lib/prolog +export INSTALL_PL_TO=~/lib/prolog + +# flags for install - BSD install seems to be different from GNU install +export INSTALL_FLAGS='-bCS' + +VER=0.91 +# ---------------- end of configuration --------------- + +main: + make -C cpp + +clean: + make -C cpp clean + +install: main + make -C cpp install + make -C prolog install + +tarball: + mkdirhier release + (cd .. && tar czf plml/release/plml-$(VER).tar.gz --exclude CVS --exclude "*.gz" --exclude release plml)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/NOTES Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,54 @@ +*** Prolog Matlab interface +*** +*** Authors: +*** Samer Abdallah +*** Centre for Digital Music, +*** Queen Mary, University of London +*** +*** Christophe Rhodes +*** Centre for Computational Creativity +*** Goldsmiths College, University of London +*** +*** Dec 2004--Nov 2006 + + +PLML is a foreign interface that enables Matlab to be used as a computational +engine from within SWI Prolog. The basic idea is that instead of using +the standard is/2 operator to evaluate a certain class of terms, we can +use the ===/2 operator to get Matlab to evaluate a (much richer) class of +terms, eg + + ?- float(A)===trace(eye(3)). + + A = 3.0 + +We can also get Matlab to perform actions with side effects, like +making sounds and graphics; obviously these do not fit into the declartive +semantics of Prolog and have to be dealt with under the procedural semantics. +If you want to execute a Matlab command in an imperative way and see the +textual output, use the ??/1 operator, eg + + ?- ??disp(`hello). + + >> hello + + +The interface works by using the Matlab Engine API, which starts up a Matlab +process on the end of a pipe. The Matlab process can be on another machine, +and multiple Matlab engines can be started on the same or different machines. +Matlab expressions are sent down the pipe and executed. Matlab's textual +output comes back through the pipe. In addition, Matlab variables can be +transferred directly between the Matlab engine's memory space and SWI's +memory space. + +See README for further details. + + +RELEASE NOTES for version 0.2 + + - Added option to enable Matlab's JVM + - Now closing Matlab engines properly at halt + - Added support for valid but non-evaluable expressions + - Fixed bug when returning integers from Matlab + - Errors in user's Matlab functions now generate mlerror(_,_) expections +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,284 @@ +*** Prolog Matlab interface +*** +*** Authors: +*** Samer Abdallah +*** Centre for Digital Music, +*** Queen Mary, University of London +*** +*** Christophe Rhodes +*** Centre for Computational Creativity +*** Goldsmiths College, University of London +*** +*** Dec 2004--January 2012 + + + + +------------------------------------------------------------------------- +OVERVIEW + +PLML is a foreign interface that enables Matlab to be used as a computational +engine from within SWI Prolog. The basic idea is that instead of using +the standard is/2 operator to evaluate a certain class of terms, we can +use the ===/2 operator to get Matlab to evaluate a (much richer) class of +terms, eg + + ?- float(A)===trace(eye(3)). + + A = 3.0 + +We can also get Matlab to perform actions with side effects, like +making sounds and graphics; obviously these do not fit into the declartive +semantics of Prolog and have to be dealt with under the procedural semantics. +If you want to execute a Matlab command in an imperative way and see the +textual output, use the ??/1 operator, eg + + ?- ??disp(`hello). + >> hello + + + +The interface works by using the Matlab Engine API, which starts up a Matlab +process on the end of a pipe. The Matlab process can be on another machine, +and multiple Matlab engines can be started on the same or different machines. +Matlab expressions are sent down the pipe and executed. Matlab's textual +output comes back through the pipe. In addition, Matlab variables can be +transferred directly between the Matlab engine's memory space and SWI's +memory space. + + +*** Expression language + +Expressions to evaluate are given in a sublanguage of terms which is +similar to but not exactly the same as Matlab. In particular, Prolog +syntax cannot accommodate the single quoted Matlab strings, +the Matlab syntax of matrices, (eg [1 2; 3 4]), and the Matlab syntax +for slicing arrays (eg A(:,3:4)) if A is a Prolog variable. +Strings are handled using the q/1 or `/1 functors, ie `hello and q(hello) +both evaluate to 'hello'. Arrays can be given either as flat lists, +which are interpreted as horizontal concatenation as in Matlab: + + ?- ??[1,2,3]. + >> ans = 1 2 3 + + ?- ??[eye(2),magic(2)]. + >> ans = + 1 0 1 3 + 0 1 4 2 + +or as nested listed for multidimensional arrays using the arr/1 functor, +where the innermost nesting corresponds to the FIRST Matlab dimensions + + ?- ??arr([1,2,3]). + >> ans = + 1 + 2 + 3 + + ?- ??arr([[1,2],[3,4]]). + >> ans = + 1 3 + 2 4 + +Cell arrays can be specified in a similar way using braces or the cell/1 functor. + + +To help with accessing array elements, see the Matlab functions general/paren, +general/row, and general/col in the matlab directory. + + + +*** Return values + +The results of computations can handled in several ways: + +1. Keep the result in a Matlab workspace variable in the engine's memory + space. The names of these variables are allocated automatically + and stored in a Prolog atom. The atoms have a garbage collection + callback which means that the Matlab workspace variable is deleted + if the Prolog atom goes out of scope. + + ?- A===2+2, ??disp(A). + >> 4 % matlab textual output + + A = ws(<ml:t_2311>) % Prolog blob pointing to Matlab variable t_2311 + + + +2. Convert the result to a prolog atom or term. The type of the resulting + prolog term depends on the *right hand side* of the ===/2 operator: + + ?- int(A)===2+2. + A = 4 + + ?- float(A)===2+2. + A = 4.0 + + + There are other types for strings and atoms: + + ?- atom(A) === q(hello). % q/1 means quote as Matlab string + A = hello. + + ?- string(A) === `hello. % `/1 is shorthand for q/1 + A = "hello". + + + You can also get the result as a Matlab binary array on the Prolog side: + + ?- mx(A)===eye(4). % identity matrix + A = <#0239c3a0> % Prolog blob handle (with garbage collection) + + + I haven't completely settled on the best way of handling arrays as + self-contained Prolog terms, but you can do this: + + ?- array(A)===magic(3). + A = [[8.0, 3.0, 4.0], [1.0, 5.0, 9.0], [6.0, 7.0, 2.0]]::[[3, 3]] + + As you can see, multidimensional arrays are returned as nested lists, and the + size of the array is given after the :: as [[3,3]]. + + +3. Store the result to a MAT file and return a Prolog term which points to the + file. The names are generated automatically. This allows for persistence + of values which are referred to by stable names that can be stored, eg + in a database: + + ?- mat(A)===fft(buffer(wavread('somefile.wav'),256,128)). + + A = mat:d0608/m48598|x % dynamically generated unique locator + + This relies on the mechanism provided by the functions in matlab/db. + A certain directory is designed the root of a 'matbase' (MAT file database). + The Matlab function dbroot returns or sets this directory: + + ?- ??dbroot. + >> + ans = + + /Users/samer/matbase + + ?- ??dbroot(q('/usr/share/lib/matbase')). % switch to shared matbase + >> + ans = + + /usr/share/lib/matbase + + In this case, the locator mat:d0608/m48598|x refers to a Matlab variable called + 'x' (it's always 'x') in the file /usr/share/lib/matbase/d0608/m48598.mat. + A new directory is created each month, and the filenames are chosen dynamically + to avoid clashes with existing files. + + + +*** Debugging/tracing + +To help with debugging, you can issue the command: + + ?- utils:set_state(debug,on). + +which will cause each Matlab expression to be printed in its Matlab form +before execution. +I'm afraid the best documentation is the code itself, but I do intend to +produce a manual once some of the more embarrassing aspects of system +are resolved! + + + +------------------------------------------------------------------------- +BUILDING/INSTALLATION + +See INSTALL + + + + +------------------------------------------------------------------------- +CHANGES + +12/04 - Using Prolog blobs with garbage collection to handle + Matlab workspace temporary variables. Works but code is + still a little messy. Would like to unify variable handling- + the important functions are alloc, free, get, put. + Also, garbage collection seems to be rather difficult to + provoke. + +2005-08-08 + Handle Matlab errors in mlEXEC by setting lasterr() before and + checking it after engEvalString. If we do get a Matlab + error, then throw a Prolog exception, because nothing + else is safe in general. CSR. + +2005-08-09 + Be a little more paranoid in handling workspace variables, both + in terms of checking matlab engine error codes and for + bounds checking of our own functions such as uniquevar. + +2005-09-26 + Added matbase_mat/1 to enumerate all mat objects actually in + the file system pointed to by the matlab function dbroot. + +2005-11-11 + Now sending very long Matlab commands via a char array + Progress in Prolog-side mxArray support: + done: + MXINFO - get size and type of array + MXSUB2IND - convert multi-dim subscript to linear index + MXGETFLOAT - get one element of numeric array (must be real) + MXGETLOGICAL - get element of logical or 0|1 array + MXGETCELL - get sub mxArray from cell array + MXGETREALS - get reals part of all elements as flat Prolog list + MXCREATENUMERIC - create double array + MXCREATECELL - create cell array + MXCREATESTRING - create char array from string or atom + MXPUTFLOAT - write one element of double array + MXPUTFLOATS - write list of elements to double array + MXPUTCELL - put an mxArray into a cell array + MXCOPYNOGC - deep copy of array, return NON-MANAGED mx ref + MXNEWREFGC - return memory managed ref to array, ie will be GCed + to do: + Imaginary parts? + Reading list of fields from a structure + Getting cell array contents as a list + tidy up: error checking and function names + possibly reduce the amount of bounds checking to improve speed? + -> need to do proper profiling! + +2006-11-28 + Errors generated on the matlab side (ie errors in user functions + rather than the mechanisms of this library) throw exceptions of + the form mlerror(Engine,Message) instead of just atomic messages. + + Some changes to plml.pl - see header in that file for details. + +2008-11-25 + Moved declaration of \ operator to ops.pl + Changed interface and implementation of ml_open to use list of options. + Changed build procedure to use build script and two makefiles. + +2010-02-25 + Replaced use of mxFree with mxDestroyArray to release resources + obtained using engGetVariable - this was causing a malloc error + in Matlab 7.9. Also replaced -nojvm with -noawt option when starting + Matlab, as -nojvm is no longer supported. Apparently they're going + to withdraw support for X11 graphics at some point. I hate them. + I'm not 'upgrading' any more. + +2010-03-19 + Removed dependency on flists + +2010-05-30 + Merged hostname module into utils. + +2012-01 + Big overhaul of Prolog part to simplify and speed up. + Removed unused Matlab functions from matlab/general. + Version 1! + +------------------------------------------------------------------------- +ACKNOWLEDGMENTS + +This work was partially supported by UK EPSRC grants GR/S84750/01 and +GR/S82213/01. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cpp/Makefile Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,27 @@ +# This will not work as a stand-alone make file - it must be +# called recursively from the make file in the directory above. + +TARGET=plml.$(SO) +INCML=$(MATLAB)/extern/include +LIBML=$(MATLAB)/bin/$(MLARCH) +PLLDFLAGS=-c++ g++-apple-4.2 $(INCLUDES) -I$(INCML) -Wall + +.SUFFIXES: .cpp .o .so .dylib + +main: $(TARGET) + +clean: + rm $(TARGET) + +.cpp.so: + $(PLLD) -v $(PLLDFLAGS) -L$(LIBML) -shared $(MLLIBS) -o $@ $< + strip -x $@ + +.cpp.dylib: + $(PLLD) -v $(PLLDFLAGS) -L$(LIBML) -shared $(MLLIBS) -o $@ $< + strip -x $@ + +install: + install -d $(INSTALL_LIB_TO) + install $(INSTALL_FLAGS) $(TARGET) $(INSTALL_LIB_TO) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cpp/plml.cpp Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,979 @@ +/* + * Prolog-MATLAB interface + * Samer Abdallah (2004) + * Christophe Rhodes (2005) + * + * These are some foreign for procedures to enable SWI Prolog to run + * and communicate with a MATLAB computational engine, which is + * started as a separate process on the local or a remote machine. + * + * Communication is handled by the MATLAB engine API (engFoo + * functions) which in turn use a pair of pipes connected to the + * standard input and output of the MATLAB process. + * + * Computations are carried out in the workspace of the MATLAB + * process, but results can be transported in and out of Prolog's + * memory space using the {Get,Put}Variable APIs. + * + * mxArrays can be created and manipulated on the Prolog side. + * For example, a large numeric array can be created and send to + * the engine instead of building a huge expression to evaluate. + * + * NOTE: memory management of mxArrays on the Prolog side is complicated + * by the use of cell arrays. Currently, there are two kinds of + * mxArray reference atom: garbage collected ones and non-garbage + * collected ones. You should generally use GCed atoms, but mxArrays + * going in or out of cell arrays should not be GCed because the + * parent cell array should manage them. Hence, newly created arrays + * (using CREATENUMERIC, CREATECELL and CREATESTRING) are NOT + * marked for GC because you might want to put them straight into a + * cell array. Also, mx atoms returned from GETCELL are not GCed. + * If a new array is not going into a cell array, you should use + * NEWREFGC to obtain a managed atom as soon as possible. + * + * If you have a managed array you want to put into a cell array, + * you should use COPYNOGC to make an unmanaged DEEP COPY of the + * original which can safely be put in the cell array using PUTCELL + * + * A better solution would be to flip the management status of a given + * mx_blob atom as necessary. + * + * TODO + * + * - (See plmatlab.pl for comments about the syntax for Prolog-side + * users) + * + * - There is a problem if the Matlab script decides to pause - there + * is apparently no way to communicate a keypress to the engine. + * + * - Similarly, there is no way to interrupt a long computation. + * Pressing Ctrl-C to interrupt Prolog seems to have some effect but + * it seems to confuse the Matlab engine. Empirically, matlab + * processes handle some signals (try kill -SEGV `pidof MATLAB`) but + * not in a useful way. + * + * - There is no established protocol for freeing variables from + * engGetVariable: they are likely to persist for ever, or at least + * for a long time, except for those handled by the finalization of + * prolog terms. + * + * - Memory management of mxArray references (see above notes) + * + * Changes + * 3/10/04: Added code to retrieve logical variables. + * Added error checking - eval predicates should fail on error. + * + * 5/10/04: Added eng::fp which points to input and output streams + * of matlab process. This will enable asynchronous evals + * + * 22/10/04: Blob handling for mxArray corrected by liberal sprinkling + * of asterisks. + * + * 12/12/04: Removed non-blob mxArray code and added blobs for Matlab + * workspace variables. + * + * 13/12/04: Removed all traces of old ws var handling code. + * + * (Later changes may be found in the README file) + */ + +#include <SWI-cpp.h> +#include <stdio.h> +#include "engine.h" + +/* The maximum number of simultaneous connections to Matlab from one + Prolog process. */ +#define MAXENGINES 4 +#define BUFSIZE 32768 // buffer for matlab output +#define MAXCMDLEN 256 +// #define EVALFMT "t__ex=[];\ntry\n%s\ncatch t__ex\ndisp(getReport(t__ex))\nend" +#define EVALFMT "lasterr(''); %s\nt__ex=lasterr;" + +using namespace std; + +// This is for a SWI Prolog BLOB type to manage mxArray pointers. It +// means that the Prolog garbage collector can deal with freeing +// unreferenced mxArrays automatically. + + +#ifdef MX_API_VER +#if MX_API_VER >= 0x07030000 +#else +typedef int mwSize; +typedef int mwIndex; +#endif +#else +typedef int mwSize; +typedef int mwIndex; +#endif + +static PL_blob_t mx_blob; +static PL_blob_t mxnogc_blob; +static functor_t mlerror; + +// Extract an mxArray * from a BLOB atom +static mxArray *term_to_mx(term_t t) { + PL_blob_t *type; + size_t len; + void *p; + + PL_get_blob(t, &p, &len, &type); + if (type != &mx_blob && type != &mxnogc_blob) { + throw PlException("Not an mx variable"); + } + return *(mxArray **) p; +} + +static mxArray *ablob_to_mx(atom_t a) { + return term_to_mx(PlTerm(PlAtom(a))); +} + +// This is for a SWI Prolog BLOB type to manage Matlab workspace +// variables. The variable is cleared and the name reclaimed +// when the blob is garbage collected. This kind of blob has no data +// apart from the atom's name (ie the variable's name) + +static PL_blob_t ws_blob; + +// structure for keeping track of workspace variables +struct wsvar { + char name[8]; // designed for short machine generated names + Engine *engine; // the matlab engine which owns this variable + atom_t id; // the id of this engine +}; + +// extract wsvar from blob term +static struct wsvar *term_to_wsvar(term_t t) { + PL_blob_t *type; + size_t len; + void *p; + + PL_get_blob(t, &p, &len, &type); + if (type != &ws_blob) { + throw PlException("Not a ws variable"); + } + return (struct wsvar *) p; +} + +// extract wsvar from atom by converting to term first +static struct wsvar *atom_to_wsvar(atom_t a) { + return term_to_wsvar(PlTerm(PlAtom(a))); +} + + +/* MATLAB engine wrapper class */ +class eng { +public: + Engine *ep; // MATLAB API engine pointer + atom_t id; // atom associated with this engine + char *outbuf; // buffer for textual output from MATLAB + + eng(): ep(NULL), id(PL_new_atom("")), outbuf(NULL) {} + + void open(const char *cmd, atom_t id) { + ep=engOpen(cmd); + + if (ep) { + this->id=id; + outbuf=new char[BUFSIZE]; + outbuf[BUFSIZE-1]=0; + engOutputBuffer(ep,outbuf,BUFSIZE-1); + printf("Matlab engine (%s) open.\n",PL_atom_chars(id)); + } else { + throw PlException("open engine failed"); + } + } + void close() { + engClose(ep); + id = PL_new_atom(""); + delete [] outbuf; + ep=0; + } + + bool matches(atom_t id) const { return id==this->id; } + bool isOpen() const { return ep!=NULL; } +}; + +// pool of engines, all initially closed +static eng engines[MAXENGINES]; +// functor to be used to wrap array pointers + +extern "C" { +// Functions for mx array atom type + int mx_release(atom_t a); + int mx_compare(atom_t a, atom_t b); + // int mx_write(IOSTREAM *s, atom_t a, int flags); + int mxnogc_release(atom_t a); + +// Functions for WS variable atom type + int ws_release(atom_t a); + // int ws_write(IOSTREAM *s, atom_t a, int flags); +} + +extern "C" { + install_t install(); + foreign_t mlOpen(term_t servercmd, term_t engine); + foreign_t mlClose(term_t engine); + foreign_t mlExec(term_t engine, term_t cmd); + foreign_t mlWSGet(term_t var, term_t val); + foreign_t mlWSPut(term_t var, term_t val); + foreign_t mlWSName(term_t engine, term_t var, term_t id); + foreign_t mlWSAlloc(term_t engine, term_t var); + foreign_t mlMx2Atom(term_t mx, term_t atom); + foreign_t mlMx2Float(term_t mx, term_t num); + foreign_t mlMx2Logical(term_t mx, term_t num); + foreign_t mlMx2String(term_t mx, term_t num); + foreign_t mlMxInfo(term_t mx, term_t size, term_t type); + foreign_t mlMxSub2Ind(term_t mx, term_t subs, term_t ind); + foreign_t mlMxGetFloat(term_t mx, term_t index, term_t value); + foreign_t mlMxGetLogical(term_t mx, term_t index, term_t value); + foreign_t mlMxGetCell(term_t mx, term_t index, term_t value); + foreign_t mlMxGetField(term_t mx, term_t index, term_t field, term_t value); + foreign_t mlMxGetReals(term_t mx, term_t values); + foreign_t mlMxCreateNumeric(term_t size, term_t mx); + foreign_t mlMxCreateCell(term_t size, term_t mx); + foreign_t mlMxCreateString(term_t string, term_t mx); + foreign_t mlMxPutFloat(term_t mx, term_t index, term_t value); + foreign_t mlMxPutFloats(term_t mx, term_t index, term_t values); + foreign_t mlMxPutCell(term_t mx, term_t index, term_t value); + foreign_t mlMxCopyNoGC(term_t src, term_t dst); + foreign_t mlMxNewRefGC(term_t src, term_t dst); +} + +install_t install() { + PL_register_foreign("mlOPEN", 2, (void *)mlOpen, 0); + PL_register_foreign("mlCLOSE", 1, (void *)mlClose, 0); + PL_register_foreign("mlEXEC", 2, (void *)mlExec, 0); + PL_register_foreign("mlWSNAME", 3, (void *)mlWSName, 0); + PL_register_foreign("mlWSALLOC", 2, (void *)mlWSAlloc, 0); + PL_register_foreign("mlWSGET", 2, (void *)mlWSGet,0); + PL_register_foreign("mlWSPUT", 2, (void *)mlWSPut, 0); + PL_register_foreign("mlMX2ATOM", 2, (void *)mlMx2Atom, 0); + PL_register_foreign("mlMX2FLOAT", 2, (void *)mlMx2Float, 0); + PL_register_foreign("mlMX2LOGICAL", 2, (void *)mlMx2Logical, 0); + PL_register_foreign("mlMX2STRING", 2, (void *)mlMx2String, 0); + PL_register_foreign("mlMXINFO", 3, (void *)mlMxInfo, 0); + PL_register_foreign("mlSUB2IND", 3, (void *)mlMxSub2Ind, 0); + PL_register_foreign("mlGETFLOAT", 3, (void *)mlMxGetFloat, 0); + PL_register_foreign("mlGETLOGICAL", 3, (void *)mlMxGetLogical, 0); + PL_register_foreign("mlGETCELL", 3, (void *)mlMxGetCell, 0); + PL_register_foreign("mlGETFIELD", 4, (void *)mlMxGetField, 0); + PL_register_foreign("mlGETREALS", 2, (void *)mlMxGetReals, 0); + PL_register_foreign("mlCREATENUMERIC", 2, (void *)mlMxCreateNumeric, 0); + PL_register_foreign("mlCREATECELL", 2, (void *)mlMxCreateCell, 0); + PL_register_foreign("mlCREATESTRING", 2, (void *)mlMxCreateString, 0); + PL_register_foreign("mlPUTFLOAT", 3, (void *)mlMxPutFloat, 0); + PL_register_foreign("mlPUTFLOATS", 3, (void *)mlMxPutFloats, 0); + PL_register_foreign("mlPUTCELL", 3, (void *)mlMxPutCell, 0); + PL_register_foreign("mlCOPYNOGC", 2, (void *)mlMxCopyNoGC, 0); + PL_register_foreign("mlNEWREFGC", 2, (void *)mlMxNewRefGC, 0); + + mx_blob.magic = PL_BLOB_MAGIC; + mx_blob.flags = PL_BLOB_UNIQUE; + mx_blob.name = (char *)"mx"; + mx_blob.acquire = 0; + mx_blob.release = mx_release; + mx_blob.compare = mx_compare; + mx_blob.write = 0; // mx_write; + + mxnogc_blob.magic = PL_BLOB_MAGIC; + mxnogc_blob.flags = PL_BLOB_UNIQUE; + mxnogc_blob.name = (char *)"mxnogc"; + mxnogc_blob.acquire = 0; + mxnogc_blob.release = mxnogc_release; + mxnogc_blob.compare = mx_compare; + mxnogc_blob.write = 0; // mx_write; + + ws_blob.magic = PL_BLOB_MAGIC; + ws_blob.flags = PL_BLOB_UNIQUE; + ws_blob.name = (char *)"ws"; + ws_blob.acquire = 0; + ws_blob.release = ws_release; + ws_blob.compare = 0; + ws_blob.write = 0; + + mlerror=PL_new_functor(PL_new_atom("mlerror"),3); +} + +void check(int rc) { if (!rc) printf("*** plml: Something failed.\n");} + +void check_array_index(mxArray *mx, long i) +{ + long n = mxGetNumberOfElements(mx); + if (i<=0 || i>n) throw PlException("Index out of bounds"); +} + +int unify_list_sizes(term_t list, const mwSize *ints, int num) +{ + list=PL_copy_term_ref(list); + + for (int i=0; i<num; i++) { + term_t head=PL_new_term_ref(); + term_t tail=PL_new_term_ref(); + if (!PL_unify_list(list,head,tail)) PL_fail; + if (!PL_unify_integer(head,ints[i])) PL_fail; + list=tail; + } + return PL_unify_nil(list); +} + +int unify_list_doubles(term_t list, double *x, int n) +{ + list=PL_copy_term_ref(list); + + for (int i=0; i<n; i++) { + term_t head=PL_new_term_ref(); + term_t tail=PL_new_term_ref(); + if (!PL_unify_list(list,head,tail)) PL_fail; + if (!PL_unify_float(head,x[i])) PL_fail; + list=tail; + } + return PL_unify_nil(list); +} + +// read list of integers from term and write to int array +int get_list_integers(term_t list, long *len, int *vals) +{ + term_t head=PL_new_term_ref(); + long n; + + // copy term ref so as not to modify original + list=PL_copy_term_ref(list); + for (n=0;PL_get_list(list,head,list);n++) { + if (!PL_get_integer(head,&vals[n])) return false; + } + if (!PL_get_nil(list)) return false; + *len=n; + return true; +} + +// read list of floats from term and write to double array +int get_list_doubles(term_t list, long *len, double *vals) +{ + term_t head=PL_new_term_ref(); + long n; + + // copy term ref so as not to modify original + list=PL_copy_term_ref(list); + for (n=0;PL_get_list(list,head,list);n++) { + if (!PL_get_float(head,&vals[n])) return false; + } + if (!PL_get_nil(list)) return false; + *len=n; + return true; +} + + + +/* + * Member functions for SWIs blob atoms, which allow SWI to manage + * garbage collection for user-defined data types. + */ +int mx_release(atom_t a) { + mxArray *p=ablob_to_mx(a); + mxDestroyArray(p); + return TRUE; +} + +int mx_compare(atom_t a, atom_t b) { + mxArray *pa=ablob_to_mx(a); + mxArray *pb=ablob_to_mx(b); + if (pa<pb) return -1; + else if (pa>pb) return 1; + else return 0; +} + +int mxnogc_release(atom_t a) { return TRUE; } + +/* +// this requires some jiggery pokery to handle IOSTREAMS. +int mx_write(IOSTREAM *s, atom_t a, int flags) { + mxArray *p=ablob_to_mx(a); + fprintf(s,"<mx:%p>",p); +} +*/ + + +int ws_release(atom_t a) { + struct wsvar *x=atom_to_wsvar(a); + // printf("."); fflush(stdout); // sweet brevity + + char buf[16]; + sprintf(buf,"clear %s",x->name); + engEvalString(x->engine,buf); + x->name[0]=0; + x->engine=0; + + return TRUE; +} + +/* see mx_write */ +//int ws_write(IOSTREAM *s, atom_t a, int flags) { +// struct wsvar *p=atom_to_wsvar(a); +// mxArray *p=ablob_to_mx(a); +// fprintf(s,"%s",p->name); +//} + + +/* Finds the engine associated with the given term + * (which should just be an atom). Throws an exception + * if the engine is not found. + */ +static eng *findEngine(term_t id_term) +{ + atom_t id; + if(!PL_get_atom(id_term, &id)) { + throw PlException("id is not an atom"); + } + for (int i=0; i<MAXENGINES; i++) { + if (engines[i].matches(id)) return &engines[i]; + } + throw PlException("engine not found"); +} + + +/* + * Open a matlab engine using the given command and associate + * it with the second term, which should be an atom. + */ + +foreign_t mlOpen(term_t servercmd, term_t id_term) +{ + try { + findEngine(id_term); + printf("mlOPEN/2: Engine %s already open\n",(const char *)PlTerm(id_term)); + PL_succeed; + } catch (...) {} + + try { + // look for an unused engine structure + for (int i=0; i<MAXENGINES; i++) { + if (!engines[i].isOpen()) { + atom_t id; + check(PL_get_atom(id_term,&id)); + engines[i].open(PlTerm(servercmd), id); + fputs(engines[i].outbuf,stdout); + PL_succeed; + } + } + return PL_warning("mlOPEN/2: no more engines available."); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Close a previously opened Matlab engine +foreign_t mlClose(term_t engine) { + try { + findEngine(engine)->close(); + PL_succeed; + } catch (PlException &e) { + return e.plThrow(); + } +} + +/* + * Workspace variable handling + */ + +// This will create a new workspace variable with an unused name, +// initialise it to an empty array (to reserve the name) and unify +// the term (which must be a prolog variable) with a blob representing +// the variable. This in turn points back to this engine so that +// if garbage collected, the workspace variable is cleared. +foreign_t mlWSAlloc(term_t eng, term_t blob) { + // if varname is already bound, we should check + // that the name has not been used in the workspace + try { + class eng *engine=findEngine(eng); + struct wsvar x; + int rc; + + // in a threaded world, there would either need to be precisely + // one engine per thread (so that there are no race conditions on + // the Matlab side) or else these lines (down to PL_unify_blob) + // need to be atomic. + x.engine = engine->ep; + x.id = engine->id; + + if (engEvalString(x.engine, "t__0=uniquevar([]);")) + throw PlException("Cannot execute uniquevar"); + + memset(x.name,sizeof(x.name),0); + mxArray *newname=engGetVariable(x.engine, "t__0"); + if (newname==NULL) { + engEvalString(x.engine,"clear(t__0)"); // half arsed attempt to fix variable leak + throw PlException("Cannot get new variable name."); + } + rc = mxGetString(newname,x.name, sizeof(x.name)); + mxDestroyArray(newname); + if (rc) throw PlException("Cannot read new variable name."); + + return PL_unify_blob(blob,&x,sizeof(x),&ws_blob); + } catch (PlException &e) { + return e.plThrow(); + } +} + +foreign_t mlWSName(term_t blob, term_t name, term_t engine) { + // if varname is already bound, we should check + // that the name has not been used in the workspace + try { + struct wsvar *x = term_to_wsvar(blob); + return ( PL_unify_atom_chars(name, x->name) + && PL_unify_atom(engine, x->id)); + } catch (PlException &e) { + PL_fail; // return e.plThrow(); + } +} + +// Get a named variable from the MATLAB workspace and return a term +// containing a pointer to an mxArray (in Prolog's memory space). +foreign_t mlWSGet(term_t var, term_t val) { + try { + struct wsvar *x = term_to_wsvar(var); + mxArray *p = engGetVariable(x->engine, x->name); + return PL_unify_blob(val, (void **)&p, sizeof(p), &mx_blob); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Put an array back in Matlab workspace under given variable name +foreign_t mlWSPut(term_t var, term_t val) { + try { + struct wsvar *x=term_to_wsvar(var); + engPutVariable(x->engine, x->name, term_to_mx(val)); + PL_succeed; + } catch (PlException &e) { + return e.plThrow(); + } +} + +/* + * Executing MATLAB code + */ + +// Call a Matlab engine to execute the given command +foreign_t mlExec(term_t engine, term_t cmd) +{ +// printf(">>> Entering mlEXEC\n"); + try { + eng *eng=findEngine(engine); + const char *cmdstr=PlTerm(cmd); + char *eval_cmd; + int cmdlen=strlen(cmdstr); + int rc; + + // if string is very long, send it via local mxArray + if (cmdlen>MAXCMDLEN) { + mxArray *mxcmd=mxCreateString(cmdstr); +// printf(" >>> Putting command\n"); + engPutVariable(eng->ep,"t__cmd",mxcmd); +// printf(" <<< Put command ok\n"); + mxDestroyArray(mxcmd); + cmdstr="eval(t__cmd)"; + cmdlen=strlen(cmdstr); + } + + eval_cmd = new char[cmdlen+strlen(EVALFMT)-1]; + if (eval_cmd==NULL) throw PlException("Failed to allocate memory for command"); + sprintf(eval_cmd, EVALFMT, cmdstr); +// printf(" >>> Calling Matlab engine...\n"), + rc=engEvalString(eng->ep,eval_cmd); +// printf(" <<< Returned from Matlab engine...\n"), + delete [] eval_cmd; + + if (rc) { + // printf("*** MATLAB evaluation error. Output buffer contains:\n"), + // fputs(eng->outbuf,stdout); + // printf("*** throwing exception.\n"); + // throw PlException("MATLAB evaluation error"); + } + + + // write whatever is in the output buffer now. + fputs(eng->outbuf,stdout); + + // SA 2010. Giving up any pretence of being thread-safe - + // each engine is to be used by one Prolog thread ONLY. + // If you want fancy threading stuff, do it in Prolog. + +// printf(" >>> Getting variable\n"); + mxArray *lasterr = engGetVariable(eng->ep, "t__ex"); +// printf(" <<< Got variable\n"); +// if (!lasterr) throw PlException("Failed to get status information."); + + if (mxGetNumberOfElements(lasterr)>0) { + //char *string=mxArrayToString(mxGetField(lasterr,0,"message")); + char *string=mxArrayToString(lasterr); + mxDestroyArray(lasterr); + + term_t desc=PL_new_term_ref(); + term_t cmd=PL_new_term_ref(); + term_t ex=PL_new_term_ref(); + + PL_put_atom_chars(desc,string); + PL_put_atom_chars(cmd,cmdstr); + mxFree(string); + check(PL_cons_functor(ex,mlerror,engine,desc,cmd)); + throw PlException(ex); + + } else mxDestroyArray(lasterr); + + // if we've got this far, then everything went well, so +// printf("<<< Returning from mlEXEC\n"); + PL_succeed; + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Get a Prolog string out of a matlab char array +foreign_t mlMx2String(term_t mx, term_t a) +{ + try { + char *str = mxArrayToString(term_to_mx(mx)); + if (!str) { + return PL_warning("array is not a character array"); + } + int rc = PL_unify_string_chars(a, str); + mxFree(str); + return rc; + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Convert Matlab char array to a Prolog atom +foreign_t mlMx2Atom(term_t mx, term_t a) +{ + try { + char *str = mxArrayToString(term_to_mx(mx)); + if (!str) { + return PL_warning("array is not a character array"); + } + int rc = PL_unify_atom_chars(a, str); + mxFree(str); + return rc; + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Convert Matlab numerical array with one element to Prolog float +foreign_t mlMx2Float(term_t mxterm, term_t a) +{ + try { + mxArray *mx = term_to_mx(mxterm); + if (!mxIsDouble(mx)) { + return PL_warning("not numeric"); + } + if (mxGetNumberOfElements(mx)!=1) { + return PL_warning("Not a scalar"); + } + double x = mxGetScalar(mx); + + return PL_unify_float(a, x); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Convert Matlab numerical (REAL) array to list +foreign_t mlMxGetReals(term_t mxterm, term_t a) +{ + try { + mxArray *mx = term_to_mx(mxterm); + int n = mxGetNumberOfElements(mx); + + if (!mxIsDouble(mx)) return PL_warning("not numeric"); + return unify_list_doubles(a,mxGetPr(mx),n); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Convert Matlab logical or numeric array with one element to +// Prolog integer 0 or 1 (does not fail or succeed depending on +// logical value - this is can be done by prolog code). +foreign_t mlMx2Logical(term_t mxterm, term_t a) +{ + try { + mxArray *mx = term_to_mx(mxterm); + if (mxGetNumberOfElements(mx) != 1) return PL_warning("Not a scalar"); + + int f; + if (mxIsLogical(mx)) { + f = mxIsLogicalScalarTrue(mx) ? 1 : 0; + } else if (mxIsDouble(mx)) { + f = (mxGetScalar(mx) > 0) ? 1 : 0; + } else { + return PL_warning("neither numeric nor logical (captain)"); + } + + return PL_unify_integer(a,f); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Get array information (size and type of elements) +foreign_t mlMxInfo(term_t mxterm, term_t size, term_t type) +{ + try { + mxArray *mx = term_to_mx(mxterm); + long ndims = mxGetNumberOfDimensions(mx); + const mwSize *dims = mxGetDimensions(mx); + const char *cnm = mxGetClassName(mx); + + if (PL_unify_atom_chars(type, cnm)) { + if (dims[ndims-1]==1) ndims--; // remove trailing singletons + return unify_list_sizes(size,dims,ndims); + } + PL_fail; + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Convert multidimensional subscript to linear index +foreign_t mlMxSub2Ind(term_t mxterm, term_t substerm, term_t indterm) +{ + try { + mxArray *mx=term_to_mx(mxterm); + mwIndex subs[64]; // 64 dimensional should be enough! + long nsubs; + + // get substerm as int array + if (!get_list_integers(substerm,&nsubs,(int *)subs)) // !! + return PL_warning("Bad subscript list"); + + // switch to zero-based subscripts + for (int i=0; i<nsubs; i++) subs[i]--; + + int ind = mxCalcSingleSubscript(mx,nsubs,subs); + check_array_index(mx,ind); + + return PL_unify_integer(indterm, ind); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Dereference double from mx array +foreign_t mlMxGetFloat(term_t mxterm, term_t index, term_t value) +{ + try { + mxArray *mx = term_to_mx(mxterm); + long i; + + check(PL_get_long(index,&i)); + check_array_index(mx,i); + if (!mxIsDouble(mx)) { return PL_warning("not numeric"); } + + double *p = (double *)mxGetData(mx); + return PL_unify_float(value, p[i-1]); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Dereference logical from mx array +foreign_t mlMxGetLogical(term_t mxterm, term_t index, term_t value) +{ + try { + mxArray *mx = term_to_mx(mxterm); + long i; + + check(PL_get_long(index,&i)); + check_array_index(mx,i); + + if (mxIsLogical(mx)) { + mxLogical *p = mxGetLogicals(mx); + return PL_unify_integer(value,(p[i-1]) ? 1 : 0); + } else if (mxIsDouble(mx)) { + double *p = (double *)mxGetData(mx); + return PL_unify_integer(value, (p[i-1]>0) ? 1 : 0); + } else { + return PL_warning("neither logical nor numeric"); + } + + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Dereference mxArray from cell array +// Note that we return a non-gargage collected atom, otherwise, +// the parent cell array would be spoiled when one of its elements +// is released and destroyed. However, if the parent cell is +// released and destroyed, any remaining references to elements +// will be prematurely invalidated. +// FIXME: This is almost certain to confuse the garbage collector +foreign_t mlMxGetCell(term_t mxterm, term_t index, term_t value) +{ + try { + mxArray *mx = term_to_mx(mxterm); + long i; + + check(PL_get_long(index,&i)); + check_array_index(mx,i); + if (!mxIsCell(mx)) { return PL_warning("not numeric"); } + + mxArray *p = mxGetCell(mx,i-1); + return PL_unify_blob(value, (void **)&p, sizeof(p), &mxnogc_blob); + } catch (PlException &e) { + return e.plThrow(); + } +} + +foreign_t mlMxGetField(term_t mxterm, term_t index, term_t field, term_t value) +{ + try { + mxArray *mx = term_to_mx(mxterm); + long i; + char *fname; + + check(PL_get_long(index,&i)); + check(PL_get_atom_chars(field,&fname)); + check_array_index(mx,i); + if (!mxIsStruct(mx)) { return PL_warning("not a structure"); } + + mxArray *p = mxGetField(mx,i-1,fname); + if (!p) return PL_warning("Field not present"); + return PL_unify_blob(value, (void **)&p, sizeof(p), &mxnogc_blob); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Create numeric array. Currently only real double arrays created +foreign_t mlMxCreateNumeric(term_t size, term_t mx) { + try { + mwSize dims[64]; + long ndims; + + // get size as int array + if (!get_list_integers(size,&ndims,(int *)dims)) + return PL_warning("Bad size list"); + + mxArray *p = mxCreateNumericArray(ndims,dims,mxDOUBLE_CLASS,mxREAL); + return PL_unify_blob(mx, (void **)&p, sizeof(p), &mxnogc_blob); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Create cell array. +foreign_t mlMxCreateCell(term_t size, term_t mx) { + try { + mwSize dims[64]; + long ndims; + + // get size as int array + if (!get_list_integers(size,&ndims,(int *)dims)) + return PL_warning("Bad size list"); + + mxArray *p = mxCreateCellArray(ndims,dims); + return PL_unify_blob(mx, (void **)&p, sizeof(p), &mxnogc_blob); + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Create numeric array. Currently only real double arrays created +foreign_t mlMxCreateString(term_t string, term_t mx) { + try { + mxArray *p = mxCreateString(PlTerm(string)); + return PL_unify_blob(mx, (void **)&p, sizeof(p), &mxnogc_blob); + } catch (PlException &e) { + return e.plThrow(); + } +} + + +// Write float into double array +foreign_t mlMxPutFloat(term_t mxterm, term_t index, term_t value) +{ + try { + mxArray *mx = term_to_mx(mxterm); + long i; + double val; + + if (!mxIsDouble(mx)) { return PL_warning("not numeric"); } + check(PL_get_long(index,&i)); + check(PL_get_float(value,&val)); + check_array_index(mx,i); + *(mxGetPr(mx)+i-1)=val; + return true; + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Write list of floats into double array starting at given index +foreign_t mlMxPutFloats(term_t mxterm, term_t index, term_t values) +{ + try { + mxArray *mx = term_to_mx(mxterm); + long i, len; + + if (!mxIsDouble(mx)) { return PL_warning("not numeric"); } + check(PL_get_long(index,&i)); + check_array_index(mx,i); + get_list_doubles(values,&len,mxGetPr(mx)+i-1); + return true; + } catch (PlException &e) { + return e.plThrow(); + } +} + +// Put an mxArray into a cell array +// IMPORTANT: the object being put must in a non-memory managed atom +foreign_t mlMxPutCell(term_t mxterm, term_t index, term_t element) +{ + try { + mxArray *mx = term_to_mx(mxterm); + mxArray *el = term_to_mx(element); + long i; + + if (!mxIsCell(mx)) { return PL_warning("not a cell array"); } + check(PL_get_long(index,&i)); + check_array_index(mx,i); + mxSetCell(mx,i-1,el); + return true; + } catch (PlException &e) { + return e.plThrow(); + } +} + +foreign_t mlMxCopyNoGC(term_t in, term_t out) +{ + try { + mxArray *mx = term_to_mx(in); + mxArray *p = mxDuplicateArray(mx); + return PL_unify_blob(out, (void **)&p, sizeof(p), &mxnogc_blob); + } catch (PlException &e) { + return e.plThrow(); + } +} + +foreign_t mlMxNewRefGC(term_t in, term_t out) +{ + try { + mxArray *p = term_to_mx(in); + return PL_unify_blob(out, (void **)&p, sizeof(p), &mx_blob); + } catch (PlException &e) { + return e.plThrow(); + } +} + + +/* + * Local Variables: + * c-basic-offset: 2 + * indent-tabs-mode: nil + * End: + */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/contents.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,42 @@ +% *** db :: functions for dealing with MAT file database +% +% The Prolog-Matlab interface module plml requires these functions +% to manage a database of MAT files and a few other tasks. The MAT +% file database (matbase) is stored in the file system under a root +% directory specified by a call to dbroot, eg to specify the +% directory lib/matbase off your home directory on a host named +% 'godzilla' (the host name must be supplied to allow for multiple +% hosts to use the same root directory): +% +% dbroot('~/lib/matbase','godzilla'); +% +% From this point, any calls to dbsave will result in files being +% created under ~/lib/matbase/godzilla. MAT files in the matbase +% are referred to using a locator, which is a string of the form +% +% <host>/<path>/<filename>|<varname> +% +% This refers to a Matlab variable named <varname> in a MAT file +% whos path is constructed using the matbase root, the host name, +% and the <path> component of the locator. Eg, after the above +% dbroot statement, the locator +% +% 'godzilla/d0608/m34521|x' +% +% refers to the MAT file ~/lib/matbase/godzilla/d0608/m34521.mat +% NOTE: this is probably not going to work for Windows users due +% to silly slash vs backslash file separator issues. +% The functions in this directory are: +% +% dbdrop - delete matfile at given locator from matbase +% dbload - Load value from given matbase locator +% dbpath - Return full path of matfile given locator +% dbread - Load values from ASCII file under matbase tree +% dbroot - Set or retrieve Matbase root directory +% dbsave - Save object to MatBase using given name +% dbsaveas - Save object to MatBase using given name +% dbtmp - Save object to matbase under tmp subtree +% typecode - Return basic typing information about a value +% uniquefile - Allocate a unique unused filename +% uniquevar - Allocate a unique unused variable name +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/dbdrop.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,22 @@ +function dbdrop(loc) +% dbdrop - delete matfile at given locator from matbase +% +% dbdrop :: locator(A) -> action unit. +% +% The files containing the specified locators are deleted from +% the file system. If the file pointed to by the locator does +% not exist, a warning is given but the function completes. + +% SA 2008-06 - No longer maps over multiple arguments. + + n=strfind(loc,'|'); + if n>1, matname=loc(1:n-1); else matname=loc; end + fn=fullfile(dbroot,[matname '.mat']); + + if exist(fn,'file') + fprintf('*** DELETING FILE: %s\n', fn); + delete(fn); + else + fprintf('*** Warning: %s does not exist\n', fn); + end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/dbload.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,13 @@ +% dbload - Load value from given matbase locator +% +% dbload :: locator(A) -> A. + +% SA: 2008-06 - much simplified by using functional form of load +function x=dbload(loc) + n=strfind(loc,'|'); + + if n>1, + x=getfield(load(fullfile(dbroot,loc(1:n-1))),loc(n+1:end)); + else + error('dbload:badlocator',sprintf('%s is not a valid locator',loc)); + end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/dbpath.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,8 @@ +function p=dbpath(loc) +% dbpath - Return full path of matfile given locator. +% +% dbpath :: locator(A) -> path. + + n=strfind(loc,'|'); + p=[fullfile(dbroot,loc(1:n-1)) '.mat']; +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/dbread.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,10 @@ +% dbread - Load values from ASCII file under matbase tree. +% +% dbread :: string -> A. +% +% This looks for a file given a path relative to the current +% matbase root as returned by dbroot. It then attempts to +% read it as an ASCII file using READ. + +function x=dbread(file), x=read(fullfile(dbroot,file)); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/dbroot.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,37 @@ +function [p,h]=dbroot(newroot, newhost) +% dbroot - Set or retrieve Matbase root directory +% +% dbroot :: unit -> string ~'current root', string ~'current name'. +% dbroot :: string ~'new root' -> action unit. +% dbroot :: string ~'new root', string ~'new host' -> action unit. +% +% The matbase system uses the root and host name to decided where to +% put the MAT files saved by dbsave, dbsaveas and dbtmp. A directory named +% after the host is created under the given root. Directories based on +% the current date are created under the per-host directories. This means +% the root can be on a shared filesystem as long as each host has a unique +% name. +% +% Note that dbroot MUST be called at least once before the matbase is used +% otherwise the results are undefined. +% +% If no hostname is given, dbroot will attempt to read the HOSTNAME +% environment variable. If this is empty, a error will be raised. + +global DBROOT +global HOSTNAME + +if nargin>=1, + DBROOT=newroot; + if nargin<2, newhost=getenv('HOSTNAME'); end + if isempty(newhost) + error('dbroot:nohostname','Could not get host name'); + end + HOSTNAME=[newhost filesep]; +else + % no input arguments so return current values + p=DBROOT; + h=HOSTNAME; +end + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/dbsave.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,23 @@ +function locator=dbsave(x) +% dbsave - Save object to MatBase using given name +% +% dbsave :: A -> action locator(A). + +% SA: 2008-05-20 - no longer saving file name in mat file + +dt=clock; +[root,host]=dbroot; +dir=[host dirname(dt)]; % makes up a directory name based on the date. +if ~exist(fullfile(root,dir),'dir') + [rc,msg]=mkdir(strrep(root,'~',getenv('HOME')),dir); +end + +fn=uniquefile(dt,root,dir,'m%s.mat'); % make up a filename +save(fullfile(root,fn),'x'); +locator=[removeext(fn),'|x']; + +% make up directory name based on the year and month +function dir=dirname(dt) + dir=sprintf('d%s%s',datestr(dt,'yy'),datestr(dt,'mm')); + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/dbsaveas.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,11 @@ +function locator=dbsaveas(fn,x) +% dbsaveas - Save object to MatBase using given name +% +% dbsaveas :: string, A -> action locator(A). +% +% Note: the file must not already exist; an error is raised if it does. + +fullfn=fullfile(dbroot,[fn,'.mat']); +if exist(fullfn,'file'), error('dbsaveas:file exists'); end +save(fullfn,'x'); +locator=[fn '|x'];
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/dbtmp.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,17 @@ +function locator=dbtmp(x) +% dbtmp - Save object to matbase under tmp subtree +% +% dbtmp :: A -> action locator(A). + +dt=clock; +root=dbroot; +dir='tmp'; +if ~exist(fullfile(root,dir),'dir') + [rc,msg]=mkdir(root,dir); +% if rc==0, error(msg); end +end + +fn=uniquefile(dt,root,dir,'m%s'); % make up a filename +save(fullfile(root,[fn '.mat']),'fn','x'); +locator=[fn '|x']; +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/typecode.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,9 @@ +% typecode - Return basic typing information about a value +% +% typecode :: A -> natural ~'number of elements', bool ~'is numeric', bool ~'is char'. +function [n,isnum,isch]=typecodes(X) + +n=numel(X); +isnum=isnumeric(X); +isch=ischar(X); +%tp=class(X);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/uniquefile.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,21 @@ +function fn=uniquefile(dt,root,dir,pattern) +% uniquefile - Allocate a unique unused filename +% +% uniquefile :: +% [[1,6]] ~'date as returned by clock (currently not used)', +% path ~'implicit root directory', +% path ~'explicit directory relative to implicit root', +% string ~'filepath pattern with exactly one %s somewhere to accept id' +% -> path ~'unique path relative to implicit root'. + +% SA 2005-04-25 Seems that 10000 files per directory is not enough.. +% SA 2008-06-27 Pattern must now contain %s, not %d. + + exists=1; + numpat=sprintf(pattern,'%05d'); + while exists, + fn=fullfile(dir,sprintf(numpat,floor(100000*rand))); + exists=exist(fullfile(root,fn),'file'); + end + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/uniquevar.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,19 @@ +function vn=uniquevar(x) +% uniquevar - Allocate a unique unused variable name +% +% uniquevar :: unit -> action string. +% uniquevar :: A~'initialied value' -> action string. +% +% If no initial value is given the variable is NOT allocated. +% There are up to 100000 variable names available. + + exists=1; + while exists, + vn=sprintf('t_%05d',floor(100000*rand)); + exists=evalin('base',['exist(''',vn,''',''var'')']); + end + if nargin>0, + assignin('base',vn,x); + end + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/db/unknown.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,1 @@ +function x=unknown, x=nan;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/matlab/general/cellmap.m Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,10 @@ +function Y=cellmap(fn,X) +% cellmap - Map a function over a cell array +% +% cellmap :: (A->B, {[Size]->A}) -> {[Size]->B} + +% preallocate to fix size +Y=cell(size(X)); +for i=1:numel(X) + Y{i}=feval(fn,X{i}); +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prolog/Makefile Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,8 @@ + +install: + install -d $(INSTALL_PL_TO) + install $(INSTALL_FLAGS) -m 644 ops.pl $(INSTALL_PL_TO) + install $(INSTALL_FLAGS) -m 644 plml.pl $(INSTALL_PL_TO) + install $(INSTALL_FLAGS) -m 644 utils.pl $(INSTALL_PL_TO) + install $(INSTALL_FLAGS) -m 644 dcgu.pl $(INSTALL_PL_TO) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prolog/dcgu.pl Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,950 @@ +:- module(dcgu, [ + writedcg/1 + + , nop/2 + , out//1 + , (>>)//2 + , (\<)//1 + , (\>)//1 + , (\#)//2 + , run_left//3 + , run_right//3 + , trans//2 + + , maybe//1 + , opt//1 + , if//3, if//2 + , parmap//2, parmap//3, parmap//4, parmap//5, parmap//6 + , seqmap//2, seqmap//3, seqmap//4, seqmap//5, seqmap//6 + , seqmap_n//3, seqmap_n//4, seqmap_n//5 + , seqmap_with_sep//3 + , seqmap_with_sep//4 + , seqmap_with_sep//5 + , seqmap_ints//3 + , seqmap_args//4 + , seqmap_args//5 + , seqmap_args//6 + , iterate//3 + %, apply/4, apply/5 + , seq//1, seq//2, seq_n//3 + , smap//2 + , rep//2, rep_nocopy//2 + , at//1, wr//1, str//1, fmt//2 + , brace//1, paren//1, sqbr//1 + , q//1, qq//1 + , escape//2, escape_with//3 + , null//0, cr//0, sp//0, fs//0 + , fssp/2, tb/2, comma/2, commasp/2 + , padint/5 + + , do_then_call/5 + , do_then_call/6 + , do_then_call/7 + + , any/3, notany/3, arb/2, arbno/3, bal/2 + , span/3, break/3, len/3 + , exhaust/3 + , set/3, get/3, set_with/3 + , with/4, iso/3 + , once/3 + , repeat/2 + , (//)//2 + , until//2 + + , findall//3 + , setof//3 + + , op(900,fy,\<) + , op(900,fy,\>) + , op(900,xfy,\#) + + , lift//1 + , lift//2 + , lift//3 +]). + +/** <module> DCG utilities + +This module contains predicates for working with definite clause +grammars and the related stateful programming style where state +arguments are automatically threaded through sequences +of calls. Some useful DCG procedures are also included. + +When a predicate is declared with type =|foo(...)// is Det|=, +any requirements on the type of the DCG state are hidden, i.e. the +types of the two extra arguments are hidden. In these cases, +the documentation below will sometimes state that the predicate +'runs in the =|S|= DCG'. + +---+++ Types used in this module + +We use the following to denote types of terms that can +be interpreted as DCG phrases with or without further +arguments. + * phrase(S) + If P is a term of type =|phrase(S)|=, then P is a valid DCG phrase + when the DCG state is of type =|S|=, i.e. =|phrase(P,S1,S2)|= is + valid Prolog goal when S1 and S2 are of type =|S|=. N.B. the type + =|phrase(S)|= is almost but not quite equivalent to the binary + predicate type =|pred(S,S)|=. All such predicates are valid phrases, + but phrases involving braces (e.g. {Goal}), commas, semicolons, + and if-then constructs (->) are not equivalent to predicates + with two extra arguments. + * phrase(A,S) + If P is of type =|phrase(A,S)|= and X has type A, then =|call(P,X)|= + is a valid DCG phrase when the DCG is of type S. This type _|is|_ + equivalent to =|pred(A,S,S)|= because the only way to call it + is with call//1 inside a DCG or call/3 outside it. + * phrase(A,B,S) + If P is of type =|phrase(A,B)|= and =|X|= and =|Y|= are of types + =|A|= and =|B|= respectively, then =|call(P,X,Y)|= + is a valid DCG phrase. And so on. You get the idea. + +The type =|pair(A,B)|= will be used to denote the type of terms +with functor (,)/2 and arguments of types =|A|= and =|B|= respectively: +== +pair(A,B) ---> (A,B). +== +This type is used to support a set of general purpose predicates +for combining commands in two distinct DCGs into a single DCG +over a product space of states. +*/ + +:- use_module(library(ops)). + +:- module_transparent seq/3, seq/4, smap/4. + +:- meta_predicate + writedcg(2) + , if(0,0,0,?,?) + , if(0,0,?,?) + , maybe(2,?,?) + , opt(2,?,?) + , once(2,?,?) + , repeat(?,?) + , >>(2,2,?,?) + , //(2,?,?,?) + , \<(2,?,?) + , \>(2,?,?) + , \#(?,2,?,?) + , brace(2,?,?) + , paren(2,?,?) + , sqbr(2,?,?) + , qq(2,?,?) + , q(2,?,?) + , arbno(2,?,?) + , rep(?,2,?,?) + , rep_nocopy(+,2,?,?) + , exhaust(2,?,?) + , with(?,2,?,?) + , iso(2,?,?) + , set_with(1,?,?) + , run_left(2,?,?,?,?) + , run_right(2,?,?,?,?) + , iterate(2,?,?,?,?) + , parmap(1,?,?,?) + , parmap(2,?,?,?,?) + , parmap(3,?,?,?,?,?) + , parmap(4,?,?,?,?,?,?) + , parmap(5,?,?,?,?,?,?,?) + , seqmap(1,?,?,?) + , seqmap(2,?,?,?,?) + , seqmap(3,?,?,?,?,?) + , seqmap(4,?,?,?,?,?,?) + , seqmap(5,?,?,?,?,?,?,?) + , seqmap_n(+,1,?,?,?) + , seqmap_n(+,2,?,?,?,?) + , seqmap_n(+,3,?,?,?,?,?) + , seqmap_ints(1,+,+,?,?) + , seqmap_with_sep(0,1,?,?,?) + , seqmap_with_sep(0,2,?,?,?,?) + , seqmap_with_sep(0,3,?,?,?,?,?) + , seqmap_args(1,+,+,?,?,?) + , seqmap_args(2,+,+,?,?,?,?) + , seqmap_args(3,+,+,?,?,?,?,?) + , do_then_call(0,1,?,?,?) + , do_then_call(0,2,?,?,?,?) + , do_then_call(0,3,?,?,?,?,?) + , until(0,2,?,?) + . + +:- op(900,fy,\<). +:- op(900,fy,\>). +:- op(900,xfy,\#). + + +%%% +%%% The first lot of stuff is completely general for any stateful system. +%%% + + +%% trans( ?Old:S, ?New:S, ?S1:S, ?S2:S) is det. +% +% Unifies Old and New with the states S1 and S2 respectively. +trans(X,Y,X,Y). + +% these will be useful for seq (they define a sort of generalised +% lazy mapping over sequences of DCG terms) +empty([]). +empty(_:[]). +empty(map(_,L)) :- empty(L). +empty(_:map(_,L)) :- empty(L). +empty(M..N) :- N<M. + +singleton([H|T],H) :- empty(T). +singleton(M:[H|T],M:H) :- empty(T). +singleton(map(F,L),call(F,H)) :- singleton(L,H). +singleton(M:map(F,L),call(M:F,H)) :- singleton(L,H). +singleton(M..M,M). + +properlist([H|T],H,T) :- \+empty(T). +properlist(M:[H|T],M:H,M:T) :- \+empty(T). +properlist(map(F,L),call(F,H),map(F,T)) :- properlist(L,H,T). +properlist(M:map(F,L),call(M:F,H),M:map(F,T)) :- properlist(L,H,T). +properlist(M..N,M,M1..N) :- N>M, succ(M,M1). + +%% nop// is det. +% +% Do nothing. (More neutral than []). +nop(X,X). + +%% set(S:A, S1:_, S2:A) is det. +% Set state to S. Implemented by goal expansion. +set(S,_,S). + +%% get(S:A, S1:A, S2:A) is det. +% Get state to S. Implemented by goal expansion. +get(S,S,S). + +%% with(S:A, P:phrase(A), S1:B, S2:B) is nondet. +% +% Run phrase P starting from state S and discarding +% the final state, meanwhile preserving the state +% of the current system, i.e. guarantees S1=S2. +with(S,G) --> {phrase(G,S,_)}. + +%% iso(P:phrase(A), S1:A, S2:A) is nondet. +% +% Run phrase P starting with current state but discarding +% its final state and preserving the current state, so +% that S1=S2. +iso(G) --> get(S), {phrase(G,S,_)}. + +%% set_with(+G:pred(A), S1:_, S2:A) is det. +% +% Set current state using a given callable goal G, which should accept one argument. +% should be of type pred( -S:A), ie it should set S to the new desired +% state, which is installed in the DCG state. +set_with(G,_,S) :- call(G,S). + +%% \<(P:phrase(A), ?S1:pair(A,B), ?S2:pair(A,B)) is nondet. +% +% Apply phrase P to left part of a paired state. +% Implemented by goal expansion so incurs only very small +% speed penalty. +\<(P,(A1,B),(A2,B)) :- phrase(P,A1,A2). + +%% \>(P:phrase(B), ?S1:pair(A,B), ?S2:pair(A,B)) is nondet. +% +% Apply phrase P which must be of type pred(B,B) to right +% part of a paired state. +% Implemented by goal expansion so incurs only very small +% speed penalty. +\>(P,(A,B1),(A,B2)) :- phrase(P,B1,B2). + +%% run_left(P:phrase(pair(A,B)), ?A1:A, ?A2:A, ?B1:B, ?B2:B) is multi. +% +% Applies DCG phrase P to state formed by pairing A1 and A2 with +% current DCG states B1 and B2. Phrase can use (\<) to access the +% A state and (\>) to access the underlying B state. +run_left(P,S1,S2,T1,T2) :- phrase(P,(S1,T1),(S2,T2)). + +%% run_right(P:phrase(pair(A,B)), ?B1:B, ?B2:B, ?A1:A, ?A2:A) is multi. +% +% Applies DCG phrase P to state formed by pairing A1 and A2 with +% current DCG states B1 and B2. Phrase can use (\<) to access the +% A state and (\>) to access the underlying B state. +run_right(P,S1,S2,T1,T2) :- phrase(P,(T1,S1),(T2,S2)). + +%% \#(N:natural, P:phrase(A), ?S1, ?S2) is nondet. +% +% Apply phrase P to the Nth argument of state which must +% be a compound term (with arbitrary functor), with the +% Nth argument of type A. +\#(N, P, S1, S2) :- with_nth_arg(N,P,S1,S2). + + +system:goal_expansion(run_left(P,S1,S2,T1,T2), phrase(P,(S1,T1),(S2,T2))). +system:goal_expansion(run_right(P,S1,S2,T1,T2), phrase(P,(T1,S1),(T2,S2))). +system:goal_expansion( \<(P,S1,S2), (S1=(L1,R),S2=(L2,R),phrase(P,L1,L2)) ). +system:goal_expansion( \>(P,S1,S2), (S1=(L,R1),S2=(L,R2),phrase(P,R1,R2)) ). +system:goal_expansion( nop(S1,S2), (S1=S2) ). +system:goal_expansion( out(X,S1,S2), (S1=[X|S2]) ). +system:goal_expansion( get(S,S1,S2), (S=S1,S1=S2) ). +system:goal_expansion( set(S,_,S2), (S=S2) ). +system:goal_expansion( A >> B, (A,B) ). +system:goal_expansion( set_with(C,_,S2), Call) :- mk_call(C,[S2],Call). +system:goal_expansion( trans(A1,A2,S1,S2), (S1=A1,S2=A2) ). + +mk_call(C,XX,Call) :- var(C), !, mk_call(call(C),XX,Call). +mk_call(M:C,XX,M:Call) :- !, mk_call(C,XX,Call). +mk_call(C,XX,Call) :- C =.. CL, append(CL,XX,CL2), Call =.. CL2. + + +%% pushl(S:A,S1:B,S2:pair(A,B)) is det. +% Create a paired state by putting S on the left and the +% old state on the right. +pushl(S,S0,(S,S0)). + +%% pushr(S:A,S1:B,S2:pair(B,A)) is det. +% Create a paired state by putting S on the right and the +% old state on the left. +pushr(S,S0,(S0,S)). + +%% popl(S:A,S1:pair(A,B),S2:B) is det. +% Unpair state by removing left state and unifying it with S. +popl(S,(S,S0),S0). + +%% popr(S:A,S1:(B,A),S2:B) is det. +% Unpair state by removing right state and unifying it with S. +popr(S,(S0,S),S0). + +%% >>(G1:phrase(S), G2:phrase(S))// is nondet. +% Sequential conjuction of phrases G1 and G2, equivalent to (G1,G2), +% but sometimes more convenient in terms of operator priorities. +% Implemented by goal expansion. +A >> B --> A, B. + +%% once(G:phrase(_))// is semidet. +% Call DCG phrase G succeeding at most once. +once(G,A,B) :- once(phrase(G,A,B)). + +%% repeat// is nondet. +% Create an infinite number of choice points. +repeat(A,A) :- repeat. + +%% maybe(P:phrase(_))// is det. +% Try P, if it fails, then do nothing. If it succeeds, +% cut choicepoints and continue. +maybe(P) --> P -> nop; nop. + +%% opt(P:phrase(_))// is nondet. +% P or nothing. Like maybe but does not cut if P succeeds. +opt(P) --> P; nop. + +%% if(G:pred,P,Q)// is det. +%% if(G:pred,P)// is det. +% +% If Prolog goal =|call(G)|= succeeds, do P, otherwise, do Q. +% if(G,P) is equivalent to if(G,P,nop), i.e. does nothing +% if P fails. +if(A,B,C) --> {nonvar(A), call(A)} -> B; C. +if(A,B) --> if(A,B,nop). + + +%% exhaust( P:phrase(_))// is det. +% +% Run phrase sequentially as many times as possible until it fails. +% Any choice points left by G are cut. +exhaust(G) --> G -> exhaust(G); nop. + + +%% until( +Q:pred, +P:phrase(_))// is det. +% +% Repeatedly call phrase P and test ordinary Prolog goal +% Q until Q fails. P and Q are copied together before each +% iteration, so variables can be shared between them, but +% are not shared between iterations. +until( Pred, Op) --> + {copy_term(Pred/Op,Pred1/Op1)}, + call(Op1), + ( {call(Pred1)} + -> {Pred/Op=Pred1/Op1} + ; until(Pred, Op) + ). + +%% iterate( +P:phrase(A,A,S), +X:A, -Y:A)// is nondet. +% +% Sequentially call P zero or more times, passing in X on +% the first call and threading the result through subsequent calls, +% (as well as threading the DCG state in the normal way) +% ending in Y. + +iterate(_,A,A) --> []. +iterate(F,A1,A3) --> call(F,A1,A2), iterate(F,A2,A3). + + +%% rep( +N:natural, +P:phrase(_))// is nondet. +%% rep( -N:natural, +P:phrase(_))// is nondet. +% +% Equivalent to N sequential copies of phrase P. +% Free variables in P are *not* shared between copies. +% If N is unbound on entry, rep//2 is _cautious_: it tries +% gradually increasing N from 0 on backtracking. + +rep(N,G,S1,S2) :- + ( var(N) + -> rep_var(N,G,S1,S2) + ; rep_nonvar(N,G,S1,S2) + ). + +rep_var(0,_,S,S). +rep_var(N,G,S1,S3) :- + copy_term(G,G1), phrase(G1,S1,S2), + rep_var(M,G,S2,S3), succ(M,N). + +rep_nonvar(0,_,S,S) :- !. +rep_nonvar(N,G,S1,S3) :- + copy_term(G,G1), phrase(G1,S1,S2), + succ(M,N), rep_nonvar(M,G,S2,S3). + + +%% rep_nocopy( +N:natural, +P:phrase(_))// is nondet. +% +% Like rep//2 but does not copy P before calling, so +% any variables in P are shared between all calls. +% Also, N cannot be a variable in this implementation. +rep_nocopy(0,_) --> !. +rep_nocopy(N,P) --> call(P), {succ(M,N)}, rep_nocopy(M,P). + + +%% seq( +L:plist, +Sep)// is nondet. +%% seq( +L:plist)// is nondet. +% Sequence list of phrases with separator. L can be a sort of _generalised_ +% list of phrases, which can be: +% == +% plist ---> list(A) % ordinary list +% ; map(phrase(B),plist) % map phrase head P over list +% . +% == +% Sep is inserted strictly betweened elements of L. seq(L) is equivalent +% to seq(L,nop). + +seq(L,_) --> {dcgu:empty(L)}. +seq(L,_) --> {dcgu:singleton(L,H)}, H. +seq(L,S) --> {dcgu:properlist(L,H,T)}, H, S, seq(T,S). +seq(L) --> seq(L,nop). % if no separator specified, use nop. + + +%% seq_n( N:natural, +L:plist, +Sep)// is nondet. +% Sequence list of phrases with separator and counting. +% +% @see seq//2. + +seq_n(0,L,_) --> {dcgu:empty(L)}. +seq_n(1,L,_) --> {dcgu:singleton(L,H)}, H. +seq_n(N,L,S) --> {dcgu:properlist(L,H,T)}, H, S, seq_n(M,T,S), {succ(M,N)}. + +%% smap(+F,+L:list)// is nondet. +% Equivalent to seq(map(F,L),nop). +smap(F,L) --> seq(map(F,L),nop). + + + +%% seqmap( +P:phrase(A,S), X:list(A))// is nondet. +%% seqmap( +P:phrase(A,B,S), X:list(A), Y:list(B))// is nondet. +%% seqmap( +P:phrase(A,B,C,S), X:list(A), Y:list(B), Z:list(C))// is nondet. +%% seqmap( +P:phrase(A,B,C,D,S), X:list(A), Y:list(B), Z:list(C), W:list(D))// is nondet. +%% seqmap( +P:phrase(A,B,C,D,E,S), X:list(A), Y:list(B), Z:list(C), W:list(D), V:list(E))// is nondet. +% +% seqmap//N is like maplist/N except that P is an incomplete _phrase_ +% rather an ordinary goal, which is applied to the elements of the supplied +% lists _|in order|_, while threading the DCG state correctly through all +% the calls. +% +% seqmap//N is very powerful - it is like =foldl= and =mapaccum= in functional +% languages, but with the added flexibility of bidirectional Prolog variables. +% +% @see maplist/2. + +seqmap(_,[]) --> []. +seqmap(P,[A|AX]) --> call(P,A), seqmap(P,AX). +seqmap(_,[],[]) --> []. +seqmap(P,[A|AX],[B|BX]) --> call(P,A,B), seqmap(P,AX,BX). +seqmap(_,[],[],[]) --> []. +seqmap(P,[A|AX],[B|BX],[C|CX]) --> call(P,A,B,C), seqmap(P,AX,BX,CX). +seqmap(_,[],[],[],[]) --> []. +seqmap(P,[A|AX],[B|BX],[C|CX],[D|DX]) --> call(P,A,B,C,D), seqmap(P,AX,BX,CX,DX). +seqmap(_,[],[],[],[],[]) --> []. +seqmap(P,[A|AX],[B|BX],[C|CX],[D|DX],[E|EX]) --> call(P,A,B,C,D,E), seqmap(P,AX,BX,CX,DX,EX). + +true(_,_). +parmap(_,[]) --> true. +parmap(P,[A|AX]) --> call(P,A) // parmap(P,AX). +parmap(_,[],[]) --> true. +parmap(P,[A|AX],[B|BX]) --> call(P,A,B) // parmap(P,AX,BX). +parmap(_,[],[],[]) --> true. +parmap(P,[A|AX],[B|BX],[C|CX]) --> call(P,A,B,C) // parmap(P,AX,BX,CX). +parmap(_,[],[],[],[]) --> true. +parmap(P,[A|AX],[B|BX],[C|CX],[D|DX]) --> call(P,A,B,C,D) // parmap(P,AX,BX,CX,DX). +parmap(_,[],[],[],[],[]) --> true. +parmap(P,[A|AX],[B|BX],[C|CX],[D|DX],[E|EX]) --> call(P,A,B,C,D,E) // parmap(P,AX,BX,CX,DX,EX). + +%% seqmap_n( +N:natural, +P:phrase(A), X:list(A))// is nondet. +%% seqmap_n( +N:natural, +P:phrase(A,B), X:list(A), Y:list(B))// is nondet. +%% seqmap_n( +N:natural, +P:phrase(A,B,C), X:list(A), Y:list(B), Z:list(C))// is nondet. +% +% seqmap_n//.. is like seqmap/N except that the lists of arguments are of lenght N. + +seqmap_n(0,_,[]) --> []. +seqmap_n(N,P,[A|AX]) --> {succ(M,N)}, call(P,A), seqmap_n(M,P,AX). +seqmap_n(0,_,[],[]) --> []. +seqmap_n(N,P,[A|AX],[B|BX]) --> {succ(M,N)}, call(P,A,B), seqmap_n(M,P,AX,BX). +seqmap_n(0,_,[],[],[]) --> []. +seqmap_n(N,P,[A|AX],[B|BX],[C|CX]) --> {succ(M,N)}, call(P,A,B,C), seqmap_n(M,P,AX,BX,CX). + + +/* + * Goal expansions + */ + +cons(A,B,[A|B]). + +expand_seqmap_with_prefix(Sep0, Callable0, SeqmapArgs, Goal) :- + ( Callable0 = M:Callable + -> NextGoal = M:NextCall + ; Callable = Callable0, + NextGoal = NextCall + ), + + append(Lists, [St1,St2], SeqmapArgs), + + Callable =.. [Pred|Args], + length(Args, Argc), + length(Argv, Argc), + length(Lists, N), + length(Vars, N), + MapArity is N + 4, + format(atom(AuxName), '__aux_seqmap/~d_~w_~w+~d', [MapArity, Sep0, Pred, Argc]), + build_term(AuxName, Lists, Args, St1, St2, Goal), + + AuxArity is N+Argc+2, + prolog_load_context(module, Module), + ( current_predicate(Module:AuxName/AuxArity) + -> true + ; rep(N,[[]],BaseLists,[]), + length(Anon, Argc), + build_term(AuxName, BaseLists, Anon, S0, S0, BaseClause), + + length(Vars,N), + maplist(cons, Vars, Tails, NextArgs), + ( Sep0=_:Sep -> true; Sep=Sep0 ), + ( is_list(Sep) -> append(Sep,S2,S1), NextThing=NextGoal + ; build_term(phrase, [Sep0], [], S1, S2, NextSep), + NextThing = (NextSep,NextGoal) + ), + build_term(Pred, Argv, Vars, S2, S3, NextCall1), + build_term(AuxName, Tails, Argv, S3, S4, NextIterate), + build_term(AuxName, NextArgs, Argv, S1, S4, NextHead), + + ( goal_expansion(NextCall1,NextCall) -> true + ; NextCall1=NextCall), + + NextClause = (NextHead :- NextThing, NextIterate), + + ( predicate_property(Module:NextGoal, transparent) + -> compile_aux_clauses([ (:- module_transparent(Module:AuxName/AuxArity)), + BaseClause, + NextClause + ]) + ; compile_aux_clauses([BaseClause, NextClause]) + ) + ). + +expand_call_with_prefix(Sep0, Callable0, InArgs, (SepGoal,CallGoal)) :- + append(CallArgs, [S1,S3], InArgs), + + ( Sep0=_:Sep -> true; Sep=Sep0 ), + ( is_list(Sep) -> append(Sep,S2,SS), SepGoal=(S1=SS) + ; build_term(phrase, [Sep0], [], S1, S2, SepGoal) + ), + + ( var(Callable0) + -> build_term(call,[Callable0], CallArgs, S2, S3, CallGoal1) + ; ( Callable0 = M:Callable + -> CallGoal1 = M:NextCall + ; Callable = Callable0, + CallGoal1 = NextCall + ), + Callable =.. [Pred|Args], + build_term(Pred, Args, CallArgs, S2, S3, NextCall) + ), + ( goal_expansion(CallGoal1,CallGoal) -> true + ; CallGoal1=CallGoal + ). + +seqmap_with_sep_first_call(P,[A1|AX],AX) --> call(P,A1). +seqmap_with_sep_first_call(P,[A1|AX],[B1|BX],AX,BX) --> call(P,A1,B1). +seqmap_with_sep_first_call(P,[A1|AX],[B1|BX],[C1|CX],AX,BX,CX) --> call(P,A1,B1,C1). + +expand_seqmap_with_sep(Sep, Pred, SeqmapArgs, (dcgu:FirstCall,dcgu:SeqmapCall)) :- + prolog_load_context(module,Context), + (Sep=SMod:Sep1 -> true; SMod=Context, Sep1=Sep), + (Pred=CMod:Pred1 -> true; CMod=Context, Pred1=Pred), + append(Lists, [St1,St3], SeqmapArgs), + length(Lists, N), + length(Tails, N), + build_term(seqmap_with_sep_first_call, [CMod:Pred1|Lists], Tails, St1, St2, FirstCall), + build_term(seqmap_with_prefix, [SMod:Sep1,CMod:Pred1], Tails, St2, St3, SeqmapCall). + +build_term(H,L1,L2,S1,S2,Term) :- + append(L2,[S1,S2],L23), + append(L1,L23,L123), + Term =.. [H | L123]. + + +expand_dcgu(Term, Goal) :- + functor(Term, seqmap, N), N >= 4, + Term =.. [seqmap, Callable | Args], + callable(Callable), !, + expand_seqmap_with_prefix([],Callable, Args, Goal). + +expand_dcgu(Term, Goal) :- + functor(Term, seqmap_with_sep, N), N >= 5, + Term =.. [seqmap_with_sep, Sep, Callable | Args], + nonvar(Sep), callable(Callable), !, + expand_seqmap_with_sep(Sep, Callable, Args, Goal). + +expand_dcgu(Term, Goal) :- + functor(Term, seqmap_with_prefix, N), N >= 5, + Term =.. [seqmap_with_prefix, Sep, Callable | Args], + callable(Callable), nonvar(Sep), !, + expand_seqmap_with_prefix(Sep, Callable, Args, Goal). + +expand_dcgu(Term, Goal) :- + functor(Term, do_then_call, N), N >= 2, + Term =.. [do_then_call, Prefix, Callable | Args], + nonvar(Prefix), !, + expand_call_with_prefix(Prefix, Callable, Args, Goal). + +system:goal_expansion(GoalIn, GoalOut) :- + \+current_prolog_flag(xref, true), + expand_dcgu(GoalIn, GoalOut). +% prolog_load_context(module,Mod), +% writeln(expanded(Mod:GoalIn)). + + +%% seqmap_with_sep(+S:phrase, +P:phrase(A), X:list(A))// is nondet. +%% seqmap_with_sep(+S:phrase, +P:phrase(A,B), X:list(A), Y:list(B))// is nondet. +%% seqmap_with_sep(+S:phrase, +P:phrase(A,B,C), X:list(A), Y:list(B), Z:list(C))// is nondet. +% +% As seqmap//2.. but inserting the separator phrase S between each call to P. +% NB: *Fails* for empty lists. +% +% @see seqmap//2 +%seqmap_with_sep(S,P,[A|AX]) --> call(P,A), seqmap_with_prefix(S,P,AX). +%seqmap_with_sep(S,P,[A|AX],[B|BX]) --> call(P,A,B), seqmap_with_prefix(S,P,AX,BX). +%seqmap_with_sep(S,P,[A|AX],[B|BX],[C|CX]) --> call(P,A,B,C), seqmap_with_prefix(S,P,AX,BX,CX). +seqmap_with_sep(S,P,[A|AX]) --> call(P,A), seqmap(do_then_call(S,P),AX). +seqmap_with_sep(S,P,[A|AX],[B|BX]) --> call(P,A,B), seqmap(do_then_call(S,P),AX,BX). +seqmap_with_sep(S,P,[A|AX],[B|BX],[C|CX]) --> call(P,A,B,C), seqmap(do_then_call(S,P),AX,BX,CX). + +%seqmap_with_prefix(_,_,[]) --> []. +%seqmap_with_prefix(S,P,[A|AX]) --> S, call(P,A), seqmap_with_prefix(S,P,AX). +%seqmap_with_prefix(_,_,[],[]) --> []. +%seqmap_with_prefix(S,P,[A|AX],[B|BX]) --> S, call(P,A,B), seqmap_with_prefix(S,P,AX,BX). +%seqmap_with_prefix(_,_,[],[],[]) --> []. +%seqmap_with_prefix(S,P,[A|AX],[B|BX],[C|CX]) --> S, call(P,A,B,C), seqmap_with_prefix(S,P,AX,BX,CX). + + +% do_then_call( +S:phrase, +P:phrase(A), X:A)// is nondet. +% do_then_call( +S:phrase, +P:phrase(A,B), X:A, Y:B)// is nondet. +% do_then_call( +S:phrase, +P:phrase(A,B,C), X:A, Y:B, Z:C)// is nondet. +% +% Call phrase S, then call phrase P with arguments A, B, C etc. +do_then_call(S,P,A) --> S, call(P,A). +do_then_call(S,P,A,B) --> S, call(P,A,B). +do_then_call(S,P,A,B,C) --> S, call(P,A,B,C). + + +%% seqmap_ints( +P:phrase(integer), +I:integer, +J:integer)// is nondet. +% +% Equivalent to seqmap(P) applied to the list of integers from I to J inclusive. +% +% @see seqmap//2. +seqmap_ints(P,L,N) --> + ( {L>N} -> [] + ; {M is L+1}, call(P,L), seqmap_ints(P,M,N) + ). + + +%% seqmap_args( +P:phrase(integer), +I:integer, +J:integer, X:term)// is nondet. +%% seqmap_args( +P:phrase(integer), +I:integer, +J:integer, X:term, Y:term)// is nondet. +%% seqmap_args( +P:phrase(integer), +I:integer, +J:integer, X:term, Y:term, Z:term)// is nondet. +% +% Like seqmap//N, but applied to the arguments of term X, Y and Z, from the I th to the +% J th inclusive. +% +% @see seqmap//2. + +seqmap_args(P,L,N,A) --> + ( {L>N} -> [] + ; {succ(L,M), arg(L,A,AA)}, + call(P,AA), seqmap_args(P,M,N,A) + ). + +seqmap_args(P,L,N,A,B) --> + ( {L>N} -> [] + ; {succ(L,M), arg(L,A,AA), arg(L,B,BB)}, + call(P,AA,BB), seqmap_args(P,M,N,A,B) + ). + +seqmap_args(P,L,N,A,B,C) --> + ( {L>N} -> [] + ; {succ(L,M), arg(L,A,AA), arg(L,B,BB), arg(L,C,CC)}, + call(P,AA,BB,CC), seqmap_args(P,M,N,A,B,C) + ). + + + +%%% ------------------------------------------------------------------ +%%% These are for sequence building DCGs. +%%% ------------------------------------------------------------------ + + + +%% out(?X)// is det. +% +% Equivalent to [X]. prepends X to the difference list represented by +% the DCG state variables. +out(L,[L|L0],L0). + + +% SNOBOL4ish rules +% +% Others: +% maxarb +% pos rpos +% tab rtab +% rem + + +%% any(+L:list(_))// is nondet. +% Matches any element of L. +any(L) --> [X], {member(X,L)}. + +%% notany(+L:list(_))// is nondet. +% Matches anything not in L. +notany(L) --> [X], {maplist(dif(X),L)}. + +%% arb// is nondet. +% Matches an arbitrary sequence. Proceeds cautiously. +arb --> []; [_], arb. + +%% arbno(+P:phrase)// is nondet. +% Matches an arbitrary number of P. Proceeds cautiously. +% Any variables in P are shared across calls. +arbno(P) --> []; P, arbno(P). + +%% bal// is nondet. +% Matches any expression with balanced parentheses. +bal --> balexp, arbno(balexp). +balexp --> "(", bal, ")". +balexp --> notany("()"). + +%% span(+L:list(_))// is nondet. +% Matches the longest possible sequence of symbols from L. +span(L,A,[]) :- any(L,A,[]). +span(L) --> any(L), span(L). +span(L), [N] --> any(L), [N], { maplist( dif( N), L) }. + +%% break(+L:list(_))// is nondet. +% Matches the longest possible sequence of symbols not in L. +break(L,A,[]) :- notany(L,A,[]). +break(L) --> notany(L), break(L). +break(L), [N] --> notany(L), [N], { member(N,L) }. + +%% len(N:natural)// is nondet. +% Matches any N symbols. +len(0) --> []. +len(N) --> [_], ({var(N)} -> len(M), {succ(M,N)}; {succ(M,N)}, len(M)). + + +%% //(+P:phrase(A), ?C:list(A), ?S1:list(A), ?S2:list(A)) is nondet. +%% //(+P:phrase(A), +C:phrase(A), ?S1:list(A), ?S2:list(A)) is nondet. +% +% Sequence capture operator - captures the matching sequence C of any +% phrase P, eg. +% == +% ?- phrase(paren(arb)//C,"(hello)world",_) +% C = "(hello)". +% true +% == +% If nonvar(C) and C is a phrase, it is called after calling P. + +//(H,C,L,T) :- + ( var(C) + -> phrase(H,L,T), append(C,T,L) + ; phrase(H,L,T), phrase(C,L,T) + ). + +%%% ------------------------------------------------------------------ +%%% These are for character sequences DCGs. + +%% writedcg(+P:phrase) is nondet. +% +% Run the phrase P, which must be a standard list-of-codes DCG, +% and print the output. +writedcg(Phrase) :- + phrase(Phrase,Codes), + format('~s',[Codes]). + +%% null// is det. +% Empty string. +null --> "". + +%% cr// is det. +% Carriage return "\n". +cr --> "\n". + +%% sp// is det. +% Space " ". +sp --> " ". + +%% fs// is det. +% Full stop (period) ".". +fs --> ".". + +%% fssp// is det. +% Full stop (period) followed by space. +fssp --> ". ". + +%% tb// is det. +% Tab "\t". +tb --> "\t". + +%% comma// is det. +% Comma ",". +comma --> ",". + +%% commasp// is det. +% Comma and space ", ". +commasp --> ", ". + +%% at(X:atom)// is det. +% Generate code list for textual representation of atom X. +at(A,C,T) :- atomic(A), with_output_to(codes(C,T),write(A)). + +%% wr(X:term)// is det. +% Generate the list of codes for term X, as produced by write/1. +wr(X,C,T) :- ground(X), with_output_to(codes(C,T),write(X)). + +%% wq(X:term)// is det. +% Generate the list of codes for term X, as produced by writeq/1. +wq(X,C,T) :- ground(X), with_output_to(codes(C,T),writeq(X)). + +%% str(X:term)// is det. +% Generate the list of codes for string X, as produced by writeq/1. +str(X,C,T):- string(X), with_output_to(codes(C,T),write(X)). + +%% fmt(+F:atom,+Args:list)// is det +% Generate list of codes using format/3. +fmt(F,A,C,T) :- format(codes(C,T),F,A). + +%% brace(P:phrase)// is nondet. +% Generate "{" before and "}" after the phrase P. +brace(A) --> "{", A, "}". + +%% paren(P:phrase)// is nondet. +% Generate "(" before and ")" after the phrase P. +paren(A) --> "(", A, ")". + +%% sqbr(P:phrase)// is nondet. +% Generate "[" before and "]" after the phrase P. +sqbr(A) --> "[", A, "]". + +%% q(P:phrase(list(code)))// is nondet. +% Generate list of codes from phrase P, surrounds it with single quotes, +% and escapes (by doubling up) any internal quotes so that the +% generated string is a valid quoted string. Must be list of codes DCG. +q(X,[39|C],T) :- T1=[39|T], escape_with(39,39,X,C,T1). % 39 is ' + +%% qq(P:phrase(list(code)))// is nondet. +% Generate list of codes from phrase P, surrounds it with double quotes, +% and escapes (by doubling up) any double quotes so that the +% generated string is a valid double quoted string. +qq(X,[34|C],T) :- T1=[34|T], escape_with(34,34,X,C,T1). % 34 is " + +% escape difference list of codes with given escape character +escape_codes(_,_,A,A,A). +escape_codes(E,Q,[Q|X],[E,Q|Y],T) :-escape_codes(E,Q,X,Y,T). +escape_codes(E,Q,[A|X],[A|Y],T) :- Q\=A, escape_codes(E,Q,X,Y,T). + +%% escape_with(E:C, Q:C, P:phrase(list(C)))// is nondet. +% +% Runs phrase P to generate a list of elements of type C and +% then escapes any occurrences of Q by prefixing them with E, e.g., +% =|escape_with(92,39,"some 'text' here")|= escapes the single quotes +% with backslashes, yielding =|"some \'text\' here"|=. +escape_with(E,Q,Phrase,L1,L2) :- + phrase(Phrase,L0,L2), + escape_codes(E,Q,L0,L1,L2). + +%% escape(Q:C, P:phrase(list(C)))// is nondet. +% +% Runs phrase P to generate a list of elements of type C and +% then escapes any occurrences of Q by doubling them up, e.g., +% =|escape(39,"some 'text' here")|= doubles up the single quotes +% yielding =|"some ''text'' here"|=. +escape(Q,A) --> escape_with(Q,Q,A). + +%% padint( +N:integer, +Range, +X:integer)// is nondet. +% +% Write integer X padded with zeros ("0") to width N. +padint(N,L..H,X,C,T) :- + between(L,H,X), + format(atom(Format),'~~`0t~~d~~~d|',[N]), + format(codes(C,T),Format,[X]). + +difflength(A-B,N) :- unify_with_occurs_check(A,B) -> N=0; A=[_|T], difflength(T-B,M), succ(M,N). + +% tail recursive version +difflength_x(A-B,M) :- difflength_x(A-B,0,M). +difflength_x(A-B,M,M) :- unify_with_occurs_check(A,B). +difflength_x([_|T]-A,M,N) :- succ(M,L), difflength_x(T-A,L,N). + + +%term_codes(T,C) :- with_output_to(codes(C),write(T)). + + + + +% try these? +%setof(X,Q,XS,S1,S2) :- setof(X,phrase(Q,S1,S2),XS). +%findall(X,Q,XS,S1,S2) :- findall(X,phrase(Q,S1,S2),XS). + +with_nth_arg(K,P,T1,T2) :- + functor(T1,F,N), + functor(T2,F,N), + with_nth_arg(N,K,P,T1,T2). + +with_nth_arg(K,K,P,T1,T2) :- + arg(K,T1,C1), phrase(P,C1,C2), + arg(K,T2,C2), succ(N,K), + copy_args(N,T1,T2). + +with_nth_arg(N,K,P,T1,T2) :- + arg(N,T1,C), + arg(N,T2,C), + succ(M,N), + with_nth_arg(M,K,P,T1,T2). + +copy_args(0,_,_) :- !. +copy_args(N,T1,T2) :- + succ(M,N), arg(N,T1,X), arg(N,T2,X), + copy_args(M,T1,T2). + + +%% setof( Template:X, Phrase:phrase(S), Results:list(X), S1:S, S2:S) is nondet. +setof(X,Q,XS,S1,S2) :- setof(X,phrase(Q,S1,S2),XS). + +%% findall( Template:X, Phrase:phrase(S), Results:list(X), S1:S, S2:S) is nondet. +findall(X,Q,XS,S1,S2) :- findall(X,phrase(Q,S1,S2),XS). + + +:- meta_predicate lift(0,?,?), lift(1,?,?), lift(2,?,?). + +lift(P) --> { call(P) }. +lift(P,X) --> { call(P,X) }. +lift(P,X,Y) --> { call(P,X,Y) }. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prolog/ops.pl Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,62 @@ + +:- module(ops,[ + op(550,xfx,..) % range of integers + , op(550,xfx,--) % closed real interval + , op(600,xfx,--\) % half open real interval + , op(1100,xfx,:<:) % subtype declaration + , op(1100,xfx,::=) % definition + , op(1100,xfx,:=:) % type equivalence/definition + , op(1100,xfx,::) % declaration + , op(1100,xfx,<-) % element of, instance etc. + %, op(1050,yfx,¬) % restriction, eg real¬integer means fractional + , op(750,xfy,\\) % lambda abdstraction + , op(400,xfy,\) % reverse matrix division + , op(800,xfx,~) % for annotations + , op(900,fy,struct) + , op(900,fy,options) + , op(100,yfx,@) % used for signal@rate... + , op(200,yfx,++) % used for sequential composition + , op(700,xfx,in) + , op(150,yfx,`) % function application + , op(100,yfx,/) + %, op(400,xfy,>>) % monad sequencing NB: standard prolog has yfx not xfy + %, op(400,xfy,>>=) % monad bind + , op(1050,xfy,>>) % monad sequencing NB: standard prolog has yfx not xfy + , op(1050,xfy,>>=) % monad bind + , op(100,fx,'<?>') + , op(800,xfx,'</>') + , op(100,fx,?) + ]). + +/** <module> - Operator declarations + +This module consists entirely of operator declarations, as follows: +== +op(550,xfx,..). +op(550,xfx,--). +op(600,xfx,--\). +op(1100,xfx,:<:). +op(1100,xfx,::=). +op(1100,xfx,:=:). +op(1100,xfx,::). +op(1100,xfx,<-). +op(750,xfy,\\). +op(400,xfy,\). +op(800,xfx,~). +op(900,fy,maybe). +op(900,fy,struct). +op(900,fy,options). +op(100,yfx,@). +op(200,yfx,++). +op(700,xfx,in). +op(150,yfx,`). +op(100,yfx,/). +op(1050,xfy,>>). +op(1050,xfy,>>=). +op(100,fx,'<?>'). +op(800,xfx,'</>'). +op(100,fx,?). +== + +@author Samer Abdallah +*/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prolog/plml.pl Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,969 @@ +/* + * Prolog part of Prolog-Matlab interface + * Version 2 + * + * Samer Abdallah (2004-2012) + * Centre for Digital Music, QMUl. + */ + +:- module(plml, + [ ml_open/1 % (+Id) + , ml_open/2 % (+Id, +Host) + , ml_open/3 % (+Id, +Host, +Options) + , ml_close/1 % (+Id) + + , ml_exec/2 % (+Id, +Expr) + , ml_eval/4 % (+Id, +Expr, +Types, -Vals) + , ml_test/2 % (+Id, +Expr) + + , (??)/1 % (+Expr) ~execute Matlab expression + , (???)/1 % (+Expr) ~test Matlab boolean expression + , (===)/2 % (-Vals,+Expr) ~evaluate Matlab expression + + , term_mlstring/3 % (+Id, +Expr, -String) ~Prolog term to Matlab string + , term_texatom/2 % (+Expr, -Atom) ~Prolog term to TeX expression + , ml_debug/1 % (+Bool) + , wsvar/3 % (+WSBlob, -Name, -Id) + + % MATBASE + , persist_item/2 % (+Expr,-Expr) ~ convert volatile subterms to persistent form + , matbase_mat/2 % (+Dir, -Loc) ~ Find matbase MAT files + , dropmat/2 % (+Id, +Loc) ~ remove MAT file from matbase + , exportmat/3 % (+Id, +Loc, +Dir) ~ export MAT file from matbase + + + % Utilities + , compileoptions/2 + , multiplot/2 + , mhelp/1 + + , op(650,fy,`) % quoting things + , op(160,xf,``) % postfix transpose operator + , op(100,fy,@) % function handles + + % note slightly reduced precedence of array operators - + % hope this doesn't break everything... + , op(210,xfy,.^) % array exponentiation + , op(410,yfx,.*) % array times + , op(410,yfx,./) % array division + , op(410,xfy,.\) % reverse array division + , op(400,xfy,\) % reverse matrix division + , op(700,xfx,===) % variable binding/assignment in matlab query + , op(700,xfx,:==) % variable binding/assignment in matlab query + , op(951,fx,??) % evaluate term as matlab + , op(951,fx,???) % evaluate term as matlab boolean + , op(100,yfx,#) % field indexing (note left-associativity) + , op(750,fy,\\) % thunk abdstraction + , op(750,xfy,\\) % lambda abdstraction + + % exported after being imported from ops + , op(1100,xfx,::) % type specification (esp for arrays) + ]). + + +:- multifile(user:optionset/2). +:- multifile(user:matlab_path/2). +:- multifile(user:matlab_init/2). +:- multifile(user:pl2ml_hook/2). + + +/** <module> Prolog-Matlab interface + + ---++++ Types + + *|ml_eng|* - Any atom identifying a Matlab engine. + + *|ml_stmt|* - A Matlab statement + == + X;Y :: ml_stmt :- X:ml_stmt, Y:ml_stmt. + X,Y :: ml_stmt :- X:ml_stmt, Y:ml_stmt. + X=Y :: ml_stmt :- X:ml_lval, Y:ml_expr. + hide(X) :: ml_stmt :- X:ml_stmt. + == + + == + ml_expr(A) % A Matlab expression, possibly with multiple return values + ml_loc ---> mat(atom,atom). % Matbase locator + == + + ---++++ Matlab expression syntax + + The Matlab expression syntax adopted by this module allows Prolog terms to represent + or denote Matlab expressions. Let T be the domain of recognised Prolog terms (corresponding to + the type ml_expr), and M be the domain of Matlab expressions written in Matlab syntax. + Then V : T->M is the valuation function which maps Prolog term X to Matlab expression V[X]. + These are some of the constructs it recognises: + + Constructs valid only in top level statements, not subexpressions: + == + X;Y % |--> V[X]; V[Y] (sequential evaluation hiding first result) + X,Y % |--> V[X], V[Y] (sequential evaluation displaying first result) + X=Y % |--> V[X]=V[Y] (assignment, X must denote a valid left-value) + hide(X) % |--> V[X]; (execute X but hide return value) + == + + Things that look and work like Matlab syntax (more or less): + == + +X % |--> uplus(V[X]) + -X % |--> uminus(V[X]) + X+Y % |--> plus(V[X],V[Y]) + X-Y % |--> minus(V[X],V[Y]) + X^Y % |--> mpower(V[X],V[Y]) + X*Y % |--> mtimes(V[X],V[Y]) + X/Y % |--> mrdivide(V[X],V[Y]) + X\Y % |--> mldivide(V[X],V[Y]) + X.^Y % |--> power(V[X],V[Y]) + X.*Y % |--> times(V[X],V[Y]) + X./Y % |--> rdivide(V[X],V[Y]) + X.\Y % |--> ldivide(V[X],V[Y]) + X:Y:Z % |--> colon(V[X],V[Y],V[Z]) + X:Z % |--> colon(V[X],V[Z]) + X>Z % |--> gt(V[X],V[Y]) + X>=Z % |--> ge(V[X],V[Y]) + X<Z % |--> lt(V[X],V[Y]) + X=<Z % |--> le(V[X],V[Y]) + X==Z % |--> eq(V[X],V[Y]) + [X1,X2,...] % |--> [ V[X1], V[X2], ... ] + [X1;X2;...] % |--> [ V[X1]; V[X2]; ... ] + {X1,X2,...} % |--> { V[X1], V[X2], ... } + {X1;X2;...} % |--> { V[X1]; V[X2]; ... } + @X % |--> @V[X] (function handle) + == + + Things that do not look like Matlab syntax but provide standard Matlab features: + == + 'Infinity' % |--> inf (positive infinity) + 'Nan' % |--> nan (not a number) + X`` % |--> ctranpose(V[X]) (conjugate transpose, V[X]') + X#Y % |--> getfield(V[X],V[q(Y)]) + X\\Y % |--> @(V[X])V[Y] (same as lambda(X,Y)) + \\Y % |--> @()V[Y] (same as thunk(Y)) + lambda(X,Y) % |--> @(V[X])V[Y] (anonymous function with arguments X) + thunk(Y) % |--> @()V[Y] (anonymous function with no arguments) + vector(X) % |--> horzcat(V[X1],V[X2], ...) + atvector(X) % as vector but assumes elements of X are assumed all atomic + cell(X) % construct 1xN cell array from elements of X + `X % same as q(X) + q(X) % wrap V[X] in single quotes (escaping internal quotes) + qq(X) % wrap V[X] in double quotes (escaping internal double quotes) + tq(X) % wrap TeX expression in single quotes (escape internal quotes) + == + + Referencing different value representations. + == + mat(X,Y) % denotes a value in the Matbase using a dbload expression + mx(X:mx_blob) % denotes an MX Matlab array in SWI memory + ws(X:ws_blob) % denotes a variable in a Matlab workspace + wsseq(X:ws_blob) % workspace variable containing list as cell array. + == + + Tricky bits. + == + apply(X,AX) % X must denote a function or array, applied to list of arguments AX. + cref(X,Y) % cell dereference, |--> V[X]{ V[Y1], V[Y2], ... } + arr(Lists) % multidimensional array from nested lists. + arr(Lists,Dims) % multidimensional array from nested lists. + == + + Things to bypass default formatting + == + noeval(_) % triggers a failure when processed + atom(X) % write atom X as write/1 + term(X) % write term X as write/1 + \(P) % escape and call phrase P directly to generate Matlab string + $(X) % calls pl2ml_hook/2, denotes V[Y] where plml_hook(X,Y). + '$VAR'(N) % gets formatted as p_N where N is assumed to be atomic. + == + + All other Prolog atoms are written using write/1, while other Prolog terms + are assumed to be calls to Matlab functions named according to the head functor. + Thus V[ <head>( <arg1>, <arg2>, ...) ] = <head>(V[<arg1>, V[<arg2>], ...). + + There are some incompatibilities between Matlab syntax and Prolog syntax, + that is, syntactic structures that Prolog cannot parse correctly: + + * 'Command line' syntax, ie where a function of string arguments: + "save('x','Y')" can be written as "save x Y" in Matlab, + but in Prolog, you must use function call syntax with quoted arguments: + save(`x,`'Y'). + + * Matlab's postfix transpose operator "x'" must be written using a different + posfix operator "x``" or function call syntax "ctranspose(x)". + + * Matlab cell referencing using braces, as in x{1,2} must be written + as "cref(x,1,2)". + + * Field referencing using dot (.), eg x.thing - currently resolved + by using hash (#) operator, eg x#thing. + + * Using variables as arrays and indexing them. The problem is that + Prolog doesn't let you write a term with a variable as the head + functor. + + + @tbd + + Use mat(I) and tmp(I) as types to include engine Id. + + Clarify relationship between return values and valid Matlab denotation. + + Reshape/2 array representation: reshape([ ... ],Size) + Expression language: arr(Vals,Shape,InnerFunctor) - allows efficient + representation of arrays of arbitrary things. Will require more strict + nested list form. + + Deprecate old array(Vals::Type) and cell(Vals::Type) left-value syntax. + + Remove I from ml_expr//2 and add to mx type? +*/ + +:- use_module(library(apply_macros)). +:- use_module(library(ops)). +:- use_module(library(utils)). +:- use_module(library(dcgu)). + +:- load_foreign_library(foreign(plml)). + +:- op(700,xfx,===). % variable binding/assignment in matlab query +:- op(951,fx,??). % evaluate term as matlab +:- op(951,fx,???). % evaluate term as matlab boolean +:- op(650,fy,`). % quoting things +:- op(160,xf,``). % postfix transpose operator +:- op(100,fy,@). % function handles +:- op(200,xfy,.^). % array exponentiation +:- op(410,yfx,.*). % array times +:- op(410,yfx,./). % array division +:- op(410,xfy,.\). % array reverse division +:- op(400,xfy,\). % matrix reverse division +:- op(100,yfx,#). % field indexing (note left-associativity) + +:- dynamic plml_flag/2. + +set_flag(Flag,Value) :- + ground(Flag), + retractall(plml_flag(Flag,_)), + assert(plml_flag(Flag,Value)). + +:- at_halt(ml_closeall). + +ml_closeall :- + forall(plml_flag(ml(Id),open), + ( format('Closing Matlab engine (~w)...',[Id]), + ml_close(Id))). + +%% matlab_init( -Key, -Cmd:ml_expr) is nondet. +% Each user-defined clause of matlab_init/2 causes Cmd to be executed +% whenever a new Matlab session is started. + +%% matlab_path( -Key, -Path:list(atom)) is nondet. +% Each user-defined clause of matlab_path/2 causes the directories in Path +% to be added to the Matlab path of every new Matlab session. Directories +% are relative to the root directory as returned by Matlab function proot. + +%% pl2ml_hook(+X:term,-Y:ml_expr) is nondet. +% Clauses of pl2ml_hook/2 allow for extensions to the Matlab expression +% language such that =|V[$X] = V[Y]|= if =|pl2ml_hook(X,Y)|=. + + + +%% ml_open(+Id:ml_eng,+Host:atom,+Options:list(_)) is det. +%% ml_open(+Id:ml_eng, +Host:atom) is det. +%% ml_open(+Id:ml_eng) is det. +% +% Start a Matlab session on the given host. If Host=localhost +% or the name of the current current host as returned by hostname/1, +% then a Matlab process is started directly. Otherwise, it is +% started remotely via SSH. Options defaults to []. Host defaults to +% localhost. +% +% Start a Matlab session on the specified host using default options. +% If Host is not given, it defaults to localhost. Session will be +% associated with the given Id, which should be an atom. See ml_open/3. +% +% Valid options are +% * noinit +% If present, do not run initialisation commands specified by +% matlab_path/2 and matlab_init/2 clauses. Otherwise, do run them. +% * debug(In,Out) +% if present, Matlab is started in a script which captures standard +% input and output to files In and Out respectively. +% +% [What if session is already open and attached to Id?] + +ml_open(Id) :- ml_open(Id,localhost,[]). +ml_open(Id,Host) :- ml_open(Id,Host,[]). +ml_open(Id,Host,Options) :- + options_flags(Options,Flags), + ( (Host=localhost;hostname(Host)) + -> Exec='exec matlab' % using exec fixes Ctrl-C bug + ; Exec='ssh /usr/local/bin/matlab' + ), + ( member(debug(In,Out),Options) + -> format(atom(Exec1),'stdio_catcher ~w ~w nohup ~w',[In,Out,Exec]) + ; Exec1=Exec + ), + format(atom(Cmd),'~w ~w',[Exec,Flags]), + mlOPEN(Cmd,Id), + set_flag(ml(Id),open), + ( member(noinit,Options) -> true + ; forall( matlab_path(_,Dir), maplist(nofail(addpath),Dir)), + forall( matlab_init(_,Cmd), nofail(Cmd)) + ). + +addpath(local(D)) :- !, ml_exec(ml,padl(q(D))). +addpath(D) :- !, ml_exec(ml,padd(q(D))). + +%% ml_close(+Id:ml_eng) is det. +% Close Matlab session associated with Id. +ml_close(Id) :- mlCLOSE(Id), set_flag(ml(Id),closed). + +nofail(P) :- catch(ignore(call(P)), E, print_message(warning,E)). +nofail(P,X) :- catch(ignore(call(P,X)), E, print_message(warning,E)). + +options_flags(_,'nodesktop -nosplash -noawt'). + + +%% ml_exec(+Id:ml_eng, +Expr:ml_expr) is det. +% +% Execute Matlab expression without returning any values. +ml_exec(Id,X) :- + term_mlstring(Id,X,C), !, + (plml_flag(debug,true) -> format('ml_exec(~w):~s\n',[Id,C]); true), + mlEXEC(Id,C). + +%% ml_eval(+Id:ml_eng, +Expr:ml_expr, +Types:list(type), -Res:list(ml_val)) is det. +% +% Evaluate Matlab expression binding return values to results list Res. This new +% form uses an explicit output types list, so Res can be completely unbound on entry +% even when multiple values are required. +ml_eval(Id,X,Types,Vals) :- + maplist(alloc_ws(Id),Types,Vars), + ml_exec(Id,hide(wsx(Vars)=X)), + maplist(convert_ws,Types,Vars,Vals). + +alloc_ws(I,_,Z) :- mlWSALLOC(I,Z). + +%% ml_test(+Id:ml_eng, +X:ml_expr(bool)) is semidet. +% Succeeds if X evaluates to true in Matlab session Id. +ml_test(Id,X) :- ml_eval(Id,X,[bool],[1]). + + + +%% ===(Y:ml_vals(A), X:ml_expr(A)) is det. +% Evaluate Matlab expression X as in ml_eval/4, binding one or more return values +% to Y. If Y is unbound or a single ml_val(_), only the first return value is bound. +% If Y is a list, multiple return values are processed. +Y === X :- + ( is_list(Y) + -> maplist(leftval,Y,TX,VX), ml_eval(ml,X,TX,VX) + ; leftval(Y,T,V), ml_eval(ml,X,[T],[V]) + ). + +%% leftval( +TVal:tagged(T), -T:type, -Val:T) is det. +% True if TVal is a tagged value whos type is T and value is Val. +leftval( ws(X), ws, ws(X)). +leftval( mx(X), mx, mx(X)). +leftval( float(X), float, X). +leftval( int(X), int, X). +leftval( bool(X), bool, X). +leftval( atom(X), atom, X). +leftval( term(X), term, X). +leftval( string(X), string,X). +leftval( mat(X), mat, X). +leftval( tmp(X), tmp, X). +leftval( loc(X), loc, X). +leftval( wsseq(X), wsseq, wsseq(X)). +leftval( list(T,X), list(T), X). +leftval( array(X::[Size->Type]), array(Type,Size), X) :- !. +leftval( array(X::[Size]), array(float,Size), X) :- !. +leftval( cell(X::[Size->Type]), cell(Type,Size), X) :- !. +leftval( cell(X::[Size]), cell(mx,Size), X) :- !. +leftval( Val:Type, Type, Val). + + +%% ??(X:ml_expr(_)) is det. +% Execute Matlab expression X as with ml_exec/2, without returning any values. +?? X :- ml_exec(ml,X). + +%% ???(X:ml_expr(bool)) is semidet. +% Evaluate Matlab boolean expression X as with ml_test/2. +??? Q :- ml_test(ml,Q). + + +%% ml_debug(+Flag:boolean) is det. +% Set or reset debug state. =|ml_debug(true)|= causes formatted Matlab +% statements to be printed before being sent to Matlab engine. +ml_debug(F) :- set_flag(debug,F). + +/* + * DCG for term to matlab conversion + * the big problem with Matlab syntax is that you cannot always replace + * a name representing a value with an expression that reduces to that + * value. Eg + * X=magic(5), X(3,4) + * is ok, but + * (magic(5))(3,4) + * is not. Similarly x=@sin, x(0.5) but not (@sin)(0.5) + * This is really infuriating. + */ + + +% top level statement rules +stmt(I,hide(A)) --> !, stmt(I,A), ";". +stmt(I,(A;B)) --> !, stmt(I,A), ";", stmt(I,B). +stmt(I,(A,B)) --> !, stmt(I,A), ",", stmt(I,B). +stmt(I,A=B) --> !, ml_expr(I,A), "=", ml_expr(I,B). +stmt(I,Expr) --> !, ml_expr(I,Expr). + + +%% ml_expr(+Id:ml_eng,+X:ml_expr(A))// is nondet. +% Convert Matlab expression as a Prolog term to string representation. +ml_expr(_,\X) --> !, X. +ml_expr(I,$X) --> !, {pl2ml_hook(X,Y)}, ml_expr(I,Y). +ml_expr(I,q(X)) --> !, q(stmt(I,X)). +ml_expr(I,qq(X)) --> !, qq(stmt(I,X)). +ml_expr(_,tq(X)) --> !, q(pl2tex(X)). +ml_expr(_,atom(X)) --> !, atm(X). +ml_expr(_,term(X)) --> !, wr(X). % this could be dangerous +ml_expr(_,mat(X,Y)) --> !, "dbload(", loc(X,Y), ")". +ml_expr(_,loc(L)) --> !, { L=mat(X,Y) }, loc(X,Y). +ml_expr(I,mx(X)) --> !, { mlWSALLOC(I,Z), mlWSPUT(Z,X) }, ml_expr(I,ws(Z)). +ml_expr(I,ws(A)) --> !, { mlWSNAME(A,N,I) }, atm(N). +ml_expr(I,wsx([A|B])) --> !, { mlWSNAME(A,N,I) }, "[", atm(N), wsx(B), "]". +ml_expr(I,wsseq(A)) --> !, { mlWSNAME(A,N,I) }, atm(N). +ml_expr(_,noeval(_)) --> !, {fail}. % causes evaluation to fail. + +ml_expr(_,'Infinity') --> !, "inf". +ml_expr(_,'Nan') --> !, "nan". + +ml_expr(I,A+B) --> !, "plus", args(I,A,B). +ml_expr(I,A-B) --> !, "minus", args(I,A,B). +ml_expr(I, -B) --> !, "uminus", args(I,B). +ml_expr(I, +B) --> !, "uplus", args(I,B). +ml_expr(I,A^B) --> !, "mpower", args(I,A,B). +ml_expr(I,A*B) --> !, "mtimes", args(I,A,B). +ml_expr(I,A/B) --> !, "mrdivide", args(I,A,B). +ml_expr(I,A\B) --> !, "mldivide", args(I,A,B). +ml_expr(I,A.^B)--> !, "power", args(I,A,B). +ml_expr(I,A.*B)--> !, "times", args(I,A,B). +ml_expr(I,A./B)--> !, "rdivide", args(I,A,B). +ml_expr(I,A.\B)--> !, "ldivide", args(I,A,B). +ml_expr(I,A>B) --> !, "gt",args(I,A,B). +ml_expr(I,A<B) --> !, "lt",args(I,A,B). +ml_expr(I,A>=B)--> !, "ge",args(I,A,B). +ml_expr(I,A=<B)--> !, "le",args(I,A,B). +ml_expr(I,A==B)--> !, "eq",args(I,A,B). +ml_expr(I,A:B) --> !, range(I,A,B). + +ml_expr(_,[]) --> !, "[]". +ml_expr(_,{}) --> !, "{}". +ml_expr(I,[X]) --> !, "[", matrix(v,I,X), "]". +ml_expr(I,[X|XX]) --> !, "[", ml_expr(I,X), seqmap(do_then_call(",",ml_expr(I)),XX), "]". +ml_expr(I,{X}) --> !, "{", matrix(_,I,X), "}". + +ml_expr(I, `B) --> !, q(stmt(I,B)). +ml_expr(I,A#B) --> !, "getfield", args(I,A,q(B)). +ml_expr(I,B``) --> !, "ctranspose", args(I,B). +ml_expr(_,@B) --> !, "@", atm(B). +ml_expr(I, \\B) --> !, "@()", ml_expr(I,B). +ml_expr(I, A\\B) --> !, { term_variables(A,V), varnames(V) }, + "@(", ml_expr(I,A), ")", ml_expr(I,B). +ml_expr(I,lambda(A,B)) --> !, ml_expr(I,A\\B). +ml_expr(I,thunk(B)) --> !, ml_expr(I, \\B). + + +% !! This is problematic: we are using apply to represent both +% function application and array dereferencing. For function +% calls, A must be a function name atom or a function handle +% If A is an array, it cannot be an expression, unless we +% switch to using the paren Matlab function, which will be slower. +ml_expr(I,apply(A,B)) --> !, ml_expr(I,A), arglist(I,B). +ml_expr(I,cref(A,B)) --> !, ml_expr(I,A), "{", clist(I,B), "}". + +% array syntax +ml_expr(I,arr($X)) --> !, { pl2ml_hook(X,L) }, ml_expr(I,arr(L)). +ml_expr(I,arr(L)) --> !, { array_dims(L,D) }, array(D,I,L). +ml_expr(I,arr(D,L)) --> !, array(D,I,L). +ml_expr(I,arr(D,L,P)) --> !, array(D,I,P,L). +ml_expr(I,atvector(L))--> !, "[", clist_at(I,L), "]". +ml_expr(I,vector(L)) --> !, "[", clist(I,L), "]". +ml_expr(I,cell(L)) --> !, "{", clist(I,L), "}". +ml_expr(_,'$VAR'(N)) --> !, "p_", atm(N). + +% catch these and throw exception +ml_expr(_,hide(A)) --> {throw(ml_illegal_expression(hide(A)))}. +ml_expr(_,(A;B)) --> {throw(ml_illegal_expression((A;B)))}. +ml_expr(_,(A,B)) --> {throw(ml_illegal_expression((A,B)))}. +ml_expr(_,A=B) --> {throw(ml_illegal_expression(A=B))}. + +% these are the catch-all clauses which will deal with matlab names, and literals +% should we filter on the head functor? +ml_expr(_,A) --> {atomic(A)}, !, atm(A). +ml_expr(I,F) --> {F=..[H|AX]}, atm(H), arglist(I,AX). + +ml_expr_with(I,Lambda,Y) --> {copy_term(Lambda,Y\\PY)}, ml_expr(I,PY). + + +% dimensions implicit in nested list representation +array_dims([X|_],M) :- !, array_dims(X,N), succ(N,M). +array_dims(_,0). + +% efficiently output row vector of workspace variable names +wsx([]) --> []. +wsx([A|AX]) --> { mlWSNAME(A,N,_) }, ",", atm(N), wsx(AX). + +%% array(+Dims:natural, +Id:ml_eng, +Array)// is det. +% +% Format nested lists as Matlab multidimensional array. +% Dims is the number of dimensions of the resulting array and +% should equal the nesting level of Array, ie if Array=[1,2,3], +% Dims=1; if Array=[[1,2],[3,4]], Dims=2, etc. +array(0,I,X) --> !, ml_expr(I,X). +array(1,I,L) --> !, "[", seqmap_with_sep(";",ml_expr(I),L), "]". +array(2,I,L) --> !, "[", seqmap_with_sep(",",array(1,I),L), "]". +array(N,I,L) --> {succ(M,N)}, "cat(", atm(N), ",", seqmap_with_sep(",",array(M,I),L), ")". + +array(0,I,P,X) --> !, ml_expr_with(I,P,X). +array(1,I,P,L) --> !, "[", seqmap_with_sep(";",ml_expr_with(I,P),L), "]". +array(2,I,P,L) --> !, "[", seqmap_with_sep(",",array(1,I,P),L), "]". +array(N,I,P,L) --> {succ(M,N)}, "cat(", atm(N), ",", seqmap_with_sep(",",array(M,I,P),L), ")". + +matrix(h,I,(A,B)) --> !, ml_expr(I,A), ",", matrix(h,I,B). +matrix(v,I,(A;B)) --> !, ml_expr(I,A), ";", matrix(v,I,B). +matrix(_,I,A) --> !, ml_expr(I,A). + + +% colon syntax for ranges +range(I,A,B:C) --> !, "colon", arglist(I,[A,B,C]). +range(I,A,B) --> !, "colon", args(I,A,B). + + +%% clist(+Id:ml_eng, +Items:list(ml_expr))// is det. +% Format list of Matlab expressions in a comma separated list. +clist(_,[]) --> []. +clist(I,[L1|LX]) --> ml_expr(I,L1), seqmap(do_then_call(",",ml_expr(I)),LX). + + +%% clist_at(+Id:ml_eng, +Items:list(ml_expr))// is det. +% Format list of atoms in a comma separated list. +clist_at(_,[]) --> []. +clist_at(_,[L1|LX]) --> atm(L1), seqmap(do_then_call(",",atm),LX). + + +%% arglist(+Id:ml_eng, +Args:list(ml_expr))// is det. +% DCG rule to format a list of Matlab expressions as function arguments +% including parentheses. +arglist(I,X) --> "(", clist(I,X), ")". + + +%% args(+Id:ml_eng, +A1:ml_expr, +A2:ml_expr)// is det. +%% args(+Id:ml_eng, +A1:ml_expr)// is det. +% +% DCG rule to format one or two Matlab expressions as function arguments +% including parentheses. +args(I,X,Y) --> "(", ml_expr(I,X), ",", ml_expr(I,Y), ")". +args(I,X) --> "(", ml_expr(I,X), ")". + + +%% atm(+A:atom)// is det. +% DCG rule to format an atom using write/1. +atm(A,C,T) :- with_output_to(codes(C,T),write(A)). + +varnames(L) :- varnames(1,L). +varnames(_,[]). +varnames(N,[TN|Rest]) :- + atom_concat(p_,N,TN), succ(N,M), + varnames(M,Rest). + + +%% term_mlstring(+Id:ml_eng,+X:ml_expr,-Y:list(code)) is det. +% Convert term representing Matlab expression to a list of character codes. +term_mlstring(I,Term,String) :- phrase(stmt(I,Term),String), !. + +%% term_texatom(+X:tex_expr,-Y:atom) is det. +% Convert term representing TeX expression to a string in atom form. +term_texatom(Term,Atom) :- phrase(pl2tex(Term),String), !, atom_codes(Atom,String). + + + +% Once the computation has been done, the MATLAB workspace contains +% the results which must be transferred in the appropriate form the +% specified left-values, in one of several forms, eg mxArray pointer, +% a float, an atom, a string or a locator. +% +% Note that requesting a locator causes a further call +% to MATLAB to do a dbsave. +% +% If no type requestor tag is present, then a unique variable name +% is generated to store the result in the Matlab workspace. This name +% is returned in the variable as a ws blob. +% The idea is to avoid unnecessary traffic over the Matlab engine pipe. + +% conversion between different representations of values +% !! FIXME: check memory management of mxArrays here + + +%% convert_ws( +Type:type, +In:ws_blob, -Out:Type) is det. +% Convert value of Matlab workspace variable to representation +% determined by Type. +convert_ws(ws, Z, ws(Z)) :- !. +convert_ws(wsseq, Z, wsseq(Z)) :- !. +convert_ws(mx, Z, mx(Y)) :- !, mlWSGET(Z,Y). + +% conversions that go direct from workspace variables to matbase. +convert_ws(tmp, Z, Y) :- !, mlWSNAME(Z,_,I), bt_call(db_tmp(I,ws(Z),Y), db_drop(I,Y)). +convert_ws(mat, Z, Y) :- !, mlWSNAME(Z,_,I), bt_call(db_save(I,ws(Z),Y), db_drop(I,Y)). + +% return cell array as list of temporary or permanent mat file locators +% (this avoids getting whole array from WS to MX). +convert_ws(cell(tmp,Size), Z, L) :- !, + mlWSNAME(Z,_,I), + bt_call(db_tmp_all(I,ws(Z),L,Size), db_drop_all(I,L,Size)). + +convert_ws(cell(mat,Size), Z, L) :- !, + mlWSNAME(Z,_,I), + bt_call(db_save_all(I,ws(Z),L,Size), db_drop_all(I,L,Size)). + +% Most other conversions from ws(_) go via mx(_) +convert_ws(T,Z,A) :- mlWSGET(Z,X), convert_mx(T,X,A). + + +%% convert_mx( +Type:type, +In:mx_blob, -Out:Type) is det. +% Convert value of in-process Matlab array In to representation +% determined by Type. +convert_mx(atom, X, Y) :- !, mlMX2ATOM(X,Y). +convert_mx(bool, X, Y) :- !, mlMX2LOGICAL(X,Y). +convert_mx(float, X, Y) :- !, mlMX2FLOAT(X,Y). +convert_mx(int, X, Y) :- !, mlMX2FLOAT(X,Z), Y is truncate(Z). +convert_mx(string, X, Y) :- !, mlMX2STRING(X,Y). +convert_mx(term, X, Y) :- !, mlMX2ATOM(X,Z), term_to_atom(Y,Z). +convert_mx(loc, X, mat(Y,W)) :- !, mlMX2ATOM(X,Z), term_to_atom(Y|W,Z). + +convert_mx(mat, X, Y) :- !, % !!! use first engine to save to its matbase + plml_flag(ml(I),open), + bt_call( db_save(I,mx(X),Y), db_drop(I,Y)). +convert_mx(tmp, X, Y) :- !, % !!! use first engine to save to its matbase + plml_flag(ml(I),open), + bt_call( db_tmp(I,mx(X),Y), db_drop(I,Y)). + +convert_mx(list(float), X, Y) :- !, mlGETREALS(X,Y). + +convert_mx(cell(Type,Size), X, L) :- !, + mx_size_type(X,Size,cell), + prodlist(Size,1,Elems), % total number of elements + mapnats(conv_cref(Type,X),Elems,[],FL), + reverse(Size,RSize), + unflatten(RSize,FL,L). + +convert_mx(array(Type,Size), X, L) :- !, + mx_size_type(X,Size,MXType), + compatible(MXType,Type), + prodlist(Size,1,Elems), % total number of elements + mapnats(conv_aref(Type,X),Elems,[],FL), + reverse(Size,RSize), + unflatten(RSize,FL,L). + +compatible(double,float). +compatible(double,int). +compatible(double,bool). +compatible(logical,float). +compatible(logical,int). +compatible(logical,bool). + +% !! Need to worry about non gc mx atoms +conv_aref(bool, X,I,Y) :- !, mlGETLOGICAL(X,I,Y). +conv_aref(float, X,I,Y) :- !, mlGETFLOAT(X,I,Y). +conv_aref(int, X,I,Y) :- !, mlGETFLOAT(X,I,W), Y is truncate(W). + +conv_cref(mx,Z,I,Y) :- !, mlGETCELL(Z,I,Y). % !! non gc mx +conv_cref(Ty,Z,I,Y) :- !, conv_cref(mx,Z,I,X), convert_mx(Ty,X,Y). + +%convert(W, field(Z,N,I)) :- convert(mx(X),Z), mlGETFIELD(X,I,N,Y), convert_mx(W,Y). +%convert(W, field(Z,N)) :- convert(mx(X),Z), mlGETFIELD(X,1,N,Y), convert_mx(W,Y). + +% Utilities used by convert/2 + +mapnats(P,N,L1,L3) :- succ(M,N), !, call(P,N,PN), mapnats(P,M,[PN|L1],L3). +mapnats(_,0,L,L) :- !. + +prodlist([],P,P). +prodlist([X1|XX],P1,P3) :- P2 is P1*X1, prodlist(XX,P2,P3). + +concat(0,_,[]) --> !, []. +concat(N,L,[X1|XX]) --> { succ(M,N), length(X1,L) }, X1, concat(M,L,XX). + +% convert a flat list into a nested-list array representation +% using given size specification +unflatten([N],Y,Y) :- !, length(Y,N). +unflatten([N|NX],Y,X) :- + length(Y,M), + L is M/N, integer(L), L>=1, + phrase(concat(N,L,Z),Y), + maplist(unflatten(NX),Z,X). + +% thin wrappers +mx_size_type(X,Sz,Type) :- mlMXINFO(X,Sz,Type). +mx_sub2ind(X,Subs,Ind) :- mlSUB2IND(X,Subs,Ind). + + +% these create memory managed arrays, which are not suitable +% for putting into a cell array + +% roughly, mx_create :: type -> mxarray. +mx_create([Size],mx(X)) :- mlCREATENUMERIC(Size,Z), mlNEWREFGC(Z,X). +mx_create({Size},mx(X)) :- mlCREATECELL(Size,Z), mlNEWREFGC(Z,X). +mx_string(string(Y),mx(X)) :- mlCREATESTRING(Y,Z), mlNEWREFGC(Z,X). + +% MX as MUTABLE variables +mx_put(aref(mx(X),I),float(Y)) :- mlPUTFLOAT(X,I,Y). +mx_put(cref(mx(X),I),mx(Y)) :- mlPUTCELL(X,I,Y). % !! ensure that Y is non gc +mx_put(mx(X),list(float,Y)) :- mlPUTFLOATS(X,1,Y). + +%% wsvar(+X:ws_blob(A), -Nm:atom, -Id:ml_eng) is semidet. +% True if X is a workspace variable in Matlab session Id. +% Unifies Nm with the name of the Matlab variable. +wsvar(A,Name,Engine) :- mlWSNAME(A,Name,Engine). + +/* __________________________________________________________________________________ + * Dealing with the Matbase + * + * The Matbase is a file system tree which contains lots of + * MAT files which have been created by using the dbsave + * Matlab function. + */ + + +%% loc(Dir,File)// is det. +% DCG rule for matbase locator strings. Dir must be an atom slash-separated +% list of atoms representing a path relative to the matbase root (see Matlab +% function dbroot). File must be an atom. Outputs a single-quoted locator +% string acceptable to Matlab db functions. +loc(X,Y) --> "'", wr(X),"|",atm(Y), "'". + + +% saving and dropping matbase files +db_save(I,Z,Y) :- ml_eval(I,dbsave(Z),[loc],[Y]). +db_tmp(I,Z,Y) :- ml_eval(I,dbtmp(Z),[loc],[Y]). +db_drop(I,mat(A,B)) :- ml_exec(I,dbdrop(\loc(A,B))). + +db_save_all(I,Z,L,Size) :- ml_eval(I,cellmap(@dbsave,Z),[cell(loc,Size)],[L]). +db_tmp_all(I,Z,L,Size) :- ml_eval(I,cellmap(@dbtmp,Z),[cell(loc,Size)],[L]). +db_drop_all(I,L,Size) :- + length(Size,Dims), + ml_exec(I,hide(foreach(@dbdrop,arr(Dims,L,X\\{loc(X)})))). + + +%% dropmat(+Id:ml_id, +Mat:ml_loc) is det. +% Deleting MAT file from matbase. +dropmat(Eng,mat(A,B)) :- db_drop(Eng,mat(A,B)). + +%% exportmat(+Id:ml_id, +Mat:ml_loc, +Dir:atom) is det. +% Export specified MAT file from matbase to given directory. +exportmat(Eng,mat(A,B),Dir) :- ml_exec(Eng,copyfile(dbpath(\loc(A,B)),\q(wr(Dir)))). + +%% matbase_mat(+Id:ml_eng,-X:ml_loc) is nondet. +% Listing mat files actually in matbase at given root directory. +matbase_mat(Id,mat(SubDir/File,x)) :- + ml_eval(Id,[dbroot,q(/)],[atom],[DBRoot]), % NB with trailing slash + + atom_concat(DBRoot,'*/d*',DirPattern), + expand_file_name(DirPattern,Dirs), + member(FullDir,Dirs), + atom_concat( DBRoot,SubDirAtom,FullDir), + term_to_atom(SubDir,SubDirAtom), + atom_concat(FullDir,'/m*.mat',FilePattern), + expand_file_name(FilePattern,Files), + member(FullFile,Files), + file_base_name(FullFile,FN), + atom_concat(File,'.mat',FN). + + +%% persist_item(+X:ml_expr(A),-Y:ml_expr(A)) is det. +% Convert Matlab expression to persistent form not dependent on +% current Matlab workspace or MX arrays in Prolog memory space. +% Large values like arrays and structures are saved in the matbase +% replaced with matbase locators. Scalar values are converted to +% literal numeric values. Character strings are converted to Prolog atoms. +% Cell arrays wrapped in the wsseq/1 functor are converted to literal +% form. +% +% NB. any side effects are undone on backtracking -- in particular, any +% files created in the matbase are deleted. +persist_item($T,$T) :- !. +persist_item(mat(A,B),mat(A,B)) :- !. + +persist_item(ws(A),B) :- !, + mlWSNAME(A,_,Eng), + ml_eval(Eng,typecode(ws(A)),[int,bool,bool],[Numel,IsNum,IsChar]), + ( Numel=1, IsNum=1 + -> convert_ws(float,A,B) + ; IsChar=1 + -> convert_ws(atom,A,AA), B= `AA + ; convert_ws(mat,A,B) + ). + + +% !! TODO - +% deal with collections - we can either save the aggregate +% OR save the elements individually and get a prolog list of the +% locators. +persist_item(wsseq(A),cell(B)) :- + mlWSNAME(A,_,Eng), + ml_test(Eng,iscell(ws(A))), + ml_eval(Eng,wsseq(A),[cell(mat,_)],[B]). + +persist_item(mx(X),B) :- + mx_size_type(X,Size,Type), + ( Size=[1], Type=double + -> convert_mx(float,X,B) + ; Type=char + -> convert_mx(atom,X,AA), B= `AA + ; convert_mx(mat,X,B) + ). + +persist_item(A,A) :- atomic(A). + + +/* ----------------------------------------------------------------------- + * From here on, we have straight Matlab utilities + * rather than basic infrastructure. + */ + + + +% for dealing with option lists + +%% mhelp(+Name:atom) is det. +% Lookup Matlab help on the given name. Equivalent to executing help(`X). +mhelp(X) :- ml_exec(ml,help(q(X))). + + + +%% compileoptions(+Opts:list(ml_options), -Prefs:ml_expr(options)) is det. +% +% Convert list of option specifiers into a Matlab expression representing +% options (ie a struct). Each specifier can be a Name:Value pair, a name +% to be looked up in the optionset/2 predicate, a nested list of ml_options +% compileoptions :: list (optionset | atom:value | struct) -> struct. +% NB. option types are as follows: +% == +% X :: ml_options :- optionset(X,_). +% X :: ml_options :- X :: ml_option(_). +% X :: ml_options :- X :: list(ml_options). +% X :: ml_options :- X :: ml_expr(struct(_)). +% +% ml_option(A) ---> atom:ml_expr(A). +% == +compileoptions(Opts,Prefs) :- + rec_optslist(Opts,OptsList), + Prefs=..[prefs|OptsList]. + +rec_optslist([],[]). +rec_optslist([H|T],L) :- + ( % mutually exclusive types for H + optionset(H,Opts1) -> rec_optslist(Opts1,Opts) + ; H=Name:Value -> Opts=[`Name,Value] + ; is_list(H) -> rec_optslist(H,Opts) + ; /* assume struct */ Opts=[H] + ), + rec_optslist(T,TT), + append(Opts,TT,L). + +rtimes(X,Y,Z) :- + ( var(X) -> X is Z/Y + ; var(Y) -> Y is Z/X + ; Z is X*Y). + + +% Execute several plots as subplots. The layout can be +% vertical, horizontal, or explicity given as Rows*Columns. + + +% mplot is a private procedure used by multiplot +mplot(subplot(H,W),N,Plot,Ax) :- ?? (subplot(H,W,N); Plot), Ax===gca. +mplot(figure,N,Plot,Ax) :- ?? (figure(N); Plot), Ax===gca. + +%% multiplot(+Type:ml_plot, +Cmds:list(ml_expr(_))) is det. +%% multiplot(+Type:ml_plot, +Cmds:list(ml_expr(_)), -Axes:list(ml_val(handle))) is det. +% +% Executes plotting commands in Cmds in multiple figures or axes as determined +% by Type. Valid types are: +% * figs(Range) +% Executes each plot in a separate figure, Range must be P..Q where P +% and Q are figure numbers. +% * vertical +% Executes each plot in a subplot; +% subplots are arranged vertically top to bottom in the current figure. +% * horizontal +% Executes each plot in a subplot; +% subplots are arranged horizontally left to right in the current figure. +% * [Type, link(Axis)] +% As for multplot type Type, but link X or Y axis scales as determined by Axis, +% which can be `x, `y, or `xy. +% +% Three argument form returns a list containing the Matlab handles to axes objects, +% one for each plot. +multiplot(Type,Plots) :- multiplot(Type,Plots,_). + +multiplot([Layout|Opts],Plots,Axes) :- !, + multiplot(Layout,Plots,Axes), + member(link(A),Opts) -> + ?? (linkaxes(Axes,`off); hide(linkaxes(Axes,`A))) + ; true. + +multiplot(figs(P..Q),Plots,Axes) :- !, + length(Plots,N), + between(1,inf,P), Q is P+N-1, + numlist(P,Q,PlotNums), + maplist(mplot(figure),PlotNums,Plots,Axes). + +multiplot(Layout,Plots,Axes) :- + length(Plots,N), + member(Layout:H*W,[vertical:N*1, horizontal:1*N, H*W:H*W]), + rtimes(H,W,N), % bind any remaining variables + numlist(1,N,PlotNums), + maplist(mplot(subplot(H,W)),PlotNums,Plots,Axes). + + +%% optionset( +Key:term, -Opts:list(ml_options)) is semidet. +% +% Extensible predicate for mapping arbitrary terms to a list of options +% to be processed by compileoptions/2. + +%user:portray(A|B) :- print(A), write('|'), print(B). +user:portray(Z) :- mlWSNAME(Z,N,ID), format('<~w:~w>',[ID,N]). + +prolog:message(ml_illegal_expression(Expr),[ 'Illegal Matlab expression: ~w'-[Expr] | Z], Z). +prolog:message(mlerror(Eng,Msg,Cmd),[ +'Error in Matlab engine (~w):\n * ~w\n * while executing "~w"'-[Eng,Msg,Cmd] | Z], Z). + + +%% pl2tex(+Exp:tex_expr)// is det. +% +% DCG for texifying expressions (useful for matlab text) +pl2tex(A=B) --> !, pl2tex(A), "=", pl2tex(B). +pl2tex(A+B) --> !, pl2tex(A), "+", pl2tex(B). +pl2tex(A-B) --> !, pl2tex(A), "-", pl2tex(B). +pl2tex(A*B) --> !, pl2tex(A), "*", pl2tex(B). +pl2tex(A.*B) --> !, pl2tex(A), "*", pl2tex(B). +pl2tex(A/B) --> !, pl2tex(A), "/", pl2tex(B). +pl2tex(A./B) --> !, pl2tex(A), "/", pl2tex(B). +pl2tex(A\B) --> !, pl2tex(A), "\\", pl2tex(B). +pl2tex(A.\B) --> !, pl2tex(A), "\\", pl2tex(B). +pl2tex(A^B) --> !, pl2tex(A), "^", brace(pl2tex(B)). +pl2tex(A.^B) --> !, pl2tex(A), "^", brace(pl2tex(B)). +pl2tex((A,B))--> !, pl2tex(A), ", ", pl2tex(B). +pl2tex(A;B)--> !, pl2tex(A), "; ", pl2tex(B). +pl2tex(A:B)--> !, pl2tex(A), ": ", pl2tex(B). +pl2tex({A}) --> !, "\\{", pl2tex(A), "\\}". +pl2tex([]) --> !, "[]". +pl2tex([X|XS]) --> !, "[", seqmap_with_sep(", ",pl2tex,[X|XS]), "]". + +pl2tex(A\\B) --> !, "\\lambda ", pl2tex(A), ".", pl2tex(B). +pl2tex(@A) --> !, "@", pl2tex(A). +pl2tex(abs(A)) --> !, "|", pl2tex(A), "|". +pl2tex(A) --> {atomic(A)}, escape_with(0'\\,0'_,at(A)). +pl2tex(A) --> + {compound(A), A=..[H|T] }, + pl2tex(H), paren(seqmap_with_sep(", ",pl2tex,T)). +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prolog/update Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,2 @@ +#!/bin/sh +cp -p ~/src/sapl-2.0/{dcgu,ops,utils}.pl .
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prolog/utils.pl Fri Jan 13 15:29:02 2012 +0000 @@ -0,0 +1,960 @@ +% Some general utilities + +:- module(utils,[ + + % type testing and enumeration + natural/1 % test or enumerate natural numbers + , isfinite/1 % check number is non NaN or Inf + , int/1 % test or enumerate integers + , in/2 + + % mathematical utilities + , max/3 + , min/3 + + % list utilities + , list_idx1_member/3 % like nth1 but more useful argument order + , apply_to_nth1/4 + , measure/3 % match list lengths + , equal_length/2 % match 2 list lengths + , getopts/2 + , rep/3 % make a list of repeats of the same term + , cons/3 % list constructror + , decons/3 % list deconstructor + + % comma lists + , cl_list/2 + , cl_list_vt/2 + , cl_length/2 + , cl_length_vt/2 + , cl_member/2 + + + % term manipulation + , copy_head/2 % check terms for same head and arity + , unify_args/5 + , reinstatevars/3 + , reinstatevars/4 + + % formatting and parsing + , aphrase/2 % like phrase but makes an atom instead of a string + , aphrase/3 % like aphrase but takes code list + , print_list/1 % writes each element on a new line + , printq_list/1 % as print_list but quotes atom as necessary + , print_numbered_list/1 + + % database management + , extensible/1 % declare dynamic multifile predicate + , bt_assert/1 + , bt_retract/1 + , strict_assert/1 + , strict_retract/1 + , browse/2 % browse predicate with unknown arity + , current_state/2 + , set_state/2 + + % file system utilities + , dir/2 % directory listing + , path_atom/2 % expand paths + , expand_path/2 % expand paths + + % operating system + , open_with/2 % apply shell command to a file (SIDE EFFECTS) + , open_with/3 % apply shell command to a file with options (SIDE EFFECTS) + , shellcmd/2 % apply shell command to arguments + , open_/1 % open with 'open' command (Mac OS X). + , fmt_shellcmd/3 % format a shell command with switches and args + , hostname/1 + + % user interaction + , read_char_echo/1 % read one character and echo it immediately + , get_key/2 % read and validate keypress + , userchk/1 % unary predicate which allows user to force failure + , prompt_for_key/3 + + % ---- high order stuff ---- + + % modes of computation + , retry/1 % keep trying until success + , parallel/1 % parallel computation using threads + , bt_call/2 % Construct backtrackable operation + , on_backtracking/1 + + % iteration + , exhaust/1 % trigger all solutions to a goal (SIDE EFFECTS) + , iterate/3 % apply predicate recursively till failure + + % mapping + , for_naturals/2 % call predicate with natural numbers N down to 1 + , mapints/2 % call predicate with integers + , mapints/3 + , mapargs/2, mapargs/3, mapargs/4 + , mapargs_x/4, mapargs_x/5, mapargs_x/6 + , mapargs_xx/6 + , sfold/4 % structural fold for lists + , take/3 + , drop/3 + , drop_while/3 + , take_while/3 + + % lambda expressions + , mcall/2, mcall/3, mcall/4 + + ]). + +/** <module> General utilities + +The predicates in this module can be divided into several groups as listed +below. + +---+++ Type testing and enumeration + * natural/1 - test or enumerate natural numbers + * isfinite/1 - check number is non NaN or Inf + * int/1 - test or enumerate integers + * in/2 + +---+++ mathematical utilities + * max/3 + * min/3 + +---+++ list utilities + * list_idx1_member/3 % like nth1 but more useful argument order + * measure/3 - match list lengths + * equal_length/2 - match two list lenghts + * getopts/2 + * rep/3 - make a list of repeats of the same term + * cons/3 - make list from head and tail + * decons/3 - get head and tail from list + +---+++ term manipulation + * copy_head/2 - check terms for same head and arity + * unify_args/5 + * reinstatevars/3 + * reinstatevars/4 + +---+++ formatting and parsing + * aphrase/2 - like phrase but makes an atom instead of a string + * aphrase/3 - like aphrase but takes code list + * print_list/1 - write/1 each element, one per line + * printq_list/1 - as print_list but quotes atom as with writeq/1 + * print_numbered_list/1 - as print_list/1 but with line numbers + +---+++ database management + * extensible/1 - declare dynamic multifile predicate + * bt_assert/1 + * bt_retract/1 + * strict_assert/1 + * strict_retract/1 + * browse/2 - browse predicate with unknown arity + * current_state/2 + * set_state/2 + +---+++ file system utilities + * dir/2 - directory listing + * path_atom/2 - expand paths + * expand_path/2 - expand paths + +---+++ operating system + * open_with/2 - apply shell command to a file (SIDE EFFECTS) + * open_with/3 - apply shell command to a file with options (SIDE EFFECTS) + * shellcmd/2 - apply shell command to arguments + * open_/1 - open with 'open' command (Mac OS X). + * fmt_shellcmd/3 - format a shell command with switches and args + * hostname/1 + +---+++ user interaction + * read_char_echo/1 - read one character and echo it immediately + * get_key/2 - read and validate keypress + * userchk/1 - unary predicate which allows user to force failure + * prompt_for_key/3 - print message and get keypress from user + +---+++ High order stuff + +---++++ modes of computation + * retry/1 - keep trying until success + * parallel/1 - parallel computation using threads + * bt_call/2 - Construct backtrackable operation + , on_backtracking/1 + +---++++ iteration + * exhaust/1 - trigger all solutions to a goal (SIDE EFFECTS) + * iterate/3 - apply predicate recursively till failure + +---++++ mapping + * for_naturals/2 - call predicate with natural numbers N down to 1 + * mapints/2 - call predicate with integers + * mapints/3 + * mapargs/2, mapargs/3, mapargs/4 + * mapargs_xx/6 + * sfold/4 - structural fold for lists + * take/3 + * drop/3 + * drop_while/3 + * take_while/3 + +---++++ lambda expressions + * mcall/2, mcall/3, mcall/4 + */ + +:- use_module(library(ops)). + +:- meta_predicate + exhaust(0) + , retry(0) + , iterate(2,?,?) + , drop_while(1,?,?) + , take_while(1,?,?) + , apply_to_nth1(?,2,?,?) + , for_naturals(+,1) + , for_ints(+,+,1) + , mapints(1,?) + , mapints(2,?,?) + , mapargs(1,?) + , mapargs(2,?,?) + , mapargs(3,?,?,?) + , mapargs_x(+,+,1,?) + , mapargs_x(+,+,2,?,?) + , mapargs_x(+,+,3,?,?,?) + , mapargs_xx(2,?,?,?,?,?) + , on_backtracking(0) + , bt_call(0,0) + , bt_assert(:) + , bt_retract(:) + , strict_assert(:) + , strict_retract(:) + , extensible(:) + , aphrase(2,?) + , aphrase(2,?,?) + . + +:- multifile user:path/2. +:- multifile user:demo/1. +:- dynamic current_state/2. + + +%% extensible(+P) is det. +% declares 'extensible' predicates, ie ones that can have new clauses +% added in other files. Equivalent to dynamic(P), multifile(P). +extensible(P) :- dynamic(P), multifile(P). + + +%% natural(+N) is semidet. +%% natural(-N:natural) is multi. +% +% Means N is a natural number (includes 0). If N is +% a variable, succeeds an infinite number of times on backtracking, +% returning all natural numbers. +natural(N) :- (var(N) -> between(0,inf,N); integer(N), N>=0). + + +%% int(+N) is semidet. +%% int(-N:integer) is multi. +% +% Means N is an integer. If N is +% a variable, succeeds an infinite number of times on backtracking, +% returning all integers starting at zero and interleaving positive +% and negative values. +int(N) :- nonvar(N), integer(N). +int(N) :- var(N), (N=0; (between(1,inf,M), (N=M; N is -M))). + + +%% isfinite(+N:number) is semidet. +% +% Succeeds when N is a finite number. +isfinite(N):- catch(_ is N+0,error(_,_),fail). % !! workable hack + + +%% in(+X,Set) is semidet. +%% in(-X,Set) is nondet. +% +% Simple testing and enumration of values in some sets. +% Set can be +% * {A,B,_} +% Explicit list of values. +% * natural +% Natural numbers starting from 0. +% * integer +% Natural numbers. +% * real +% Real (floating point) numbers. +% * A..B +% Integer range from A to B inclusive. +% * A--B +% Closed interval [A,B]. +X in A--\B :- X in A--(\B). +X in \A--(\B):- !, ch(A<X), ch(X<B). +X in \A--B :- !, ch(A<X), ch(X=<B). +X in A--(\B) :- !, ch(A=<X), ch(X<B). +X in A--B :- !, ch(A=<X), ch(X=<B). +X in A..B :- integer(A), integer(B), between(A,B,X). +X in {CList} :- member_clist(X,CList). +X in natural :- natural(X). % enumerate! +X in integer :- int(X). % enumerates! +X in real :- number(X). % same as X :: real + + +ch(_ =<inf) :- !. +ch(inf=< _ ) :- !, fail. +ch(-inf=< _) :- !. +ch(_ =<(-inf)) :- !, fail. +ch(A=<B) :- !, A=<B. + +ch(inf<_ ) :- !, fail. +ch(_ <inf) :- !. +ch(_ <(-inf)) :- !, fail. +ch(-inf<_ ) :- !. +ch(A<B) :- !, A<B. + + + +%% exhaust(:Goal) is det. +% +% Repeat Goal until failure, then succeed. +exhaust(Q) :- call(Q), fail; true. + +%% iterate(+P:pred(A,A), X:A, Y:A) is semidet. +% apply P recursively to X until failure, then return final value Y. +iterate(P,X,Y) :- call(P,X,Z) -> iterate(P,Z,Y); Y=X. + +%% sfold(Functor,Initial,L:list,Final) is semidet. +% *Structural* fold applied to a term, +% rather than a relational fold using a predicate name. +sfold(_,E,[],E). +sfold(O,E,[X|XX],R) :- R=..[O,X,YY], sfold(O,E,XX,YY). + + +%% dir( +Pattern, -File) is nondet. +% +% Directory listing for directory matching Pattern. File +% names are returned one by one on backtracking. +dir(Pattern,File) :- + expand_file_name(Pattern,List), + member(File,List). + + +%% path_atom( +Spec:path_expr, -Path:atom) is nondet. +% +% Expand a 'path expression' into a flat path (an atom). +% A path expression is defined by: +% == +% path_expr ---> atom % literal path component name +% ; path_expr/atom % child of path_expr +% ; path_macro % a previously defined abbr for a path. +% == +% A path_macro is defined using path/2. + +path_atom(P,C) :- path(P,C), atom(C). + +path_atom(PA/B,C) :- + once((nonvar(C); nonvar(B); nonvar(PA))), + path_atom(PA,A), + concat3atoms(A,'/',B,C). + + +path_atom(Path,Atom) :- path(Path, Def), \+atom(Def), path_atom(Def,Atom). +path_atom(Path,Atom) :- + nonvar(Path), + \+path(Path,_), + Path\=_/_, + Atom=Path. + +concat3atoms(A,B,C,ABC) :- + nonvar(A), nonvar(B), nonvar(C), !, concat_atom([A,B,C],ABC). + +concat3atoms(_,_,_,ABC) :- var(ABC), !, fail. +concat3atoms(A,B,C,ABC) :- nonvar(C), !, atom_concat(AB,C,ABC), atom_concat(A,B,AB). +concat3atoms(A,B,C,ABC) :- nonvar(A), !, atom_concat(A,BC,ABC), atom_concat(B,C,BC). +concat3atoms(A,B,C,ABC) :- + maplist(atom_codes,[B,ABC],[BX,ABCX]), + append(ABX,CX,ABCX), + append(AX,BX,ABX), + maplist(atom_codes,[A,C],[AX,CX]). + +%% expand_path( +P:path_expr, -FP:atom) is semidet. +% +% Expand path_exp including wildcards to fully qualified path +expand_path(P,FP) :- path_atom(P,PP), expand_file_name(PP,[FP]). + + +%% open_with( +Program:atom, +Thing:path_expr, +Options:list) is semidet. +%% open_with( +Program:atom, +Thing:path_expr) is semidet. +% +% The only option is bg, which adds "&" to make command execute in background. +open_with(Q,P) :- open_with(Q,P,[]). +open_with(Q,P,Opts) :- + expand_path(P,FP), + (member(bg,Opts) -> OO=' &'; OO=''), + sformat(S,'~w ~q~w',[Q,FP,OO]), + shell(S). + +%% open_( +Thing:path_expr) is semidet. +% Equivalent to open_with(open,Thing). +open_(P) :- open_with(open,P). + +%% shellcmd( +Head:atom, +Args:list(atom)) is det. +% +% Execute a shell command on a given list of arguments +shellcmd(Head,Args) :- + concat_atom([Head|Args],' ',Cmd), + shell(Cmd,_Status). + +%% fmt_shellcmd( +Prog:atom, +Args:list(shellarg), -Cmd) is det. +% make a shell command string. +fmt_shellcmd(Prog,Args,Cmd) :- + phrase(utils:shellcmd(l_(Args)),FArgs), + concat_atom([Prog|FArgs],' ',Cmd). + +shellcmd(l_([])) --> !, []. +shellcmd(l_([H|T])) --> !, shellcmd(H), shellcmd(l_(T)). +shellcmd(s(N,V)) --> !, shellcmd(s(N)), shellcmd(V). +shellcmd(q(X)) --> !, { concat_atom(['"',X,'"'],A) }, [A]. +shellcmd(s(N)) --> !, { + (atom_codes(N,[_]) -> F='-' ; F='--'), + atom_concat(F,N,A) }, [A]. +shellcmd(l(X)) --> [X]. + + + +%% read_char_echo( -C:atom) is det. +% +% Read a single character from the current input, +% echo it to the output. +read_char_echo(C) :- + get_single_char(Code), + put_code(Code), flush_output, + char_code(C,Code). + + + +%% set_state( +Key, +Value) is det. +% +% Maintains a mutable global set of Key-Value pairs, sets the value +% associated with Key to Value. +set_state(Flag,Value) :- + ground(Flag), + retractall(current_state(Flag,_)), + assert(current_state(Flag,Value)). + + +%% current_state( -Key, -Value) is nondet. +%% current_state( +Key, -Value) is semidet. +% +% Lookup the value associated with Key, or enumerate all the +% key value pairs. + + + +%% parallel( +List:list(query)) is semidet. +% +% Use this by giving a list of queries of the form +% [Vars2:Goal, Vars2:Goal2, ...] +% where Vars is the term that each thread must return +% when it has finished computing its Goal. The +% parallel predicate finishes when all the threads +% have finished, and should result in all the Vars +% being bound appropriately. + +parallel(Queries) :- + maplist(async,Queries,Collecters), + maplist(call,Collecters). + +% these are used to initiate and wait for each +% computation thread. +async_collect(Id,X:_) :- thread_join(Id,exited(X)). +async(X:Goal,utils:async_collect(Id,X:_)) :- + thread_create((Goal,thread_exit(X)),Id,[]). + +%% browse( +PredSpec, -Goal) is nondet. +% +% PredSpec is a term like (PredicateName/Arity). Goal +% is unified with solutions of PredicateName/Arity. +browse(P/A,Goal) :- + current_predicate(P/A), + length(L,A), + Goal=..[P|L], + call(Goal). + + + +%% aphrase(P:phrase(code), -A:atom, +S:list(code)) is nondet. +%% aphrase(P:phrase(code), +A:atom, -S:list(code)) is nondet. +%% aphrase(P:phrase(code), -A:atom, -S:list(code)) is nondet. +%% aphrase(P:phrase(code), -A:atom) is nondet. +% +% Generate or parse an atom using given DCG phrase P. +% aphrase(P,A) is equivalent to aphrase(P,A,_). +aphrase(X,A) :- aphrase(X,A,_). +aphrase(X,A,S) :- + ( ground(A) + -> atom_codes(A,S), phrase(X,S) + ; phrase(X,S), atom_codes(A,S)). + + +%% print_list( +L:list) is det. +% +% Print a list, one item per line. +print_list([]) :- writeln('~'), nl. +print_list([H|T]) :- print(H), nl, print_list(T). + +%% printq_list( +L:list) is det. +% +% Print a list, one item per line, as with writeq/1. +printq_list([]) :- writeln('~'), nl. +printq_list([H|T]) :- writeq(H), nl, printq_list(T). + +%% print_numbered_list( +L:list) is det. +% +% Print a list with numbered lines. +print_numbered_list(L) :- + length(L,Max), + number_codes(Max,MC), + length(MC,Width), + print_num_list(Width,1,L). + +print_num_list(_,_,[]) :- nl. +print_num_list(Width,N,[H|T]) :- succ(N,M), + copy_term(H,H1), + numbervars(H1,0,_), + number_codes(N,NC), " "=[Pad], + padleft(Pad,Width,NC,PNC), + format('~s. ~q\n',[PNC,H1]), + print_num_list(Width,M,T). + +padleft(_,W,In,In) :- length(In,W). +padleft(P,W,In,[P|Out]) :- succ(V,W), padleft(P,V,In,Out). + + + + +%% get_key( +Valid:list(char), -C:char) is det. +% +% Get and validate a key press from the user. The character +% must be one of the ones listed in Valid, otherwise, an +% error message is printed and the user prompted again. +get_key(Valid,C) :- + read_char_echo(D), nl, + ( member(D,Valid) -> C=D + ; D='\n' -> get_key(Valid,C) % this improves interaction with acme + ; format('Unknown command "~q"; valid keys are ~q.\n', [D,Valid]), + write('Command? '), + get_key(Valid,C)). + + +%% userchk(T) is semidet. +% +% Write T and ask this user if it is ok. User presses y or n. +% userchk succeeds if if the keypress was y and fails if it was n. +userchk(T) :- prompt_for_key(T,[y,n],y). + + +%% prompt_for_key( +Msg:atom, +Keys:list(char), -Key:char) is semidet. +% +% Prompt user for a keypress. Prompt message is Msg, and valid keys are +% listed in Keys. +prompt_for_key(Msg,Keys,Key) :- format('~p ~q? ',[Msg,Keys]), get_key(Keys,Key). + +% ------------------- TERM MANIPULATION ------------------------------ + + +%% copy_head(+T1,-T2) is det. +%% copy_head(+T1,+T2) is semidet. +% +% true if T1 and T2 have the same head and arity +copy_head(T1,T2) :- functor(T1,N,A), functor(T2,N,A). + + + +%% reinstatevars( F:atom, V:list, Eh, What) is nondet. +%% reinstatevars( V:list, Eh, What) is nondet. +% +% Reverse of numbervars. Each '$VAR'(N) subterm of X is replaced +% with the Nth element of V, which can be uninstantiated on entry +% reinstatevars/4 uses an arbitrary functor F instead of $VAR. + +reinstatevars(V,'$VAR'(N),Y) :- !, nth0(N,V,Y). +reinstatevars(_,X,Y) :- atomic(X), !, Y=X. +reinstatevars(V,X,Y) :- mapargs(reinstatevars(V),X,Y). + +reinstatevars(F,V,T,Y) :- functor(T,F,1), !, arg(1,T,N), nth0(N,V,Y). +reinstatevars(_,_,X,Y) :- atomic(X), !, Y=X. +reinstatevars(F,V,X,Y) :- mapargs(reinstatevars(F,V),X,Y). + + +%% unify_args( Src, SrcIndex, Dest, DestIndex, Num) is det. +% +% this unifies N consecutive arguments of Src and Dest starting +% from SI and DI in each term respectively +unify_args(_,_,_,_,0). +unify_args(Src,SI,Dest,DI,N) :- + arg(SI,Src,X), arg(DI,Dest,X), !, + succ(SI,SI2), succ(DI,DI2), succ(N2,N), + unify_args(Src,SI2,Dest,DI2,N2). + + +% ---------------------- LIST UTILITIES ----------------------------- + + +%member_clist(_,Z) :- var(Z), !, fail. +member_clist(A,A) :- A\=(_,_). +member_clist(A,(A,_)). +member_clist(A,(_,B)) :- member_clist(A,B). + + + +%% measure(Ruler,In,Out) is det. +% true if Out is the same length as Ruler but matches In as far as possible +measure([],_I,[]). +measure([_|R],[],[_|O]) :- measure(R,[],O). +measure([_|R],[X|I],[X|O]) :- measure(R,I,O). + +%% equal_length( ?In, ?Out) is nondet. +% equal_length( +L1:list, -L2:list) is det. +% equal_length( -L1:list, +L2:list) is det. +% +% True if L1 and L2 are the same length. +equal_length([],[]). +equal_length([_|T1],[_|T2]) :- equal_length(T1,T2). + +%split_at(0,T,I-I,T). +%split_at(N,[H|T],[H|I1]-Z,T1) :- succ(M,N), split_at(M,T,I1-Z,T1). + +%split_at2(0,T,I-I,T). +%split_at2(N,[H|T],[H|I1]-Z,T1) :- split_at2(M,T,I1-Z,T1), succ(M,N). + +%% max(+X:number, +Y:number, -Z:number) is det. +% +% Unify Z with the larger of X and Y. Legal values are +% any numerical value or inf or -inf. +max(_,inf,inf) :- !. +max(inf,_,inf) :- !. +max(X,-inf,X) :- !. +max(-inf,X,X) :- !. +max(X,Y,Z) :- X<Y -> Z=Y; Z=X. + +%% max(+X:number, +Y:number, -Z:number) is det. +% +% Unify Z with the larger of X and Y. Legal values are +% any numerical value or inf or -inf. +min(_,-inf,-inf) :- !. +min(-inf,_,-inf) :- !. +min(X,inf,X) :- !. +min(inf,X,X) :- !. +min(X,Y,Z) :- X<Y -> Z=X; Z=Y. + +%% list_idx1_member( +L:list(A), +N:natural, ?X:A) is semidet. +%% list_idx1_member( ?L:list(A), ?N:natural, ?X:A) is nondet. +% +% Equivalent to nth1(N,L,X). +list_idx1_member(L,I,X) :- nth1(I,L,X). + + +%% getopts( +Opts:list(option), ?Spec:list(optspec)) is det. +% +% Get value from option list. +% == +% option(A) ---> term(A). +% optspec ---> option(A)/A. +% == +getopts(OptsIn,Spec) :- maplist(getopt(OptsIn),Spec). +getopt(OptsIn,Option/Default) :- option(Option,OptsIn,Default). + +%% cons( ?Head:A, ?Tail:list(A), ?List:list(A)) is det. +% +% List constructor. +cons(H,T,[H|T]). + +%% decons( ?Head:A, ?List:list(A), ?Tail:list(A)) is det. +% +% List deconstructor. +decons(H,[H|T],T). + +% ---------------------- MAPPING, HIGH ORDER STUFF --------------------- + +%% for_naturals(+N:natural, P:pred(natural)) is nondet. +% apply predicate to each natural number from 1 to N (backwards) +for_naturals(0,_). +for_naturals(N,P) :- succ(M,N), call(P,N), for_naturals(M,P). + +%% mapints( +P:pred(integer,A), +R:intrange, -X:list) is nondet. +%% mapints( +P:pred(integer), +R:intrange) is nondet. +% +% Mapping predicates over lists of integers. Range is like M..N. +% mapints/3 maps 2 argument predicate over implicit list of +% integers M..N and explicit list of values X. +mapints(_,M..N) :- N<M, !. +mapints(P,M..N) :- call(P,M), plus(M,1,L), mapints(P,L..N). + +mapints(_,M..N,[]) :- N<M, !. +mapints(P,M..N,[X|T]) :- call(P,M,X), plus(M,1,L), mapints(P,L..N,T). + +%% rep( +N:natural, ?X:A, -L:list(A)) is det. +%% rep( -N:natural, ?X:A, -L:list(A)) is multi. +% Make a list consisting of N repeats of the same term. If called +% with N unbount, creates progressively longer and longer lists +% on backtracking. +rep(0,_,[]). +rep(N,A,[A|X]) :- + ( nonvar(N) + -> succ(M,N), rep(M,A,X) + ; rep(M,A,X), succ(M,N) + ). + +%% mapargs( P:pred(A,B,C), T1:tuple(F,A), T2:tuple(F,B), T3:tuple(F,C)) is nondet. +%% mapargs( P:pred(A,B), T1:tuple(F,A), T2:tuple(F,B)) is nondet. +%% mapargs( P:pred(A), T1:term) is nondet. +% +% Map predicate over to args of a term preserving head. +% A tuple(F,A) is a term with head functor F and any number of arguments +% of type A, ie +% == +% tuple(F,A) ---> F ; F(A) ; F(A,A) ; F(A,A,A) ; .. . +% == + +mapargs(P,T1) :- + functor(T1,_,N), + mapargs_x(1,N,P,T1). + +mapargs(P,T1,T2) :- + ( nonvar(T1) + -> functor(T1,F,N), functor(T2,F,N) + ; functor(T2,F,N), functor(T1,F,N)), + mapargs_x(1,N,P,T1,T2). + +mapargs(P,T1,T2,T3) :- + functor(T1,F,N), + functor(T2,F,N), + functor(T3,F,N), + mapargs_x(1,N,P,T1,T2,T3). + +mapargs_x(I,N,P,T1) :- + ( I>N -> true + ; arg(I,T1,X1), + call(P,X1), + succ(I,J), mapargs_x(J,N,P,T1)). + +mapargs_x(I,N,P,T1,T2) :- + ( I>N -> true + ; arg(I,T1,X1), + arg(I,T2,X2), + call(P,X1,X2), + succ(I,J), mapargs_x(J,N,P,T1,T2)). + +mapargs_x(I,N,P,T1,T2,T3) :- + ( I>N -> true + ; arg(I,T1,X1), + arg(I,T2,X2), + arg(I,T3,X3), + call(P,X1,X2,X3), + succ(I,J), mapargs_x(J,N,P,T1,T2,T3)). + +%% drop( +N:natural, +In:list(A), -Out:list(A)) is det. +drop(0,T,T). +drop(N,[_|T],V) :- succ(M,N), drop(M,T,V). + + +%% take( +N:natural, +In:list(A), -Out:list(A)) is det. +take(N,T,X) :- length(X,N), append(X,_,T). + + +%% drop_while( +P:pred(A), +In:list(A), -Out:list(A)) is det. +% +% Remove all elements from head of In that are accepted by P +% and return the remained in Out. +drop_while(P,[X|T],V) :- call(P,X) -> drop_while(P,T,V); V=[X|T]. + + +%% take_while( +P:pred(A), +In:list(A), -Out:list(A)) is det. +% +% Remove all elements from head of In that are accepted by P +% and return them in Out. +take_while(P,[X|T],O) :- call(P,X) -> O=[X|V], take_while(P,T,V); O=[]. + + + +%% retry( :Goal) is det. +% +% Keep retrying Goal until it succeeds. Only makes sense if Goal +% has side effects. Might be nonterminating. +retry(G) :- once((repeat,G)). + +%% apply_to_nth1( N:natural, Op:pred(A,A), +In:list(A), +Out:list(A)) is nondet. +% +% Apply predicate Op to the N th element of list In and unify Out with the result. +%apply_to_nth1(N,Op,Old,Init) :- +% ( nonvar(N) +% -> succ(M,N), split_at(M,Old,Init-[Y|Tail],[X|Tail]) +% ; split_at2(M,Old,Init-[Y|Tail],[X|Tail]), succ(M,N) +% ), +% call(Op,X,Y). + +apply_to_nth1(1,P,[X|XX],[Y|XX]) :- call(P,X,Y). +apply_to_nth1(N,P,[X|X1],[X|Y1]) :- nonvar(N), !, N>1, succ(M,N), apply_to_nth1(M,P,X1,Y1). +apply_to_nth1(N,P,[X|X1],[X|Y1]) :- var(N), !, apply_to_nth1(M,P,X1,Y1), succ(M,N). + + +%% mapargs_xx( +P:pred(A,B), +Src:term(_,A), +SrcIndex:natural, +Dest:term(_,B), +DestIndex:natural, +N:natural) is nondet. +% +% Maps predicate P over N consecutive arguments of Src and Dest. Starts +% at SrcIndex th argument of Src and DestIndex th argument of Dest. +mapargs_xx(_,_,_,_,_,0). +mapargs_xx(Pred,Src,SI,Dest,DI,N) :- + arg(SI,Src,SX), arg(DI,Dest,DX), call(Pred,SX,DX), !, + succ(SI,SI2), succ(DI,DI2), succ(N2,N), + mapargs_xx(Pred,Src,SI2,Dest,DI2,N2). + + +%% mcall(P:pred(A), X:A) is nondet. +%% mcall(P:pred(A,B), X:A, Y:B) is nondet. +%% mcall(P:pred(A,B,C), X:A, Y:B, Z:C) is nondet. +%% mcall(P:pred(A,B,C,D), X:A, Y:B, Z:C, W:D) is nondet. +% +% Like call/N but P can additionally be a lambda expression in one of several +% forms: +% * Tuple :- Body +% If functor(Tuple,\,N), Body is executed after unifying tuple arguments +% with arguments to mcall, eg =mcall(\(X):-member(X,[a,b,c]),Y)= is equivalent +% to member(Y,[a,b,c]), or =mcall(\(X,Y):-nth1(X,[a,b,c],Y),2,c)=. +% * Tuple +% Equivalent to Tuple:-true. + +mcall(P,A) :- mc(P,\(A),Q), Q. +mcall(P,A,B) :- mc(P,\(A,B),Q), Q. +mcall(P,A,B,C) :- mc(P,\(A,B,C),Q), Q. +mcall(P,A,B,C,D) :- mc(P,\(A,B,C,D),Q), Q. + +mc(Tuple:-Body,Params,Goal) :- !, copy_term(Tuple/Body,Params/Goal). +mc(Tuple,Params,true) :- functor(Tuple,\,_), !, copy_term(Tuple,Params). +mc(P,Params,apply(P,Args)) :- Params=..[\|Args]. + + +%% on_backtracking( :Goal) is det. +% +% The first time this is called, it succeeds and does nothing. +% On backtracking, Goal is called and then a failure is generated. + +on_backtracking(_). +on_backtracking(P) :- P, !, fail. + + +%% bt_call( :Do, :Undo) is nondet. +% +% Creates a backtrackable operation from a non-backtrackable Do +% operation and a corresponding operation to undo it. Do can +% be non-deterministic, in which case bt_call(Do,Undo) will also +% have multiple solutions. Undo is called inside once/1. +% +% bt_call/2 is implemented both as a predicate and as a goal +% expansion (see goal_expansion/2). +bt_call(Do,Undo) :- Do, (true; once(Undo), fail). + +user:goal_expansion( bt_call(Do,Undo), (Do, (true; once(Undo), fail))). + + + +/* Might include these at some point + +% apply lambda term to another term +app(X\\F,Y,G) :- !, copy_term(X\\F,Y\\G). +app(T,A,Z) :- addargs(T,[A],Z). +app(T,A,B,Z) :- addargs(T,[A,B],Z). +app(T,A,B,C,Z) :- addargs(T,[A,B,C],Z). + +applist(F,N,A,Z) :- length(Z,N), maplist(app(F),A,Z). +applist(F,N,A,B,Z) :- length(Z,N), maplist(app(F),A,B,Z). +applist(F,N,A,B,C,Z) :- length(Z,N), maplist(app(F),A,B,C,Z). + +*/ + + +% ------------------ DATABASE ------------------------------ + +%% bt_assert(Clause) is det. +% Backtrackable assert. +bt_assert(H) :- bt_call(assert(H),retract(H)). + +%% bt_retract(Clause) is det. +% Backtrackable retract. +bt_retract(H) :- bt_call(retract(H), assert(H)). + +%% strict_assert(Fact) is semidet. +% +% Asserts fact only if it is not already true. Fails +% if fact is already provable. Retracts fact on backtracking. +strict_assert(H) :- \+call(H), bt_call(assert(H),retract(H)). + + +%% strict_retract(Fact) is semidet. +% +% Retracts fact only if it is currently in the database. Fails +% if fact is not provable. Reasserts fact on backtracking. +strict_retract(H) :- call(H), bt_call(retract(H), assert(H)). + + +% when loaded, this sets the hostname/1 predicate. +:- dynamic hostname/1. + +%% hostname( -A:atom) is det. +% +% Unifies A with the computer's hostname. This is set when the +% module is loaded by calling the system command 'hostname -s'. + +% init_hostname is det - read hostname from UNIX command hostname. +init_hostname :- + setup_call_cleanup( + open(pipe('hostname -s'),read,SID), + (read_line_to_codes(SID,C), atom_codes(H,C), retractall(hostname(_)), assert(hostname(H))), + close(SID)). + +:- ( hostname(H) + -> format('% hostname already set to ~w\n',[H]) + ; init_hostname, hostname(H), format('% hostname set to ~w\n',[H]) + ). + +% Comma lists +% ie, lists built up using (,) as a pairing functor +% Note, these functor lists do NOT have a nil element - the +% last item in the list is the 2nd argument to the final +% functor term, which can therefore be a term headed by any +% other functor. Eg: +% (1,(2,3)) <-> [1,2,3] +% (1,(2,(3+4)) <-> [1,2,(3+4)] + +%% cl_list( +CL:clist(A), -L:list(A)) is det. +%% cl_list( -CL:clist(A), +L:list(A)) is det. +% +% Convert between comma lists and ordinary lists +cl_list((A,B),[A|BL]) :- cl_list(B,BL). +cl_list(A,[A]) :- A\=(_,_). + +%% cl_length( +L:clist, -N:natural) is det. +%% cl_length( -L:clist, +N:natural) is det. +% +% Length of a comma-list. +cl_length((_,B),N) :- cl_length(B,M), succ(M,N). +cl_length(X,1) :- X\=(_,_). + + +%% cl_list_vt( +CL:clist(A), -L:list(A)) is det. +%% cl_list_vt( -CL:clist(A), +L:list(A)) is det. +% +% Convert between comma lists (with open tails) and ordinary lists. +cl_list_vt(FL,[FL]) :- var(FL), !. +cl_list_vt(FL,[A|BL]) :- FL = (A,B), cl_list_vt(B,BL). +cl_list_vt(A,[A]) :- A\=(_,_). + + +%% cl_length_vt( +L:clist, -N:natural) is det. +%% cl_length_vt( -L:clist, +N:natural) is det. +% +% Length of a comma-list with possible variable tail. +% This version handles lists where the last element is variable (counts as 1) +cl_length_vt(FL,1) :- var(FL), !. +cl_length_vt(FL,N) :- FL=(_,B), cl_length_vt(B,M), succ(M,N). +cl_length_vt(FL,1) :- FL\=(_,_). + +%% cl_member(-X, +L:clist) is nondet. +% List membership for comma lists. +cl_member(X,(X,_)). +cl_member(X,(_,T)) :- cl_member(X,T). +cl_member(X,X) :- X\=(_,_). +