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\=(_,_).
+