diff options
50 files changed, 7925 insertions, 0 deletions
@@ -0,0 +1,4 @@ +tddpirate: Omer Zak <w1@zak.co.il> +# +# Created the PyGuile package. +# @@ -0,0 +1,33 @@ +Installation Instructions +************************* + +Copyright (C) 2008 Omer Zak. + +This file is free documentation; Omer Zak gives unlimited permission to +copy, distribute and modify it. + +Dependencies +============ +Before installing the package, ensure that you have the following +dependencies installed: +1. Python 2.4.x (if you use a different version of Python, edit the + values assigned to PYINC and PYLIB in the Makefile). +2. The CPAN module TAP::Harness (only if you intend to run tests). + If necessary, edit the value assigned to TEST_LIBDIRS to point + at the lib directory where this package is installed. +3. Guile 1.6.x (if you use a different version of Guile and encounter + problems, let me know about this). + +Basic Installation +================== +1. Gunzip and untar the archive file into a directory. + You will see a subdirectory having name of the form pyguile-x.y.z. +2. `cd' into this subdirectory. +3. Type `make' to build the library file `libpyguile.so'. +4. Optionally, type `make check' to run the package's self tests. +5. Currently, there is no `make install'. + You'll have to manually copy the library file `libpyguile.so' and + the module definition file `pyguile.scm' to a location where they + will be found by Guile. + You can obtain the locations searched by Guile by invoking the + command `(display %load-path)(newline)'. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2cef720 --- /dev/null +++ b/Makefile @@ -0,0 +1,146 @@ +# Infrastructure for invoking Python from Guile, via extension +# functions. +######################################################################## +# +# Copyright (C) 2008 Omer Zak. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library, in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA +# +# For licensing issues, contact <w1@zak.co.il>. +# +######################################################################## +# +# make +# Build the software +# +# make coverage +# Build a special version which includes coverage analysis code +# +# make clean +# Clean intermediate files +# +# make distclean +# Clean also targets +# +# make check +# Run all tests in the test suite +# +# make gcov.out +# Summarize coverage analysis files into gcov.out +# +######################################################################## + +#PYVERSION = `python -V 2>&1 | cut -d\ -f2 | cut -d. -f1-2` +PYVERSION = `python -c "import sys;sys.stdout.write(sys.version[:3])"` + +PYINC = -I/usr/include/python$(PYVERSION) +PYLIB = -lpython$(PYVERSION) +PERL = /usr/bin/perl +TEST_VERBOSE = 0 +TEST_FILES = t/*.t +TEST_LIBDIRS = +RUN_GUILE_TESTS = ./t/scripts/RunGuileTests.pl +EXTRACT_CONVERSION_FUNCTIONS_PY = ./extract_conversion_functions.py +EXTRACT_CONVERSION_FUNCTIONS = $(EXTRACT_CONVERSION_FUNCTIONS_PY) --inc +EXTRACT_CONVERSION_EXPORTS = $(EXTRACT_CONVERSION_FUNCTIONS_PY) --scm pyguile.scm.in + +CDEBUG = -g -Wall +CFLAGS = $(CDEBUG) `guile-config compile` $(PYINC) $(GCOVFLAGS) +CPPFLAGS = `guile-config compile` $(PYINC) +LDFLAGS = `guile-config link` $(GCOVFLAGS) +RM = rm -v + + +SRCS = pyguile.c pysmob.c pytoguile.c guiletopy.c g2p2g_smob.c verbose.c pyscm.c +TARGETS = libpyguile.so pyguile.scm + +all: $(TARGETS) +all: GCOVFLAGS = +coverage: $(TARGETS) +coverage: GCOVFLAGS = -fprofile-arcs -ftest-coverage + +libpyguile.so: $(SRCS:.c=.o) + $(CC) -shared $(LDFLAGS) -o $@ $^ $(PYLIB) $(LIBS) + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ -fPIC $< + +guiletopy.inc: guiletopy.c $(EXTRACT_CONVERSION_FUNCTIONS_PY) + $(EXTRACT_CONVERSION_FUNCTIONS) < $< > $@ + +pytoguile.inc: pytoguile.c $(EXTRACT_CONVERSION_FUNCTIONS_PY) + $(EXTRACT_CONVERSION_FUNCTIONS) < $< > $@ + +pyguile.scm: pyguile.scm.in guiletopy.c pytoguile.c $(EXTRACT_CONVERSION_FUNCTIONS_PY) + cat guiletopy.c pytoguile.c | $(EXTRACT_CONVERSION_EXPORTS) > $@ + +version.h: BUILD + touch BUILD + mv BUILD BUILD~ + echo $$(( 1 + `cat BUILD~` )) > BUILD + echo "#define PYGUILE_VERSION \"0.3.1\"" > $@ + echo "#define PYGUILE_BUILD \""`cat $<`"\"" >> $@ + +.build: + echo 1 > $@ + echo Initial build number file was created + +######################################################################## +# Clean up + +FILES_TO_CLEAN = *~ *.d *.inc *.o core *.pyc \ + version.h \ + *.gcda *.gcno *.gcov gcov.out +clean: + -$(RM) $(FILES_TO_CLEAN) + -cd t; pwd; $(RM) $(FILES_TO_CLEAN) + -cd t/scripts; pwd; $(RM) $(FILES_TO_CLEAN) + +distclean: clean + -$(RM) $(TARGETS) + +######################################################################## +# Test and coverage analysis + +check: $(TARGETS) + $(PERL) $(TEST_LIBDIRS) $(RUN_GUILE_TESTS) $(TEST_FILES) + +gcov.out: + gcov -a -l -p *.c + -$(RM) *usr*include*.h.gcov + cat *.gcov > gcov.out + +manualcheck: $(TARGETS) + for testfile in $(TEST_FILES); do guile -s $$testfile; done + +######################################################################## +# Build dependencies + +-include $(SRCS:.c=.d) + +%.d: %.c + @set -e; rm -f $@; \ + $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ + sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ + rm -f $@.$$$$ + +# We want to force those *.inc files to be created before building +# the *.d files. +pyguile.d: version.h +pytoguile.d: pytoguile.inc +guiletopy.d: guiletopy.inc + +######################################################################## +# End of PyGuile Makefile @@ -0,0 +1,4 @@ +Shlomi Fish <shlomif@iglu.org.il> + * For the initial impetus for actually working on the project. + * For testing an initial version of PyGuile on a system with + Python2.5 and Guile 1.8.5. @@ -0,0 +1,37 @@ +Roadmap for future PyGuile versions: + +* Make PyGuile work with Python 2.7, Python 3, guile 1.8, and guile 2.0. + +* Option for turning on/off verbosity. + Infrastructure: DONE + Modules: DONE (except for two functions in pytoguile.c) + Testing: partially done + +* Allow Python objects (PyObject *) to own Guile objects (SCM). + Function objects: DONE + Objects with attributes: not done (implemented but not tested) + +* Allow Python code to invoke Guile functions. + DONE. + +* Macros which will allow Python code to be invoked from Guile without + special keywords or data formats. Introspection of imported Python + modules to allow Guile to recognize Python code without having to do + anything special. + - Also, natural way to set/fetch values of Python variables. + +* Support for lazy conversion between heavy Python and Guile values + +Roadmap for the build process: + +* libtool/Autoconf/Automake + +* Deb packaging auxiliary files + +* RPM packaging auxiliary files + +Roadmap for other similar libraries: + +* Support for invoking Perl code from Guile. + +* Support for other Scheme implementations. diff --git a/extract_conversion_functions.py b/extract_conversion_functions.py new file mode 100755 index 0000000..c20f1e1 --- /dev/null +++ b/extract_conversion_functions.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# +# extract_conversion_functions.py +# +# Extract pytoguile and guiletopy conversion functions, and create +# calls to bind_g2p_function/bind_p2g_function as needed. +# +# This script is an auxiliary script for building PyGuile. +# +######################################################################## +# +# Copyright (C) 2008 Omer Zak. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library, in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA +# +# For licensing issues, contact <w1@zak.co.il>. +# +######################################################################## +# $Id: $ +######################################################################## + +import re +import sys +import exceptions +import time +from optparse import OptionParser + +######################################################################## + +class error(exceptions.Exception): + def __init__(self,*args): + self.args = args + +######################################################################## +# Common code +######################################################################## + +def extract_fnames(inptext=None): + """Extract g2p and p2g function names from the input text. + The return value is 2-tuple of (g2p_funcs,p2g_funcs), each + element being list of the names of the corresponding + functions. + """ + g2p_pattern = re.compile(r'PyObject\s+\*\s+(g\w+)\s*\(\s*SCM\s+[^)]+\)',re.S) + p2g_pattern = re.compile(r'SCM\s+(p\w+)\s*\(\s*PyObject\s*\*\s*[^)]+\)',re.S) + + g2p_funcs = g2p_pattern.findall(inptext) + p2g_funcs = p2g_pattern.findall(inptext) + + return((g2p_funcs,p2g_funcs)) + +######################################################################## + +def extract_converters(stdin=sys.stdin,stdout=sys.stdout,stderr=sys.stderr): + """Extract function declarations for *.inc files, as needed + by the PyGuile project. + """ + (g2p_funcs,p2g_funcs) = extract_fnames(inptext=stdin.read()) + tstamp = time.strftime("%Y%b%d-%H:%M:%S") + + if (len(g2p_funcs) > 0): + stdout.write("// Registration of guiletopy functions\n") + stdout.write("// Autogenerated at date %s\n" % tstamp) + stdout.write("void\nbind_g2p_functions(void)\n{\n") + for g2p_func in g2p_funcs: + if (g2p_func == "guile2python"): + stdout.write(""" guile2python_smob = scm_permanent_object(bind_g2p_function(%(fname)s,"%(fname)s"));\n""" + % { "fname" : g2p_func}) + stdout.write(""" guile2python_template_default = scm_permanent_object(scm_list_1(guile2python_smob));\n""") + stdout.write(""" guile2python_pair_template_default = scm_permanent_object(scm_cons(guile2python_template_default,guile2python_template_default));\n""") + stdout.write(""" guileassoc2pythondict_default = scm_permanent_object(scm_c_make_hash_table (2));\n""") + stdout.write(""" g2p_alist_template_default = scm_permanent_object(scm_list_1(scm_cons(guile2python_smob,guile2python_smob)));\n""") + else: + stdout.write(""" bind_g2p_function(%(fname)s,"%(fname)s");\n""" + % { "fname" : g2p_func}) + stdout.write("}\n") + stdout.write("// End of guiletopy functions\n") + if (len(p2g_funcs) > 0): + stdout.write("// Registration of pytoguile functions\n") + stdout.write("// Autogenerated at date %s\n" % tstamp) + stdout.write("void\nbind_p2g_functions(void)\n{\n") + for p2g_func in p2g_funcs: + if (p2g_func == "python2guile"): + stdout.write(""" python2guile_smob = scm_permanent_object(bind_p2g_function(%(fname)s,"%(fname)s"));\n""" + % { "fname" : p2g_func}) + stdout.write(""" python2guile_dict_default = scm_permanent_object(scm_cons(python2guile_smob,python2guile_smob));\n""") + else: + stdout.write(""" bind_p2g_function(%(fname)s,"%(fname)s");\n""" + % { "fname" : p2g_func}) + stdout.write("}\n") + stdout.write("// End of pytoguile functions\n") + +######################################################################## + +def extract_exports(template=None,stdin=sys.stdin,stdout=sys.stdout,stderr=sys.stderr): + """Prepare list of exports for pyguile.scm. + """ + (g2p_funcs,p2g_funcs) = extract_fnames(inptext=stdin.read()) + tstamp = time.strftime("%Y%b%d-%H:%M:%S") + + exports = "(export " + " ".join(g2p_funcs) + " " + " ".join(p2g_funcs) + ")\n" + + stdout.write(template % {"timestamp" : tstamp, "autogeneratedexports" : exports}) + +######################################################################## +# Main Program +######################################################################## + +def main(argv=None): + """Main program of the script - used to test the actual script code. + """ + op = OptionParser() + op.add_option("-i","--input",action="store", + dest="input",default=None, + help="Name of input file. Default is sys.stdin.") + op.add_option("-o","--output",action="store", + dest="output",default=None, + help="Name of output file. Default is sys.stdout.") + op.add_option("-e","--error",action="store", + dest="error",default=None, + help="Name of error output file. Default is sys.stderr.") + + op.add_option("--inc",action="store_true", + dest="inc",default=False, + help="Create *.inc file.") + op.add_option("--scm",action="store", + dest="scm",default=None, + help="Create *.scm file. The argument is the corresponding .scm.in file to be used as template.") + + (options,args) = op.parse_args(argv[1:]) + + # Process input/output/error files' parameters. + + if (options.input == None): + stdin = sys.stdin + else: + stdin = open(options.input) + if (options.output == None): + stdout = sys.stdout + else: + stdout = open(options.output,'w') + if (options.error == None): + stderr = sys.stderr + else: + stderr = open(options.error,'w') + + if (options.inc): + extract_converters(stdin=stdin,stdout=stdout,stderr=stderr) + elif (options.scm != None): + scminfile = open(options.scm) + scmintxt = scminfile.read() + scminfile.close() + extract_exports(template=scmintxt,stdin=stdin,stdout=stdout,stderr=stderr) + return(0) + +######################################################################## + +if (__name__ == '__main__'): + sys.exit(main(sys.argv)) + +else: + # I have been imported as a module. + pass + +# End of extract_conversion_functions.py diff --git a/g2p2g_smob.c b/g2p2g_smob.c new file mode 100644 index 0000000..14a50c7 --- /dev/null +++ b/g2p2g_smob.c @@ -0,0 +1,164 @@ +// g2p2g_smob implementation +// +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// +// +// Implements the G2P2G_SMOB type, which encapsulates g2p and p2g +// conversion routines, for use in defining templates for converting +// arguments and results of Python functions. + +#include "g2p2g_smob.h" + +static scm_t_bits tag_g2p2g_smob; + +//////////////////////////////////////////////////////////////////////// +// Private functions +//////////////////////////////////////////////////////////////////////// + +// static SCM mark_g2p2g_smob(SCM g2p2g_smob) +// not needed, as the default will be used. + +// static size_t free_g2p2g_smob(SCM g2p2g_smob) +// not needed, as the default will be used. + +static int +print_g2p2g_smob(SCM smob, SCM port, scm_print_state* prstate) +{ + if (!SCM_SMOB_PREDICATE(tag_g2p2g_smob,smob)) { + scm_wrong_type_arg("print-g2p2g-smob",SCM_ARG1,smob); // NOT COVERED BY TESTS + } + if (!SCM_PORTP(port)) { + scm_wrong_type_arg("print-g2p2g-smob",SCM_ARG2,port); // NOT COVERED BY TESTS + } + // I don't know how to validate the 3rd argument. + + const char *name = (const char *) SCM_CELL_WORD_2(smob); + scm_puts("'",port); + scm_puts(name,port); + return(1); // Nonzero means success. +} + +// static SCM equalp_g2p2g_smob(SCM smob1, SCM smob2) +// not needed, as the default will be used. + +//////////////////////////////////////////////////////////////////////// +// Public functions +//////////////////////////////////////////////////////////////////////// + +// Return nonzero if sobj is of type g2p2g_smob. +//extern int IS_G2P2G_SMOBP(SCM sobj); +#define IS_G2P2G_SMOBP(sobj) SCM_SMOB_PREDICATE(tag_g2p2g_smob,sobj) + +// Return nonzero if sobj is of type pysmob. +//int +//IS_G2P2G_SMOBP(SCM sobj) +//{ +// return(SCM_SMOB_PREDICATE(tag_g2p2g_smob,sobj)); +//} + +int +IS_G2P_SMOBP(SCM sobj) +{ + if (!SCM_SMOB_PREDICATE(tag_g2p2g_smob,sobj)) { + return (0); + } + const char *name = (const char *) SCM_CELL_WORD_2(sobj); + return('g' == name[0]); +} + +int +IS_P2G_SMOBP(SCM sobj) +{ + if (!SCM_SMOB_PREDICATE(tag_g2p2g_smob,sobj)) { + return (0); + } + const char *name = (const char *) SCM_CELL_WORD_2(sobj); + return('p' == name[0]); +} + +g2p_func_ptr +get_g2p_function(SCM smob) +{ + return((g2p_func_ptr) SCM_CELL_WORD_1(smob)); +} + +p2g_func_ptr +get_p2g_function(SCM smob) +{ + return((p2g_func_ptr) SCM_CELL_WORD_1(smob)); +} + +SCM +bind_g2p_function(PyObject *(*g2p_func)(SCM,SCM), const char* name) +{ + if (NULL == g2p_func) { + if (NULL == name) { // NOT COVERED BY TESTS + name = "<null>"; // NOT COVERED BY TESTS + } + scm_misc_error("bind_g2p_function","NULL g2p_func pointer for ~S",scm_list_1(scm_mem2string(name,strlen(name)))); // NOT COVERED BY TESTS + } + if (NULL == name) { + scm_misc_error("bind_g2p_function","no name was specified",SCM_UNSPECIFIED); // NOT COVERED BY TESTS + } + if ('g' != name[0]) { + scm_misc_error("bind_g2p_function","name does not start with 'g': ~S",scm_list_1(scm_mem2string(name,strlen(name)))); // NOT COVERED BY TESTS + } + SCM scm_g2p; + SCM_NEWSMOB2(scm_g2p,tag_g2p2g_smob,g2p_func,name); + scm_c_define(name,scm_g2p); + return(scm_g2p); +} + +SCM +bind_p2g_function(SCM(*p2g_func)(PyObject *,SCM), const char* name) +{ + if (NULL == p2g_func) { + if (NULL == name) { // NOT COVERED BY TESTS + name = "<null>"; // NOT COVERED BY TESTS + } + scm_misc_error("bind_p2g_function","NULL p2g_func pointer for ~S",scm_list_1(scm_mem2string(name,strlen(name)))); // NOT COVERED BY TESTS + } + if (NULL == name) { + scm_misc_error("bind_p2g_function","no name was specified",SCM_UNDEFINED); // NOT COVERED BY TESTS + } + if ('p' != name[0]) { + scm_misc_error("bind_p2g_function","name does not start with 'p': ~S",scm_list_1(scm_mem2string(name,strlen(name)))); // NOT COVERED BY TESTS + } + SCM scm_p2g; + SCM_NEWSMOB2(scm_p2g,tag_g2p2g_smob,p2g_func,name); + scm_c_define(name,scm_p2g); + return(scm_p2g); +} + + +//////////////////////////////////////////////////////////////////////// + +void +init_g2p2g_smob_type(void) +{ + tag_g2p2g_smob = scm_make_smob_type("g2p2g_smob",0); + scm_set_smob_print(tag_g2p2g_smob,print_g2p2g_smob); + bind_g2p_functions(); // defined in guiletopy.inc + bind_p2g_functions(); // defined in pytoguile.inc +} + +// End of g2p2g_smob.c diff --git a/g2p2g_smob.h b/g2p2g_smob.h new file mode 100644 index 0000000..4c7fa1e --- /dev/null +++ b/g2p2g_smob.h @@ -0,0 +1,79 @@ +// g2p2g_smob header file +// +//////////////////////////////////////////////////////////////////////// + +#ifndef G2P2G_SMOB_H +#define G2P2G_SMOB_H + +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// +// +// Declares the G2P2G_SMOB type, which encapsulates g2p and p2g conversion +// routines, for use in defining templates for converting arguments and +// results of Python functions. + +#include <Python.h> // Must be first header file +#include <libguile.h> + + +// The following are the ones to be used in validating conversion +// templates. +extern int IS_G2P_SMOBP(SCM sobj); +extern int IS_P2G_SMOBP(SCM sobj); + +typedef PyObject *(*g2p_func_ptr)(SCM,SCM); +extern g2p_func_ptr get_g2p_function(SCM smob); + +// The name must start with 'g' which is used as part of the +// real name (which by convention always starts with 'g2p_'). +// The function does NOT take over ownership of the name, so the +// caller must ensure it continues to exist. +// +// This function creates a g2p2g_smob pointing at the g2p function, +// and also defines (in the current environment) a symbol +// whose value is the g2p2g_smob. +extern SCM bind_g2p_function(g2p_func_ptr, const char* name); + +typedef SCM (*p2g_func_ptr)(PyObject *,SCM); +extern p2g_func_ptr get_p2g_function(SCM smob); + +// The name must start with 'p' which is used as part of the +// real name (which by convention always starts with 'p2g_'). +// The function does NOT take over ownership of the name, so the +// caller must ensure it continues to exist. +// +// This function creates a g2p2g_smob pointing at the p2g function, +// and also defines (in the current environment) a symbol +// whose value is the g2p2g_smob. +extern SCM bind_p2g_function(p2g_func_ptr, const char* name); + +extern void init_g2p2g_smob_type(void); + +//////////////////////////////////////////////////////////////////////// +// The following functions are defined in autogenerated *.inc files. + +extern void bind_g2p_functions(void); +extern void bind_p2g_functions(void); + +#endif /* G2P2G_SMOB_H */ +//////////////////////////////////////////////////////////////////////// +// End of g2p2g_smob.h diff --git a/guiletap.scm b/guiletap.scm new file mode 100644 index 0000000..514b9bd --- /dev/null +++ b/guiletap.scm @@ -0,0 +1,115 @@ +; Define functions for running Guile-written tests under the TAP protocol. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; To invoke it: +;;; (use-modules (guiletap)) +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(define-module (guiletap)) +(export plan) +(export ok) +(export bail-out) +(export diag) +(export is-ok) +(export like) +(export diagprint) + +(use-modules (ice-9 format)) +(use-modules (ice-9 regex)) + +; n is the number of tests. +(define plan + (lambda (n) (display (format "1..~d~%" n)))) + +; n - test number +; testdesc - test descriptor +; res - result which is #f at failure, other at success. +(define ok + (lambda (n testdesc res) + (if (not res)(display "not ")) + (display (format "ok ~d - ~a~%" n testdesc)))) + +; testdesc - test descriptor +(define bail-out + (lambda (testdesc) + (display (format "Bail out! - ~a~%" testdesc)))) + +; diagmsg - diagnostic message +(define diag + (lambda (diagmsg) + (display (format "# ~a~%" diagmsg)))) + +; n - test number +; testdesc - test descriptor +; expres - expected test result +; actres - actual test result +; Does not print expected+actual results even when they differ. +(define is-ok-silent + (lambda (n testdesc expres actres) + (ok n testdesc (equal? expres actres)))) + +; Has the same arguments as is-ok and like, but +; instead of performing comparisons, it just prints +; the information. +(define diagprint + (lambda (n testdesc exp actres) + (display (format "# Test ~d - ~a:~%" n testdesc)) + (display (format "# Exp: ~a~%# Act: ~a~%" exp actres)))) + +; Match the actual result to a POSIX extended regular expression +; (which is supported by Guile, by default). +; n - test number +; testdesc - test descriptor +; exppatt - pattern to match expected test result +; actres - actual test result +(define like + (lambda (n testdesc exppatt actres) + (ok n testdesc (string-match exppatt actres)) + (if (not (string-match exppatt actres)) + (diagprint n testdesc exppatt actres)))) + +; Same as is-ok-silent except that it prints expected and +; actual results if they differ. +(define is-ok + (lambda (n testdesc expres actres) + (is-ok-silent n testdesc expres actres) + (if (not (equal? expres actres)) + (diagprint n testdesc expres actres)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; !!! TODO: +; !!! To be implemented also: +; plan-no-plan +; plan-skip-all [REASON] +; +; is RESULT EXPECTED [NAME] +; isnt RESULT EXPECTED [NAME] +; unlike RESULT PATTERN [NAME] +; pass [NAME] +; fail [NAME] +; +; skip CONDITION [REASON] [NB_TESTS=1] +; Specify TODO mode by setting $TODO: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; End of guiletap.scm diff --git a/guiletopy.c b/guiletopy.c new file mode 100644 index 0000000..967dffc --- /dev/null +++ b/guiletopy.c @@ -0,0 +1,1141 @@ +// guiletopy functions +// Functions for conversion from Guile SCMs into PyObjects. +// +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// +//#include <Python.h> // included in guiletopy.h +//#include <libguile.h> // included in guiletopy.h +#include "guiletopy.h" +#include "pysmob.h" +#include <guile/gh.h> +#include "g2p2g_smob.h" // used in guiletopy.inc +#include "verbose.h" +#include "pyscm.h" + +//////////////////////////////////////////////////////////////////////// +// +// VERBOSITY HANDLING CONVENTIONS: +// +// PYGUILE_VERBOSE_G2P2G_SUCCESSFUL is tested whenever conversion in +// a primitive data type conversion function (g2p*) is successful. +// This condition is intended to capture all successful conversions +// of primitive values. +// +// PYGUILE_VERBOSE_G2P2G_ALWAYS is tested in all conversion failures +// and also when successfully converting aggregate data structures +// (such as Tuples, Lists and Dicts). It is intended to provide +// detailed trace of operation of the conversion functions. +// +//////////////////////////////////////////////////////////////////////// +// Convert data from Guile (SCM) representation into Python +// representation +//////////////////////////////////////////////////////////////////////// + +// The following functions convert each a single data type. +// They have to be efficient. +// The interface conventions are: +// 1. The function gets a single SCM argument, and upon +// success - returns a single PyObject, which has already +// been Py_INCREF()-ed. +// 2. The function is responsible for checking the data type of +// and whether the value of its argument is in range. +// If any of them fails, the function returns NULL. +// NOTE: no error is raised with the NULL return (unlike the +// usual convention in Python code). +// 3. If there is any error not associated with wrong data type +// of its argument, the function throws a scm exception. +// 4. Naming convention: +// g2p_{Guile datatype name}2{Python datatype name} +// The reason for (2),(3) above is that those functions are +// intended to be called one after one, until one of them +// succeeds in converting a data item. + +//////////////////////// general template handling ///////////////////// + +// Apply a template to sobj. +// The template consists of pair of g2p* token and data structure +// which serves as the stemplate argument when the token's function +// is invoked. +// Alternatively, the template may consist of a single g2p* token. +// In that case, the corresponding function gets SCM_UNSPECIFIED +// as its stemplate argument. +// The invoked function is responsible for ensuring that the stemplate +// which it received is appropriate to sobj. Inappropriateness +// means that NULL is returned, which allows g2p_leaf (see below) +// to backtrace and try another template. +PyObject * +g2p_apply(SCM sobj,SCM stemplate) +{ + PyObject *pobj; + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# Entered g2p_apply: sobj=~S stemplate=~S\n"),scm_list_2(sobj,stemplate)); + } + if (IS_G2P_SMOBP(stemplate)) { + pobj = (get_g2p_function(stemplate))(sobj,SCM_UNSPECIFIED); + } + else if (!SCM_EQ_P(SCM_BOOL_F,scm_pair_p(stemplate))) { + if (IS_G2P_SMOBP(SCM_CAR(stemplate))) { + pobj = (get_g2p_function(SCM_CAR(stemplate)))(sobj,SCM_CDR(stemplate)); + } + else { + scm_misc_error("g2p_apply","bad template CAR item ~S", + scm_list_1(SCM_CAR(stemplate))); + } + } + else { + scm_misc_error("g2p_apply","bad template item ~S", + scm_list_1(stemplate)); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(), + scm_makfrom0str("# Leaving g2p_apply: with ~A result\n"), + scm_list_1(NULL == pobj ? scm_makfrom0str("null") + : scm_makfrom0str("non-null"))); + } + return(pobj); // is NULL if conversion failed, otherwise a new + // reference to a PyObject (Py_INCREF has been invoked on it). +} + +////////////////////////// leaf //////////////////////////////////////// + +// Perform 'leaf' data conversion. +// Normally, stemplate is a list of templates, to be tried one by one +// until one of them succeeds. +PyObject * +g2p_leaf(SCM sobj,SCM stemplate) +{ + PyObject *pobj; + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# Entered g2p_leaf: sobj=~S stemplate=~S\n"),scm_list_2(sobj,stemplate)); + } + if (IS_G2P_SMOBP(stemplate)) { + pobj = (get_g2p_function(stemplate))(sobj,SCM_UNSPECIFIED); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(), + scm_makfrom0str("# Leaving g2p_leaf, after G2P_SMOBP conversion, with ~A result\n"), + scm_list_1(NULL == pobj ? scm_makfrom0str("null") + : scm_makfrom0str("non-null"))); + } + return(pobj); // Will be NULL if the conversion failed. + } + if (!SCM_EQ_P(SCM_BOOL_F,scm_list_p(stemplate))) { + // Each template list item is examined. + // If the template list item is itself a list, then its CAR is invoked + // and gets its CDR as template, and the whole sobj (which is + // expected to be a list, too) as the argument. + // If the template list item is a G2P_SMOB, then it is invoked with + // sobj. + // + // At any case, template list items are invoked one by one until + // one of them succeeds. + SCM slist; + for (slist = stemplate; (!SCM_EQ_P(slist,SCM_EOL)); + slist = SCM_CDR(slist)) { + SCM scandidate = SCM_CAR(slist); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_leaf: trying another stemplate ~S on sobj\n"),scm_list_1(scandidate)); + } + pobj = g2p_apply(sobj,scandidate); + if (NULL != pobj) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_leaf: successful conversion\n"),SCM_EOL); + } + return(pobj); + } + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_leaf: unsuccessful conversion, no stemplate fit the sobj\n"),SCM_EOL); + } + return(NULL); // None of the templates in the list fit the sobj. + // NULL return supports backtracking if the template is so designed. + } + scm_wrong_type_arg("g2p_leaf",SCM_ARG2,stemplate); // Bad template +} + +////////////////////////// null //////////////////////////////////////// + +PyObject * +g2p_null2PyNone(SCM sobj,SCM stemplate) +{ + if (SCM_NULLP(sobj)) { //(SCM_BOOL_F != scm_null_p(sobj)) + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_null2PyNone: successful conversion of ~S into Python None\n"),scm_list_1(sobj)); + } + Py_INCREF(Py_None); + return(Py_None); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_null2PyNone: unsuccessful conversion: ~S is not null\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +PyObject * +g2p_null2Tuple0(SCM sobj,SCM stemplate) +{ + if (SCM_NULLP(sobj)) { //(SCM_BOOL_F != scm_null_p(sobj)) + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_null2Tuple0: successful conversion of ~S into Python ()\n"),scm_list_1(sobj)); + } + PyObject *pres = PyTuple_New(0); + if (NULL == pres) { + scm_memory_error("g2p-null2Tuple0"); // NOT COVERED BY TESTS + } + return(pres); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_null2Tuple0: unsuccessful conversion: ~S is not null\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +PyObject * +g2p_null2List0(SCM sobj,SCM stemplate) +{ + if (SCM_NULLP(sobj)) { //(SCM_BOOL_F != scm_null_p(sobj)) + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_null2List0: successful conversion of ~S into Python []\n"),scm_list_1(sobj)); + } + PyObject *pres = PyList_New(0); + if (NULL == pres) { + scm_memory_error("g2p-null2List0"); // NOT COVERED BY TESTS + } + return(pres); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_null2List0: unsuccessful conversion: ~S is not null\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +PyObject * +g2p_null2DictEmpty(SCM sobj,SCM stemplate) +{ + if (SCM_NULLP(sobj)) { //(SCM_BOOL_F != scm_null_p(sobj)) + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_null2DictEmpty: successful conversion of ~S into Python {}\n"),scm_list_1(sobj)); + } + PyObject *pres = PyDict_New(); + if (NULL == pres) { + scm_memory_error("g2p-null2DictEmpty"); // NOT COVERED BY TESTS + } + return(pres); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_null2DictEmpty: unsuccessful conversion: ~S is not null\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +// Python's None is different from Python's () or Python's [] +// !!! Also, check for token such as '*none* or '*None* for +// !!! conversion into Python None instead of Python string. + +///////////////////////// Numeric ////////////////////////////////////// + +PyObject * +g2p_bool2Bool(SCM sobj,SCM stemplate) +{ + if (SCM_BOOLP(sobj)) { //(SCM_BOOL_F != scm_boolean_p(sobj)) + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_bool2Bool: successful conversion of ~S into a Python Bool value\n"),scm_list_1(sobj)); + } + if (SCM_EQ_P(SCM_BOOL_T,sobj)) { + Py_INCREF(Py_True); + return(Py_True); + } + else { + Py_INCREF(Py_False); + return(Py_False); + } + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_bool2Bool: unsuccessful conversion: ~S is not a bool value\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +PyObject * +g2p_num2Int(SCM sobj,SCM stemplate) +{ + if (SCM_INUMP(sobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_num2Int: successful conversion of ~S into a Python Int value\n"),scm_list_1(sobj)); + } + return(PyInt_FromLong(scm_num2long(sobj,0,"g2p_long2Int"))); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_num2Int: unsuccessful conversion: ~S is not a num value\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +PyObject * +g2p_real2Float(SCM sobj,SCM stemplate) +{ + if (SCM_INUMP(sobj) || SCM_REALP(sobj)) { //(SCM_BOOL_F != scm_real_p(sobj)) + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_real2Float: successful conversion of ~S into a Python Float value\n"),scm_list_1(sobj)); + } + return(PyFloat_FromDouble(scm_num2double(sobj,0,"g2p_real2Float"))); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_real2Float: unsuccessful conversion: ~S is not a real value\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +PyObject * +g2p_complex2Complex(SCM sobj,SCM stemplate) +{ + if (SCM_COMPLEXP(sobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_complex2Complex: successful conversion of ~S into a Python Complex value\n"),scm_list_1(sobj)); + } + double re = SCM_COMPLEX_REAL(sobj); + double im = SCM_COMPLEX_IMAG(sobj); + return(PyComplex_FromDoubles(re,im)); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_complex2Complex: unsuccessful conversion: ~S is not a complex value\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +PyObject * +g2p_bignum2Long(SCM sobj,SCM stemplate) +{ + // Like schemepy, we accomplish this conversion by first + // converting into string and then evaluating the string. + if (SCM_BIGP(sobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_bignum2Long: successful conversion of ~S into a Python Long value\n"),scm_list_1(sobj)); + } + SCM swrite_proc = scm_variable_ref(scm_c_lookup("write")); + SCM sbignumstr = scm_object_to_string(sobj,swrite_proc); + char *pstr = gh_scm2newstr(sbignumstr,NULL); + if (NULL == pstr) { + scm_memory_error("g2p_bignum2Long"); // NOT COVERED BY TESTS + } + + PyObject *pres = PyInt_FromString(pstr, NULL, 10); // Will return PyLong if the value does not fit into PyInt. + free(pstr); + PyObject *pexception = PyErr_Occurred(); + if (pexception) { + Py_XDECREF(pres); // NOT COVERED BY TESTS + PyErr_Clear(); // NOT COVERED BY TESTS + scm_misc_error("g2p_bignum2Long","internal conversion error of bignum ~S", // NOT COVERED BY TESTS + sobj); + } + return(pres); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_bignum2Long: unsuccessful conversion: ~S is not a bignum value\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +///////////////////// Pairs and Lists ////////////////////////////////// + +PyObject * +g2p_pair2Tuple(SCM sobj,SCM stemplate) +{ + // We expect the template to be a pair. + // SCM_CAR(stemplate) is used to convert SCM_CAR(sobj), and similarly + // for SCM_CDR. + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(sobj))) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_pair2Tuple: unsuccessful conversion: ~S is not a pair\n"),scm_list_1(sobj)); + } + return(NULL); + } + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(stemplate))) { + scm_misc_error("g2p_pair2Tuple","bad template ~S", + scm_list_1(stemplate)); + } + + // Transform it into Python tuple. + PyObject *ppair = PyTuple_New(2); + if (NULL == ppair) { + scm_memory_error("g2p_pair2Tuple"); // NOT COVERED BY TESTS + } + + // CAR + PyObject *pitem = g2p_apply(SCM_CAR(sobj),SCM_CAR(stemplate)); + if (NULL == pitem) { + Py_DECREF(ppair); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_pair2Tuple: unsuccessful conversion: CAR conversion failure\n"),SCM_EOL); + } + return(NULL); // Conversion failure. + } + if (-1 == PyTuple_SetItem(ppair, 0, pitem)) { + Py_DECREF(ppair); // NOT COVERED BY TESTS + // Py_DECREF(pitem); // already performed by PyTuple_SetItem + scm_misc_error("g2p_pair2Tuple","PyTuple_SetItem car failure (~S)", // NOT COVERED BY TESTS + scm_list_1(SCM_CAR(sobj))); + } + + // CDR + pitem = g2p_apply(SCM_CDR(sobj),SCM_CDR(stemplate)); + if (NULL == pitem) { + Py_DECREF(ppair); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_pair2Tuple: unsuccessful conversion: CDR conversion failure\n"),SCM_EOL); + } + return(NULL); // Conversion failure. + } + if (-1 == PyTuple_SetItem(ppair, 1, pitem)) { + Py_DECREF(ppair); // NOT COVERED BY TESTS + // Py_DECREF(pitem); // already performed by PyTuple_SetItem + scm_misc_error("g2p_pair2Tuple","PyTuple_SetItem cdr failure (~S)", // NOT COVERED BY TESTS + scm_list_1(SCM_CDR(sobj))); + } + + // Done + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_pair2Tuple: successful conversion of ~S into a Python 2-Tuple\n"),scm_list_1(sobj)); + } + return(ppair); +} + +// Very similar to g2p_pair2Tuple() above. +PyObject * +g2p_pair2List(SCM sobj,SCM stemplate) +{ + // We expect the template to be a pair. + // SCM_CAR(stemplate) is used to convert SCM_CAR(sobj), and similarly + // for SCM_CDR. + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(sobj))) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_pair2List: unsuccessful conversion: ~S is not a pair\n"),scm_list_1(sobj)); + } + return(NULL); + } + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(stemplate))) { + scm_misc_error("g2p_pair2List","bad template ~S", + scm_list_1(stemplate)); + } + + // Transform it into Python tuple. + PyObject *ppair = PyList_New(2); + if (NULL == ppair) { + scm_memory_error("g2p_pair2List"); // NOT COVERED BY TESTS + } + + // CAR + PyObject *pitem = g2p_apply(SCM_CAR(sobj),SCM_CAR(stemplate)); + if (NULL == pitem) { + Py_DECREF(ppair); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_pair2List: unsuccessful conversion: CAR conversion failure\n"),SCM_EOL); + } + return(NULL); + } + if (-1 == PyList_SetItem(ppair, 0, pitem)) { + Py_DECREF(ppair); // NOT COVERED BY TESTS + // Py_DECREF(pitem); // already performed by PyList_SetItem + scm_misc_error("g2p_pair2List","PyList_SetItem car failure (~S)", // NOT COVERED BY TESTS + scm_list_1(SCM_CAR(sobj))); + } + + // CDR + pitem = g2p_apply(SCM_CDR(sobj),SCM_CDR(stemplate)); + if (NULL == pitem) { + Py_DECREF(ppair); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_pair2List: unsuccessful conversion: CDR conversion failure\n"),SCM_EOL); + } + return(NULL); + } + if (-1 == PyList_SetItem(ppair, 1, pitem)) { + Py_DECREF(ppair); // NOT COVERED BY TESTS + // Py_DECREF(pitem); // already performed by PyList_SetItem + scm_misc_error("g2p_pair2List","PyList_SetItem cdr failure (~S)", // NOT COVERED BY TESTS + scm_list_1(SCM_CDR(sobj))); + } + + // Done + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_pair2List: successful conversion of ~S into a Python 2-List\n"),scm_list_1(sobj)); + } + return(ppair); +} + +PyObject * +g2p_list2Tuple(SCM sobj,SCM stemplate) +{ + // sobj is expected to be a list. + + if (SCM_EQ_P(SCM_BOOL_F,scm_list_p(sobj))) { + // sobj is not a list, so this is the wrong conversion function for it. + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2Tuple: unsuccessful conversion: ~S is not a list\n"),scm_list_1(sobj)); + } + return(NULL); + } + + // The template may be either a G2P_SMOB (to be used for converting + // all list items) or a list of templates. + long listlen = scm_num2long(scm_length(sobj),0,"g2p_list2Tuple"); + PyObject *plist = PyTuple_New(listlen); + if (NULL == plist) { + scm_memory_error("g2p_list2Tuple"); // NOT COVERED BY TESTS + } + + // Conversion loop for the case in which the template is a single G2P_SMOB + if (IS_G2P_SMOBP(stemplate)) { + long ind1; + for (ind1 = 0; ind1 < listlen; sobj = SCM_CDR(sobj),++ind1) { + SCM sitem = SCM_CAR(sobj); + PyObject *pobj1 = g2p_apply(sitem,stemplate); + if (NULL == pobj1) { + Py_DECREF(plist); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2Tuple: unsuccessful conversion of element ~A: ~S does not match template\n"),scm_list_2(scm_long2num(ind1),sitem)); + } + return(NULL); // Conversion failure. + } + if (-1 == PyTuple_SetItem(plist, ind1, pobj1)) { + Py_DECREF(plist); // NOT COVERED BY TESTS + //Py_DECREF(pobj1); + scm_misc_error("g2p_list2Tuple","PyTuple_SetItem ~S failure (~S)", // NOT COVERED BY TESTS + scm_list_2(scm_long2num(ind1),sitem)); + } + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2Tuple: successful conversion of list ~S\n"),scm_list_1(sobj)); + } + return(plist); + } + + if (SCM_EQ_P(SCM_BOOL_F,scm_list_p(stemplate))) { + // Bad template. + scm_wrong_type_arg("g2p_list2Tuple",SCM_ARG2,stemplate); + } + + // Conversion loop for the case in which the template is a list. + long ind2; + SCM stemp = SCM_EOL; // We loop over stemplate again and again as needed. + for (ind2 = 0; ind2 < listlen; + sobj = SCM_CDR(sobj), stemp=SCM_CDR(stemp), ++ind2) { + if (SCM_EQ_P(stemp,SCM_EOL)) { + stemp = stemplate; // Loop back to stemplate's beginning. + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2Tuple - processing item CAR(sobj)=~S CAR(stemp)=~S\n"),scm_list_2(SCM_CAR(sobj),SCM_CAR(stemp))); + } + PyObject *pobj2 = g2p_apply(SCM_CAR(sobj),SCM_CAR(stemp)); + if (NULL == pobj2) { + Py_DECREF(plist); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2Tuple: unsuccessful conversion of element ~A: ~S does not match personalized template\n"),scm_list_2(scm_long2num(ind2),SCM_CAR(sobj))); + } + return(NULL); // Conversion failure. + } + if (-1 == PyTuple_SetItem(plist, ind2, pobj2)) { + Py_DECREF(plist); // NOT COVERED BY TESTS + //Py_DECREF(pobj2); + scm_misc_error("g2p_list2Tuple","PyTuple_SetItem ~S failure (~S)", // NOT COVERED BY TESTS + scm_list_2(scm_long2num(ind2),SCM_CAR(stemp))); + } + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2Tuple: successful conversion of list ~S by template\n"),scm_list_1(sobj)); + } + return(plist); +} + +// Very similar to g2p_list2Tuple() above. +PyObject * +g2p_list2List(SCM sobj,SCM stemplate) +{ + // sobj is expected to be a list. + + if (SCM_EQ_P(SCM_BOOL_F,scm_list_p(sobj))) { + // sobj is not a list, so this is the wrong conversion function for it. + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2List: unsuccessful conversion: ~S is not a list\n"),scm_list_1(sobj)); + } + return(NULL); + } + + // The template may be either a G2P_SMOB (to be used for converting + // all list items) or a list of templates. + long listlen = scm_num2long(scm_length(sobj),0,"g2p_list2List"); + PyObject *plist = PyList_New(listlen); + if (NULL == plist) { + scm_memory_error("g2p_list2List"); // NOT COVERED BY TESTS + } + + // Conversion loop for the case in which the template is a single G2P_SMOB + if (IS_G2P_SMOBP(stemplate)) { + long ind1; + for (ind1 = 0; ind1 < listlen; sobj = SCM_CDR(sobj),++ind1) { + SCM sitem = SCM_CAR(sobj); + PyObject *pobj1 = g2p_apply(sitem,stemplate); + if (NULL == pobj1) { + Py_DECREF(plist); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2List: unsuccessful conversion of element ~A: ~S does not match template\n"),scm_list_2(scm_long2num(ind1),sitem)); + } + return(NULL); // Conversion failure. + } + if (-1 == PyList_SetItem(plist, ind1, pobj1)) { + Py_DECREF(plist); // NOT COVERED BY TESTS + //Py_DECREF(pobj1); + scm_misc_error("g2p_list2List","PyList_SetItem ~S failure (~S)", // NOT COVERED BY TESTS + scm_list_2(scm_long2num(ind1),sitem)); + } + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2List: successful conversion of list ~S\n"),scm_list_1(sobj)); + } + return(plist); + } + + if (SCM_EQ_P(SCM_BOOL_F,scm_list_p(stemplate))) { + // Bad template. + scm_wrong_type_arg("g2p_list2List",SCM_ARG2,stemplate); + } + + // Conversion loop for the case in which the template is a list. + long ind2; + SCM stemp = SCM_EOL; // We loop over stemplate again and again as needed. + for (ind2 = 0; ind2 < listlen; + sobj = SCM_CDR(sobj), stemp=SCM_CDR(stemp), ++ind2) { + if (SCM_EQ_P(stemp,SCM_EOL)) { + stemp = stemplate; // Loop back to stemplate's beginning. + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# DEBUG: g2p_list2List - processing item CAR(sobj)=~S CAR(stemp)=~S\n"),scm_list_2(SCM_CAR(sobj),SCM_CAR(stemp))); + } + PyObject *pobj2 = g2p_apply(SCM_CAR(sobj),SCM_CAR(stemp)); + if (NULL == pobj2) { + Py_DECREF(plist); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2List: unsuccessful conversion of element ~A: ~S does not match personalized template\n"),scm_list_2(scm_long2num(ind2),SCM_CAR(sobj))); + } + return(NULL); // Conversion failure + } + if (-1 == PyList_SetItem(plist, ind2, pobj2)) { + Py_DECREF(plist); // NOT COVERED BY TESTS + //Py_DECREF(pobj2); + scm_misc_error("g2p_list2List","PyList_SetItem ~S failure (~S)", // NOT COVERED BY TESTS + scm_list_2(scm_long2num(ind2),SCM_CAR(stemp))); + } + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_list2List: successful conversion of list ~S by template\n"),scm_list_1(sobj)); + } + return(plist); +} + +///////////////////////// strings and symbols ////////////////////////// + +PyObject * +g2p_char2String(SCM sobj,SCM stemplate) +{ + if (SCM_CHARP(sobj)) { + return(PyString_FromFormat("%c",(int) SCM_CHAR(sobj))); + } + else { + return(NULL); + } +} + +PyObject * +g2p_string2String(SCM sobj,SCM stemplate) +{ + if (SCM_STRINGP(sobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_string2String: successful conversion of ~S into a Python String value\n"),scm_list_1(sobj)); + } + PyObject *pstr = PyString_FromStringAndSize(SCM_STRING_CHARS(sobj),SCM_STRING_LENGTH(sobj)); + if (NULL == pstr) { + scm_memory_error("g2p_string2String"); // NOT COVERED BY TESTS + } + return(pstr); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_string2String: unsuccessful conversion: ~S is not a string value\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +// Guile is case-sensitive by default, so we don't have to +// ensure that symbols always lowercase their characters. +PyObject * +g2p_symbol2String(SCM sobj,SCM stemplate) +{ + if (SCM_SYMBOLP(sobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_symbol2String: successful conversion of ~S into a Python String value\n"),scm_list_1(sobj)); + } + PyObject *pstr = PyString_FromStringAndSize(SCM_SYMBOL_CHARS(sobj),SCM_SYMBOL_LENGTH(sobj)); + if (NULL == pstr) { + scm_memory_error("g2p_symbol2String"); // NOT COVERED BY TESTS + } + return(pstr); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_symbol2String: unsuccessful conversion: ~S is not a symbol value\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +PyObject * +g2p_keyword2String(SCM sobj,SCM stemplate) +{ + if (SCM_KEYWORDP(sobj)) { //(SCM_BOOL_F != scm_keyword_p(sobj)) + SCM symb = scm_keyword_dash_symbol(sobj); + // We want to remove the leading '-' from the keyword, so that + // it can be used in keyword function arguments in a natural way. + const char *symchars = SCM_SYMBOL_CHARS(symb); + int symlength = SCM_SYMBOL_LENGTH(symb); + if (symlength < 1) { + scm_out_of_range("g2p_keyword2String",sobj); // NOT COVERED BY TESTS + // The symbol is too short to have dash. + // We allow symbols like '#:', which are converted here into empty + // strings. Guile does not accept them for version 1.8.x and later, + // but this does not matter here. + } + PyObject *pstr = PyString_FromStringAndSize(symchars+1,symlength-1); + if (NULL == pstr) { + scm_memory_error("g2p_keyword2String"); // NOT COVERED BY TESTS + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_keyword2String: successful conversion of ~S into a Python String value\n"),scm_list_1(sobj)); + } + return(pstr); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_keyword2String: unsuccessful conversion: ~S is not a keyword value\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + +///////////////////////// opaque datatypes ///////////////////////////// + +// scm_procedure_p +// scm_procedure_with_setter_p - not implemented yet. + +// Reasonable default template for procedure objects +static SCM g2p_procedure2PySCMObject_template_default; +// The above variable is to be assigned the value: +// scm_permanent_object(scm_vector(scm_list_5(...))) +// where the arguments of scm_list_5() are: +// scm_variable_ref(scm_c_lookup("python2guile")) +// scm_variable_ref(scm_c_lookup("python2guile")) +// guile2python_smob +// scm_variable_ref(scm_c_lookup("apply")) +// SCM_BOOL_F +// +// To clone the above and set other templates, use +// Scheme procedure: (set! cloned (copy-tree obj)) +// and C function: SCM scopy = scm_copy_tree(SCM sobj) + +PyObject * +g2p_procedure2PySCMObject(SCM sobj,SCM stemplate) +{ + if (SCM_EQ_P(stemplate,SCM_UNSPECIFIED)) { + stemplate = g2p_procedure2PySCMObject_template_default; + } + if (SCM_EQ_P(SCM_BOOL_F,scm_vector_p(stemplate))) { + // Bad template + scm_wrong_type_arg("g2p_procedure2PySCMObject",SCM_ARG2,stemplate); + } + if (SCM_EQ_P(SCM_BOOL_F,scm_procedure_p(sobj))) { + // Not a procedure + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_procedure2PySCMObject: unsuccessful conversion: ~S is not a procedure\n"),scm_list_1(sobj)); + } + return(NULL); + } + PyObject *pobj = wrap_scm(sobj,stemplate); + if (NULL != pobj) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_procedure2PySCMObject: successful conversion: ~S has been wrapped\n"),scm_list_1(sobj)); + } + } + return(pobj); +} + + +PyObject * +g2p_opaque2Object(SCM sobj,SCM stemplate) +{ + if (IS_PYSMOBP(sobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_opaque2Object: the Python object inside opaque pysmob ~S is unwrapped\n"),scm_list_1(sobj)); + } + PyObject *pobj = unwrap_pysmob(sobj); + Py_INCREF(pobj); + return(pobj); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_opaque2Object: unsuccessful conversion: ~S is not an opaque pysmob value\n"),scm_list_1(sobj)); + } + return(NULL); + } +} + + +//////////////////////////////////////////////////////////////////////// +// Big default conversion function +//////////////////////////////////////////////////////////////////////// +// The guile2python function chooses reasonable defaults, whenever +// there is a possibility for ambiguity concerning the desired +// Python datatype. + +static SCM guile2python_smob; // Used when we need it as hash default value. +static SCM guile2python_template_default; // used by guile2python(). +static SCM guile2python_pair_template_default; // used by guile2python(). +static SCM guileassoc2pythondict_default; // used by guileassoc2pythondict. + // It is set to an empty hash table. +static SCM g2p_alist_template_default; // used by g2p_alist2Dict. + +// This function does Py_INCREF() to its return values. +PyObject * +guile2python(SCM sobj,SCM stemplate) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# guile2python: entry: seeking to convert sobj=~S; unused stemplate=~S\n"),scm_list_2(sobj,stemplate)); + } + PyObject *pres = NULL; + + //////////// Scheme sequences + + // SCM '() is converted into Python None. + pres = g2p_null2PyNone(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + // We check lists before pairs because every list in Scheme is + // also a pair. + //printf("# DEBUG: guile2python, before 1st call to g2p_list2List\n"); + pres = g2p_list2List(sobj,guile2python_template_default); + if (NULL != pres) return(pres); + + pres = g2p_pair2Tuple(sobj,guile2python_pair_template_default); + if (NULL != pres) return(pres); + + //////////// Scheme numbers + + pres = g2p_bool2Bool(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + pres = g2p_num2Int(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + pres = g2p_bignum2Long(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + pres = g2p_real2Float(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + // NOTE: in Guile 1.6, rational numbers and real numbers are + // internally represented the same way. + + pres = g2p_complex2Complex(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + //////////// Scheme strings + + pres = g2p_char2String(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + pres = g2p_string2String(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + pres = g2p_symbol2String(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + pres = g2p_keyword2String(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + //////////// Other Scheme data types + + pres = g2p_procedure2PySCMObject(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + pres = g2p_opaque2Object(sobj,SCM_UNSPECIFIED); + if (NULL != pres) return(pres); + + // !!! More simple data types: regexp + // !!! To wrap: procedures, macros(opaque), variables, + // !!! opaque: asyncs, dynamic roots, fluids, hooks, ports + + // !!! Scheme objects + + // !!! More complex data types: vectors,records,structures,arrays, + + + else { + scm_wrong_type_arg("guile2python",SCM_ARG1,sobj); + } +} + +//////////////////////////////////////////////////////////////////////// +// Quick conversion from alist into Dict +//////////////////////////////////////////////////////////////////////// +// Treat the SCM object as an association list and convert it into +// Python hash, whose keys are strings corresponding to the +// association list's keys (which must be keywords). +// +// This version is meant for quick conversion of keyword arguments, +// and it allows only keywords as keys. +// +// Checks: +// 1. Validity of keys. +// 2. No duplicate keys. +// +// The stemplate argument must be an hash table (keys are checked +// using eq?). + +PyObject * +guileassoc2pythondict(SCM sobj,SCM stemplate) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# guileassoc2pythondict: entry: seeking to convert sobj=~S using stemplate=~S\n"),scm_list_2(sobj,stemplate)); + } + + if (SCM_UNBNDP(stemplate)) { + stemplate = guileassoc2pythondict_default; + } + PyObject *pdict = NULL; + PyObject *pkey = NULL; + PyObject *pval = NULL; + + if (SCM_EQ_P(SCM_BOOL_F,scm_list_p(sobj))) { + scm_wrong_type_arg("guileassoc2pythondict",SCM_ARG1,sobj); + } + // TODO: add here a call to SCM_HASHP(stemplate) to validate that the + // template is hashtable. + + long listlen = scm_num2long(scm_length(sobj),0,"guileassoc2pythondict"); + pdict = PyDict_New(); + if (NULL == pdict) { + scm_memory_error("guileassoc2pythondict"); // NOT COVERED BY TESTS + } + long ind; + for (ind = 0,pkey = NULL,pval = NULL; ind < listlen; ++ind) { + SCM spair = SCM_CAR(sobj); + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(spair))) { + // Not a pair. + Py_DECREF(pdict); + scm_wrong_type_arg("guileassoc2pythondict",ind+1,spair); + } + + pkey = g2p_keyword2String(SCM_CAR(spair),SCM_UNSPECIFIED); + if (NULL == pkey) { + // Illegal key - it must be a keyword. + scm_wrong_type_arg("guileassoc2pythondict",ind+1,SCM_CAR(spair)); + } + if (0 != PyDict_Contains(pdict,pkey)) { + // Duplicate key or some error + Py_DECREF(pdict); + Py_DECREF(pkey); + scm_misc_error("guileassoc2pythondict","duplicate key (~S)", + scm_list_1(SCM_CAR(spair))); + } + + SCM sitem_template = scm_hashq_ref(stemplate,SCM_CAR(spair),guile2python_smob); + pval = g2p_apply(SCM_CDR(spair),sitem_template); + if (NULL == pval) { + Py_DECREF(pdict); + Py_DECREF(pkey); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# guileassoc2pythondict: unsuccessful conversion of element ~A: ~S does not match template\n"),scm_list_2(SCM_CAR(spair),SCM_CDR(spair))); + } + return(NULL); // Conversion failure. + } + + if (-1 == PyDict_SetItem(pdict,pkey,pval)) { + Py_XDECREF(pdict); // NOT COVERED BY TESTS + //Py_XDECREF(pkey); + //Py_XDECREF(pval); + scm_misc_error("guileassoc2pythondict","PyDict_SetItem failure (~S : ~S)", // NOT COVERED BY TESTS + scm_list_2(SCM_CAR(spair),SCM_CDR(spair))); + } + sobj = SCM_CDR(sobj); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# guileassoc2pythondict: successful conversion of ~S\n"),scm_list_1(sobj)); + } + return(pdict); +} + +//////////////////////////////////////////////////////////////////////// +// Generalized conversion from alist into Dict +//////////////////////////////////////////////////////////////////////// +// The following function is meant for conversion of alists into +// general Python Dicts. +// It allows any immutable object as key. +// NOTE: no check for immutability is made here! +// +// The stemplate argument must be a list of pairs (i.e. structured +// like an alist). + +PyObject * +g2p_alist2Dict(SCM sobj,SCM stemplate) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_alist2Dict sobj=~S stemplate=~S\n"),scm_list_2(sobj,stemplate)); + } + if (SCM_UNBNDP(stemplate) || SCM_EQ_P(stemplate,SCM_EOL)) { + stemplate = g2p_alist_template_default; + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_alist2Dict - default template was chosen: ~S\n"),scm_list_1(stemplate)); + } + } + + if (SCM_EQ_P(SCM_BOOL_F,scm_list_p(sobj))) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_alist2Dict: unsuccessful conversion: argument is not list, let alone alist\n"),SCM_EOL); + } + return(NULL); // Conversion failure. + } + if (SCM_EQ_P(SCM_BOOL_F,scm_list_p(stemplate))) { + scm_wrong_type_arg("g2p_alist2Dict",SCM_ARG2,sobj); // Bad template. + } + + long listlen = scm_num2long(scm_length(sobj),0,"g2p_alist2Dict"); + PyObject *pdict = PyDict_New(); + if (NULL == pdict) { + scm_memory_error("g2p_alist2Dict"); // NOT COVERED BY TESTS + } + + long ind; + SCM stemp = SCM_EOL; // We loop over stemplate again and again as needed. + for (ind = 0; ind < listlen; + sobj = SCM_CDR(sobj),stemp=SCM_CDR(stemp), ++ind) { + if (SCM_EQ_P(stemp,SCM_EOL)) { + stemp = stemplate; // Loop back to stemplate's beginning. + } + + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(SCM_CAR(stemp)))) { + // Not a template pair - bad template. + Py_DECREF(pdict); + scm_wrong_type_arg("g2p_alist2Dict",SCM_ARG2,SCM_CAR(stemp)); + } + SCM spair = SCM_CAR(sobj); + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(spair))) { + // Not a data pair - conversion failure. + Py_DECREF(pdict); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_alist2Dict: unsuccessful conversion: alist item is not pair: ~S\n"),scm_list_1(spair)); + } + return(NULL); + } + + PyObject *pkey = g2p_apply(SCM_CAR(spair),SCM_CAAR(stemp)); + if (NULL == pkey) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_alist2Dict: unsuccessful conversion: key ~S, template ~S\n"),scm_list_2(SCM_CAR(spair),SCM_CAAR(stemp))); + } + return(NULL); // Conversion failure. + } + if (0 != PyDict_Contains(pdict,pkey)) { + // Duplicate key or some error. + Py_DECREF(pdict); + Py_DECREF(pkey); + scm_misc_error("g2p_alist2Dict","duplicate key (~S)", + scm_list_1(SCM_CAR(spair))); + } + + PyObject *pval = g2p_apply(SCM_CDR(spair),SCM_CDAR(stemp)); + if (NULL == pval) { + Py_DECREF(pdict); + Py_DECREF(pkey); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_alist2Dict: unsuccessful conversion: value ~S, template ~S\n"),scm_list_2(SCM_CDR(spair),SCM_CDAR(stemp))); + } + return(NULL); // Conversion failure. + } + + if (-1 == PyDict_SetItem(pdict,pkey,pval)) { + Py_XDECREF(pdict); // NOT COVERED BY TESTS + //Py_XDECREF(pkey); + //Py_XDECREF(pval); + scm_misc_error("g2p_alist2Dict","PyDict_SetItem failure (~S : ~S)", // NOT COVERED BY TESTS + scm_list_2(SCM_CAR(spair),SCM_CDR(spair))); + } + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# g2p_alist2Dict: successful conversion\n"),SCM_EOL); + } + return(pdict); +} + +// NOTE: +// After all, we'll not support hash tables. +// This is because I found no function to identify an hash table. +// Hash table can be converted into alist using: +// (hash-fold acons '() hashtable) + +//////////////////////////////////////////////////////////////////////// +// Register all g2p_* functions + +#include "guiletopy.inc" + +// The following must happen after registration of all g2p_* and p2g_* +// functions. +void +init_default_guiletopy_templates(void) +{ + SCM s_python2guile = scm_variable_ref(scm_c_lookup("python2guile")); + g2p_procedure2PySCMObject_template_default + = scm_permanent_object(scm_vector(scm_list_5( + s_python2guile, + s_python2guile, + guile2python_smob, + scm_variable_ref(scm_c_lookup("apply")), + SCM_BOOL_F))); + scm_c_define("pyscm-default-template",g2p_procedure2PySCMObject_template_default); +} + +//////////////////////////////////////////////////////////////////////// +// End of guiletopy.c diff --git a/guiletopy.h b/guiletopy.h new file mode 100644 index 0000000..00c3369 --- /dev/null +++ b/guiletopy.h @@ -0,0 +1,104 @@ +// guiletopy header file +// Functions for conversion from Guile SCMs into PyObjects. + +#ifndef GUILETOPY_H +#define GUILETOPY_H + +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// + +#include <Python.h> // Must be first header file +#include <libguile.h> + +//////////////////////////////////////////////////////////////////////// +// The format of stemplate for SCM->PyObject conversions +//////////////////////////////////////////////////////////////////////// +// +// The following cases are to be considered: +// ----------------------------------------- +// All g2p* functions have a template argument. +// However, those which are genuine leaf conversion functions +// ignore it. +// +// 1. "Leaf" data item (such as a number or a string): +// We want to try few g2p* functions until one succeeds. +// The template in this case consists of 'g2p_leaf followed by +// a list of one or more g2p* symbols. +// It is possible for the template corresponding to a "leaf" +// data item to consist of a single g2p* function. In this case, +// the function is directly applied to the data item. +// 2. Data item which is pair: +// The template consists of list of g2p_pair2Tuple or g2p_pair2List, +// and pair of two templates for processing the CAR and CDR of the +// data item. Example: +// (list g2p_pair2Tuple +// ((list g2p_leaf g2p_real2Float) . (list g2p_leaf g2p_num2Int))) +// 3. Data item which is a list: +// The template data structure is a list of g2p_list2List or +// g2p_list2Tuple followed by a list of one or more templates, which +// are used to convert the list members. +// It is possible for the data list to have more items than the +// corresponding template list. In this case, the template list +// items are cyclically processed. +// This way it is possible to use a single template to convert all +// list items, if they are to be converted in the same way. +// 4. Data item which is an alist: +// The data structure is TBD. +// (need to consider handling of keys) +// +//////////////////////////////////////////////////////////////////////// +// SCM -> PyObject +//////////////////////////////////////////////////////////////////////// + +// Basic conversion of a SCM object according to template. +extern PyObject *g2p_apply(SCM sobj,SCM stemplate); + +// Convert a SCM object into a Python object. +// If cannot convert, abort. +extern PyObject *guile2python(SCM sobj,SCM stemplate); + +// Treat the SCM object as an association list and convert it into +// Python hash, whose keys are strings corresponding to the +// association list's keys (which must be either strings or symbols). +// +// Checks: +// 1. Validity of keys. +// 2. No duplicate keys. +PyObject *guileassoc2pythondict(SCM sobj,SCM stemplate); + +// Convert from Guile list into Python tuple. +// Need separate function because the default behavior is to convert +// it into Python list. +extern PyObject *g2p_list2Tuple(SCM sobj,SCM stemplate); + +//////////////////////////////////////////////////////////////////////// +// Extra initialization after registration of all g2p_* and p2g_* +// functions. + +extern void init_default_guiletopy_templates(void); + +//////////////////////////////////////////////////////////////////////// + +#endif /* GUILETOPY_H */ + +//////////////////////////////////////////////////////////////////////// +// End of guiletopy.h diff --git a/pyguile.c b/pyguile.c new file mode 100644 index 0000000..ebf9d83 --- /dev/null +++ b/pyguile.c @@ -0,0 +1,497 @@ +// pyguile functions +// +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// + +//#include <Python.h> // included in pyguile.h +//#include <libguile.h> // included in pyguile.h +#include "pyguile.h" +#include "pytoguile.h" +#include "guiletopy.h" +#include "pysmob.h" +#include <guile/gh.h> +#include "g2p2g_smob.h" +#include "verbose.h" +#include "pyscm.h" +#include "version.h" + +//////////////////////////////////////////////////////////////////////// +// Invoke Python function +//////////////////////////////////////////////////////////////////////// + +// !!! TODO: +// Problems: +// 1. There is a general problem that exceptions seem not to be properly +// harvested! + + +static SCM sargtemplate_default; +static SCM skwtemplate_default; +static SCM srestemplate_default; + +// python_apply implements the function call: +// (python-apply ("module.submodule" 'obj 'func) +// (arg1 arg2 arg3) +// (('keyword1 . val4) ('keyword2 . val5)) +// sargtemplate +// skwtemplate) +// which is the basic way to invoke a Python function. +// +// sfunc specifies the function to be invoked. The possibilities +// are: +// String - denotes a top level function ("func" means __main__.func). +// pysmob - assumed to be a callable object. +// ("module.submodule" ...) - a List of strings/symbols/keywords +// in which the first item must be a string denotes: +// Module "module.submodule" (which should have already been imported +// using python-import) +// followed by name of object in that module, followed by attribute,..., +// until the final callable attribute. +// (pysmob ...) - a List starting with pysmob followed by +// strings/symbols/keywords - processed similarly, except that the +// pysmob stands for the module. +// sarg is a list of arguments (in Python it's *arg) +// skw is an alist (in Python it's **kw). +// sargtemplate - specifies how to convert sarg - optional argument. +// skwtemplate - specifies how to convert skw - optional argument. +// srestemplate - specifies how to convert the result back into +// SCM - optional argument. +SCM +python_apply(SCM sfunc, SCM sarg, SCM skw, + SCM sargtemplate, SCM skwtemplate, SCM srestemplate) +{ + PyObject *pfunc = NULL; + PyObject *parg = NULL; + PyObject *pkw = NULL; + + PyObject *pfuncobj = NULL; + PyObject *pres = NULL; + SCM sres = SCM_UNDEFINED; + + if (SCM_UNBNDP(sargtemplate)) { //(sargtemplate == SCM_UNDEFINED) // SCM_UNSPECIFIED + sargtemplate = sargtemplate_default; + } + if (SCM_UNBNDP(skwtemplate)) { + skwtemplate = skwtemplate_default; + } + if (SCM_UNBNDP(srestemplate)) { + srestemplate = srestemplate_default; + } + + // Retrieve the function object. + + pfunc = guile2python(sfunc,SCM_UNSPECIFIED); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYTHON_APPLY)) { + char *preprfunc = PyString_AsString(PyObject_Repr(pfunc)); + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# python_apply: decoded pfunc ~S\n"),scm_list_1(scm_makfrom0str(preprfunc))); + } + if (NULL == pfunc) { + scm_misc_error("python-apply","conversion failure (~S)", + scm_list_1(SCM_CDR(sfunc))); + } + // If it is a string, prepend it with "__main__". + if (PyString_CheckExact(pfunc)) { + // Convert it into a List of two items, to unify + // subsequent treatment. + PyObject *plist = PyList_New(2); + if (NULL == plist) { + Py_DECREF(pfunc); // NOT COVERED BY TESTS + scm_memory_error("python-apply"); // NOT COVERED BY TESTS + } + if (-1 == PyList_SetItem(plist,0,PyString_FromString("__main__"))) { + Py_DECREF(pfunc); // NOT COVERED BY TESTS + Py_DECREF(plist); // NOT COVERED BY TESTS + scm_misc_error("python-apply","PyList_SetItem 0 failure (~S)", // NOT COVERED BY TESTS + scm_list_1(SCM_CAR(sfunc))); + } + if (-1 == PyList_SetItem(plist,1,pfunc)) { + Py_DECREF(pfunc); // NOT COVERED BY TESTS + Py_DECREF(plist); // NOT COVERED BY TESTS + scm_misc_error("python-apply","PyList_SetItem 1 failure (~S)", // NOT COVERED BY TESTS + scm_list_1(SCM_CAR(sfunc))); + } + pfunc = plist; // plist stole previous pfunc's value's reference. + } + else if (IS_PYSMOBP(sfunc)) { + // We check the SCM object because guile2python() destroys + // the indication whether the SCM was originally a pysmob, when it + // converts it into PyObject. + PyObject *plist1 = PyList_New(1); + if (NULL == plist1) { + Py_DECREF(pfunc); // NOT COVERED BY TESTS + scm_memory_error("python-apply"); // NOT COVERED BY TESTS + } + if (-1 == PyList_SetItem(plist1,0,pfunc)) { + Py_DECREF(pfunc); // NOT COVERED BY TESTS + Py_DECREF(plist1); // NOT COVERED BY TESTS + scm_misc_error("python-apply","PyList_SetItem 0 failure (~S)", // NOT COVERED BY TESTS + scm_list_1(SCM_CAR(sfunc))); + } + pfunc = plist1; // plist1 stole previous pfunc's value's reference. + // Now pfunc is an 1-member list, and this member is + // expected to be callable. + } + else if (!PyList_CheckExact(pfunc)) { + // Now, the qualified function name must be a proper list. + scm_wrong_type_arg("python-apply",SCM_ARG1,sfunc); + } + + if (1 > PyList_Size(pfunc)) { + // The list must consist of at least one callable module name/object. + scm_misc_error("python-apply", + "first argument must contain at least one callable object (~S)", + scm_list_1(SCM_CAR(sfunc))); + } + + if (PyString_CheckExact(PyList_GetItem(pfunc,0))) { + // If it is a string, we assume it to be the name of a module + // which has already been imported. + // Due to the existence of dots, + // we don't allow it to be symbol or keyword. + + pfuncobj = PyImport_AddModule(PyString_AsString(PyList_GetItem(pfunc,0))); + if (NULL == pfuncobj) { + Py_DECREF(pfunc); + scm_misc_error("python-apply", + "module ~S could not be accessed - probably not imported", + scm_list_1(SCM_CAR(sfunc))); + } + Py_INCREF(pfuncobj); + } + else { + // We assume that it is a callable or object with attributes. + pfuncobj = PyList_GetItem(pfunc,0); + if (NULL == pfuncobj) { + Py_DECREF(pfunc); + scm_misc_error("python-apply", + "could not access object starting ~S", + scm_list_1(sfunc)); + } + Py_INCREF(pfuncobj); + } + + // Here we dereference attributes (if any). + int listsize = PyList_Size(pfunc); + int ind; + + for (ind = 1; ind < listsize; ++ind) { + PyObject *pnextobj = PyObject_GetAttr(pfuncobj,PyList_GetItem(pfunc,ind)); + if (NULL == pnextobj) { + PyObject *pexception = PyErr_Occurred(); + Py_DECREF(pfunc); + Py_DECREF(pfuncobj); + if (pexception) { + PyErr_Clear(); + // An AttributeError exception is expected here. + if (!PyErr_GivenExceptionMatches(pexception,PyExc_AttributeError)) { + PyObject *prepr = PyObject_Repr(pexception); + if (NULL == prepr) { + scm_misc_error("python-apply", + "python exception - could not be identified", + SCM_UNSPECIFIED); + } + else { + int strlength = PyString_Size(prepr); + char *pstr = PyString_AsString(prepr); + SCM srepr = scm_list_1(scm_mem2string(pstr,strlength)); + Py_DECREF(prepr); + scm_misc_error("python-apply", + "Python exception (~A) while dereferencing object attribute", + srepr); + } + } + // else we got the expected AttributeError exception. + } + // else we got NULL==pnextobj without Python exception. + scm_misc_error("python-apply", + "could not dereference ~Ath level attribute in ~S", + scm_list_2(scm_long2num(ind),sfunc)); + } + Py_INCREF(pnextobj); + Py_DECREF(pfuncobj); + pfuncobj = pnextobj; + } + Py_DECREF(pfunc); // We do not need it anymore. pfuncobj points at + // the function actually to be invoked. + if (!PyCallable_Check(pfuncobj)) { + Py_DECREF(pfuncobj); + scm_misc_error("python-apply","function denoted by ~S is not callable",scm_list_1(sfunc)); + } + + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYTHON_APPLY)) { + char *preprfuncobj = PyString_AsString(PyObject_Repr(pfuncobj)); + scm_simple_format(scm_current_output_port(), + scm_makfrom0str("# python_apply: decoded function actually to be invoked: ~S\n"), + scm_list_1(scm_makfrom0str(preprfuncobj))); + } + + // Retrieve positional arguments + + parg = g2p_apply(sarg,sargtemplate); + if (NULL == parg) { + Py_DECREF(pfuncobj); + scm_misc_error("python-apply","positional arguments conversion failure (~S)", + scm_list_1(sarg)); + } + // Validate that it is indeed a tuple. + if (!PyTuple_CheckExact(parg)) { + Py_DECREF(pfuncobj); + Py_DECREF(parg); + scm_wrong_type_arg("python-apply",SCM_ARG2,sarg); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYTHON_APPLY)) { + char *pposarg = PyString_AsString(PyObject_Repr(parg)); + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# python_apply: decoded positional arguments ~S\n"),scm_list_1(scm_makfrom0str(pposarg))); + } + + // Retrieve keyword arguments. + + pkw = guileassoc2pythondict(skw,skwtemplate); + if (NULL == pkw) { + // Seems that PyDict_CheckExact() does not handle NULL argument gracefully. + Py_DECREF(pfuncobj); + Py_DECREF(parg); + scm_misc_error("python-apply","keyword arguments conversion failure (~S)", + scm_list_1(skw)); + } + if (!PyDict_CheckExact(pkw)) { + Py_DECREF(pfuncobj); + Py_DECREF(parg); + Py_DECREF(pkw); + scm_misc_error("python-apply", + "keyword arguments (~S) not properly converted into Python Dict", + scm_list_1(skw)); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYTHON_APPLY)) { + char *pkwarg = PyString_AsString(PyObject_Repr(pkw)); + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# python_apply: decoded keyword arguments ~S\n"),scm_list_1(scm_makfrom0str(pkwarg))); + } + + // Ready to invoke the function. + + pres = PyEval_CallObjectWithKeywords(pfuncobj,parg,pkw); + PyObject *pexception = PyErr_Occurred(); + if (pexception) { + PyObject *prepr = PyObject_Repr(pexception); + Py_DECREF(pfuncobj); + Py_DECREF(parg); + Py_DECREF(pkw); + Py_XDECREF(pres); + PyErr_Clear(); + if (NULL == prepr) { + scm_misc_error("python-apply", + "python exception - could not be identified", + SCM_UNSPECIFIED); + } + else { + int strlength = PyString_Size(prepr); + char *pstr = PyString_AsString(prepr); + SCM srepr = scm_list_1(scm_mem2string(pstr,strlength)); + Py_DECREF(prepr); + scm_misc_error("python-apply","Python exception: ~A", + srepr); + } + } + if (NULL != pres) { + sres = p2g_apply(pres,srestemplate); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYTHON_APPLY)) { + char *presstr = PyString_AsString(PyObject_Repr(pres)); + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# python_apply: decoded results:\n# Python: ~S\n# Scheme: ~S\n"),scm_list_2(scm_makfrom0str(presstr),sres)); + } + } + else { + // else sres remains SCM_UNDEFINED. + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYTHON_APPLY)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# python_apply: Python code returned <NULL>\n"),SCM_EOL); + } + } + return(sres); +} + +//////////////////////////////////////////////////////////////////////// +// Run Python code +//////////////////////////////////////////////////////////////////////// + +SCM +python_eval(SCM sobj,SCM smode) +{ + if (!SCM_STRINGP(sobj)) { + scm_wrong_type_arg("python-eval",SCM_ARG1,sobj); + } + + int start = (SCM_UNBNDP(smode)) || (SCM_EQ_P(SCM_BOOL_F,smode)) + ? Py_file_input + : Py_eval_input; + + char *pstr = gh_scm2newstr(sobj,NULL); + if (NULL == pstr) { + scm_memory_error("python-eval"); // NOT COVERED BY TESTS + //return(SCM_UNSPECIFIED); + } + + PyObject *pmaindict = + PyModule_GetDict(PyImport_AddModule("__main__")); + if (NULL == pmaindict) { + scm_misc_error("python-eval","could not get __main__ for (~S), mode ~A", // NOT COVERED BY TESTS + scm_list_2(sobj,smode)); + } + Py_INCREF(pmaindict); + PyObject *pres = PyRun_String(pstr, start, pmaindict, pmaindict); + Py_DECREF(pmaindict); + free(pstr); + + PyObject *pexception = PyErr_Occurred(); + if (pexception) { + PyObject *prepr = PyObject_Repr(pexception); + Py_XDECREF(pres); + PyErr_Clear(); + if (NULL == prepr) { + scm_misc_error("python-eval", // NOT COVERED BY TESTS + "python exception - could not be identified", + SCM_UNSPECIFIED); + } + else { + int strlength = PyString_Size(prepr); + char *pstr = PyString_AsString(prepr); + SCM slist = scm_list_1(scm_mem2string(pstr,strlength)); + Py_DECREF(prepr); + scm_misc_error("python-eval","Python exception: ~A", + slist); + } + } + + switch(start) { + case Py_eval_input: + { + if (NULL != pres) { + SCM sres = p2g_apply(pres, + SCM_EQ_P(SCM_BOOL_T,smode) + ? srestemplate_default : smode); + Py_DECREF(pres); + return(sres); + } + else { + scm_misc_error("python-eval","could not return result of evaluation", + SCM_UNSPECIFIED); + return(SCM_UNSPECIFIED); + } + } + case Py_file_input: + default: + { + Py_XDECREF(pres); + return(SCM_UNSPECIFIED); + } + } +} + +//////////////////////////////////////////////////////////////////////// +// Import a Python module and return a wrapped pointer to it. +//////////////////////////////////////////////////////////////////////// + +SCM +python_import(SCM smodulename) +{ + if (!SCM_STRINGP(smodulename)) { + scm_wrong_type_arg("python-import",SCM_ARG1,smodulename); + } + else { + char *mname = gh_scm2newstr(smodulename,NULL); + PyObject *pmodule = PyImport_ImportModule(mname); + PyObject *pexception = PyErr_Occurred(); + if (pexception) { + PyObject *prepr = PyObject_Repr(pexception); + Py_XDECREF(pmodule); + PyErr_Clear(); + + SCM smname = scm_list_1(scm_mem2string(mname,strlen(mname))); + free(mname); + if (NULL == prepr) { + scm_misc_error("python-import", // NOT COVERED BY TESTS + "Python exception during module ~A import - could not be identified", + smname); + } + else { + int strlength = PyString_Size(prepr); + char *pstr = PyString_AsString(prepr); + SCM slist = scm_list_2(SCM_CAR(smname),scm_mem2string(pstr,strlength)); + Py_DECREF(prepr); + scm_misc_error("python-import", + "Python exception during module ~A import: ~A", + slist); + } + } + // OK, exception did not occur. Do we have a module? + if (NULL == pmodule) { + SCM slist = scm_list_1(scm_mem2string(mname,strlen(mname))); + free(mname); + scm_misc_error("python-eval","could not import module ~S", + slist); + } + free(mname); + SCM smodule = wrap_pyobject(pmodule); + Py_DECREF(pmodule); // wrap_pyobject did Py_INCREF + return(smodule); + } +} + +//////////////////////////////////////////////////////////////////////// +// Version and build information +//////////////////////////////////////////////////////////////////////// + +// The macros used below are defined in version.h. +SCM +pyguile_version(void) +{ + return(scm_makfrom0str("PyGuile Version " PYGUILE_VERSION ", Build " PYGUILE_BUILD)); +} + +void +init_wrapper (void) +{ + Py_Initialize(); + if (atexit(Py_Finalize)) { + fprintf(stderr,"cannot set Python finalization function\n"); // NOT COVERED BY TESTS + exit(1); // NOT COVERED BY TESTS + } + initpyscm(); + + init_pysmob_type(); + init_g2p2g_smob_type(); + + // The following must happen after init_g2p2g_smob_type(). + init_default_guiletopy_templates(); + + SCM s_default_g2p = scm_variable_ref(scm_c_lookup("guile2python")); + sargtemplate_default = scm_permanent_object(scm_list_2(scm_variable_ref(scm_c_lookup("g2p_list2Tuple")),s_default_g2p)); + skwtemplate_default = SCM_UNDEFINED; // guileassoc2pythondict will choose the right default. + srestemplate_default = scm_permanent_object(scm_variable_ref(scm_c_lookup("python2guile"))); + + scm_c_define_gsubr ("python-eval",1,1,0,python_eval); + scm_c_define_gsubr ("python-apply",3,3,0,python_apply); + scm_c_define_gsubr ("python-import",1,0,0,python_import); + scm_c_define_gsubr ("pyguile-verbosity-set!",1,0,0,pyguile_verbosity_set); + scm_c_define_gsubr ("pyguile-version",0,0,0,pyguile_version); +} + +//////////////////////////////////////////////////////////////////////// +// End of pyguile.c diff --git a/pyguile.h b/pyguile.h new file mode 100644 index 0000000..f524232 --- /dev/null +++ b/pyguile.h @@ -0,0 +1,87 @@ +// pyguile header file +#ifndef PYGUILE_H +#define PYGUILE_H + +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// + +#include <Python.h> // Must be first header file +#include <libguile.h> + +//////////////////////////////////////////////////////////////////////// +// Functions exported by the library to Guile +//////////////////////////////////////////////////////////////////////// + + +// Import Python module whose name is given in the SCM. +// The returned value is a pysmob pointing at the module PyObject. +// +// NOTE: support for representing nested module names (like "os.path") +// by a list like ("os" "path") will be implemented by means +// of Scheme level macros. +// NOTE: we support only strings - not symbols or keywords - due to +// potential existence of dots. +extern SCM python_import(SCM smodulename); + +//////////////////////////////////////////////////////////////////////// +// Final pyguile interface functions +//////////////////////////////////////////////////////////////////////// + +// Evaluate a string, which is a Python script. +// sobj - the string to be evaluated. +// smode - #f or unspecified - if the caller does not expect a return value. +// The string is evaluated under Py_file_input. +// #t if the caller expects a return value and wants to +// convert it using the default template (python2guile). +// The string is evaluated under Py_eval_input. +// Any other value - is caller-supplied template for +// converting the return value. +// PyRun_String() 2nd argument value is +// smode ? Py_eval_input : Py_file_input +// This is an optional argument, defaulting to #f. +// +// Typical use: +// import sys; sys.path = ['']+sys.path +// in order to be able to load modules from home directory. +extern SCM python_eval(SCM sobj,SCM smode); + + +// The Python call is func(*args,**kwargs) +extern SCM python_apply(SCM sfunc, SCM sarg, SCM skw, + SCM sargtemplate, SCM skwtemplate, + SCM srestemplate); +// sfunc can either be a function object or a list of strings, the first +// of which names a module and the others select attributes of the +// module's attributes. +// sarg is a list of values, which are to serve as positional +// arguments for the function. +// skw is a list of pairs, each pair consisting of keyword (a string) +// and a value. +// sargtemplate - specifies how to convert sarg - optional argument. +// skwtemplate - specifies how to convert skw - optional argument. +// srestemplate - specifies how to convert the result back into +// SCM - optional argument. + +#endif /* PYGUILE_H */ + +//////////////////////////////////////////////////////////////////////// +// End of pyguile.h diff --git a/pyguile.scm.in b/pyguile.scm.in new file mode 100644 index 0000000..7782c50 --- /dev/null +++ b/pyguile.scm.in @@ -0,0 +1,49 @@ +; Load extension library pyguile.so +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Autogenerated at time %(timestamp)s +;;; +;;; To invoke it: +;;; (use-modules (pyguile)) +;;; Then, can use: (python-import "mname") +;;; (python-apply '("mname" "fname") '(#t) '()) +;;; +;;; Note: before importing modules from your working directory, +;;; you have to invoke: +;;; (exec-python-str "import sys;sys.path = ['']+sys.path\n") +;;; before trying to import anything. +;;; +(define-module (pyguile)) +(export python-eval) +(export python-apply) +(export python-import) +(export pyguile-verbosity-set!) +(export python-version) +;To obtain guile version, use (version) +(export pyguile-version) +%(autogeneratedexports)s +(export pyscm-default-template) ; Default template for wrapping procedures + ; into PyObjects. +(load-extension "./libpyguile.so" "init_wrapper") +(define python-version + (lambda () (python-eval "__import__('sys').version" #t))) +; End of pyguile.scm @@ -0,0 +1,577 @@ +// pyscm implementation file +// Python data type for wrapping Guile SCM objects +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// +// +// Implements PySCM data type - used for wrapping SCM objects in Python +// and making it possible to call them and access/manipulate their +// attributes. + +#include "pyscm.h" +#include "guiletopy.h" +#include "pytoguile.h" +#include "verbose.h" + +//////////////////////////////////////////////////////////////////////// +// Guile Data Structures +//////////////////////////////////////////////////////////////////////// + +static SCM pyscm_registration_hash; // Hash table for preservation of SCMs referred to by PySCM instances. We use *_hashv_* functions for the keys. !!!to ensure that eqv? comparisons are OK here +static long pyscm_registration_index; // Used for building keys for the above hash table. + + +//////////////////////////////////////////////////////////////////////// +// Python Data Structures +//////////////////////////////////////////////////////////////////////// + +//static PyObject *ErrorObject; + +typedef struct { + PyObject_HEAD + long ob_scm_index; // Index into the SCM registration hash table. +} pyscm_PySCMObject; + + +//static struct PyMethodDef pyscm_PySCM_methods[] = { +// {NULL, NULL} /* sentinel */ +//}; + + +static char pyscm_PySCMtype__doc__[] = +"PyGuile SCM wrapper object" +; + + +static int pyscm_PySCM_print(pyscm_PySCMObject *self, FILE *fp, int flags); +static PyObject *pyscm_PySCM_getattr(pyscm_PySCMObject *self, char *name); +static int pyscm_PySCM_setattr(pyscm_PySCMObject *self, + char *name, PyObject *v); +static long pyscm_PySCM_hash(pyscm_PySCMObject *self); +static PyObject *pyscm_PySCM_call(pyscm_PySCMObject *self, + PyObject *args, PyObject *kwargs); +static PyObject *pyscm_PySCM_str(pyscm_PySCMObject *self); +static void pyscm_PySCM_dealloc(pyscm_PySCMObject *self); +static PyObject *pyscm_PySCM_new(PyTypeObject *type, + PyObject *args, PyObject *kwds); + + +static PyTypeObject pyscm_PySCMType = { + PyObject_HEAD_INIT(&PyType_Type) + 0, /*ob_size*/ + "pyscm.PySCM", /*tp_name*/ + sizeof(pyscm_PySCMObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)pyscm_PySCM_dealloc, /*tp_dealloc*/ + (printfunc)pyscm_PySCM_print, /*tp_print*/ + (getattrfunc)pyscm_PySCM_getattr, /*tp_getattr*/ + (setattrfunc)pyscm_PySCM_setattr, /*tp_setattr*/ + (cmpfunc)0, /*tp_compare*/ + (reprfunc)0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + (hashfunc)pyscm_PySCM_hash, /*tp_hash */ + (ternaryfunc)pyscm_PySCM_call, /*tp_call*/ + (reprfunc)pyscm_PySCM_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT /*| Py_TPFLAGS_BASETYPE*/ , /*tp_flags*/ // We don't expect to subclass this class. + pyscm_PySCMtype__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)0, /* tp_init */ + 0, /* tp_alloc */ + (newfunc)pyscm_PySCM_new, /* tp_new */ +}; + + +//////////////////////////////////////////////////////////////////////// +// Functions +//////////////////////////////////////////////////////////////////////// + +static int +pyscm_PySCM_print(pyscm_PySCMObject *self, FILE *fp, int flags) +{ + PyObject *pstr = pyscm_PySCM_str(self); + if (NULL == pstr) { + scm_memory_error("pyscm_PySCM_print"); + } + int retval = PyObject_Print(pstr,fp,flags); + Py_DECREF(pstr); + return(retval); +} + + +// Documentation of the stemplate data structure, which is paired +// with the actual SCM wrapped by an PySCM instance. +// It is a 5-element vector with the following elements. +// The first 4 elements deal with the SCM being wrapped by a callable +// PySCM. +// If any of the three templates is SCM_EOL, then the corresponding type +// of arguments/result is not expected to exist (if the SCM object +// returns a value to template of SCM_EOL, then the value is discarded +// and None is returned to Python). +// If GET_APPLY_FUNC() has the value SCM_EOL instead of a function, +// it means that the object is not callable. +// +// If the relevant template value is #t, then a default is used. (CANCELLED - when building the template, can use a macro to fill in defaults.) +// 0. p2g template for positional arguments +#define GET_P2G_POSITIONAL_ARGS_TEMPLATE(stemplate) scm_vector_ref(stemplate,scm_long2num(0)) +// 1. p2g template for keyword arguments +#define GET_P2G_KEYWORD_ARGS_TEMPLATE(stemplate) scm_vector_ref(stemplate,scm_long2num(1)) +// 2. g2p template for result +#define GET_G2P_RESULT_TEMPLATE(stemplate) scm_vector_ref(stemplate,scm_long2num(2)) +// 3. function for actually applying the SCM object on the arguments +// (default being 'apply'). +#define GET_APPLY_FUNC(stemplate) scm_vector_ref(stemplate,scm_long2num(3)) +// 4. Either #f or a hash (_hashv_ type) whose keys are described below. +#define GET_ATTRS_HASH(stemplate) scm_vector_ref(stemplate,scm_long2num(4)) +// +// If the 5th element is #f, then the SCM has no attributes. +// Otherwise, the SCM has attributes (which can be either data or methods), +// and the 5th element is supposed to be an hash. +// +// The hash keys are as follows: +// #t - for default values (CANCELLED - when building the template, can use +// a macro to fill in defaults.) +// #f - how to deal with a missing attribute - value can be either +// another #f (throw an attribute error exception to Python) or a +// 4-element vector as described below. +// #:-keyword - refers to attribute 'keyword' +// The values are either #t (to use defaults for everything) (CANCELLED - when +// building the template, can use a macro to fill +// in defaults.) +// or 4-element vectors: +// 0. p2g template for converting __setattr__ value +#define GET_H_P2G_SETATTR_TEMPLATE(shashvalue) scm_vector_ref(shashvalue,scm_long2num(0)) +// 1. g2p template for converting __getattr__ value +#define GET_H_G2P_GETATTR_TEMPLATE(shashvalue) scm_vector_ref(shashvalue,scm_long2num(1)) +// 2. function (func sobj #:-keyword . value) for doing the real +// setattr work; if the value is missing, do delattr. +// It is expected to return #f if it failed, or any other value (including +// SCM_UNDEFINED) if it succeeded. +#define GET_H_SETATTR_FUNC(shashvalue) scm_vector_ref(shashvalue,scm_long2num(2)) +// 3. function (func sobj #:-keyword) for doing the real getattr +// work. +#define GET_H_GETATTR_FUNC(shashvalue) scm_vector_ref(shashvalue,scm_long2num(3)) +// If any of the above is #t then get the corresponding element from +// the default vector. (CANCELLED) +// If any of the GET_H_{GET,SET}ATTR_FUNC values is SCM_EOL, then the +// corresponding function is suppressed. The *_TEMPLATE values must be +// valid whenever the corresponding function exists. +// The value corresponding to the key #f can also be another #f, which +// would cause Python attribute error to be raised. This +// mechanism allows objects to decide how they wish currently-nonexistent +// attributes to be handled. +// In the case of an attribute which is recognized by Python as a method, +// the g2p template for __getattr__ would be a pair of g2p_opaque2PySCM +// and a whole stemplate as described above. +// +// Signatures of SCM functions to be invoked by Python: +// Callable SCM objects wrapped by PySCM - always have two arguments. +// When default templates are used, the first argument's value is a list, +// and the second argument's value is an alist. +// Apply procedure (obtained by GET_APPLY_FUNC()) - has the same signature +// as Scheme's apply procedure i.e. (apply func . args) + + +// Functions for manipulating vectors: +// SCM_VECTORP() +// SCM_VECTOR_LENGTH() +// scm_vector(scm_list_2(sobj1,sobj2)) +// scm_vector_ref(vector,scm_long2num(index_zero_based)) + +// PROBLEM: need to prepend "-" to name before converting it into +// #:-keyword - inefficient! How to eliminate this? + +// Common code for pyscm_PySCM_getattr() and pyscm_PySCM_setattr(): +// Retrieve and return the 4-element vector corresponding to desired +// attribute of the pyscm_PySCMObject. +// Perform also validity checking and raise Python exception if +// invalid. +// Since it is needed later, also the SCM object, corresponding to the +// pyscm_PySCMObject, is returned to the caller, put into 2-element +// list together with the #:-keyword corresponding to name. +static SCM +retrieve_sattr_vector(pyscm_PySCMObject *self, char *name, SCM *sobj_keyword) +{ + SCM shandle = scm_hashv_get_handle(pyscm_registration_hash,scm_long2num(self->ob_scm_index)); + if (SCM_BOOLP(shandle) && SCM_EQ_P(SCM_BOOL_F,shandle)) { + Py_FatalError("PySCM object lost its associated SCM object"); + } + // Now: + // SCM_CADR(shandle) is the SCM object itself + // SCM_CDDR(shandle) is the stemplate. + SCM sattrshash = GET_ATTRS_HASH(SCM_CDDR(shandle)); + + if (SCM_EQ_P(SCM_BOOL_F,sattrshash)) { + PyErr_SetString(PyExc_AttributeError, name); + return(SCM_UNDEFINED); // Error return + } + + // The object has attributes. Build the hash key (a keyword). + + size_t nlength = strlen(name); + char *dashstr = malloc(nlength+2); + dashstr[0] = '-'; + dashstr[1] = '\0'; + strncat(dashstr,name,nlength); + SCM skeyword = scm_make_keyword_from_dash_symbol(scm_str2symbol(dashstr)); + // !!! Do we have to free dashstr? + // !!! Similar code is used also in pytoguile.c - review it. + + SCM sattr_vector_handle = scm_hashv_get_handle(sattrshash,skeyword); + if (SCM_EQ_P(SCM_BOOL_F,sattr_vector_handle)) { + // Missing attribute. How to deal with it? + sattr_vector_handle = scm_hashv_get_handle(sattrshash,SCM_BOOL_F); + if (SCM_EQ_P(SCM_BOOL_F,sattr_vector_handle)) { + // Hash value corresponding to key #f is itself another #f, which + // means that the object does not wish to exhibit to Python + // unknown attributes. + PyErr_SetString(PyExc_AttributeError, name); + return(SCM_UNDEFINED); // Error return + } + // Otherwise, we'll use the hash value corresponding to #f as + // a catch-all for all attributes not otherwise defined. + } + *sobj_keyword = scm_list_2(SCM_CADR(shandle),skeyword); + return(SCM_CDR(sattr_vector_handle)); +} + +static PyObject * +pyscm_PySCM_getattr(pyscm_PySCMObject *self, char *name) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# pyscm_PySCM_getattr: trying to get attribute=~S from pobj=~S\n"),scm_list_2(scm_makfrom0str(name),verbosity_repr((PyObject *)self))); + } + SCM sobj_keyword; + SCM sattr_vector = retrieve_sattr_vector(self,name,&sobj_keyword); + if (SCM_UNBNDP(sattr_vector)) { + // Attribute error exception was raised by retrieve_sattr_vector(). + return(NULL); + } + + SCM sgetattr_func = GET_H_GETATTR_FUNC(sattr_vector); + if (SCM_EQ_P(SCM_EOL,sgetattr_func)) { + PyErr_SetString(PyExc_AttributeError, name); + return(NULL); + } + SCM stemplate = GET_H_G2P_GETATTR_TEMPLATE(sattr_vector); + + SCM sresult = scm_apply(sgetattr_func,sobj_keyword,SCM_EOL); + return(g2p_apply(sresult,stemplate)); +} + +static int +pyscm_PySCM_setattr(pyscm_PySCMObject *self, char *name, PyObject *v) +{ + /* Set attribute 'name' to value 'v'. v==NULL means delete */ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# pyscm_PySCM_setattr: trying to set attribute=~S from pobj=~S to value ~S\n"),scm_list_3(scm_makfrom0str(name),verbosity_repr((PyObject *)self),verbosity_repr(v))); + } + SCM sobj_keyword; + SCM sattr_vector = retrieve_sattr_vector(self,name,&sobj_keyword); + if (SCM_UNBNDP(sattr_vector)) { + // Attribute error exception was raised by retrieve_sattr_vector(). + return(-1); + } + + SCM ssetattr_func = GET_H_SETATTR_FUNC(sattr_vector); + if (SCM_EQ_P(SCM_EOL,ssetattr_func)) { + PyErr_SetString(PyExc_AttributeError, name); + return(-1); + } + + if (NULL != v) { + SCM sval = p2g_apply(v, + GET_H_P2G_SETATTR_TEMPLATE(sattr_vector)); + scm_append_x(scm_list_2(sobj_keyword,sval)); + } + + SCM sresult = scm_apply(ssetattr_func,sobj_keyword,SCM_EOL); + return(SCM_EQ_P(SCM_BOOL_F,sresult) ? (-1) : 0); +} + +static long +pyscm_PySCM_hash(pyscm_PySCMObject *self) +{ + /* Return a hash of self (or -1) */ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# pyscm_PySCM_hash: hash is ~S\n"),scm_list_1(scm_long2num(self->ob_scm_index))); + } + return(self->ob_scm_index); +} + +// Compute logical XOR of a and b +int logical_xor(int a,int b) +{ + return((a == 0) + ? (b != 0) + : (b == 0)); +} +// Compute logical equivalence of a and b (logical inverse of XOR) +int logical_equiv(int a,int b) +{ + return((a != 0) + ? (b != 0) + : (b == 0)); +} + +static PyObject * +pyscm_PySCM_call(pyscm_PySCMObject *self, PyObject *args, PyObject *kwargs) +{ + /* Return the result of calling self with argument args */ + + SCM shandle = scm_hashv_get_handle(pyscm_registration_hash,scm_long2num(self->ob_scm_index)); + if (SCM_BOOLP(shandle) && SCM_EQ_P(SCM_BOOL_F,shandle)) { + Py_FatalError("PySCM object lost its associated SCM object"); // NOT COVERED BY TESTS + } + // Now: + // SCM_CADR(shandle) is the SCM object itself + // SCM_CDDR(shandle) is the stemplate. + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# pyscm_PySCM_call: calling ~S with args=~S and keywords=~S; stemplate=~S\n"),scm_list_4(SCM_CADR(shandle),verbosity_repr(args),verbosity_repr(kwargs),SCM_CDDR(shandle))); + } + + SCM sapply_func = GET_APPLY_FUNC(SCM_CDDR(shandle)); + if (SCM_EQ_P(SCM_EOL,sapply_func)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# pyscm_PySCM_call: raising exceptions.TypeError due to \"PySCM wraps a non-callable SCM\"\n"),SCM_EOL); + } + PyErr_SetString(PyExc_TypeError, "PySCM wraps a non-callable SCM"); + return(NULL); + } + + // Process arguments. + SCM sargs_template = GET_P2G_POSITIONAL_ARGS_TEMPLATE(SCM_CDDR(shandle)); + SCM skwargs_template = GET_P2G_KEYWORD_ARGS_TEMPLATE(SCM_CDDR(shandle)); + /*if (logical_xor(SCM_EQ_P(SCM_EOL,sargs_template),(NULL==args)) + || logical_xor(SCM_EQ_P(SCM_EOL,skwargs_template),(NULL==kwargs)))*/ + // The following allows template to exist without actual arguments. + if ((SCM_EQ_P(SCM_EOL,sargs_template) && (NULL != args)) + || (SCM_EQ_P(SCM_EOL,skwargs_template) && (NULL != kwargs))) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# pyscm_PySCM_call: raising exceptions.TypeError due to \"wrapped SCM does not take some of the provided arguments\"\n"),SCM_EOL); + } + PyErr_SetString(PyExc_TypeError, "wrapped SCM does not take some of the provided arguments"); + return(NULL); + } + + SCM sargs = SCM_EQ_P(SCM_EOL,sargs_template) || (NULL == args) + ? SCM_EOL : p2g_apply(args,sargs_template); + SCM skwargs = SCM_EQ_P(SCM_EOL,skwargs_template) || (NULL == kwargs) + ? SCM_EOL : p2g_apply(kwargs,skwargs_template); + + SCM sresult = scm_apply(sapply_func,scm_list_2(SCM_CADR(shandle),scm_list_2(sargs,skwargs)),SCM_EOL); + SCM sresult_template = GET_G2P_RESULT_TEMPLATE(SCM_CDDR(shandle)); + if (SCM_EQ_P(SCM_EOL,sresult_template)) { + Py_RETURN_NONE; + } + else { + return(g2p_apply(sresult,sresult_template)); + } +} + +// Does not include the template object in the string representation. +static PyObject * +pyscm_PySCM_str(pyscm_PySCMObject *self) +{ + if (0 == self->ob_scm_index) { + return(PyString_FromString("<no SCM association>")); + } + SCM shandle = scm_hashv_get_handle(pyscm_registration_hash,scm_long2num(self->ob_scm_index)); + if (SCM_BOOLP(shandle) && SCM_EQ_P(SCM_BOOL_F,shandle)) { + Py_FatalError("PySCM object lost its associated SCM object"); + } + SCM sstr = scm_object_to_string(SCM_CADR(shandle),scm_variable_ref(scm_c_lookup("write"))); + + PyObject *pstr = PyString_FromStringAndSize(SCM_STRING_CHARS(sstr),SCM_STRING_LENGTH(sstr)); + return(pstr); // possibly NULL. +} + +static void +pyscm_PySCM_dealloc(pyscm_PySCMObject *self) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# pyscm_PySCM_dealloc: deallocating PySCMObject with hash ~S\n"),scm_list_1(scm_long2num(self->ob_scm_index))); + } + if (0 != self->ob_scm_index) { + // Unregister the associated SCM from the hash table. + SCM shashkey = scm_long2num(self->ob_scm_index); + scm_hashv_remove_x(pyscm_registration_hash,shashkey); + // If ob_scm_index is zero, no SCM was associated yet with + // this PySCM instance. + } + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject * +pyscm_PySCM_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + pyscm_PySCMObject *self; + if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# pyscm_PySCM_new: was called\n"),SCM_EOL); + } + self = (pyscm_PySCMObject *)type->tp_alloc(type,0); + if (NULL != self) { + self->ob_scm_index = 0; + } + return((PyObject *)self); +} + +//////////////////////////////////////////////////////////////////////// +// Interface to the rest of PyGuile +//////////////////////////////////////////////////////////////////////// + +// Create a pyscm_PySCMObject instance, which wraps sobj and associates +// with it with template for data conversions when python accesses data +// and functions/methods associated with sobj. +PyObject * +wrap_scm(SCM sobj,SCM stemplate) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# wrap_scm: was called to wrap ~S\n"),scm_list_1(sobj)); + } + pyscm_PySCMObject *pwrapper = PyObject_New(pyscm_PySCMObject,&pyscm_PySCMType); + if (NULL == pwrapper) { + scm_memory_error("wrap_scm"); // NOT COVERED BY TESTS + } + //PyObject_Init(pwrapper,&pyscm_PySCMType); // Is it needed or does PyObject_New() take care of it? + //if (NULL == pwrapper) { + // scm_misc_error("wrap_scm","could not wrap object ~S with PySCM when using conversion template ~S", + // scm_list_2(sobj,stemplate)); + //} + else { + SCM sconsed = scm_cons(sobj,stemplate); + SCM shashkey = scm_long2num(++pyscm_registration_index); + scm_hashv_create_handle_x(pyscm_registration_hash,shashkey,sconsed); + pwrapper->ob_scm_index = pyscm_registration_index; + return((PyObject *)pwrapper); + } +} + +// Return 0 if pobj is not of this type and/or does not wrap a SCM. +// Otherwise, return a nonzero value. +int +PySCMObject_Check(PyObject *pobj) +{ + if (!PyObject_TypeCheck(pobj, &pyscm_PySCMType)) { + return(0); + } + return ((0 == ((pyscm_PySCMObject *)pobj)->ob_scm_index) + ? 0 // pobj does not actually wrap a SCM. + : 1); +} + +// Unwrap a pyscm_PySCMObject instance and get from it the original +// SCM object. If the object is not a pyscm_PySCMObject or does not +// wrap a SCM object, raise an error. +SCM +unwrap_pyscm_object(PyObject *pobj) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_PYSCM)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# unwrap_pyscm_object: trying to unwrap pobj=~S\n"),scm_list_1(verbosity_repr(pobj))); + } + + if (!PySCMObject_Check(pobj)) { + Py_FatalError("Trying to pyscm-unwrap a non-PySCM"); + } + SCM shandle = scm_hashv_get_handle(pyscm_registration_hash,scm_long2num(((pyscm_PySCMObject *)pobj)->ob_scm_index)); + return(SCM_CADR(shandle)); +} + +//////////////////////////////////////////////////////////////////////// +// Initializer +//////////////////////////////////////////////////////////////////////// + +static struct PyMethodDef pyscm_methods[] = { + + {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ +}; + + +/* Initialization function for the module (*must* be called initpyscm) */ + +static char pyscm_module_documentation[] = +"pyscm - defines the Custom Python datatype PySCM for wrapping SCM objects" +; + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC +initpyscm(void) +{ + PyObject *m; + + /*pyscm_PySCMType.tp_new = PyType_GenericNew;*/ + if (PyType_Ready(&pyscm_PySCMType) < 0) { + return; // NOT COVERED BY TESTS + } + + /* Create the module and add the functions */ + m = Py_InitModule4("pyscm", pyscm_methods, + pyscm_module_documentation, + (PyObject*)NULL,PYTHON_API_VERSION); + if (NULL == m) { + return; // NOT COVERED BY TESTS + } + + Py_INCREF(&pyscm_PySCMType); + PyModule_AddObject(m, "PySCM", (PyObject *)&pyscm_PySCMType); + + /* Add some symbolic constants to the module */ + //PyObject *d; + //d = PyModule_GetDict(m); + //ErrorObject = PyString_FromString("pyscm.error"); + //PyDict_SetItemString(d, "error", ErrorObject); + + /* Add constants here */ + // Currently, none is needed. + + /* Check for errors */ + if (PyErr_Occurred()) { + Py_FatalError("can't initialize module pyscm"); // NOT COVERED BY TESTS + } + + // This part initializes the Guile data structures needed + // by this module. + pyscm_registration_hash = scm_permanent_object(scm_c_make_hash_table(65537)); + pyscm_registration_index = 0; +} + +//////////////////////////////////////////////////////////////////////// +// End of pyscm.c @@ -0,0 +1,55 @@ +// pyscm header file +// Python data type for wrapping Guile SCM objects +//////////////////////////////////////////////////////////////////////// + +#ifndef PYSCM_H +#define PYSCM_H + +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// + +#include <Python.h> // Must be first header file +#include <libguile.h> + +//////////////////////////////////////////////////////////////////////// +// pyscm.PySCM +//////////////////////////////////////////////////////////////////////// + +PyObject *wrap_scm(SCM sobj,SCM stemplate); + +// Return 0 if pobj is not of this type and/or does not wrap a SCM. +// Otherwise, return a nonzero value. +int PySCMObject_Check(PyObject *pobj); + +// Unwrap a pyscm_PySCMObject instance and get from it the original +// SCM object. If the object is not a pyscm_PySCMObject or does not +// wrap a SCM object, raise an error. +SCM unwrap_pyscm_object(PyObject *pobj); + +void initpyscm(void); + +//////////////////////////////////////////////////////////////////////// + +#endif /* PYSCM_H */ + +//////////////////////////////////////////////////////////////////////// +// End of pyscm.h diff --git a/pysmob.c b/pysmob.c new file mode 100644 index 0000000..98c26f4 --- /dev/null +++ b/pysmob.c @@ -0,0 +1,342 @@ +// pysmob implementation +// +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// +// +// Implements the Guile SMOB type, which encapsulates Python objects. +// While those SMOBs can be used to encapsulate any PyObject* object, +// they are typically used to encapsulate only class instances. +// +//////////////////////////////////////////////////////////////////////// +// +// Note about verbosity handling: +// +// This module uses printf() rather than scm_simple_format & Co. for +// PYGUILE_VERBOSE_GC and PYGUILE_VERBOSE_GC_DETAILED. +// The reason is that scm_* functions cannot be used during garbage +// collection. +// +//////////////////////////////////////////////////////////////////////// +#include "pysmob.h" +#include "verbose.h" + +#ifndef Py_ssize_t +#define Py_ssize_t int +#endif /* Py_ssize_t */ + +static scm_t_bits tag_pysmob; + +//////////////////////////////////////////////////////////////////////// +// Wrapped PyObject memory management +//////////////////////////////////////////////////////////////////////// + +static PyObject *pdict_wrapped_pyobjects; +// Pointer at Dict, whose keys are addresses of wrapped PyObjects. +// (The keys have to be addresses, because the objects themselves +// can be mutable and hence unsuitable for use as Dict keys.) + +//scm_t_c_hook_function clear_pdict_values; +//scm_t_c_hook_function delete_unmarked_pdict_keys; + +void * +clear_pdict_values(void *hook_data, void *func_data, void *data) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC)) { + //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# Starting garbage collection (mark phase) - entered clear_pdict_values\n"),SCM_EOL); + printf("# Starting garbage collection (mark phase) - entered clear_pdict_values\n"); + } + PyObject *pdict = (PyObject *)func_data; + if (!PyDict_CheckExact(pdict)) { + scm_misc_error("clear_pdict_values","invalid pysmobs Dict",SCM_EOL); // NOT COVERED BY TESTS + } + + PyObject *key; + PyObject *value; + Py_ssize_t pos = 0; + while(PyDict_Next(pdict,&pos,&key,&value)) { + Py_INCREF(Py_False); + if (0 != PyDict_SetItem(pdict, + key, + Py_False)) { + Py_DECREF(Py_False); // NOT COVERED BY TESTS + scm_misc_error("clear_pdict_values","failed to clear pysmobs Dict",SCM_EOL); // NOT COVERED BY TESTS + } + } + return(NULL); +} + +void * +delete_unmarked_pdict_keys(void *hook_data, void *func_data, void *data) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC)) { + //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# Going to finish garbage collection (sweep phase) - entered delete_unmarked_pdict_keys\n"),SCM_EOL); + printf("# Going to finish garbage collection (sweep phase) - entered delete_unmarked_pdict_keys\n"); + } + PyObject *pdict = (PyObject *)func_data; + if (!PyDict_CheckExact(pdict)) { + scm_misc_error("delete_unmarked_pdict_keys","invalid pysmobs Dict",SCM_EOL); // NOT COVERED BY TESTS + } + + PyObject *pdict_clone = PyDict_Copy(pdict); + if (NULL == pdict_clone) { + PyErr_Clear(); // NOT COVERED BY TESTS + scm_misc_error("delete_ummarked_pdict_keys","failed to prepare for deleting from Dict",SCM_EOL); // NOT COVERED BY TESTS + } + + PyObject *key; + PyObject *value; + Py_ssize_t pos = 0; + while(PyDict_Next(pdict_clone,&pos,&key,&value)) { + if (Py_False == value) { + Py_INCREF(key); + if (0 != PyDict_DelItem(pdict,key)) { + PyErr_Clear(); // NOT COVERED BY TESTS + Py_DECREF(key); // NOT COVERED BY TESTS + Py_DECREF(pdict_clone); // NOT COVERED BY TESTS + scm_misc_error("delete_ummarked_pdict_keys","failed to delete item from Dict",SCM_EOL); // NOT COVERED BY TESTS + } + long pptr = PyLong_AsLong(key); + Py_DECREF((PyObject *)pptr); // Actually delete (if necessary) the formerly-wrapped PyObject. + Py_DECREF(key); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC_DETAILED)) { + //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# delete_unmarked_pdict_keys: deleting ~S\n"),scm_list_1(scm_long2num(pptr))); + printf("# delete_unmarked_pdict_keys: deleting 0x%08lX\n",pptr); + } + } + } + Py_DECREF(pdict_clone); + return(NULL); +} + + +//////////////////////////////////////////////////////////////////////// +// Private functions +//////////////////////////////////////////////////////////////////////// + +static SCM +mark_pysmob(SCM psmob) +{ + // New system of garbage collection: + // Each time we wrap a PyObject, we add its address, as a key, to + // a Python Dict, which will serve as hash table. + // + // Python facilities will be used for this purpose. + // + // When starting the mark phase, we clear all values in the + // aforementioned Python Dict. + // + // For each pysmob to be marked, we mark the corresponding Python + // Dict value. + // + // At end of the sweep phase, we delete from the Python Dict any + // items not marked. + // The reference counting will be maintained by ownership by the + // Python Dict. Guile will not deal with reference counting + // related to pysmobs at all. + + long key = (long) unwrap_pysmob(psmob); + PyObject *pkey = PyLong_FromLong(key); + if (NULL == pkey) { + PyErr_Clear(); // NOT COVERED BY TESTS + scm_memory_error("mark_pysmob"); // NOT COVERED BY TESTS + } + int ret = PyDict_Contains(pdict_wrapped_pyobjects,pkey); + if (-1 == ret) { + PyErr_Clear(); // NOT COVERED BY TESTS + Py_DECREF(pkey); // NOT COVERED BY TESTS + scm_misc_error("mark_pysmob","error when checking pysmobs Dict for ~S",scm_list_1(psmob)); // NOT COVERED BY TESTS + } + if (0 == ret) { + Py_DECREF(pkey); + scm_misc_error("mark_pysmob","smob not found in pysmobs Dict: ~S",scm_list_1(psmob)); + } + Py_INCREF(Py_True); + if (0 != PyDict_SetItem(pdict_wrapped_pyobjects, + pkey, + Py_True)) { + Py_DECREF(pkey); + scm_misc_error("mark_pysmob","failed to mark pysmob ~S",scm_list_1(psmob)); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC_DETAILED)) { + //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# mark_pysmob: marking ~S\n"),scm_list_1(scm_long2num(key))); + printf("# mark_pysmob: marking 0x%08lX\n",key); + } + + // scm_gc_mark(any SCM object referred to by the Python object); + // One SCM object is returned to the caller (who will mark it). + return(SCM_UNSPECIFIED); // No need to mark any SCM object for now + // (until PyObjects learn to hold wrapped SCM objects). +} + + +//static size_t +//free_pysmob(SCM psmob) +//{ +// PyObject *pobj = unwrap_pysmob(psmob); +// if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC_DETAILED)) { +// scm_simple_format(scm_current_output_port(),scm_makfrom0str("# free_pysmob: freeing object ~S\n"),scm_list_1(scm_long2num((long)pobj))); +// } +// Py_XDECREF(pobj); +// return(0); +//} + +static int +print_pysmob(SCM smob, SCM port, scm_print_state* prstate) +{ + if (!SCM_SMOB_PREDICATE(tag_pysmob,smob)) { + scm_wrong_type_arg("print-pysmob",SCM_ARG1,smob); // NOT COVERED BY TESTS + } + if (!SCM_PORTP(port)) { + scm_wrong_type_arg("print-pysmob",SCM_ARG2,port); // NOT COVERED BY TESTS + } + // I don't know how to validate the 3rd argument. + PyObject *prepr = PyObject_Repr(unwrap_pysmob(smob)); + if (NULL != prepr) { + char *pstr = PyString_AsString(prepr); + scm_puts("(python-eval ",port); + scm_puts(pstr, port); + scm_puts(" #t)",port); + Py_DECREF(prepr); // also invalidates pstr. + } + else { + scm_misc_error("print-pysmob","repr(~S) failure",scm_list_1(smob)); // NOT COVERED BY TESTS + //scm_puts("*nil*", port); + } + return(1); // Nonzero means success. +} + +static SCM +equalp_pysmob(SCM smob1, SCM smob2) +{ + if (!SCM_SMOB_PREDICATE(tag_pysmob,smob1)) { + scm_wrong_type_arg("equalp-pysmob",SCM_ARG1,smob1); + } + if (!SCM_SMOB_PREDICATE(tag_pysmob,smob2)) { + scm_wrong_type_arg("equalp-pysmob",SCM_ARG2,smob2); + } + PyObject *pobj1 = unwrap_pysmob(smob1); + if (NULL == pobj1) { + scm_misc_error("equalp-pysmob","argument 1 (~S) unwrapping failure", // NOT COVERED BY TESTS + scm_list_1(smob1)); + } + PyObject *pobj2 = unwrap_pysmob(smob2); + if (NULL == pobj2) { + scm_misc_error("equalp-pysmob","argument 2 (~S) unwrapping failure", // NOT COVERED BY TESTS + scm_list_1(smob2)); + } + switch (PyObject_RichCompareBool(pobj1,pobj2,Py_EQ)) { + case 0: + return(SCM_BOOL_F); + case 1: + return(SCM_BOOL_T); + case -1: + default: + // Error. + scm_misc_error("equalp-pysmob","comparison failure ~S vs. ~S", + scm_list_2(smob1,smob2)); + return(SCM_UNDEFINED); + } +} + +//////////////////////////////////////////////////////////////////////// +// Public functions +//////////////////////////////////////////////////////////////////////// + + +// Return nonzero if sobj is of type pysmob. +int +IS_PYSMOBP(SCM sobj) +{ + return(SCM_SMOB_PREDICATE(tag_pysmob,sobj)); +} + +// Create a pysmob corresponding to a PyObject. +SCM +wrap_pyobject(PyObject *pobj) +{ + if (NULL == pobj) { + scm_misc_error("wrap-pyobject","NULL PyObject",SCM_EOL); // NOT COVERED BY TESTS + //return(SCM_UNSPECIFIED); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_NEW_PYSMOB)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# wrap_pyobject: new wrapped object ~S\n"),scm_list_1(scm_long2num((long)pobj))); + } + Py_INCREF(pobj); + Py_INCREF(Py_True); + PyDict_SetItem(pdict_wrapped_pyobjects, + PyLong_FromLong((long)pobj), + Py_True); + SCM_RETURN_NEWSMOB(tag_pysmob,pobj); + } +} + +// Provide reference to PyObject embedded in a pysmob. +// No ownership transfer is implied. +PyObject * +unwrap_pysmob(SCM sobj) +{ + if (!SCM_SMOB_PREDICATE(tag_pysmob,sobj)) { + scm_wrong_type_arg("unwrap-pysmob",SCM_ARG1,sobj); // NOT COVERED BY TESTS + } + PyObject *pobj = (PyObject *)SCM_SMOB_DATA(sobj); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_UNWRAP_PYSMOB)) { + // The scm_simple_format code can safely be invoked if we know + // that we are not inside garbage collection. + // TODO: invoke a flag for this purpose. + //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# unwrap_pysmob: accessing object ~S\n"),scm_list_1(scm_long2num((long)pobj))); + printf("# unwrap_pysmob: accessing object 0x%08lX\n",(long)pobj); + } + return(pobj); +} + +//////////////////////////////////////////////////////////////////////// + +void finalize_pysmob_type(void) +{ + Py_DECREF(pdict_wrapped_pyobjects); +} + +void init_pysmob_type(void) +{ + tag_pysmob = scm_make_smob_type("pysmob",0); + scm_set_smob_mark (tag_pysmob, mark_pysmob); + //scm_set_smob_free(tag_pysmob,free_pysmob); // does nothing in the new scheme of managing PyObjects during garbage collection. + scm_set_smob_print(tag_pysmob,print_pysmob); + scm_set_smob_equalp(tag_pysmob,equalp_pysmob); + + // Py_Initialize must have already been invoked + // by init_pysmob_type()'s caller. + pdict_wrapped_pyobjects = PyDict_New(); + if (atexit(finalize_pysmob_type)) { + fprintf(stderr,"cannot set pysmob finalization function\n"); // NOT COVERED BY TESTS + exit(1); // NOT COVERED BY TESTS + } + + // Register garbage collection hooks. + scm_c_hook_add(&scm_before_mark_c_hook,&clear_pdict_values, + (void *)pdict_wrapped_pyobjects,0); + scm_c_hook_add(&scm_after_sweep_c_hook,&delete_unmarked_pdict_keys, + (void *)pdict_wrapped_pyobjects,0); +} + +// End of pysmob.c diff --git a/pysmob.h b/pysmob.h new file mode 100644 index 0000000..c39ebfb --- /dev/null +++ b/pysmob.h @@ -0,0 +1,53 @@ +// pysmob header file +// +//////////////////////////////////////////////////////////////////////// + +#ifndef PYSMOB_H +#define PYSMOB_H + +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// +// +// Declares the Guile SMOB type, which encapsulates Python objects. +// While those SMOBs can be used to encapsulate any PyObject* object, +// they are typically used to encapsulate only class instances. + +#include <Python.h> // Must be first header file +#include <libguile.h> + +// Return nonzero if sobj is of type pysmob. +int IS_PYSMOBP(SCM sobj); + +void init_pysmob_type(void); +// Naming convention: don't start names with "py", but use them +// if they are not the first characters of a name. + +SCM wrap_pyobject(PyObject *pobj); +// Create a pysmob corresponding to a PyObject. + +PyObject *unwrap_pysmob(SCM sobj); +// Provide reference to PyObject embedded in a pysmob. +// No ownership transfer is implied. + +#endif /* PYSMOB_H */ +//////////////////////////////////////////////////////////////////////// +// End of pysmob.h diff --git a/pytoguile.c b/pytoguile.c new file mode 100644 index 0000000..640ddaa --- /dev/null +++ b/pytoguile.c @@ -0,0 +1,747 @@ +// pytoguile functions +// Functions for conversion from PyObjects into Guile SCMs. +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// +//#include <Python.h> // included in pytoguile.h +//#include <libguile.h> // included in pytoguile.h +#include "pytoguile.h" +#include "pysmob.h" +#include "g2p2g_smob.h" // used in pytoguile.inc +#include "verbose.h" +#include "pyscm.h" + +//////////////////////////////////////////////////////////////////////// +// Convert data from Python (PyObject *) into Guile (SCM) +// representation +//////////////////////////////////////////////////////////////////////// + +// The following functions convert each a single data type. +// They have to be efficient. +// The interface conventions are: +// 1. The function gets a single PyObject * argument, and upon +// success - returns a single SCM. +// 2. The function is responsible for checking the data type of +// and whether the value of its argument is in range. +// If any of them fails, the function returns SCM_UNDEFINED. +// 3. If there is any error not associated with wrong data type +// of its argument, the function throws a scm exception. +// 4. Naming convention: +// p2g_{Python datatype name}2{Guile datatype name} + + +//////////////////////////////////////////////////////////////////////// +// Apply a template to PyObject for converting it into a SCM +//////////////////////////////////////////////////////////////////////// + +SCM +p2g_apply(PyObject *pobj,SCM stemplate) +{ + if (IS_P2G_SMOBP(stemplate)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_apply: pobj=~S smob-stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return((get_p2g_function(stemplate))(pobj,SCM_UNSPECIFIED)); + } + else if (!SCM_EQ_P(SCM_BOOL_F,scm_pair_p(stemplate))) { + if (IS_P2G_SMOBP(SCM_CAR(stemplate))) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_apply: pobj=~S pair-stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return((get_p2g_function(SCM_CAR(stemplate)))(pobj,SCM_CDR(stemplate))); + } + else { + scm_misc_error("p2g_apply","bad template CAR item ~S", + scm_list_1(SCM_CAR(stemplate))); + } + } + else { + scm_misc_error("p2g_apply","bad template item ~S", + scm_list_1(stemplate)); + } +} + +////////////////////////// leaf //////////////////////////////////////// + +// Perform 'leaf' data conversion. +// Normally, stemplate is a list of templates, to be tried one by one +// until one of them succeeds. +SCM +p2g_leaf(PyObject *pobj,SCM stemplate) +{ + if (IS_P2G_SMOBP(stemplate)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_leaf: pobj=~S smob-stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return((get_p2g_function(stemplate))(pobj,SCM_UNSPECIFIED)); + } + if (!SCM_EQ_P(SCM_BOOL_F,scm_list_p(stemplate))) { + // Each template list item is examined. + // If the template list item is itself a list, then its CAR is invoked + // and gets its CDR as template, and the whole sobj (which is + // expected to be Template or List, too) as the argument. + // If the template list item is a P2G_SMOB, then it is invoked with + // pobj. + // + // At any case, template list items are invoked one by one until + // one of them succeeds. + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_leaf: pobj=~S list-stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + SCM slist; + for (slist = stemplate; (!SCM_EQ_P(slist,SCM_EOL)); + slist = SCM_CDR(slist)) { + SCM scandidate = SCM_CAR(slist); + SCM sobj = p2g_apply(pobj,scandidate); + if (!SCM_UNBNDP(sobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_leaf: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + return(sobj); + } + } + return(SCM_UNDEFINED); // None of the templates in the list fit the sobj. + // Supports backtracking if the template is so designed. + } + scm_wrong_type_arg("p2g_leaf",SCM_ARG2,stemplate); // Bad template +} + +////////////////////////// None //////////////////////////////////////// + +// This function is somewhat unusual in that its stemplate argument, +// if specified, does not have to be a P2G_SMOB. +// This argument is the one which is returned if the Python value +// is None. +SCM +p2g_None2SCM_EOL(PyObject *pobj,SCM stemplate) +{ + //return((Py_None != pobj) + // ? SCM_UNDEFINED + // : (SCM_EQ_P(stemplate,SCM_UNSPECIFIED) || SCM_UNBNDP(stemplate)) + // ? SCM_EOL + // : stemplate); + if (Py_None != pobj) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_None2SCM_EOL: pobj=~S stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } + else { + SCM stemp = (SCM_EQ_P(stemplate,SCM_UNSPECIFIED) || SCM_UNBNDP(stemplate)) + ? SCM_EOL : stemplate; + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_None2SCM_EOL: successful conversion of None into ~S\n"),scm_list_1(stemp)); + } + return(stemp); + } +} + +///////////////////////// Boolean ////////////////////////////////////// + +SCM +p2g_Bool2SCM_BOOL(PyObject *pobj,SCM stemplate) +{ + if (PyBool_Check(pobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Bool2SCM_BOOL: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + return (PyInt_AsLong(pobj) ? SCM_BOOL_T : SCM_BOOL_F); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Bool2SCM_BOOL: failed to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } +} + +///////////////////////// Numeric ////////////////////////////////////// + +SCM +p2g_Int2num(PyObject *pobj,SCM stemplate) +{ + if (PyInt_Check(pobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Int2num: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + return (scm_long2num(PyInt_AsLong(pobj))); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Int2num: failed to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } +} + +SCM +p2g_Long2bignum(PyObject *pobj,SCM stemplate) +{ + if (PyLong_Check(pobj)) { + PyObject *pstr = PyObject_Repr(pobj); + if (NULL == pstr) { + PyObject *pexception = PyErr_Occurred(); // NOT COVERED BY TESTS + if (pexception) { // NOT COVERED BY TESTS + PyErr_Clear(); // NOT COVERED BY TESTS + scm_misc_error("p2g_Long2bignum","internal conversion error of bignum", // NOT COVERED BY TESTS + SCM_UNDEFINED); + } + else { + scm_misc_error("p2g_Long2bignum","unknown internal conversion error of bignum", // NOT COVERED BY TESTS + SCM_UNDEFINED); + } + } + char *cstr = PyString_AsString(pstr); + long cstrlen = strlen(cstr); + if (cstrlen < 1) { + scm_misc_error("p2g_Long2bignum","conversion error of bignum - too short result string", // NOT COVERED BY TESTS + SCM_UNDEFINED); + } + if ('L' == cstr[cstrlen-1]) { + --cstrlen; + } + //return(scm_istring2number(cstr,cstrlen,10)); // scm_istring2number seems to be deprecated in newer versions of Guile. + SCM sstr = scm_mem2string(cstr,cstrlen); + Py_DECREF(pstr); + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + // Note: here verbosity_repr is inefficient in that it converts again + // a PyObject into a SCM string - but it is not critical for performance + // and code clarity is more important. + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Long2bignum: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + return(scm_string_to_number(sstr,scm_long2num((long)10))); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Long2bignum: failed to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } +} + +SCM +p2g_Float2real(PyObject *pobj,SCM stemplate) +{ + if (PyFloat_Check(pobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Float2real: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + return (scm_double2num(PyFloat_AsDouble(pobj))); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Float2real: failed to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } +} + +SCM +p2g_Complex2complex(PyObject *pobj,SCM stemplate) +{ + if (PyComplex_Check(pobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Complex2complex: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + double re = PyComplex_RealAsDouble(pobj); + double im = PyComplex_ImagAsDouble(pobj); + return(scm_make_complex(re,im)); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Complex2complex: failed to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } +} + +///////////////////////// Strings ////////////////////////////////////// + +SCM +p2g_String2string(PyObject *pobj,SCM stemplate) +{ + if (PyString_CheckExact(pobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_String2string: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + int strlength = PyString_Size(pobj); + char *strtext = PyString_AsString(pobj); + //scm_makfrom0str - duplicates zero-terminated string + //scm_take0str - takes over ownership of the zero-terminated string + //scm_mem2string(const char*,len) + //scm_take_str(const char*,len) + return(scm_mem2string(strtext,strlength)); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_String2string: failed to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } +} + +SCM +p2g_String2symbol(PyObject *pobj,SCM stemplate) +{ + if (PyString_CheckExact(pobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_String2symbol: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + //int strlength = PyString_Size(pobj); + char *strtext = PyString_AsString(pobj); + return(scm_str2symbol(strtext)); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_String2symbol: failed to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } +} + +SCM +p2g_String2keyword(PyObject *pobj,SCM stemplate) +{ + if (PyString_CheckExact(pobj)) { + int strlength = PyString_Size(pobj); + if (strlength < 1) { + // Ensure that there is at least one character in the + // real string. + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_String2keyword: failed to convert pobj=~S using stemplate=~S - zero-length string\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_String2keyword: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + char *strtext = PyString_AsString(pobj); + // Prefixing dash to the string. + char *dashstr = malloc(strlength+2); + dashstr[0] = '-'; + dashstr[1] = '\0'; + strncat(dashstr,strtext,strlength); + SCM ssymbol = scm_str2symbol(dashstr); + return(scm_make_keyword_from_dash_symbol(ssymbol)); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_String2keyword: failed to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } +} + +SCM +p2g_1String2char(PyObject *pobj,SCM stemplate) +{ + if (PyString_CheckExact(pobj)) { + int strlength = PyString_Size(pobj); + if (1 != strlength) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_1String2char: failed to convert pobj=~S using stemplate=~S - wrong-length string\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_SUCCESSFUL)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_1String2char: successful conversion of ~S into SCM\n"),scm_list_1(verbosity_repr(pobj))); + } + char *strtext = PyString_AsString(pobj); + return(SCM_MAKE_CHAR(strtext[0])); + } + else { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_1String2char: failed to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } +} + +/////////////////////// Aggregates ///////////////////////////////////// + +// Convert a 2-tuple into a pair. Fails if the object is not a +// 2-tuple. The template must be a pair as well. +SCM +p2g_2Tuple2pair(PyObject *pobj,SCM stemplate) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_2Tuple2pair: trying to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(stemplate))) { + scm_misc_error("p2g_2Tuple2pair","bad template item ~S", + scm_list_1(stemplate)); + } + if (!PyTuple_CheckExact(pobj)) { + return(SCM_UNDEFINED); + } + if (2 != PyTuple_GET_SIZE(pobj)) { + return(SCM_UNDEFINED); + } + + SCM sobj_car = p2g_apply(PyTuple_GET_ITEM(pobj,0),SCM_CAR(stemplate)); + if (SCM_UNBNDP(sobj_car)) { + return(SCM_UNDEFINED); + } + SCM sobj_cdr = p2g_apply(PyTuple_GET_ITEM(pobj,1),SCM_CDR(stemplate)); + if (SCM_UNBNDP(sobj_cdr)) { + return(SCM_UNDEFINED); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_2Tuple2pair: successful conversion\n"),SCM_EOL); + } + return(scm_cons(sobj_car,sobj_cdr)); +} + +// Very similar to p2g_2Tuple2pair. +SCM +p2g_2List2pair(PyObject *pobj,SCM stemplate) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_2List2pair: trying to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(stemplate))) { + scm_misc_error("p2g_2List2pair","bad template item ~S", + scm_list_1(stemplate)); + } + if (!PyList_CheckExact(pobj)) { + return(SCM_UNDEFINED); + } + if (2 != PyList_GET_SIZE(pobj)) { + return(SCM_UNDEFINED); + } + + SCM sobj_car = p2g_apply(PyList_GET_ITEM(pobj,0),SCM_CAR(stemplate)); + if (SCM_UNBNDP(sobj_car)) { + return(SCM_UNDEFINED); + } + SCM sobj_cdr = p2g_apply(PyList_GET_ITEM(pobj,1),SCM_CDR(stemplate)); + if (SCM_UNBNDP(sobj_cdr)) { + return(SCM_UNDEFINED); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_2List2pair: successful conversion\n"),SCM_EOL); + } + return(scm_cons(sobj_car,sobj_cdr)); +} + +// Convert a Tuple (of any length) into a list. +// The template may be either P2G_SMOB (to be used for converting +// all Tuple items) or a list of templates. +// The length of the list of templates may be shorter than the length +// of the Tuple. If shorter, list items will be cyclically reused. +SCM +p2g_Tuple2list(PyObject *pobj,SCM stemplate) +{ + if (!PyTuple_CheckExact(pobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Tuple2list: pobj=~S stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } + int size = PyTuple_GET_SIZE(pobj); + if (IS_P2G_SMOBP(stemplate)) { + // Conversion loop for the case in which the template is + // a single P2G_SMOB + int ind1; + SCM slist1 = SCM_EOL; + for (ind1 = size-1; ind1 >= 0; --ind1) { + PyObject *pitem1 = PyTuple_GET_ITEM(pobj, ind1); + if (NULL == pitem1) { + PyObject *pexception = PyErr_Occurred(); // NOT COVERED BY TESTS + if (pexception) { // NOT COVERED BY TESTS + PyErr_Clear(); // NOT COVERED BY TESTS + } + scm_misc_error("p2g_Tuple2list","access error of Python Tuple", // NOT COVERED BY TESTS + SCM_UNSPECIFIED); + } + SCM sitem1 = p2g_apply(pitem1,stemplate); + if (SCM_UNBNDP(sitem1)) { + // Conversion failure + return(SCM_UNDEFINED); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Tuple2list: 1. converted item pobj=~S[~S], stemplate=~S\n"),scm_list_3(scm_long2num(ind1),verbosity_repr(pitem1),stemplate)); + } + slist1 = scm_cons(sitem1,slist1); + } + return(slist1); + } + + if (SCM_EQ_P(SCM_BOOL_F,scm_list_p(stemplate))) { + // Bad template. + scm_wrong_type_arg("p2g_Tuple2list",SCM_ARG2,stemplate); + } + // Conversion loop for the case in which the template is a list. + SCM slist2 = SCM_EOL; + SCM stemp = SCM_EOL; // We loop over stemplate again and again as needed. + int ind2; + for (ind2 = 0; ind2 < size; + stemp = SCM_CDR(stemp),++ind2) { + if (SCM_EQ_P(stemp,SCM_EOL)) { + stemp = stemplate; // Loop back to stemplate's beginning. + } + //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# DEBUG: going to convert item according to template ~S\n"),scm_list_1(SCM_CAR(stemp))); + PyObject *pitem2 = PyTuple_GET_ITEM(pobj, ind2); + if (NULL == pitem2) { + PyObject *pexception = PyErr_Occurred(); // NOT COVERED BY TESTS + if (pexception) { // NOT COVERED BY TESTS + PyErr_Clear(); // NOT COVERED BY TESTS + } + scm_misc_error("p2g_Tuple2list","access error of Python Tuple", // NOT COVERED BY TESTS + SCM_UNSPECIFIED); + } + SCM sitem2 = p2g_apply(pitem2,SCM_CAR(stemp)); + if (SCM_UNBNDP(sitem2)) { + // Conversion failure + return(SCM_UNDEFINED); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Tuple2list: 2. converted item pobj=~S[~S], stemplate=~S\n"),scm_list_3(scm_long2num(ind2),verbosity_repr(pitem2),SCM_CAR(stemp))); + } + slist2 = scm_cons(sitem2,slist2); + } + return(scm_reverse(slist2)); +} + +// Very similar to p2g_Tuple2list above. +SCM +p2g_List2list(PyObject *pobj,SCM stemplate) +{ + if (!PyList_CheckExact(pobj)) { + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_List2list: pobj=~S stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + return(SCM_UNDEFINED); + } + int size = PyList_GET_SIZE(pobj); + if (IS_P2G_SMOBP(stemplate)) { + // Conversion loop for the case in which the template is + // a single P2G_SMOB + int ind1; + SCM slist1 = SCM_EOL; + for (ind1 = size-1; ind1 >= 0; --ind1) { + PyObject *pitem1 = PyList_GET_ITEM(pobj, ind1); + if (NULL == pitem1) { + PyObject *pexception = PyErr_Occurred(); // NOT COVERED BY TESTS + if (pexception) { // NOT COVERED BY TESTS + PyErr_Clear(); // NOT COVERED BY TESTS + } + scm_misc_error("p2g_List2list","access error of Python List", // NOT COVERED BY TESTS + SCM_UNSPECIFIED); + } + SCM sitem1 = p2g_apply(pitem1,stemplate); + if (SCM_UNBNDP(sitem1)) { + // Conversion failure + return(SCM_UNDEFINED); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_List2list: converted item pobj[~S]=~S, stemplate=~S\n"),scm_list_3(scm_long2num(ind1),verbosity_repr(pitem1),stemplate)); + } + slist1 = scm_cons(sitem1,slist1); + } + return(slist1); + } + + if (SCM_EQ_P(SCM_BOOL_F,scm_list_p(stemplate))) { + // Bad template. + scm_wrong_type_arg("p2g_List2list",SCM_ARG2,stemplate); + } + // Conversion loop for the case in which the template is a list. + SCM slist2 = SCM_EOL; + SCM stemp = SCM_EOL; // We loop over stemplate again and again as needed. + int ind2; + for (ind2 = 0; ind2 < size; + stemp = SCM_CDR(stemp),++ind2) { + if (SCM_EQ_P(stemp,SCM_EOL)) { + stemp = stemplate; // Loop back to stemplate's beginning. + } + PyObject *pitem2 = PyList_GET_ITEM(pobj, ind2); + if (NULL == pitem2) { + PyObject *pexception = PyErr_Occurred(); // NOT COVERED BY TESTS + if (pexception) { // NOT COVERED BY TESTS + PyErr_Clear(); // NOT COVERED BY TESTS + } + scm_misc_error("p2g_List2list","access error of Python List", // NOT COVERED BY TESTS + SCM_UNSPECIFIED); + } + SCM sitem2 = p2g_apply(pitem2,SCM_CAR(stemp)); + if (SCM_UNBNDP(sitem2)) { + // Conversion failure + return(SCM_UNDEFINED); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_List2list: 2. converted item pobj=~S[~S], stemplate=~S\n"),scm_list_3(scm_long2num(ind2),verbosity_repr(pitem2),SCM_CAR(stemp))); + } + slist2 = scm_cons(sitem2,slist2); + } + return(scm_reverse(slist2)); +} + +// Conversion of Dict into alist. +// All keys are convertible using a single template (SCM_CAR(stemplate)). +// +// In the most general case, the keys will be used as keys into +// SCM_CDR(stemplate) which would be an hash table. +// However, this most general case is not currently implemented. +// There is a single template also in SCM_CDR(stemplate), which is +// used for converting all values. Any flexibility needed is to be +// obtained through wise p2g_leaf() usage. + +SCM +p2g_Dict2alist(PyObject *pobj,SCM stemplate) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Dict2alist: trying to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + + if (SCM_EQ_P(SCM_BOOL_F,scm_pair_p(stemplate))) { + scm_misc_error("p2g_Dict2alist","bad template ~S", + scm_list_1(stemplate)); + } + //if (!IS_P2G_SMOBP(SCM_CAR(stemplate))) { + // scm_misc_error("p2g_Dict2alist","bad template CAR item ~S", + // scm_list_1(SCM_CAR(stemplate))); + //} + //if (!IS_P2G_SMOBP(SCM_CDR(stemplate))) { + // scm_misc_error("p2g_Dict2alist","bad template CDR item ~S", + // scm_list_1(SCM_CDR(stemplate))); + //} + + if (!PyDict_CheckExact(pobj)) { + return(SCM_UNDEFINED); + } + int iterstate = 0; + PyObject *pkey = NULL; + PyObject *pval = NULL; + SCM salist = SCM_EOL; + while (PyDict_Next(pobj, &iterstate, &pkey, &pval)) { + SCM skey = p2g_apply(pkey,SCM_CAR(stemplate)); + if (SCM_UNBNDP(skey)) { + // Conversion failure. + return(SCM_UNDEFINED); + } + SCM sval = p2g_apply(pval,SCM_CDR(stemplate)); + if (SCM_UNBNDP(sval)) { + // Conversion failure. + return(SCM_UNDEFINED); + } + salist = scm_cons(scm_cons(skey,sval),salist); + } + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_Dict2alist: successful conversion of Dict into ~S\n"),scm_list_1(salist)); + } + return(salist); +} + +SCM +p2g_PySCMObject2SCM(PyObject *pobj,SCM stemplate) +{ + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# p2g_PySCMObject2SCM: trying to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + + if (!PySCMObject_Check(pobj)) { + return(SCM_UNDEFINED); + } + return(unwrap_pyscm_object(pobj)); +} + +//////////////////////////////////////////////////////////////////////// +// Big default conversion function +//////////////////////////////////////////////////////////////////////// +// The python2guile function chooses reasonable defaults, whenever +// there is a possibility for ambiguity concerning the desired +// Scheme datatype. +// It ignores the stemplate argument. + +static SCM python2guile_smob; // for use by default templates +static SCM python2guile_dict_default; // used by python2guile(). + +SCM +python2guile(PyObject *pobj,SCM stemplate) +{ + SCM sres = SCM_UNDEFINED; + if (pyguile_verbosity_test(PYGUILE_VERBOSE_G2P2G_ALWAYS)) { + scm_simple_format(scm_current_output_port(),scm_makfrom0str("# python2guile: trying to convert pobj=~S using stemplate=~S\n"),scm_list_2(verbosity_repr(pobj),stemplate)); + } + + if (NULL == pobj) { + // Regarded as an error. + scm_misc_error("python2guile","no python value to be converted", + SCM_UNSPECIFIED); + //return(SCM_UNDEFINED); + } + + sres = p2g_None2SCM_EOL(pobj,SCM_UNSPECIFIED); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + sres = p2g_Bool2SCM_BOOL(pobj,SCM_UNSPECIFIED); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + // Numeric + + sres = p2g_Int2num(pobj,SCM_UNSPECIFIED); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + sres = p2g_Long2bignum(pobj,SCM_UNSPECIFIED); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + sres = p2g_Float2real(pobj,SCM_UNSPECIFIED); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + sres = p2g_Complex2complex(pobj,SCM_UNSPECIFIED); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + // Strings + + sres = p2g_String2string(pobj,SCM_UNSPECIFIED); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + // Aggregates + + sres = p2g_Tuple2list(pobj,python2guile_smob); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + sres = p2g_List2list(pobj,python2guile_smob); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + sres = p2g_Dict2alist(pobj,python2guile_dict_default); + if (!SCM_UNBNDP(sres)) return(sres); // if (SCM_UNDEFINED != sres) return(sres); + + // PySCMObjects + + sres = p2g_PySCMObject2SCM(pobj,SCM_UNSPECIFIED); + if (!SCM_UNBNDP(sres)) return(sres); + + // !!! Implement here hooks for decoding more data types. + + // If none of the above decoded the data type, then just + // wrap the PyObject with pysmob and return the pysmob. + return(wrap_pyobject(pobj)); +} + +//////////////////////////////////////////////////////////////////////// +// Register all p2g_* functions + +#include "pytoguile.inc" + +//////////////////////////////////////////////////////////////////////// +// End of pytoguile.c diff --git a/pytoguile.h b/pytoguile.h new file mode 100644 index 0000000..a9ff4ec --- /dev/null +++ b/pytoguile.h @@ -0,0 +1,50 @@ +// pytoguile header file +// Functions for conversion from PyObjects into Guile SCMs. +//////////////////////////////////////////////////////////////////////// + +#ifndef PYTOGUILE_H +#define PYTOGUILE_H + +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// + +#include <Python.h> // Must be first header file +#include <libguile.h> + +//////////////////////////////////////////////////////////////////////// +// PyObject -> SCM +//////////////////////////////////////////////////////////////////////// + +// Basic conversion of a PyObject into a SCM object according +// to template. +extern SCM p2g_apply(PyObject *pobj,SCM stemplate); + +// Convert a Python object into a SCM object. +// If cannot convert, abort. +extern SCM python2guile(PyObject *pobj,SCM stemplate); + +//////////////////////////////////////////////////////////////////////// + +#endif /* PYTOGUILE_H */ + +//////////////////////////////////////////////////////////////////////// +// End of pytoguile.h diff --git a/t/01_basic.t b/t/01_basic.t new file mode 100755 index 0000000..acce403 --- /dev/null +++ b/t/01_basic.t @@ -0,0 +1,34 @@ +#!/usr/bin/guile -s +!# +; Basic tests of the guiletap module (for TAP based test scripting). +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) + +(plan 4) +(ok 1 "Should pass" #t) +(ok 2 "Should fail # TODO deliberate failure" #f) +(is-ok 3 "Should be okay" '(1 . "abc") (cons 1 (string-append "a" "bc"))) +(is-ok 4 "Should not be okay # TODO deliberate failure" '(3 . "def") '(3 "def")) + +; End of 01_basic.t diff --git a/t/02_pyguile.t b/t/02_pyguile.t new file mode 100644 index 0000000..5eea12f --- /dev/null +++ b/t/02_pyguile.t @@ -0,0 +1,49 @@ +#!/usr/bin/guile -s +!# +; Pyguile tests - transferring data from Python to Guile +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 10) +(python-eval "import sys;sys.path = ['']+sys.path\n") +(python-eval "from t.scripts.t2conv import *\n") + +;(if (python-import "t.scripts.t2conv") (ok 1 "Imported t2conv" #t) +; (bail-out "Could not import t2conv")) + +(is-ok 1 "None" 0 (length (python-eval "return_None()" #t))) +(is-ok 2 "True" #t (python-eval "return_True()" #t)) +(is-ok 3 "False" #f (python-eval "return_False()" #t)) +(is-ok 4 "1" 1 (python-eval "return_Int1()" #t)) +(is-ok 5 "-5" -5 (python-eval "return_Int_5()" #t)) +(is-ok 6 "2^65" 36893488147419103232 (python-eval "return_BigInt()" #t)) +(is-ok 7 "-2^65" -36893488147419103232 (python-eval "return_BigInt_neg()" #t)) +(is-ok 8 "string 1" "abcdefghi" (python-eval "return_String1()" #t)) +(is-ok 9 "string 2" "01abCD%^" (python-eval "return_String2()" #t)) +(is-ok 10 "string 3" + (string-append "bef" (list->string (list (integer->char 0) (integer->char 163))) "ore") + (python-eval "return_String_Zero()" #t)) + +; End of 02_pyguile.t diff --git a/t/03_guile2python.t b/t/03_guile2python.t new file mode 100644 index 0000000..9ef7964 --- /dev/null +++ b/t/03_guile2python.t @@ -0,0 +1,58 @@ +#!/usr/bin/guile -s +!# +; Pyguile tests - transferring data from Guile to Python. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 14) + +(define invoke-python-func + (lambda (module func arg) + (python-apply (list module func) (list arg) '()))) + +(is-ok 1 "None" "None" (invoke-python-func "__builtin__" "repr" (list))) +(is-ok 2 "True" "True" (invoke-python-func "__builtin__" "repr" #t)) +(is-ok 3 "False" "False" (invoke-python-func "__builtin__" "repr" #f)) +(is-ok 4 "int 1" "1" (invoke-python-func "__builtin__" "repr" 1)) +(is-ok 5 "int -5" "-5" (invoke-python-func "__builtin__" "repr" -5)) +(is-ok 6 "string 1" "'string 1'" + (invoke-python-func "__builtin__" "repr" "string 1")) +(is-ok 7 "char P" "'P'" (invoke-python-func "__builtin__" "repr" #\P)) +(is-ok 8 "symbol symba" "'symba'" + (invoke-python-func "__builtin__" "repr" 'symba)) +(is-ok 9 "complex 1-1i" "(1-1j)" (invoke-python-func "__builtin__" "repr" 1-1i)) +(is-ok 10 "float 3.125" "3.125" (invoke-python-func "__builtin__" "repr" 3.125)) +(define big10to7th 10000000) +(define big10to35th (* big10to7th big10to7th big10to7th big10to7th big10to7th)) +(is-ok 11 "bignum 10^35" "100000000000000000000000000000000000L" + (invoke-python-func "__builtin__" "repr" big10to35th)) +(is-ok 12 "pair" "('mycar', 42)" + (invoke-python-func "__builtin__" "repr" (cons "mycar" (* 21 2)))) +(is-ok 13 "list 1" "['item1', 'item2', 'item3']" + (invoke-python-func "__builtin__" "repr" '(item1 item2 "item3"))) +(is-ok 14 "list 2" "['lambda', ['arg1', 'arg2'], ['display', 'textA'], ['newline'], ['display', 'arg1'], ['newline'], ['display', 'arg2']]" + (invoke-python-func "__builtin__" "repr" '(lambda (arg1 arg2)(display "textA")(newline)(display arg1)(newline)(display arg2)))) + +; End of 03_guile2python.t diff --git a/t/04_python_apply.t b/t/04_python_apply.t new file mode 100644 index 0000000..a22fd3a --- /dev/null +++ b/t/04_python_apply.t @@ -0,0 +1,66 @@ +#!/usr/bin/guile -s +!# +; Pyguile tests - exercise python-apply +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 10) +(python-import "os.path") + +(is-ok 1 "exists" #t + (python-apply '("os.path" exists) '("t/04_python_apply.t") '())) +(is-ok 2 "does not exist" #f + (python-apply '("os.path" exists) '("t/04_python_apply.tnone") '())) +(is-ok 3 "join 3 arguments" "Pyguile/t/04_python_apply.t" + (python-apply '("os.path" "join") '("Pyguile" "t" "04_python_apply.t") '())) + +(python-eval "import sys;sys.path = ['']+sys.path\n") +(python-import "t.scripts.t4apply") + +(is-ok 4 "object path without arguments" "33y" + (python-apply '("t.scripts.t4apply" mainobj "cl2func") '() '())) + +(is-ok 5 "object path with positional argument as symbol" "33pos" + (python-apply '("t.scripts.t4apply" mainobj "cl2func") '(pos) '())) + +(is-ok 6 "object path with positional argument as string" "33:str" + (python-apply '("t.scripts.t4apply" mainobj "cl2func") '(":str") '())) + +(is-ok 7 "object path with kw+argument as symbols" "33symkw" + (python-apply '("t.scripts.t4apply" mainobj "cl2func") '() '((#:argx . symkw )))) + +(is-ok 8 "object path with kw+argument as strings" "33=strkw" + (python-apply '("t.scripts.t4apply" mainobj "cl2func") '() '((#:argx . "=strkw" )))) + +(is-ok 9 "object path with kw symbol, argument as string" "33<><>" + (python-apply '("t.scripts.t4apply" mainobj "cl2func") '() '((#:argx . "<><>" )))) + +(is-ok 10 "Return arguments" "positional: (True, 1, -3, 'mystr', 'symbolic') keywords: {'kw4n': 65537, 'keyword1': 'symb1', 'kw3': 'trying3', 'KW_stri2': 'symb2'}" + (python-apply '("t.scripts.t4apply" return_args) + '(#t 1 -3 "mystr" symbolic) + '((#:keyword1 . symb1) (#:KW_stri2 . symb2) + (#:kw3 . #:trying3 ) (#:kw4n . 65537)))) + +; End of 04_guile2python.t diff --git a/t/05_pysmobs.t b/t/05_pysmobs.t new file mode 100644 index 0000000..4ed2cca --- /dev/null +++ b/t/05_pysmobs.t @@ -0,0 +1,128 @@ +#!/usr/bin/guile -s +!# +; PyGuile tests - exercise pysmob handling. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 12) +(python-eval "import sys;sys.path = ['']+sys.path\n") +(define t5smobs (python-import "t.scripts.t5smobs")) + +(is-ok 1 "Creation and repr" "opaq('abc')" + (python-apply '("__builtin__" "repr") + (list (python-apply '("t.scripts.t5smobs" "opaq") + '("abc") '())) + '())) + +(is-ok 2 "Creation and object->string" "(python-eval opaq('abcd') #t)" + (object->string + (python-apply '("t.scripts.t5smobs" "opaq") + '("abcd") + '()))) + +(define objt (python-apply '("t.scripts.t5smobs" "opaq") + '(37) + '())) +(is-ok 3 "Created opaq with numeric value" "(python-eval opaq(37) #t)" + (object->string objt)) + +(python-apply (list objt "transform") '() '()) +(is-ok 4 "After transforming opaq with numeric value" + "(python-eval opaq(74) #t)" + (object->string objt)) + +(define objl (python-apply '("t.scripts.t5smobs" "opaq") + '(("el1" "el2")) + '())) +(is-ok 5 "opaq with list value" + "(python-eval opaq(['el1', 'el2']) #t)" + (object->string objl)) + +(python-apply (list objl "transform") '() '()) +(is-ok 6 "opaq with list value" + "(python-eval opaq(['el1', 'el2', 'el1', 'el2']) #t)" + (object->string objl)) + +(define equalities3? + (lambda (obj1 obj2) + (list (eq? obj1 obj2) (eqv? obj1 obj2) (equal? obj1 obj2)))) + +(is-ok 7 "verify equalities3?" + '(#f #f #t) + (equalities3? '("a" "b") '("a" "b"))) + +(define nd1 (python-apply (list t5smobs 'noisydelete) '("nd1a") '())) +(define nd2 (python-apply (list t5smobs 'noisydelete) '("nd2b") '())) +(define nd1same nd1) +(define nd1equal (python-apply (list t5smobs 'noisydelete) '("nd1a") '())) + +(is-ok 8 "two different pysmobs" + '(#f #f #f) + (equalities3? nd1 nd2)) + +(is-ok 9 "two identical pysmobs" + '(#t #t #t) + (equalities3? nd1 nd1same)) + +(is-ok 10 "two equal pysmobs" + '(#f #f #t) + (equalities3? nd1 nd1equal)) + +(define nd-me (python-apply (list t5smobs 'noisydelete) '("me") '())) +(define nd-41 (python-apply (list t5smobs 'noisydelete) '(41) '())) +(define nd-42 (python-apply (list t5smobs 'noisydelete) '(42) '())) + +(is-ok 11 "'me'!=41 to validate t5smobs.noisydelete.__cmp__ test" + '(#f #f #f) + (equalities3? nd-me nd-41)) + +(is-ok 12 "'me'==42 to prove t5smobs.noisydelete.__cmp__ is being executed" + '(#f #f #t) + (equalities3? nd-me nd-42)) + + +; The following tests do not work as expected and I do not have yet +; a way to capture outputs. +; !!! Check strports.h (object->string, scm_object_to_string). + +; Garbage collection behavior of pysmobs. +(define noisydel (python-apply (list t5smobs 'noisydelete) '("BOO!") '())) +(diagprint 1001 "verify noisydel object" + "'BOO!'" + (python-apply '("__builtin__" repr) (list noisydel) '())) +(display "# Forcing garbage collection...") +(gc) +(display "Done")(newline) +(diagprint 1002 "verify noisydel object after gc" + "'BOO!'" + (python-apply '("__builtin__" repr) (list noisydel) '())) +(set! noisydel "losing reference") +(display "# Forcing another garbage collection...") +(gc) +(display "Done - should show deletion of noisydel") +(newline) +(display "# ")(display noisydel)(newline) +(python-eval "import gc\ngc.collect()\n") +; End of 05_pysmobs.t diff --git a/t/06_guile2python.t b/t/06_guile2python.t new file mode 100644 index 0000000..346d478 --- /dev/null +++ b/t/06_guile2python.t @@ -0,0 +1,143 @@ +#!/usr/bin/guile -s +!# +; Additional Pyguile tests - transferring data from Guile to Python. +; Tests added to complete code coverage. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 13) + +(is-ok 1 "Float 1.5" "1.5" + (python-apply '("__builtin__" repr) '(1.5) '())) +(is-ok 2 "Float 1000000000.5" "1000000000.5" + (python-apply '("__builtin__" repr) '(1000000000.5) '())) +(is-ok 3 "Float 1e+20" "1e+20" + (python-apply '("__builtin__" repr) '(100000000000000000000.5) '())) + +(is-ok 4 "Complex" "[(1+1j), (2-3j), (-3-4j), (-4+5j)]" + (python-apply '("__builtin__" repr) '((1+1i 2-3i -3-4i -4+5i)) '())) + +;(if (equal? (effective-version) "1.6") +; (is-ok 5 "Dash-only keyword" "''" +; (python-apply '("__builtin__" repr) '(#:) '())) +; (ok 5 "Dash-only keyword not supported in Guile 1.8" #t)) +(ok 5 "Dash-only keyword not supported in Guile 1.8" #t) + +;(if (equal? (effective-version) "1.6") +; (is-ok 6 "String, symbol and keywords (version 1.6.x)" +; "['abc', 'def', 'ghi', 'gh', 'g', '']" +; (python-apply '("__builtin__" repr) +; '(("abc" def #:ghi #:gh #:g #:)) +; '())) +; (is-ok 6 "String, symbol and keywords (version 1.8.x)" +; "['abc', 'def', 'ghi', 'gh', 'g']" +; (python-apply '("__builtin__" repr) +; '(("abc" def #:ghi #:gh #:g)) +; '()))) +(is-ok 6 "String, symbol and keywords (version 1.8 compatible)" + "['abc', 'def', 'ghi', 'gh', 'g']" + (python-apply '("__builtin__" repr) + '(("abc" def #:ghi #:gh #:g)) + '())) + +; Illegal positional keyword arguments handling + +(is-ok 7 "Positional argument list is not legal list" + '(misc-error ("python-apply" "positional arguments conversion failure (~S)" (42) #f)) + (catch #t + (lambda () (python-apply '("__builtin__" repr) + 42 + '())) + (lambda (key . args) (list key args)))) + + +; Illegal keyword arguments handling + +(python-eval "import sys;sys.path = ['']+sys.path\n") +(python-import "t.scripts.t4apply") + +(is-ok 8 "good alist" "positional: ('a',) keywords: {'arg1': 'b'}" + (catch #t + (lambda () (python-apply '("t.scripts.t4apply" return_args) + '("a") + '((#:arg1 . "b")))) + (lambda (key . args) (list key args)))) + +(is-ok 9 "bad alist - not list" + '(wrong-type-arg ("guileassoc2pythondict" + "Wrong type argument in position ~A: ~S" + (1 "b") + #f)) + (catch #t + (lambda () (python-apply '("t.scripts.t4apply" return_args) + '("a") + '("b"))) + (lambda (key . args) (list key args)))) + +(is-ok 10 "bad alist - item not pair" + '(wrong-type-arg ("guileassoc2pythondict" + "Wrong type argument in position ~A: ~S" + (2 "c") + #f)) + (catch #t + (lambda () (python-apply '("t.scripts.t4apply" return_args) + '("a") + '((#:b . 3) "c" (#:d . 5)))) + (lambda (key . args) (list key args)))) + +(is-ok 11 "bad alist - key not string" + '(wrong-type-arg ("guileassoc2pythondict" + "Wrong type argument in position ~A: ~S" + (2 4) + #f)) + (catch #t + (lambda () (python-apply '("t.scripts.t4apply" return_args) + '("a") + '((#:b . 3) (4 . "c") ("d" . 5)))) + (lambda (key . args) (list key args)))) + + +(is-ok 12 "bad alist - duplicate key" + '(misc-error ("guileassoc2pythondict" + "duplicate key (~S)" + (#:b) + #f)) + (catch #t + (lambda () (python-apply '("t.scripts.t4apply" return_args) + '("a") + '((#:b . 3) (#:c . 4) (#:b . 5)))) + (lambda (key . args) (list key args)))) + +(like 13 "bad alist - inconvertible data" + "^\\(wrong-type-arg \\(\"guile2python\" \"Wrong type argument in position ~A: ~S\" \\(1 #<input: [^>]*>\\) #f\\)\\)$" + (catch #t + (lambda () (python-apply '("t.scripts.t4apply" return_args) + '("a") + `((#:b . 3) + (#:c . 4) + (#:d . ,(current-input-port))))) + (lambda (key . args) (object->string (list key args))))) + +; End of 06_guile2python.t diff --git a/t/07_apply.t b/t/07_apply.t new file mode 100644 index 0000000..368bc3f --- /dev/null +++ b/t/07_apply.t @@ -0,0 +1,129 @@ +#!/usr/bin/guile -s +!# +; Additional Pyguile tests - python-apply tests. +; Tests added to complete code coverage. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 18) + +(python-eval "import sys;sys.path = ['']+sys.path\n") +(python-eval "from t.scripts.t4apply import return_args") +(python-eval "def myfunc():\n return('this was myfunc')\n") + +(define myfunc-smob (python-eval "myfunc" #t)) + +(is-ok 1 "string funcname" "positional: ('xyzzy',) keywords: {}" + (python-apply "return_args" '(xyzzy) '())) + +(is-ok 2 "symbol funcname" "positional: () keywords: {'arg': 7}" + (python-apply #:return_args '() '((#:arg . 7)))) + +(is-ok 3 "pysmob func" "this was myfunc" + (python-apply myfunc-smob '() '())) + +(is-ok 4 "list consisting of pysmob func" "this was myfunc" + (python-apply (list myfunc-smob) '() '())) + + +; python-apply funcname tests + +(is-ok 5 "funcname not in list and is illegal" + '(wrong-type-arg ("python-apply" "Wrong type argument in position ~A: ~S" (1 42) #f)) + (catch #t + (lambda () (python-apply 42 '("a") '((#:arg1 . "b")))) + (lambda (key . args) (list key args)))) + +(is-ok 6 "funcname in list and is illegal" + ;like 6: "^\\(misc-error \\(\"python-apply\" \"Python exception: ~A\" \\(\"<class exceptions.TypeError at 0x[0-9a-f]{8}>\"\\) #f\\)\\)$" + "(misc-error (\"python-apply\" \"function denoted by ~S is not callable\" ((45)) #f))" + (catch #t + (lambda () (python-apply '(45) '("a") '((#:arg1 . "b")))) + (lambda (key . args) (object->string (list key args))))) + +(is-ok 7 "non-imported module" + "(misc-error (\"python-apply\" \"could not dereference ~Ath level attribute in ~S\" (1 (\"math\" sin)) #f))" + (catch #t + (lambda () (python-apply '("math" sin) '(3.14159) '())) + (lambda (key . args) (object->string (list key args))))) + +(define t2conv (python-import "t.scripts.t2conv")) + +; Run python-apply under catch harness. +(define catch-test + (lambda (func posargs kwargs) + (catch #t + (lambda () (python-apply func posargs kwargs)) + (lambda (key . args) (object->string (list key args)))))) + +(is-ok 8 "finding attribute in module by string" + -5 + (catch-test '("t.scripts.t2conv" "return_Int_5") '() '())) + +(is-ok 9 "nonexistent attribute in module by string" + "(misc-error (\"python-apply\" \"could not dereference ~Ath level attribute in ~S\" (1 (\"t.scripts.t2conv\" \"return_jnt_5\")) #f))" + (catch-test '("t.scripts.t2conv" "return_jnt_5") '() '())) + +(is-ok 10 "finding attribute in module by symbol" + -5 + (catch-test '("t.scripts.t2conv" return_Int_5) '() '())) + +(is-ok 11 "nonexistent attribute in module by symbol" + "(misc-error (\"python-apply\" \"could not dereference ~Ath level attribute in ~S\" (1 (\"t.scripts.t2conv\" return_jnt_5)) #f))" + (catch-test '("t.scripts.t2conv" return_jnt_5) '() '())) + +(is-ok 12 "finding attribute in module by pysmob" + -5 + (catch-test (list t2conv "return_Int_5") '() '())) + +(is-ok 13 "nonexistent attribute in module by pysmob" + "(misc-error (\"python-apply\" \"could not dereference ~Ath level attribute in ~S\" (1 ((python-eval <module 't.scripts.t2conv' from 't/scripts/t2conv.pyc'> #t) \"return_jnt_5\")) #f))" + (catch-test (list t2conv "return_jnt_5") '() '())) + +; Python function raises uncaught exception during its work. + +(define t7except (python-import "t.scripts.t7except")) + +(like 14 "exception inside Python code" + "^\\(misc-error \\(\"python-apply\" \"Python exception: ~A\" \\(\"<class t.scripts.t7except.myexception at 0x[0-9a-f]{8}>\"\\) #f\\)\\)$" + (catch-test (list t7except 'raiser) '(script7) '())) + +(is-ok 15 "kw argument is datum rather than list" + "(wrong-type-arg (\"guileassoc2pythondict\" \"Wrong type argument in position ~A: ~S\" (1 \"shut up\") #f))" + (catch-test '("__builtin__" repr) '((3 4 5)) "shut up")) + +(is-ok 16 "kw argument is pair rather than list" + "(wrong-type-arg (\"guileassoc2pythondict\" \"Wrong type argument in position ~A: ~S\" (1 (\"shut\" . \"up\")) #f))" + (catch-test '("__builtin__" repr) '((3 4 5)) '("shut" . "up"))) + +(is-ok 17 "no proc specified" + "(wrong-type-arg (\"python-apply\" \"Wrong type argument in position ~A: ~S\" (1 ()) #f))" + (catch-test '() '() '())) + +(is-ok 18 "nonexistent module" + "(misc-error (\"python-apply\" \"could not dereference ~Ath level attribute in ~S\" (1 (\"no.such.module\" repr)) #f))" + (catch-test '("no.such.module" repr) '((3 4 5)) '("shut" . "up"))) + +; End of 07_apply.t diff --git a/t/08_eval.t b/t/08_eval.t new file mode 100644 index 0000000..96d1dcb --- /dev/null +++ b/t/08_eval.t @@ -0,0 +1,69 @@ +#!/usr/bin/guile -s +!# +; python-eval tests. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 7) + +(is-ok 1 "regular python-eval" + 42 + (python-eval "7*3*2" #t)) + + +; Run python-eval under catch harness. +(define catch-test-eval + (lambda (txt retval) + (catch #t + (lambda () (python-eval txt retval)) + (lambda (key . args) (object->string (list key args)))))) + +(is-ok 2 "trying to run python-eval on non-string" + "(wrong-type-arg (\"python-eval\" \"Wrong type argument in position ~A: ~S\" (1 -42) #f))" + (catch-test-eval -42 #t)) + +(is-ok 3 "trying to run python-eval with non-boolean/non-P2G_SMOB argument" + "(misc-error (\"p2g_apply\" \"bad template item ~S\" (42) #f))" + (catch-test-eval "7*3*2" 42)) + +(like 4 "Raising exception inside python-eval f" + "\\(misc-error \\(\"python-eval\" \"Python exception: ~A\" \\(\"<class exceptions.ImportError at 0x[0-9a-f]{8}>\"\\) #f\\)\\)" + (catch-test-eval "import t7except\nraiser('xyzzy')\n" #f)) + +(python-eval "import sys;sys.path = ['']+sys.path\n") +(python-eval "from t.scripts.t7except import raiser") +(like 5 "Raising exception inside python-eval t" + "^\\(misc-error \\(\"python-eval\" \"Python exception: ~A\" \\(\"<class t.scripts.t7except.myexception at 0x[0-9a-f]{8}>\"\\) #f\\)\\)$" + (catch-test-eval "1+raiser('foo fee dom')" #t)) + +(like 6 "code does not return requested result" + "^\\(misc-error \\(\"python-eval\" \"Python exception: ~A\" \\(\"<class exceptions.SyntaxError at 0x[0-9a-f]{8}>\"\\) #f\\)\\)$" + (catch-test-eval "print '# no value was returned'\n" #t)) + +(is-ok 7 "code returns unsolicited result" + "#<unspecified>" + (object->string (python-eval "99+101" #f))) + +; End of 08_eval.t diff --git a/t/09_import.t b/t/09_import.t new file mode 100644 index 0000000..4d2c406 --- /dev/null +++ b/t/09_import.t @@ -0,0 +1,59 @@ +#!/usr/bin/guile -s +!# +; python-import tests. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 5) + +; Run python-import under catch harness. +(define catch-test-import + (lambda (arg) + (catch #t + (lambda () (python-import arg)) + (lambda (key . args) (object->string (list key args)))))) + +(like 1 "good python-import" + "^\\(python-eval <module 'math' from '[^']*'> #t\\)$" + (object->string (catch-test-import "math"))) + +(like 2 "nonexistent module" + "^\\(misc-error \\(\"python-import\" \"Python exception during module ~A import: ~A\" \\(\"mathalternate\" \"<class exceptions.ImportError at 0x[0-9a-f]{8}>\"\\) #f\\)\\)$" + (catch-test-import "mathalternate")) + +(is-ok 3 "bad argument datatype" + "(wrong-type-arg (\"python-import\" \"Wrong type argument in position ~A: ~S\" (1 2.7818) #f))" + (catch-test-import 2.7818)) + +(is-ok 4 "python-import with symbol" + "(wrong-type-arg (\"python-import\" \"Wrong type argument in position ~A: ~S\" (1 os.path) #f))" + (catch-test-import 'os.path)) + +(is-ok 5 "python-import with keyword" + "(wrong-type-arg (\"python-import\" \"Wrong type argument in position ~A: ~S\" (1 #:re) #f))" + (catch-test-import #:re)) + + +; End of 09_import.t diff --git a/t/10_python2guile.t b/t/10_python2guile.t new file mode 100644 index 0000000..9c57829 --- /dev/null +++ b/t/10_python2guile.t @@ -0,0 +1,56 @@ +#!/usr/bin/guile -s +!# +; Additional PyGuile tests - transferring data from Python to Guile. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 8) + +(is-ok 1 "Float 1.5" 1.5 + (python-eval "1.5" #t)) + +(is-ok 2 "Float 10000000.5" 10000000.5 + (python-eval "10000000 + 0.5" #t)) + +(is-ok 3 "Float 1e+20" 1e20 + (python-eval "1e11*1e9" #t)) + +(is-ok 4 "Complex" '(1.0+1.0i 2.0-3.0i -3.0-4.0i -4.0+5.0i) + (python-eval "[1+1j,2-3j,-3-4j,-4+5j]" #t)) + +(is-ok 5 "bigint" 123456789101112131415 + (python-eval "15 + 100*1234567891011121314" #t)) + +(is-ok 6 "list" '("ab" "cd") + (python-eval "['ab','cd']" #t)) + +(is-ok 7 "tuple" '("ab" "cd") + (python-eval "('ab','cd')" #t)) + +; !!! Need to sort to ensure consistent test results. +(is-ok 8 "dict" '((6 . (78 90)) (3 . "4.5") (1 . 2.2)) + (python-eval "{1 : 2.2, 3 : '4.5', 6 : (78,90)}" #t)) + +; End of 10_python2guile.t diff --git a/t/11_g2p2g.t b/t/11_g2p2g.t new file mode 100644 index 0000000..de7721f --- /dev/null +++ b/t/11_g2p2g.t @@ -0,0 +1,49 @@ +#!/usr/bin/guile -s +!# +; Basic g2p2g_smob tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 6) + +(is-ok 1 "g2p_null2PyNone" "'g2p_null2PyNone" + (with-output-to-string (lambda () (display g2p_null2PyNone)))) + +(is-ok 2 "guile2python" "'guile2python" + (with-output-to-string (lambda () (display guile2python)))) + +(is-ok 3 "guileassoc2pythondict" "'guileassoc2pythondict" + (with-output-to-string (lambda () (display guileassoc2pythondict)))) + +(is-ok 4 "p2g_None2SCM_EOL" "'p2g_None2SCM_EOL" + (with-output-to-string (lambda () (display p2g_None2SCM_EOL)))) + +(is-ok 5 "p2g_Dict2alist" "'p2g_Dict2alist" + (with-output-to-string (lambda () (display p2g_Dict2alist)))) + +(is-ok 6 "python2guile" "'python2guile" + (with-output-to-string (lambda () (display python2guile)))) + +; End of 11_g2p2g.t diff --git a/t/12_g2p_templated.t b/t/12_g2p_templated.t new file mode 100644 index 0000000..7956443 --- /dev/null +++ b/t/12_g2p_templated.t @@ -0,0 +1,225 @@ +#!/usr/bin/guile -s +!# +; Basic g2p template tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 36) + +(is-ok 1 "real" "1.0" + (python-apply '("__builtin__" "repr") '(1.0) '() (list g2p_list2Tuple g2p_real2Float))) + +(is-ok 2 "int" "3" + (python-apply '("__builtin__" "repr") '(3) '() (list g2p_list2Tuple g2p_num2Int))) + +(is-ok 3 "mixed int-real" "[4, 5.0, 6, 7.0, 8]" + (python-apply '("__builtin__" "repr") '((4 5 6 7 8)) '() + (list g2p_list2Tuple (list g2p_list2List g2p_num2Int g2p_real2Float)))) + +(is-ok 4 "mixed int-real(tuple)" "(4, 5.0, 6, 7.0, 8)" + (python-apply '("__builtin__" "repr") '((4 5 6 7 8)) '() + (list g2p_list2Tuple (list g2p_list2Tuple g2p_num2Int g2p_real2Float)))) + +(is-ok 5 "list2Tuple" "(4.0, 5.0, 6.0, 7.0, 8.0)" + (python-apply '("__builtin__" "repr") '((4 5 6 7 8)) '() + (list g2p_list2Tuple (cons g2p_list2Tuple g2p_real2Float)))) + +(is-ok 6 "bool vs. int -> List" "[True, 5, 6, 7, False]" + (python-apply '("__builtin__" "repr") '((#t 5 6 7 #f)) '() + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_num2Int g2p_bool2Bool))))) + +(is-ok 7 "pair2tuple" "('a', 5)" + (python-apply '("__builtin__" "repr") '(("a" . 5)) '() + (list g2p_list2Tuple (cons g2p_pair2Tuple (cons g2p_string2String g2p_num2Int))))) + +(is-ok 8 "pair2list" "['c', 55]" + (python-apply '("__builtin__" "repr") '(("c" . 55)) '() + (list g2p_list2Tuple (cons g2p_pair2List (cons g2p_string2String g2p_num2Int))))) + +; Run python-apply under catch harness. +(define catch-test-apply + (lambda (func args kws . targ) + (catch #t + (lambda () (python-apply func args kws (car targ))) + (lambda (key . args2) (object->string (list key args2)))))) + +(is-ok 9 "bad template in g2p_apply" + "(misc-error (\"g2p_apply\" \"bad template item ~S\" (\"pooh\") #f))" + (catch-test-apply '("__builtin__" "repr") '() '() "pooh")) + +(is-ok 10 "bad template 2 in g2p_apply" + "(misc-error (\"g2p_apply\" \"bad template CAR item ~S\" (\"pooh\") #f))" + (catch-test-apply '("__builtin__" "repr") '() '() '("pooh" . "bar"))) + +(is-ok 11 "g2p_leaf in pair" "'pair'" + (python-apply '("__builtin__" "repr") '("pair") '() (list g2p_list2Tuple (cons g2p_leaf g2p_string2String)))) + +(is-ok 12 "g2p_leaf missing conversion datatype" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" (((#t 5 \"rude\" 7 #f))) #f))" + (catch-test-apply '("__builtin__" "repr") '((#t 5 "rude" 7 #f)) '() + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_num2Int g2p_bool2Bool))))) + +; Exercise various g2p_* functions not otherwise covered. + +(is-ok 13 "null" "None" + (python-apply '("__builtin__" "repr") '(()) '() + (list g2p_list2Tuple (list g2p_null2PyNone)))) + +(is-ok 14 "Other nulls" "[(), [], {}]" + (python-apply '("__builtin__" "repr") '((() () ())) '() + (list g2p_list2Tuple (list g2p_list2List g2p_null2Tuple0 g2p_null2List0 g2p_null2DictEmpty)))) + +(is-ok 15 "g2p_copmlex" + "[1, 2, 3.5, (4+6j)]" + (python-apply '("__builtin__" "repr") '((1 2 3.5 4+6i)) '() + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_num2Int g2p_real2Float g2p_complex2Complex))))) + +(is-ok 16 "bignums" + "[1000000, 100000000, 10000000000L, 1000000000000L]" + (python-apply '("__builtin__" "repr") '((1000000 100000000 10000000000 1000000000000)) '() + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_bignum2Long g2p_num2Int))))) + +; Exercise g2p_pair2Tuple with bad templates and data + +(is-ok 17 "bad template for g2p_pair2Tuple" + "(misc-error (\"g2p_pair2Tuple\" \"bad template ~S\" (\"pooh\") #f))" + (catch-test-apply '("__builtin__" "repr") '((1 . 2)) '() + (list g2p_list2Tuple (cons g2p_pair2Tuple "pooh")))) + +(is-ok 18 "validate datatype tests for g2p_pair2Tuple" + "(1, 2)" + (catch-test-apply '("__builtin__" "repr") '((1 . 2)) '() + (list g2p_list2Tuple (cons g2p_pair2Tuple (cons g2p_num2Int g2p_num2Int))))) + +(is-ok 19 "bad CAR datatype for g2p_pair2Tuple" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" (((1 . 2))) #f))" + (catch-test-apply '("__builtin__" "repr") '((1 . 2)) '() + (list g2p_list2Tuple (cons g2p_pair2Tuple (cons g2p_bool2Bool g2p_num2Int))))) + +(is-ok 20 "bad CDR datatype for g2p_pair2Tuple" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" (((1 . 2))) #f))" + (catch-test-apply '("__builtin__" "repr") '((1 . 2)) '() + (list g2p_list2Tuple (cons g2p_pair2Tuple (cons g2p_num2Int g2p_bool2Bool))))) + +; Exercise g2p_pair2List with bad templates and data + +(is-ok 21 "bad template for g2p_pair2List" + "(misc-error (\"g2p_pair2List\" \"bad template ~S\" (\"pooh\") #f))" + (catch-test-apply '("__builtin__" "repr") '((1 . 2)) '() + (list g2p_list2Tuple (cons g2p_pair2List "pooh")))) + +(is-ok 22 "validate datatype tests for g2p_pair2List" + "[1, 2]" + (catch-test-apply '("__builtin__" "repr") '((1 . 2)) '() + (list g2p_list2Tuple (cons g2p_pair2List (cons g2p_num2Int g2p_num2Int))))) + +(is-ok 23 "bad CAR datatype for g2p_pair2List" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" (((1 . 2))) #f))" + (catch-test-apply '("__builtin__" "repr") '((1 . 2)) '() + (list g2p_list2Tuple (cons g2p_pair2List (cons g2p_bool2Bool g2p_num2Int))))) + +(is-ok 24 "bad CDR datatype for g2p_pair2List" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" (((1 . 2))) #f))" + (catch-test-apply '("__builtin__" "repr") '((1 . 2)) '() + (list g2p_list2Tuple (cons g2p_pair2List (cons g2p_num2Int g2p_bool2Bool))))) + +; Exercise g2p_list2Tuple and g2p_list2List with bad arguments + +(is-ok 25 "bad argument to g2p_list2Tuple" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" ((\"not a list\")) #f))" + (catch-test-apply '("__builtin__" "repr") '("not a list") '() + (list g2p_list2Tuple (list g2p_list2Tuple g2p_string2String)))) + +(is-ok 26 "bad argument to g2p_list2List" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" ((\"not a list\")) #f))" + (catch-test-apply '("__builtin__" "repr") '("not a list") '() + (list g2p_list2Tuple (list g2p_list2List g2p_string2String)))) + + +(is-ok 27 "no bad argument datatype to g2p_list2Tuple (test validation)" + "(1, 2, 3, 4, 5)" + (catch-test-apply '("__builtin__" "repr") '((1 2 3 4 5)) '() + (list g2p_list2Tuple (list g2p_list2Tuple g2p_num2Int)))) + +(is-ok 28 "bad argument datatype to g2p_list2Tuple" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" (((1 2 3 #f 4 5))) #f))" + (catch-test-apply '("__builtin__" "repr") '((1 2 3 #f 4 5)) '() + (list g2p_list2Tuple (list g2p_list2Tuple g2p_num2Int)))) + + +(is-ok 29 "bad argument datatype to g2p_list2List" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" (((6 7 #t 8 9 10))) #f))" + (catch-test-apply '("__builtin__" "repr") '((6 7 #t 8 9 10)) '() + (list g2p_list2Tuple (list g2p_list2List g2p_num2Int)))) + +(is-ok 30 "bad template (not list) to g2p_list2Tuple" + "(wrong-type-arg (\"g2p_list2Tuple\" \"Wrong type argument in position ~A: ~S\" (2 #<unspecified>) #f))" + (catch-test-apply '("__builtin__" "repr") '((1 2 3 #f 4 5)) '() + (list g2p_list2Tuple g2p_list2Tuple "fadiha"))) + + +(is-ok 31 "bad template (not list) to g2p_list2List" + "(wrong-type-arg (\"g2p_list2List\" \"Wrong type argument in position ~A: ~S\" (2 #<unspecified>) #f))" + (catch-test-apply '("__builtin__" "repr") '((6 7 #t 8 9 10)) '() + (list g2p_list2Tuple g2p_list2List "fadiha"))) + +; g2p_char2String + +(is-ok 32 "g2p_char2String" + "['a', 5, 6, 7, ' ', 'Q']" + (python-apply '("__builtin__" "repr") '((#\a 5 6 7 #\space #\Q)) '() + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_char2String g2p_num2Int g2p_bool2Bool))))) + +; g2p_symbol2String + +(is-ok 33 "g2p_symbol2String" + "['one', 'two', 'three', 'four', 'five', 'six']" + (python-apply '("__builtin__" "repr") '(("one" two #:three "four" five #:six)) '() + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_string2String g2p_symbol2String g2p_keyword2String))))) + +(python-eval "class opaq(object):\n def __init__(self,v):\n self.v=v\n def __repr__(self): return('*** opaque %s ***' % str(self.v))\n" #f) +(define opaq1 (python-eval "opaq('o p a q 1')" #t)) +(define opaq2 (python-eval "opaq(['o p a q',2])" #t)) + +(is-ok 34 "opaque data" + "[*** opaque o p a q 1 ***, 3, *** opaque ['o p a q', 2] ***]" + (python-apply '("__builtin__" "repr") (list (list opaq1 3 opaq2)) '() + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_opaque2Object g2p_num2Int))))) + +; Additional tests: g2p_list2Tuple and g2p_list2List getting data with +; item inappropriate to the single G2P_SMOB template argument. + +(is-ok 35 "list2Tuple" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" (((4 \"Gorilla!\" 6 7 8))) #f))" + (catch-test-apply '("__builtin__" "repr") '((4 "Gorilla!" 6 7 8)) '() + (list g2p_list2Tuple (cons g2p_list2Tuple g2p_real2Float)))) + +(is-ok 36 "list2Tuple" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" (((4 \"Chimpanzee!\" 6 7 8))) #f))" + (catch-test-apply '("__builtin__" "repr") '((4 "Chimpanzee!" 6 7 8)) '() + (list g2p_list2Tuple (cons g2p_list2List g2p_real2Float)))) + + +; End of 12_g2p_templated.t diff --git a/t/13_hashes.t b/t/13_hashes.t new file mode 100644 index 0000000..881039c --- /dev/null +++ b/t/13_hashes.t @@ -0,0 +1,167 @@ +#!/usr/bin/guile -s +!# +; Pyguile hash tests - keyword arguments to functions and general Dicts. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(python-eval "import sys;sys.path = ['']+sys.path\n") +(python-eval "from t.scripts.t4apply import return_args\n") + +(define check-kw-conversion + (lambda (keywords kwtemplate) + (python-apply '("__main__" "return_args") '() keywords + (list g2p_list2Tuple guile2python) + kwtemplate))) + +(define catch-test-kw-conversion + (lambda (keywords kwtemplate) + (catch #t + (lambda () (check-kw-conversion keywords kwtemplate)) + (lambda (key . args2) (object->string (list key args2)))))) + +(plan 20) + +(is-ok 1 "validate test" + "positional: () keywords: {'a': 'argument A'}" + (catch-test-kw-conversion '((#:a . "argument A")) + (make-hash-table 2))) + +(is-ok 2 "no kw template" + "positional: () keywords: {'bob': 'buba'}" + (python-apply '("__main__" "return_args") + '() + '((#:bob . "buba")))) + +(is-ok 3 "no kw template 2" + "positional: () keywords: {'n': None}" + (python-apply '("__main__" "return_args") + '() + '((#:n . ())) + (list g2p_list2Tuple guile2python))) + +(is-ok 4 "not a list of pairs" + "(wrong-type-arg (\"guileassoc2pythondict\" \"Wrong type argument in position ~A: ~S\" (1 not-a-list) #f))" + (catch-test-kw-conversion 'not-a-list + (make-hash-table 2))) + +(is-ok 5 "not a list of pairs 2" + "(wrong-type-arg (\"guileassoc2pythondict\" \"Wrong type argument in position ~A: ~S\" (1 not-list-either) #f))" + (catch-test-kw-conversion '(not-list-either) + (make-hash-table 2))) + +(is-ok 6 "bad key" + "(wrong-type-arg (\"guileassoc2pythondict\" \"Wrong type argument in position ~A: ~S\" (1 bad-key) #f))" + (catch-test-kw-conversion '((bad-key . 'val)) + (make-hash-table 2))) + +; Our own hash table for keywords + +(define kwhash (make-hash-table 7)) +(hashq-set! kwhash #:inpstr (list g2p_apply g2p_string2String)) +(hashq-set! kwhash #:inpkw (list g2p_apply g2p_keyword2String)) +(hashq-set! kwhash #:mynum (list g2p_apply g2p_num2Int)) + +(is-ok 7 "our own conversions" + "positional: () keywords: {'inpkw': 'must-be-kw', 'mynum': -3, 'inpstr': 'must be string', 'unverified': ['a', 'b', 'c']}" + (catch-test-kw-conversion '((#:unverified . (a b c)) (#:inpstr . "must be string") (#:inpkw . #:must-be-kw) (#:mynum . -3)) + kwhash)) + +(is-ok 8 "bad conversion" + "(misc-error (\"python-apply\" \"keyword arguments conversion failure (~S)\" (((#:unverified . -3) (#:inpstr . 7) (#:inpkw . #:must-be-kw) (#:mynum . -3))) #f))" + (catch-test-kw-conversion '((#:unverified . -3) (#:inpstr . 7) (#:inpkw . #:must-be-kw) (#:mynum . -3)) + kwhash)) + +; Passing a Dict argument to Python function + +(define catch-python-apply + (lambda (args keywords argtemplate) + (catch #t + (lambda () (python-apply '("__main__" "return_args") args keywords argtemplate)) + (lambda (key . args2) (object->string (list key args2)))))) + +(is-ok 9 "basic Dict" + "positional: ({1: 2},) keywords: {}" + (catch-python-apply '(((1 . 2))) '() + (list g2p_list2Tuple (list g2p_alist2Dict (cons (list g2p_apply guile2python) (list g2p_apply guile2python)))))) + +(is-ok 10 "more complicated Dict" + "positional: ({1: 2, '3': 4.0},) keywords: {}" + (catch-python-apply '(((1 . 2) ("3" . 4.0))) '() + (list g2p_list2Tuple (list g2p_alist2Dict (cons (list g2p_apply guile2python) (list g2p_apply guile2python)) (cons (list g2p_apply g2p_string2String) (list g2p_apply g2p_real2Float)))))) + +(is-ok 11 "bad conversion" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" ((((1 . 2) (\"3\" four-dot-zero)))) #f))" + (catch-python-apply '(((1 . 2) ("3" . (four-dot-zero)))) '() + (list g2p_list2Tuple (list g2p_alist2Dict (cons (list g2p_apply guile2python) (list g2p_apply guile2python)) (cons (list g2p_apply g2p_string2String) (list g2p_apply g2p_real2Float)))))) + +(is-ok 12 "no alist template" + "positional: ([[1, 3, 1, 4], ['2', '6', '2', '8']],) keywords: {}" + (python-apply '("__main__" "return_args") + '(((1 . (3 1 4)) ("2" . ("6" "2" "8")))) + '())) + +(is-ok 13 "default template 2" + "positional: ({1: [3, 1, 4], '2': ['6', '2', '8']},) keywords: {}" + (python-apply '("__main__" "return_args") + '(((1 . (3 1 4)) ("2" . ("6" "2" "8")))) + '() + (list g2p_list2Tuple (list g2p_apply g2p_alist2Dict)))) + +(is-ok 14 "Dict template with non-list argument" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" ((\"not-a-list\")) #f))" + (catch-python-apply '("not-a-list") '() + (list g2p_list2Tuple (list g2p_alist2Dict (cons (list g2p_apply guile2python) (list g2p_apply guile2python)))))) + +(is-ok 15 "basic Dict with non-list template" + "(wrong-type-arg (\"g2p_alist2Dict\" \"Wrong type argument in position ~A: ~S\" (2 'guile2python) #f))" + (catch-python-apply '(((1 . 2))) '() + (list g2p_list2Tuple (list g2p_apply g2p_alist2Dict guile2python)))) + +(is-ok 16 "Dict with malformed template" + "(wrong-type-arg (\"g2p_alist2Dict\" \"Wrong type argument in position ~A: ~S\" (2 \"notapair\") #f))" + (catch-python-apply '(((1 . 2) ("3" . 4.0))) '() + (list g2p_list2Tuple (list g2p_alist2Dict (cons (list g2p_apply guile2python) (list g2p_apply guile2python)) "notapair")))) + +(is-ok 17 "malformed alist" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" ((((1 . 2) conjugate (\"3\" . 4.0)))) #f))" + (catch-python-apply '(((1 . 2) conjugate ("3" . 4.0))) '() + (list g2p_list2Tuple (list g2p_alist2Dict (cons (list g2p_apply guile2python) (list g2p_apply guile2python)) (cons (list g2p_apply g2p_string2String) (list g2p_apply g2p_real2Float)))))) + +(is-ok 18 "Dict with keys matching template" + "positional: ({1: '2', '3': 4.0},) keywords: {}" + (catch-python-apply '(((1 . "2") ("3" . 4.0))) '() + (list g2p_list2Tuple (list g2p_alist2Dict (cons (list g2p_apply g2p_num2Int) (list g2p_apply g2p_string2String)) (cons (list g2p_apply g2p_string2String) (list g2p_apply g2p_real2Float)))))) + +(is-ok 19 "Dict with keys not matching template" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" ((((1.5 . 2) (\"3\" . 4.0)))) #f))" + (catch-python-apply '(((1.5 . 2) ("3" . 4.0))) '() + (list g2p_list2Tuple (list g2p_alist2Dict (cons (list g2p_apply g2p_num2Int) (list g2p_apply g2p_string2String)) (cons (list g2p_apply g2p_string2String) (list g2p_apply g2p_real2Float)))))) + +(is-ok 20 "alist with duplicate key" + "(misc-error (\"g2p_alist2Dict\" \"duplicate key (~S)\" (1) #f))" + (catch-python-apply '(((1 . 2) ("3" . 4.0) (1 . a55))) '() + (list g2p_list2Tuple (list g2p_alist2Dict (cons (list g2p_apply guile2python) (list g2p_apply guile2python)) (cons (list g2p_apply guile2python) (list g2p_apply guile2python)))))) + +; End of 13_hashes.t diff --git a/t/14_p2g_templated.t b/t/14_p2g_templated.t new file mode 100644 index 0000000..88611ae --- /dev/null +++ b/t/14_p2g_templated.t @@ -0,0 +1,293 @@ +#!/usr/bin/guile -s +!# +; Basic p2g template tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 57) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; p2g_None2SCM_EOL + +(is-ok 1 "default None" '() + (python-eval "None" #t)) + +(is-ok 2 "default None 2" '() + (python-eval "None" python2guile)) + +(is-ok 3 "default None 3" '() + (python-eval "None" p2g_None2SCM_EOL)) + +(is-ok 4 "default None 4" '() + (python-eval "None" (cons p2g_None2SCM_EOL '()))) + +(is-ok 5 "None becomes 'None'" "None" + (python-eval "None" (cons p2g_None2SCM_EOL "None"))) + +(is-ok 6 "non-None" "#<undefined>" + (object->string (python-eval "True" p2g_None2SCM_EOL))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; p2g_Bool2SCM_BOOL + +(is-ok 7 "p2g_Bool2SCM_BOOL True" #t + (python-eval "True" p2g_Bool2SCM_BOOL)) + +(is-ok 8 "p2g_Bool2SCM_BOOL False" #f + (python-eval "False" p2g_Bool2SCM_BOOL)) + +(is-ok 9 "p2g_Bool2SCM_BOOL Other" "#<undefined>" + (object->string (python-eval "2239" p2g_Bool2SCM_BOOL))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; p2g_Int2num, p2g_Long2bignum + +(is-ok 10 "p2g_Int2num ok" 12 + (python-eval "12" p2g_Int2num)) + +(is-ok 11 "p2g_Int2num bad" "#<undefined>" + (object->string (python-eval "None" p2g_Int2num))) + +(is-ok 12 "p2g_Int2num long" "#<undefined>" + (object->string (python-eval "1000200030004" p2g_Int2num))) + +(is-ok 13 "p2g_Long2bignum ok" 12 + (python-eval "12L" p2g_Long2bignum)) + +(is-ok 14 "p2g_Long2bignum bad" "#<undefined>" + (object->string (python-eval "'q'" p2g_Long2bignum))) + +(is-ok 15 "p2g_Long2bignum long" "1000200030004" + (object->string (python-eval "1000200030004" p2g_Long2bignum))) + +; p2g_leaf + +(is-ok 16 "p2g_Int2num+p2g_Long2bignum" "5000600070008" + (object->string (python-eval "5000600070008" (list p2g_leaf p2g_Int2num p2g_Long2bignum)))) + +(is-ok 17 "p2g_Int2num+p2g_Long2bignum/cons" "5100600070008" + (object->string (python-eval "5100600070008" (cons p2g_leaf p2g_Long2bignum)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; float and complex + +(is-ok 18 "p2g_Float2real" 2.718 + (python-eval "2.7+0.018" p2g_Float2real)) + +(is-ok 19 "p2g_Complex2complex" 1.5+3.6i + (python-eval "1.5+3.6j" p2g_Complex2complex)) + +; Tuple2list of numbers + +(is-ok 20 "Numbers in tuple" '(1023 -445 4.75 -8.1e5 2e7+3.125i) + (python-eval "(1023,-445,4.75,-8.1e5,(2e7+3.125j))" + (list p2g_apply p2g_Tuple2list (list p2g_leaf p2g_Int2num p2g_Long2bignum p2g_Float2real p2g_Complex2complex)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Strings + +(is-ok 21 "String" "a message" + (python-eval "'''a message'''" p2g_String2string)) + +(is-ok 22 "Sumbol" 'a-message + (python-eval "'''a-message'''" p2g_String2symbol)) + +(is-ok 23 "Keyword" #:another-message + (python-eval "'''another-message'''" p2g_String2keyword)) + +; string to char + +(is-ok 24 "single char" #\space + (python-eval "' '" p2g_1String2char)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; 2-tuple/2-list to pairs + +(is-ok 25 "2-tuple to pair" '(12.0 . 34) + (python-eval "(12.0,34)" (cons p2g_2Tuple2pair (cons p2g_Float2real p2g_Int2num)))) + +(is-ok 26 "2-tuple to pair, wrong template" "#<undefined>" + (object->string (python-eval "(12.0,34)" (cons p2g_2Tuple2pair (cons p2g_Int2num p2g_Float2real))))) + +(is-ok 27 "2-tuple to pair, wrong template due another reason" "#<undefined>" + (object->string (python-eval "[12.0,34]" (cons p2g_2Tuple2pair (cons p2g_Int2num p2g_Float2real))))) + +(is-ok 28 "2-list to pair" '("ab" . "cd") + (python-eval "['ab','cd']" (cons p2g_2List2pair (cons p2g_String2string p2g_String2string)))) + +(is-ok 29 "2-tuple (or 2-list) to pair" '("ef" . 3) + (python-eval "('ef',3)" + (cons p2g_leaf + (list + (cons p2g_2Tuple2pair (cons p2g_String2string p2g_Int2num)) + (cons p2g_2List2pair (cons p2g_String2string p2g_Int2num)))))) + + +(is-ok 30 "(2-tuple or) 2-list to pair" '("ef" . 3) + (python-eval "['ef',3]" + (cons p2g_leaf + (list + (cons p2g_2Tuple2pair (cons p2g_String2string p2g_Int2num)) + (cons p2g_2List2pair (cons p2g_String2string p2g_Int2num)))))) + +(is-ok 31 "2-list to pair, wrong template" "#<undefined>" + (object->string (python-eval "[12.0,34]" (cons p2g_2List2pair (cons p2g_Int2num p2g_Float2real))))) + +(is-ok 32 "2-list to pair, wrong template due another reason" "#<undefined>" + (object->string (python-eval "(12.0,34)" (cons p2g_2List2pair (cons p2g_Int2num p2g_Float2real))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Aggregates - N-Tuples/N-Lists to lists + +; from Tuples +(is-ok 33 "tuple to list, mixed datatypes" "(11 12.0 13 34.5 38 780.25)" + (object->string (python-eval "(11,12.0,13,34.5,38,780.25)" (cons p2g_Tuple2list (list p2g_Int2num p2g_Float2real))))) + +(is-ok 34 "tuple to list, wrong data type" "#<undefined>" + (object->string (python-eval "[11,12.0,13,34.5,38,780.25]" (cons p2g_Tuple2list (list p2g_Int2num p2g_Float2real))))) + +(is-ok 35 "tuple to list, P2G_SMOB" "(11 12 13 34 38 780)" + (object->string (python-eval "(11,12,13,34,38,780)" (cons p2g_Tuple2list p2g_Int2num)))) + +(is-ok 36 "tuple to list, P2G_SMOB, one bad value" "#<undefined>" + (object->string (python-eval "(11,12,'a13',34,38,780)" (cons p2g_Tuple2list p2g_Int2num)))) + +; from Lists +(is-ok 37 "List to list, mixed datatypes" "(11 12.0 13 34.5 38 780.25)" + (object->string (python-eval "[11,12.0,13,34.5,38,780.25]" (cons p2g_List2list (list p2g_Int2num p2g_Float2real))))) + +(is-ok 38 "List to list, wrong data type" "#<undefined>" + (object->string (python-eval "(11,12.0,13,34.5,38,780.25)" (cons p2g_List2list (list p2g_Int2num p2g_Float2real))))) + +(is-ok 39 "List to list, P2G_SMOB" "(11 12 13 34 38 780)" + (object->string (python-eval "[11,12,13,34,38,780]" (cons p2g_List2list p2g_Int2num)))) + +(is-ok 40 "List to list, P2G_SMOB, one bad value" "#<undefined>" + (object->string (python-eval "[11,12,'a13',34,38,780]" (cons p2g_List2list p2g_Int2num)))) + +; p2g_Dict2alist + +(define alist-properly-included? + (lambda (included includor) + (if (null? included) #t + (let ((key (caar included)) + (value (cdar included)) + (rest (cdr included))) + (let ((includor-ref (assoc key includor))) + (cond ((not includor-ref) #f) + ((not (equal? (cdr includor-ref) value)) #f) + (else (alist-properly-included? rest includor)))))))) + +(define alist-equal? + (lambda (alista alistb) + (and (alist-properly-included? alista alistb) + (alist-properly-included? alistb alista)))) + +; Quick tests of alist-properly-included? +(ok 41 "should pass 1" + (alist-equal? '((1 . 2)("3" . "4")) + '(("3" . "4")(1 . 2)))) + +(ok 42 "should pass 2" + (alist-equal? '((1 . 2)("3" . "4")) + '((1 . 2)("3" . "4")))) + +(ok 43 "should fail 1" + (not (alist-equal? '((1 . 2)("3" . "4")) + '((11 . 2)("3" . "4"))))) + +(ok 44 "should fail 2" + (not (alist-equal? '((1 . 2)("3" . "4")) + '((1 . 21)("3" . "4"))))) + +(ok 45 "should fail 3" + (not (alist-equal? '((1 . 2)("3" . "4")) + '((1 . 2)("3a" . "4"))))) + +(ok 46 "should fail 4" + (not (alist-equal? '((1 . 2)("3" . "4")) + '((1 . 2)("3" . "4b"))))) + +(ok 47 "should fail 1a" + (not (alist-equal? '(("3" . "4")(1 . 2)) + '((11 . 2)("3" . "4"))))) + +(ok 48 "should fail 2a" + (not (alist-equal? '(("3" . "4")(1 . 2)) + '((1 . 21)("3" . "4"))))) + +(ok 49 "should fail 3a" + (not (alist-equal? '(("3" . "4")(1 . 2)) + '((1 . 2)("3a" . "4"))))) + +(ok 50 "should fail 4a" + (not (alist-equal? '(("3" . "4")(1 . 2)) + '((1 . 2)("3" . "4b"))))) + +(ok 51 "should fail 5" + (not (alist-equal? '(("3" . "4")(1 . 2)(5 . 6)) + '((1 . 2)("3" . "4"))))) + +(ok 52 "should fail 5a" + (not (alist-equal? '(("3" . "4")(1 . 2)(5 . 6)) + '((1 . 2)("3" . "4")(7 . 8)(5 . 6))))) + + +; Proper p2g_Dict2alist tests + + +(ok 53 "Default p2g_Dict2alist" + (alist-equal? '((1 . 2) ("3" . "4")) + (python-eval "{1 : 2, '3' : '4'}" #t))) + +(ok 54 "Explicit p2g_Dict2alist template" + (alist-equal? '((#\b . 42) (gg . 3)) + (python-eval "{'b' : None, 'gg' : 3}" + (cons p2g_Dict2alist + (cons + (cons p2g_leaf (list p2g_1String2char p2g_String2symbol)) + (cons p2g_leaf (list (cons p2g_None2SCM_EOL 42) p2g_Int2num))))))) + +(ok 55 "P2G_SMOBP based p2g_Dict2alist template" + (alist-equal? '(("key1" . val1) ("key2" . myval2) ("k3" . yourval3)) + (python-eval "{'key1' : 'val1', 'key2' : 'myval2', 'k3' : 'yourval3'}" + (cons p2g_Dict2alist + (cons p2g_String2string p2g_String2symbol))))) + +(is-ok 56 "p2g_Dict2alist key conversion failure" "#<undefined>" + (object->string (python-eval "{'b' : None, 1.2 : 3}" + (cons p2g_Dict2alist + (cons + (cons p2g_leaf (list p2g_1String2char p2g_String2symbol)) + (cons p2g_leaf (list (cons p2g_None2SCM_EOL 42) p2g_Int2num))))))) + +(is-ok 57 "p2g_Dict2alist value conversion failure" "#<undefined>" + (object->string (python-eval "{'b' : 'None', 'gg' : 3}" + (cons p2g_Dict2alist + (cons + (cons p2g_leaf (list p2g_1String2char p2g_String2symbol)) + (cons p2g_leaf (list (cons p2g_None2SCM_EOL 42) p2g_Int2num))))))) + +; End of 14_p2g_templated.t diff --git a/t/15_p2g_errors.t b/t/15_p2g_errors.t new file mode 100644 index 0000000..13398bd --- /dev/null +++ b/t/15_p2g_errors.t @@ -0,0 +1,126 @@ +#!/usr/bin/guile -s +!# +; p2g conversion error handling tests. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 20) + +; Run python-eval under catch harness. +(define catch-test-eval + (lambda (txt template) + (catch #t + (lambda () (python-eval txt template)) + (lambda (key . args) (object->string (list key args)))))) + +(is-ok 1 "template with bad CAR" + "(misc-error (\"p2g_apply\" \"bad template CAR item ~S\" (42) #f))" + (catch-test-eval "-42" (cons 42 "None"))) + +(is-ok 2 "p2g_leaf template with bad CDR" + "(wrong-type-arg (\"p2g_leaf\" \"Wrong type argument in position ~A: ~S\" (2 \"foofoo\") #f))" + (catch-test-eval "-42" (cons p2g_leaf "foofoo"))) + +(is-ok 3 "no p2g_leaf template matches the data" + "#<undefined>" + (object->string (catch-test-eval "'a-42'" (list p2g_leaf p2g_Int2num p2g_Long2bignum)))) + +(is-ok 4 "too short string for keyword" + "#<undefined>" + (object->string (catch-test-eval "''" p2g_String2keyword))) + +(is-ok 5 "too long string for char" + "#<undefined>" + (object->string (catch-test-eval "'lg'" p2g_1String2char))) + +(is-ok 6 "too short string for char" + "#<undefined>" + (object->string (catch-test-eval "''''''" p2g_1String2char))) + +(is-ok 7 "2-tuple to pair, bad template" + "(misc-error (\"p2g_2Tuple2pair\" \"bad template item ~S\" (12.3) #f))" + (catch-test-eval "(12.0,34)" (cons p2g_2Tuple2pair 12.3))) + +(is-ok 8 "2-tuple to pair, wrong tuple length - too long" "#<undefined>" + (object->string (python-eval "(1,2,3)" (cons p2g_2Tuple2pair (cons p2g_Int2num p2g_Int2num))))) + +(is-ok 9 "2-tuple to pair, wrong tuple length - too short" "#<undefined>" + (object->string (python-eval "(1,)" (cons p2g_2Tuple2pair (cons p2g_Int2num p2g_Int2num))))) + +(is-ok 10 "2-tuple to pair, wrong 2nd item datatype" "#<undefined>" + (object->string (python-eval "(1,'zuzu')" (cons p2g_2Tuple2pair (cons p2g_Int2num p2g_Int2num))))) + +(is-ok 11 "2-list to pair, bad template" + "(misc-error (\"p2g_2List2pair\" \"bad template item ~S\" (12.3) #f))" + (catch-test-eval "[12.0,34]" (cons p2g_2List2pair 12.3))) + +(is-ok 12 "2-list to pair, wrong tuple length - too long" "#<undefined>" + (object->string (python-eval "[1,2,3]" (cons p2g_2List2pair (cons p2g_Int2num p2g_Int2num))))) + +(is-ok 13 "2-list to pair, wrong tuple length - too short" "#<undefined>" + (object->string (python-eval "[1]" (cons p2g_2List2pair (cons p2g_Int2num p2g_Int2num))))) + +(is-ok 14 "2-list to pair, wrong 1st item datatype" "#<undefined>" + (object->string (python-eval "['xyxy',11]" (cons p2g_2List2pair (cons p2g_Int2num p2g_Int2num))))) + +(is-ok 15 "2-list to pair, wrong 2nd item datatype" "#<undefined>" + (object->string (python-eval "[1,'zuzu']" (cons p2g_2List2pair (cons p2g_Int2num p2g_Int2num))))) + + +; template not a proper list + +(is-ok 16 "Tuple to list, template improper list" + "(wrong-type-arg (\"p2g_Tuple2list\" \"Wrong type argument in position ~A: ~S\" (2 ('p2g_Int2num 'p2g_Float2real . 'p2g_Float2real)) #f))" + (catch-test-eval + "(11,12.0,13,34.5,38,780.25)" + (cons p2g_Tuple2list (cons p2g_Int2num (cons p2g_Float2real p2g_Float2real))))) + +(is-ok 17 "List to list, template improper list" + "(wrong-type-arg (\"p2g_List2list\" \"Wrong type argument in position ~A: ~S\" (2 ('p2g_Int2num 'p2g_Float2real . 'p2g_Float2real)) #f))" + (catch-test-eval + "[11,12.0,13,34.5,38,780.25]" + (cons p2g_List2list (cons p2g_Int2num (cons p2g_Float2real p2g_Float2real))))) + +; p2g_Dict2alist - bad templates + +(is-ok 18 "Dict to alist, template is not pair" + "(misc-error (\"p2g_Dict2alist\" \"bad template ~S\" ('p2g_Int2num) #f))" + (catch-test-eval + "{1 : 2, 3 : 4}" + (cons p2g_Dict2alist p2g_Int2num))) + +(is-ok 19 "Dict to alist, bad template CAR" + "(misc-error (\"p2g_apply\" \"bad template item ~S\" (12) #f))" + (catch-test-eval + "{1 : 2, 3 : 4}" + (cons p2g_Dict2alist (cons 12 p2g_Int2num)))) + +(is-ok 20 "Dict to alist, bad template CDR" + "(misc-error (\"p2g_apply\" \"bad template item ~S\" (\"cde\") #f))" + (catch-test-eval + "{1 : 2, 3 : 4}" + (cons p2g_Dict2alist (cons p2g_Int2num "cde")))) + +; End of 15_p2g_errors.t diff --git a/t/16_verbose.t b/t/16_verbose.t new file mode 100755 index 0000000..58adb75 --- /dev/null +++ b/t/16_verbose.t @@ -0,0 +1,50 @@ +#!/usr/bin/guile -s +!# +; Basic tests of the PyGuile verbosity control +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(plan 4) + +(pyguile-verbosity-set! 43) +(is-ok 1 "Verbosity value should be 43" + 43 + (pyguile-verbosity-set! 10)) + +(is-ok 2 "Verbosity value should be 10" + 10 + (pyguile-verbosity-set! 0)) + +(is-ok 3 "Verbosity value should be 0" + 0 + (pyguile-verbosity-set! 0)) + +(is-ok 4 "Trying to set verbosity value to a non-number" + "(wrong-type-arg (\"pyguile-verbosity-set!\" \"Wrong type argument in position ~A: ~S\" (1 \"NAN\") #f))" + (catch #t + (lambda () (pyguile-verbosity-set! "NAN")) + (lambda (key . args) (object->string (list key args))))) + +; End of 16_verbose.t diff --git a/t/17_verbose.t b/t/17_verbose.t new file mode 100644 index 0000000..c807ed8 --- /dev/null +++ b/t/17_verbose.t @@ -0,0 +1,161 @@ +#!/usr/bin/guile -s +!# +; Tests of PyGuile verbosity messages - for full source code coverage +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) +;(use-modules (srfi srfi-6)) ; string ports +(use-modules (srfi srfi-13)) ; string-concatenate +(use-modules (ice-9 regex)) ; regexp-substitute + +;(define get-output +; (lambda (thunk) +; (let ((my-output-port open-output-string)) +; (call-with-output-string (thunk)) +; (get-output-string my-output-port)))) + +(define thunk-invoke-python-repr + (lambda (arg . template) + (lambda () + (if (null? template) + (python-apply '("__builtin__" "repr") arg '()) + (python-apply '("__builtin__" "repr") arg '() (car template)))))) + +(plan 6) + +(pyguile-verbosity-set! 1) + +(define successful-verbose-builtin-repr-conversion-report + (string-concatenate + '("# g2p_string2String: successful conversion of \"__builtin__\" into a Python String value\n" + "# g2p_string2String: successful conversion of \"repr\" into a Python String value\n"))) + +(define expres1 + (string-concatenate + (list + successful-verbose-builtin-repr-conversion-report + "# g2p_bool2Bool: successful conversion of #t into a Python Bool value\n" + "# p2g_String2string: successful conversion of \"'True'\" into SCM\n"))) +(is-ok 1 "Verbosity with a single value" + expres1 + (with-output-to-string + (thunk-invoke-python-repr '(#t)))) + +(define expres2 + (string-concatenate + (list + successful-verbose-builtin-repr-conversion-report + "# g2p_null2PyNone: successful conversion of () into Python None\n" + "# g2p_bool2Bool: successful conversion of #t into a Python Bool value\n" + "# g2p_bool2Bool: successful conversion of #f into a Python Bool value\n" + "# g2p_num2Int: successful conversion of 1 into a Python Int value\n" + "# g2p_num2Int: successful conversion of -5 into a Python Int value\n" + "# g2p_symbol2String: successful conversion of quote into a Python String value\n" + "# g2p_symbol2String: successful conversion of symba into a Python String value\n" + "# g2p_complex2Complex: successful conversion of 1.0-1.0i into a Python Complex value\n" + "# g2p_real2Float: successful conversion of 3.125 into a Python Float value\n" + "# p2g_String2string: successful conversion of \"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" into SCM\n"))) +(is-ok 2 "Verbosity when successful" + expres2 + (with-output-to-string + (thunk-invoke-python-repr '((() #t #f 1 -5 #\P 'symba 1-1i 3.125))))) + +(define expres3 + (string-concatenate + (list + successful-verbose-builtin-repr-conversion-report + "# g2p_null2Tuple0: successful conversion of () into Python ()\n" + "# g2p_null2List0: successful conversion of () into Python []\n" + "# g2p_null2DictEmpty: successful conversion of () into Python {}\n" + "# p2g_String2string: successful conversion of \"'((), [], {})'\" into SCM\n"))) +(is-ok 3 "g2p* verbosity coverage" + expres3 + (with-output-to-string + (thunk-invoke-python-repr + '((() () ())) + (list g2p_list2Tuple (list g2p_list2Tuple g2p_null2Tuple0 g2p_null2List0 g2p_null2DictEmpty))))) + +(define expres4 + (string-concatenate + (list + successful-verbose-builtin-repr-conversion-report + "# g2p_num2Int: successful conversion of 1000000 into a Python Int value\n" + "# g2p_bignum2Long: successful conversion of 1000000000000 into a Python Long value\n" + "# p2g_String2string: successful conversion of \"'[1000000, 1000000000000L]'\" into SCM\n"))) +(is-ok 4 "g2p* verbosity coverage - bignums,list2Tuple,list2List" + expres4 + (with-output-to-string + (thunk-invoke-python-repr + '((1000000 1000000000000)) + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_bignum2Long g2p_num2Int)))))) + +(define expres5 + (string-concatenate + (list + successful-verbose-builtin-repr-conversion-report + "# g2p_symbol2String: successful conversion of p1at into a Python String value\n" + "# g2p_string2String: successful conversion of \"p1bt\" into a Python String value\n" + "# g2p_keyword2String: successful conversion of #:p2al into a Python String value\n" + "# g2p_symbol2String: successful conversion of p2bl into a Python String value\n" + "# p2g_String2string: successful conversion of \"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" into SCM\n"))) +(is-ok 5 "g2p* verbosity coverage - pair2Tuple,pair2List" + expres5 + (with-output-to-string + (thunk-invoke-python-repr + '(((p1at . "p1bt") (#:p2al . p2bl))) + (list g2p_list2Tuple (list g2p_list2Tuple (cons g2p_pair2Tuple (cons guile2python guile2python)) (cons g2p_pair2List (cons guile2python guile2python))))))) + +(define substitute-hex-addresses-for-gggggggg + (lambda (strarg) + (regexp-substitute/global #f + "0x[0-9a-f]{8}" + strarg + 'pre "0xgggggggg" 'post))) + +(define expres6 + (string-concatenate + (list + successful-verbose-builtin-repr-conversion-report + "# g2p_opaque2Object: the Python object inside opaque pysmob (python-eval <function func at 0xgggggggg> #t) is unwrapped\n" + "# g2p_num2Int: successful conversion of 1 into a Python Int value\n" + "# g2p_string2String: successful conversion of \"one\" into a Python String value\n" + "# g2p_num2Int: successful conversion of 2 into a Python Int value\n" + "# g2p_string2String: successful conversion of \"two\" into a Python String value\n" + "# g2p_num2Int: successful conversion of 3 into a Python Int value\n" + "# g2p_string2String: successful conversion of \"three\" into a Python String value\n" + "# p2g_String2string: successful conversion of \"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" into SCM\n"))) +(python-eval "def func(a):\n print a\n" #f) +(define opaqueobj (python-eval "func" #t)) +(define run-test-opaque + (lambda () + (with-output-to-string + (thunk-invoke-python-repr + (list (list opaqueobj '((1 . "one")(2 . "two")(3 . "three")))) + (list g2p_list2Tuple (list g2p_list2List g2p_opaque2Object (cons g2p_alist2Dict (list (cons g2p_num2Int g2p_string2String))))))))) +(is-ok 6 "g2p* verbosity coverage - opaque2Object,g2p_alist2Dict" + expres6 + (substitute-hex-addresses-for-gggggggg + (run-test-opaque))) + +; End of 17_verbose.t diff --git a/t/18_bignum.t b/t/18_bignum.t new file mode 100644 index 0000000..d30adb6 --- /dev/null +++ b/t/18_bignum.t @@ -0,0 +1,55 @@ +#!/usr/bin/guile -s +!# +; Miscellaneous tests of PyGuile data handling +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) + +(python-eval "def func(val):\n global longint\n longint=val\n" #f) + +(plan 3) + +(python-apply "func" '("abc") '()) +; The following won't work because Guile's and Python's stdout are +; separately controlled. +;;(is-ok 1 "should yield 'abc'" +;; "abc\n" +;; (with-output-to-string +;; (lambda () +;; (python-eval "print longint\n" #f)))) +(is-ok 1 "should yield 'abc'" + "abc" + (python-eval "longint" #t)) + +(python-apply "func" '(3773) '()) +(is-ok 2 "should yield 3773" + 3773 + (python-eval "longint" #t)) + +(python-apply "func" '(36893488147419103232) '()) +(is-ok 3 "should yield the bignum" + 36893488147419103232 + (python-eval "longint" #t)) + +; End of 18_bignum.t diff --git a/t/19_verbose_always.t b/t/19_verbose_always.t new file mode 100644 index 0000000..f65f3df --- /dev/null +++ b/t/19_verbose_always.t @@ -0,0 +1,733 @@ +#!/usr/bin/guile -s +!# +; Tests of PyGuile verbosity messages - for full source code coverage +; +; Same tests as 17_verbose.t, except that the verbosity level was set +; to 3 (PYGUILE_VERBOSE_G2P2G_SUCCESSFUL+PYGUILE_VERBOSE_G2P2G_ALWAYS). +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) +(use-modules (srfi srfi-13)) ; string-concatenate +(use-modules (ice-9 regex)) ; regexp-substitute + +(define thunk-invoke-python-repr + (lambda (arg . template) + (lambda () + (if (null? template) + (python-apply '("__builtin__" "repr") arg '()) + (python-apply '("__builtin__" "repr") arg '() (car template)))))) + +(plan 13) + +(pyguile-verbosity-set! 3) + +(define successful-verbose-3-report + (string-concatenate + '("# guile2python: entry: seeking to convert sobj=(\"__builtin__\" \"repr\"); unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: (\"__builtin__\" \"repr\") is not null\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=\"__builtin__\" CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=\"__builtin__\" stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=\"__builtin__\"; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: \"__builtin__\" is not null\n" + "# g2p_list2List: unsuccessful conversion: \"__builtin__\" is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: \"__builtin__\" is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: \"__builtin__\" is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: \"__builtin__\" is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: \"__builtin__\" is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: \"__builtin__\" is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: \"__builtin__\" is not a complex value\n" + "# g2p_string2String: successful conversion of \"__builtin__\" into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=\"repr\" CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=\"repr\" stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=\"repr\"; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: \"repr\" is not null\n" + "# g2p_list2List: unsuccessful conversion: \"repr\" is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: \"repr\" is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: \"repr\" is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: \"repr\" is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: \"repr\" is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: \"repr\" is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: \"repr\" is not a complex value\n" + "# g2p_string2String: successful conversion of \"repr\" into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2List: successful conversion of list () by template\n"))) + +(define expres1 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=(#t) stemplate=('g2p_list2Tuple 'guile2python)\n" + "# g2p_list2Tuple - processing item CAR(sobj)=#t CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=#t stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=#t; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: #t is not null\n" + "# g2p_list2List: unsuccessful conversion: #t is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: #t is not a pair\n" + "# g2p_bool2Bool: successful conversion of #t into a Python Bool value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# p2g_apply: pobj=\"'True'\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"'True'\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"'True'\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"'True'\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"'True'\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"'True'\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"'True'\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"'True'\" using stemplate=#<unspecified>\n" + "# p2g_String2string: successful conversion of \"'True'\" into SCM\n"))) +(is-ok 1 "Verbosity/3 with a single value" + expres1 + (with-output-to-string + (thunk-invoke-python-repr '(#t)))) + +(define expres2 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=((() #t #f 1 -5 #\\P (quote symba) 1.0-1.0i 3.125)) stemplate=('g2p_list2Tuple 'guile2python)\n" + "# g2p_list2Tuple - processing item CAR(sobj)=(() #t #f 1 -5 #\\P (quote symba) 1.0-1.0i 3.125) CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=(() #t #f 1 -5 #\\P (quote symba) 1.0-1.0i 3.125) stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=(() #t #f 1 -5 #\\P (quote symba) 1.0-1.0i 3.125); unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: (() #t #f 1 -5 #\\P (quote symba) 1.0-1.0i 3.125) is not null\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=() CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=() stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=(); unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: successful conversion of () into Python None\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=#t CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=#t stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=#t; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: #t is not null\n" + "# g2p_list2List: unsuccessful conversion: #t is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: #t is not a pair\n" + "# g2p_bool2Bool: successful conversion of #t into a Python Bool value\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=#f CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=#f stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=#f; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: #f is not null\n" + "# g2p_list2List: unsuccessful conversion: #f is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: #f is not a pair\n" + "# g2p_bool2Bool: successful conversion of #f into a Python Bool value\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=1 CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=1 stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=1; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: 1 is not null\n" + "# g2p_list2List: unsuccessful conversion: 1 is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: 1 is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: 1 is not a bool value\n" + "# g2p_num2Int: successful conversion of 1 into a Python Int value\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=-5 CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=-5 stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=-5; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: -5 is not null\n" + "# g2p_list2List: unsuccessful conversion: -5 is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: -5 is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: -5 is not a bool value\n" + "# g2p_num2Int: successful conversion of -5 into a Python Int value\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=#\\P CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=#\\P stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=#\\P; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: #\\P is not null\n" + "# g2p_list2List: unsuccessful conversion: #\\P is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: #\\P is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: #\\P is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: #\\P is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: #\\P is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: #\\P is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: #\\P is not a complex value\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=(quote symba) CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=(quote symba) stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=(quote symba); unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: (quote symba) is not null\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=quote CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=quote stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=quote; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: quote is not null\n" + "# g2p_list2List: unsuccessful conversion: quote is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: quote is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: quote is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: quote is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: quote is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: quote is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: quote is not a complex value\n" + "# g2p_string2String: unsuccessful conversion: quote is not a string value\n" + "# g2p_symbol2String: successful conversion of quote into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=symba CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=symba stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=symba; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: symba is not null\n" + "# g2p_list2List: unsuccessful conversion: symba is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: symba is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: symba is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: symba is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: symba is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: symba is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: symba is not a complex value\n" + "# g2p_string2String: unsuccessful conversion: symba is not a string value\n" + "# g2p_symbol2String: successful conversion of symba into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2List: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=1.0-1.0i CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=1.0-1.0i stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=1.0-1.0i; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: 1.0-1.0i is not null\n" + "# g2p_list2List: unsuccessful conversion: 1.0-1.0i is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: 1.0-1.0i is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: 1.0-1.0i is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: 1.0-1.0i is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: 1.0-1.0i is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: 1.0-1.0i is not a real value\n" + "# g2p_complex2Complex: successful conversion of 1.0-1.0i into a Python Complex value\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=3.125 CAR(stemp)='guile2python\n" + "# Entered g2p_apply: sobj=3.125 stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=3.125; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: 3.125 is not null\n" + "# g2p_list2List: unsuccessful conversion: 3.125 is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: 3.125 is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: 3.125 is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: 3.125 is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: 3.125 is not a bignum value\n" + "# g2p_real2Float: successful conversion of 3.125 into a Python Float value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2List: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# p2g_apply: pobj=\"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_String2string: successful conversion of \"\\\"[None, True, False, 1, -5, 'P', ['quote', 'symba'], (1-1j), 3.125]\\\"\" into SCM\n"))) +(is-ok 2 "Verbosity/3 when successful" + expres2 + (with-output-to-string + (thunk-invoke-python-repr '((() #t #f 1 -5 #\P 'symba 1-1i 3.125))))) + +(define expres3 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=((() () ())) stemplate=('g2p_list2Tuple ('g2p_list2Tuple 'g2p_null2Tuple0 'g2p_null2List0 'g2p_null2DictEmpty))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=(() () ()) CAR(stemp)=('g2p_list2Tuple 'g2p_null2Tuple0 'g2p_null2List0 'g2p_null2DictEmpty)\n" + "# Entered g2p_apply: sobj=(() () ()) stemplate=('g2p_list2Tuple 'g2p_null2Tuple0 'g2p_null2List0 'g2p_null2DictEmpty)\n" + "# g2p_list2Tuple - processing item CAR(sobj)=() CAR(stemp)='g2p_null2Tuple0\n" + "# Entered g2p_apply: sobj=() stemplate='g2p_null2Tuple0\n" + "# g2p_null2Tuple0: successful conversion of () into Python ()\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple - processing item CAR(sobj)=() CAR(stemp)='g2p_null2List0\n" + "# Entered g2p_apply: sobj=() stemplate='g2p_null2List0\n" + "# g2p_null2List0: successful conversion of () into Python []\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple - processing item CAR(sobj)=() CAR(stemp)='g2p_null2DictEmpty\n" + "# Entered g2p_apply: sobj=() stemplate='g2p_null2DictEmpty\n" + "# g2p_null2DictEmpty: successful conversion of () into Python {}\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# p2g_apply: pobj=\"'((), [], {})'\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"'((), [], {})'\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"'((), [], {})'\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"'((), [], {})'\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"'((), [], {})'\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"'((), [], {})'\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"'((), [], {})'\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"'((), [], {})'\" using stemplate=#<unspecified>\n" + "# p2g_String2string: successful conversion of \"'((), [], {})'\" into SCM\n"))) +(is-ok 3 "g2p* verbosity/3 coverage" + expres3 + (with-output-to-string + (thunk-invoke-python-repr + '((() () ())) + (list g2p_list2Tuple (list g2p_list2Tuple g2p_null2Tuple0 g2p_null2List0 g2p_null2DictEmpty))))) + +(define expres4 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=((1000000 1000000000000)) stemplate=('g2p_list2Tuple ('g2p_list2List ('g2p_leaf 'g2p_bignum2Long 'g2p_num2Int)))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=(1000000 1000000000000) CAR(stemp)=('g2p_list2List ('g2p_leaf 'g2p_bignum2Long 'g2p_num2Int))\n" + "# Entered g2p_apply: sobj=(1000000 1000000000000) stemplate=('g2p_list2List ('g2p_leaf 'g2p_bignum2Long 'g2p_num2Int))\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=1000000 CAR(stemp)=('g2p_leaf 'g2p_bignum2Long 'g2p_num2Int)\n" + "# Entered g2p_apply: sobj=1000000 stemplate=('g2p_leaf 'g2p_bignum2Long 'g2p_num2Int)\n" + "# Entered g2p_leaf: sobj=1000000 stemplate=('g2p_bignum2Long 'g2p_num2Int)\n" + "# g2p_leaf: trying another stemplate 'g2p_bignum2Long on sobj\n" + "# Entered g2p_apply: sobj=1000000 stemplate='g2p_bignum2Long\n" + "# g2p_bignum2Long: unsuccessful conversion: 1000000 is not a bignum value\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: trying another stemplate 'g2p_num2Int on sobj\n" + "# Entered g2p_apply: sobj=1000000 stemplate='g2p_num2Int\n" + "# g2p_num2Int: successful conversion of 1000000 into a Python Int value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_leaf: successful conversion\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=1000000000000 CAR(stemp)=('g2p_leaf 'g2p_bignum2Long 'g2p_num2Int)\n" + "# Entered g2p_apply: sobj=1000000000000 stemplate=('g2p_leaf 'g2p_bignum2Long 'g2p_num2Int)\n" + "# Entered g2p_leaf: sobj=1000000000000 stemplate=('g2p_bignum2Long 'g2p_num2Int)\n" + "# g2p_leaf: trying another stemplate 'g2p_bignum2Long on sobj\n" + "# Entered g2p_apply: sobj=1000000000000 stemplate='g2p_bignum2Long\n" + "# g2p_bignum2Long: successful conversion of 1000000000000 into a Python Long value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_leaf: successful conversion\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2List: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# p2g_apply: pobj=\"'[1000000, 1000000000000L]'\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"'[1000000, 1000000000000L]'\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"'[1000000, 1000000000000L]'\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"'[1000000, 1000000000000L]'\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"'[1000000, 1000000000000L]'\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"'[1000000, 1000000000000L]'\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"'[1000000, 1000000000000L]'\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"'[1000000, 1000000000000L]'\" using stemplate=#<unspecified>\n" + "# p2g_String2string: successful conversion of \"'[1000000, 1000000000000L]'\" into SCM\n"))) +(is-ok 4 "g2p* verbosity/3 coverage - bignums,list2Tuple,list2List" + expres4 + (with-output-to-string + (thunk-invoke-python-repr + '((1000000 1000000000000)) + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_bignum2Long g2p_num2Int)))))) + +(define expres5 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=(((p1at . \"p1bt\") (#:p2al . p2bl))) stemplate=('g2p_list2Tuple ('g2p_list2Tuple ('g2p_pair2Tuple 'guile2python . 'guile2python) ('g2p_pair2List 'guile2python . 'guile2python)))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=((p1at . \"p1bt\") (#:p2al . p2bl)) CAR(stemp)=('g2p_list2Tuple ('g2p_pair2Tuple 'guile2python . 'guile2python) ('g2p_pair2List 'guile2python . 'guile2python))\n" + "# Entered g2p_apply: sobj=((p1at . \"p1bt\") (#:p2al . p2bl)) stemplate=('g2p_list2Tuple ('g2p_pair2Tuple 'guile2python . 'guile2python) ('g2p_pair2List 'guile2python . 'guile2python))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=(p1at . \"p1bt\") CAR(stemp)=('g2p_pair2Tuple 'guile2python . 'guile2python)\n" + "# Entered g2p_apply: sobj=(p1at . \"p1bt\") stemplate=('g2p_pair2Tuple 'guile2python . 'guile2python)\n" + "# Entered g2p_apply: sobj=p1at stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=p1at; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: p1at is not null\n" + "# g2p_list2List: unsuccessful conversion: p1at is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: p1at is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: p1at is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: p1at is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: p1at is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: p1at is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: p1at is not a complex value\n" + "# g2p_string2String: unsuccessful conversion: p1at is not a string value\n" + "# g2p_symbol2String: successful conversion of p1at into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# Entered g2p_apply: sobj=\"p1bt\" stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=\"p1bt\"; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: \"p1bt\" is not null\n" + "# g2p_list2List: unsuccessful conversion: \"p1bt\" is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: \"p1bt\" is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: \"p1bt\" is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: \"p1bt\" is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: \"p1bt\" is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: \"p1bt\" is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: \"p1bt\" is not a complex value\n" + "# g2p_string2String: successful conversion of \"p1bt\" into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_pair2Tuple: successful conversion of (p1at . \"p1bt\") into a Python 2-Tuple\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple - processing item CAR(sobj)=(#:p2al . p2bl) CAR(stemp)=('g2p_pair2List 'guile2python . 'guile2python)\n" + "# Entered g2p_apply: sobj=(#:p2al . p2bl) stemplate=('g2p_pair2List 'guile2python . 'guile2python)\n" + "# Entered g2p_apply: sobj=#:p2al stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=#:p2al; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: #:p2al is not null\n" + "# g2p_list2List: unsuccessful conversion: #:p2al is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: #:p2al is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: #:p2al is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: #:p2al is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: #:p2al is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: #:p2al is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: #:p2al is not a complex value\n" + "# g2p_string2String: unsuccessful conversion: #:p2al is not a string value\n" + "# g2p_symbol2String: unsuccessful conversion: #:p2al is not a symbol value\n" + "# g2p_keyword2String: successful conversion of #:p2al into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# Entered g2p_apply: sobj=p2bl stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=p2bl; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: p2bl is not null\n" + "# g2p_list2List: unsuccessful conversion: p2bl is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: p2bl is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: p2bl is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: p2bl is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: p2bl is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: p2bl is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: p2bl is not a complex value\n" + "# g2p_string2String: unsuccessful conversion: p2bl is not a string value\n" + "# g2p_symbol2String: successful conversion of p2bl into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_pair2List: successful conversion of (#:p2al . p2bl) into a Python 2-List\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# p2g_apply: pobj=\"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" using stemplate=#<unspecified>\n" + "# p2g_String2string: successful conversion of \"\\\"(('p1at', 'p1bt'), ['p2al', 'p2bl'])\\\"\" into SCM\n"))) +(is-ok 5 "g2p* verbosity/3 coverage - pair2Tuple,pair2List" + expres5 + (with-output-to-string + (thunk-invoke-python-repr + '(((p1at . "p1bt") (#:p2al . p2bl))) + (list g2p_list2Tuple (list g2p_list2Tuple (cons g2p_pair2Tuple (cons guile2python guile2python)) (cons g2p_pair2List (cons guile2python guile2python))))))) + +(define substitute-hex-addresses-for-gggggggg + (lambda (strarg) + (regexp-substitute/global #f + "0x[0-9a-f]{8}" + strarg + 'pre "0xgggggggg" 'post))) + +(define expres6 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=(((python-eval <function func at 0xgggggggg> #t) ((1 . \"one\") (2 . \"two\") (3 . \"three\")))) stemplate=('g2p_list2Tuple ('g2p_list2List 'g2p_opaque2Object ('g2p_alist2Dict ('g2p_num2Int . 'g2p_string2String))))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=((python-eval <function func at 0xgggggggg> #t) ((1 . \"one\") (2 . \"two\") (3 . \"three\"))) CAR(stemp)=('g2p_list2List 'g2p_opaque2Object ('g2p_alist2Dict ('g2p_num2Int . 'g2p_string2String)))\n" + "# Entered g2p_apply: sobj=((python-eval <function func at 0xgggggggg> #t) ((1 . \"one\") (2 . \"two\") (3 . \"three\"))) stemplate=('g2p_list2List 'g2p_opaque2Object ('g2p_alist2Dict ('g2p_num2Int . 'g2p_string2String)))\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=(python-eval <function func at 0xgggggggg> #t) CAR(stemp)='g2p_opaque2Object\n" + "# Entered g2p_apply: sobj=(python-eval <function func at 0xgggggggg> #t) stemplate='g2p_opaque2Object\n" + "# g2p_opaque2Object: the Python object inside opaque pysmob (python-eval <function func at 0xgggggggg> #t) is unwrapped\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=((1 . \"one\") (2 . \"two\") (3 . \"three\")) CAR(stemp)=('g2p_alist2Dict ('g2p_num2Int . 'g2p_string2String))\n" + "# Entered g2p_apply: sobj=((1 . \"one\") (2 . \"two\") (3 . \"three\")) stemplate=('g2p_alist2Dict ('g2p_num2Int . 'g2p_string2String))\n" + "# g2p_alist2Dict sobj=((1 . \"one\") (2 . \"two\") (3 . \"three\")) stemplate=(('g2p_num2Int . 'g2p_string2String))\n" + "# Entered g2p_apply: sobj=1 stemplate='g2p_num2Int\n" + "# g2p_num2Int: successful conversion of 1 into a Python Int value\n" + "# Leaving g2p_apply: with non-null result\n" + "# Entered g2p_apply: sobj=\"one\" stemplate='g2p_string2String\n" + "# g2p_string2String: successful conversion of \"one\" into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# Entered g2p_apply: sobj=2 stemplate='g2p_num2Int\n" + "# g2p_num2Int: successful conversion of 2 into a Python Int value\n" + "# Leaving g2p_apply: with non-null result\n" + "# Entered g2p_apply: sobj=\"two\" stemplate='g2p_string2String\n" + "# g2p_string2String: successful conversion of \"two\" into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# Entered g2p_apply: sobj=3 stemplate='g2p_num2Int\n" + "# g2p_num2Int: successful conversion of 3 into a Python Int value\n" + "# Leaving g2p_apply: with non-null result\n" + "# Entered g2p_apply: sobj=\"three\" stemplate='g2p_string2String\n" + "# g2p_string2String: successful conversion of \"three\" into a Python String value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_alist2Dict: successful conversion\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2List: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# p2g_apply: pobj=\"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" using stemplate=#<unspecified>\n" + "# p2g_String2string: successful conversion of \"\\\"[<function func at 0xgggggggg>, {1: 'one', 2: 'two', 3: 'three'}]\\\"\" into SCM\n"))) +(python-eval "def func(a):\n print a\n" #f) +(define opaqueobj (python-eval "func" #t)) +(define run-test-opaque + (lambda () + (with-output-to-string + (thunk-invoke-python-repr + (list (list opaqueobj '((1 . "one")(2 . "two")(3 . "three")))) + (list g2p_list2Tuple (list g2p_list2List g2p_opaque2Object (cons g2p_alist2Dict (list (cons g2p_num2Int g2p_string2String))))))))) +(is-ok 6 "g2p* verbosity/3 coverage - opaque2Object,g2p_alist2Dict" + expres6 + (substitute-hex-addresses-for-gggggggg + (run-test-opaque))) + +; Additional tests + +(define expres7 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=((#t 5)) stemplate=('g2p_list2Tuple ('g2p_list2List ('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=(#t 5) CAR(stemp)=('g2p_list2List ('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool))\n" + "# Entered g2p_apply: sobj=(#t 5) stemplate=('g2p_list2List ('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool))\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=#t CAR(stemp)=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_apply: sobj=#t stemplate=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_leaf: sobj=#t stemplate=('g2p_num2Int 'g2p_bool2Bool)\n" + "# g2p_leaf: trying another stemplate 'g2p_num2Int on sobj\n" + "# Entered g2p_apply: sobj=#t stemplate='g2p_num2Int\n" + "# g2p_num2Int: unsuccessful conversion: #t is not a num value\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: trying another stemplate 'g2p_bool2Bool on sobj\n" + "# Entered g2p_apply: sobj=#t stemplate='g2p_bool2Bool\n" + "# g2p_bool2Bool: successful conversion of #t into a Python Bool value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_leaf: successful conversion\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=5 CAR(stemp)=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_apply: sobj=5 stemplate=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_leaf: sobj=5 stemplate=('g2p_num2Int 'g2p_bool2Bool)\n" + "# g2p_leaf: trying another stemplate 'g2p_num2Int on sobj\n" + "# Entered g2p_apply: sobj=5 stemplate='g2p_num2Int\n" + "# g2p_num2Int: successful conversion of 5 into a Python Int value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_leaf: successful conversion\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2List: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# p2g_apply: pobj=\"'[True, 5]'\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"'[True, 5]'\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"'[True, 5]'\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"'[True, 5]'\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"'[True, 5]'\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"'[True, 5]'\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"'[True, 5]'\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"'[True, 5]'\" using stemplate=#<unspecified>\n" + "# p2g_String2string: successful conversion of \"'[True, 5]'\" into SCM\n"))) +(is-ok 7 "g2p_leaf verbosity/3 coverage - all values recognized by leaf" + expres7 + (with-output-to-string + (thunk-invoke-python-repr + '((#t 5 )) + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_num2Int g2p_bool2Bool)))))) + +; Run thunk-invoke-python-repr under catch harness. +(define catch-thunk-invoke-python-repr + (lambda (arg . template) + (lambda () + (catch + #t + (lambda () + (if (null? template) + (python-apply '("__builtin__" "repr") arg '()) + (python-apply '("__builtin__" "repr") arg '() (car template)))) + (lambda (key . args2) (object->string (list key args2))))))) + +(define expres8 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=((3.1 5)) stemplate=('g2p_list2Tuple ('g2p_list2List ('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=(3.1 5) CAR(stemp)=('g2p_list2List ('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool))\n" + "# Entered g2p_apply: sobj=(3.1 5) stemplate=('g2p_list2List ('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool))\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=3.1 CAR(stemp)=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_apply: sobj=3.1 stemplate=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_leaf: sobj=3.1 stemplate=('g2p_num2Int 'g2p_bool2Bool)\n" + "# g2p_leaf: trying another stemplate 'g2p_num2Int on sobj\n" + "# Entered g2p_apply: sobj=3.1 stemplate='g2p_num2Int\n" + "# g2p_num2Int: unsuccessful conversion: 3.1 is not a num value\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: trying another stemplate 'g2p_bool2Bool on sobj\n" + "# Entered g2p_apply: sobj=3.1 stemplate='g2p_bool2Bool\n" + "# g2p_bool2Bool: unsuccessful conversion: 3.1 is not a bool value\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: unsuccessful conversion, no stemplate fit the sobj\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_list2List: unsuccessful conversion of element 0: 3.1 does not match personalized template\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_list2Tuple: unsuccessful conversion of element 0: (3.1 5) does not match personalized template\n" + "# Leaving g2p_apply: with null result\n"))) +(is-ok 8 "g2p_leaf verbosity/3 coverage - first value not recognized by leaf" + expres8 + (with-output-to-string + (catch-thunk-invoke-python-repr + '((3.1 5)) + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_num2Int g2p_bool2Bool)))))) + +(define expres9 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=((#t \"string\")) stemplate=('g2p_list2Tuple ('g2p_list2List ('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=(#t \"string\") CAR(stemp)=('g2p_list2List ('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool))\n" + "# Entered g2p_apply: sobj=(#t \"string\") stemplate=('g2p_list2List ('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool))\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=#t CAR(stemp)=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_apply: sobj=#t stemplate=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_leaf: sobj=#t stemplate=('g2p_num2Int 'g2p_bool2Bool)\n" + "# g2p_leaf: trying another stemplate 'g2p_num2Int on sobj\n" + "# Entered g2p_apply: sobj=#t stemplate='g2p_num2Int\n" + "# g2p_num2Int: unsuccessful conversion: #t is not a num value\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: trying another stemplate 'g2p_bool2Bool on sobj\n" + "# Entered g2p_apply: sobj=#t stemplate='g2p_bool2Bool\n" + "# g2p_bool2Bool: successful conversion of #t into a Python Bool value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_leaf: successful conversion\n" + "# Leaving g2p_apply: with non-null result\n" + "# DEBUG: g2p_list2List - processing item CAR(sobj)=\"string\" CAR(stemp)=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_apply: sobj=\"string\" stemplate=('g2p_leaf 'g2p_num2Int 'g2p_bool2Bool)\n" + "# Entered g2p_leaf: sobj=\"string\" stemplate=('g2p_num2Int 'g2p_bool2Bool)\n" + "# g2p_leaf: trying another stemplate 'g2p_num2Int on sobj\n" + "# Entered g2p_apply: sobj=\"string\" stemplate='g2p_num2Int\n" + "# g2p_num2Int: unsuccessful conversion: \"string\" is not a num value\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: trying another stemplate 'g2p_bool2Bool on sobj\n" + "# Entered g2p_apply: sobj=\"string\" stemplate='g2p_bool2Bool\n" + "# g2p_bool2Bool: unsuccessful conversion: \"string\" is not a bool value\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: unsuccessful conversion, no stemplate fit the sobj\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_list2List: unsuccessful conversion of element 1: \"string\" does not match personalized template\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_list2Tuple: unsuccessful conversion of element 0: (#t \"string\") does not match personalized template\n" + "# Leaving g2p_apply: with null result\n"))) +(is-ok 9 "g2p_leaf verbosity/3 coverage - last value not recognized by leaf" + expres9 + (with-output-to-string + (catch-thunk-invoke-python-repr + '((#t "string")) + (list g2p_list2Tuple (list g2p_list2List (list g2p_leaf g2p_num2Int g2p_bool2Bool)))))) + +(define saved-verbosity (pyguile-verbosity-set! 0)) + +(define catch-test-apply + (lambda (func args kws . targ) + (catch #t + (lambda () (python-apply func args kws (car targ))) + (lambda (key . args2) (object->string (list key args2)))))) + +(is-ok 10 "g2p_null2* validate test - non-null values" + "(misc-error (\"python-apply\" \"positional arguments conversion failure (~S)\" ((\"string\")) #f))" + (catch-test-apply '("__builtin__" "repr") '("string") '() + (list g2p_list2Tuple (list g2p_leaf g2p_null2Tuple0 g2p_null2List0 g2p_null2DictEmpty)))) + +(pyguile-verbosity-set! saved-verbosity) + +(define expres11 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=(\"string\") stemplate=('g2p_list2Tuple ('g2p_leaf 'g2p_null2Tuple0 'g2p_null2List0 'g2p_null2DictEmpty))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=\"string\" CAR(stemp)=('g2p_leaf 'g2p_null2Tuple0 'g2p_null2List0 'g2p_null2DictEmpty)\n" + "# Entered g2p_apply: sobj=\"string\" stemplate=('g2p_leaf 'g2p_null2Tuple0 'g2p_null2List0 'g2p_null2DictEmpty)\n" + "# Entered g2p_leaf: sobj=\"string\" stemplate=('g2p_null2Tuple0 'g2p_null2List0 'g2p_null2DictEmpty)\n" + "# g2p_leaf: trying another stemplate 'g2p_null2Tuple0 on sobj\n" + "# Entered g2p_apply: sobj=\"string\" stemplate='g2p_null2Tuple0\n" + "# g2p_null2Tuple0: unsuccessful conversion: \"string\" is not null\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: trying another stemplate 'g2p_null2List0 on sobj\n" + "# Entered g2p_apply: sobj=\"string\" stemplate='g2p_null2List0\n" + "# g2p_null2List0: unsuccessful conversion: \"string\" is not null\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: trying another stemplate 'g2p_null2DictEmpty on sobj\n" + "# Entered g2p_apply: sobj=\"string\" stemplate='g2p_null2DictEmpty\n" + "# g2p_null2DictEmpty: unsuccessful conversion: \"string\" is not null\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_leaf: unsuccessful conversion, no stemplate fit the sobj\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_list2Tuple: unsuccessful conversion of element 0: \"string\" does not match personalized template\n" + "# Leaving g2p_apply: with null result\n"))) +(is-ok 11 "g2p_null2* verbosity/3 coverage - non-null values" + expres11 + (with-output-to-string + (catch-thunk-invoke-python-repr + '("string") + (list g2p_list2Tuple (list g2p_leaf g2p_null2Tuple0 g2p_null2List0 g2p_null2DictEmpty))))) + + +(define expres12 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=(()) stemplate=('g2p_list2Tuple ('g2p_leaf . 'g2p_null2Tuple0))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=() CAR(stemp)=('g2p_leaf . 'g2p_null2Tuple0)\n" + "# Entered g2p_apply: sobj=() stemplate=('g2p_leaf . 'g2p_null2Tuple0)\n" + "# Entered g2p_leaf: sobj=() stemplate='g2p_null2Tuple0\n" + "# g2p_null2Tuple0: successful conversion of () into Python ()\n" + "# Leaving g2p_leaf, after G2P_SMOBP conversion, with non-null result\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# p2g_apply: pobj=\"'()'\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"'()'\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"'()'\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"'()'\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"'()'\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"'()'\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"'()'\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"'()'\" using stemplate=#<unspecified>\n" + "# p2g_String2string: successful conversion of \"'()'\" into SCM\n"))) +(is-ok 12 "g2p_leaf verbosity/3 coverage - G2P_SMOB, successful" + expres12 + (with-output-to-string + (catch-thunk-invoke-python-repr + '(()) + (list g2p_list2Tuple (cons g2p_leaf g2p_null2Tuple0))))) + +(define expres13 + (string-concatenate + (list + successful-verbose-3-report + "# Entered g2p_apply: sobj=(#t) stemplate=('g2p_list2Tuple ('g2p_leaf . 'g2p_null2Tuple0))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=#t CAR(stemp)=('g2p_leaf . 'g2p_null2Tuple0)\n" + "# Entered g2p_apply: sobj=#t stemplate=('g2p_leaf . 'g2p_null2Tuple0)\n" + "# Entered g2p_leaf: sobj=#t stemplate='g2p_null2Tuple0\n" + "# g2p_null2Tuple0: unsuccessful conversion: #t is not null\n" + "# Leaving g2p_leaf, after G2P_SMOBP conversion, with null result\n" + "# Leaving g2p_apply: with null result\n" + "# g2p_list2Tuple: unsuccessful conversion of element 0: #t does not match personalized template\n" + "# Leaving g2p_apply: with null result\n"))) +(is-ok 13 "g2p_leaf verbosity/3 coverage - G2P_SMOB, failed" + expres13 + (with-output-to-string + (catch-thunk-invoke-python-repr + '(#t) + (list g2p_list2Tuple (cons g2p_leaf g2p_null2Tuple0))))) + +; End of 19_verbose_always.t diff --git a/t/20_pyscm.t b/t/20_pyscm.t new file mode 100644 index 0000000..ef19cea --- /dev/null +++ b/t/20_pyscm.t @@ -0,0 +1,247 @@ +#!/usr/bin/guile -s +!# +; PySCM related tests. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (guiletap)) +(use-modules (pyguile)) +(use-modules (srfi srfi-13)) ; string-concatenate +(load "scripts/test_auxiliary_functions.scm") + +(plan 7) + +(define guilefunc0 + (lambda (dummy1 dummy2) 2.73)) +(define pyfunc0code + (string-concatenate + '("def pyfunc0(func):\n" + " return(4.1 + func())\n"))) +(python-eval pyfunc0code) +(define pyfunc0-smob (python-eval "pyfunc0" #t)) + + +(is-ok 1 "show argumentless guile procedure and Python function" + "#<procedure guilefunc0 (dummy1 dummy2)>\n(python-eval <function pyfunc0 at 0xgggggggg> #t)\n" + (substitute-hex-addresses-for-gggggggg + (cdr + (capture-result-output-catch + (lambda () + (display guilefunc0) + (newline) + (display pyfunc0-smob) + (newline)))))) + +(is-ok 2 "pysmob func, using argumentless guile procedure" + 6.83 + (python-apply pyfunc0-smob (list guilefunc0) '() + (list g2p_list2Tuple (cons g2p_procedure2PySCMObject pyscm-default-template)))) + +(define expres3_cdr + (string-concatenate + (list + "# guile2python: entry: seeking to convert sobj=(python-eval <function pyfunc0 at 0xgggggggg> #t); unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not null\n" + "# g2p_list2List: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a complex value\n" + "# g2p_string2String: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a string value\n" + "# g2p_symbol2String: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a symbol value\n" + "# g2p_keyword2String: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a keyword value\n" + "# g2p_procedure2PySCMObject: unsuccessful conversion: (python-eval <function pyfunc0 at 0xgggggggg> #t) is not a procedure\n" + "# g2p_opaque2Object: the Python object inside opaque pysmob (python-eval <function pyfunc0 at 0xgggggggg> #t) is unwrapped\n" + "# python_apply: decoded pfunc \"<function pyfunc0 at 0xgggggggg>\"\n" + "# python_apply: decoded function actually to be invoked: \"<function pyfunc0 at 0xgggggggg>\"\n" + "# Entered g2p_apply: sobj=(#<procedure guilefunc0 (dummy1 dummy2)>) stemplate=('g2p_list2Tuple ('g2p_procedure2PySCMObject . #('python2guile 'python2guile 'guile2python #<procedure apply (fun . args)> #f)))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=#<procedure guilefunc0 (dummy1 dummy2)> CAR(stemp)=('g2p_procedure2PySCMObject . #('python2guile 'python2guile 'guile2python #<procedure apply (fun . args)> #f))\n" + "# Entered g2p_apply: sobj=#<procedure guilefunc0 (dummy1 dummy2)> stemplate=('g2p_procedure2PySCMObject . #('python2guile 'python2guile 'guile2python #<procedure apply (fun . args)> #f))\n" + "# wrap_scm: was called to wrap #<procedure guilefunc0 (dummy1 dummy2)>\n" + "# g2p_procedure2PySCMObject: successful conversion: #<procedure guilefunc0 (dummy1 dummy2)> has been wrapped\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# python_apply: decoded positional arguments \"(<pyscm.PySCM object at 0xgggggggg>,)\"\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# python_apply: decoded keyword arguments \"{}\"\n" + "# pyscm_PySCM_call: calling #<procedure guilefunc0 (dummy1 dummy2)> with args=\"()\" and keywords=\"(null PyObject)\"; stemplate=#('python2guile 'python2guile 'guile2python #<procedure apply (fun . args)> #f)\n" + "# p2g_apply: pobj=\"()\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"()\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"()\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"()\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"()\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"()\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"()\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"()\" using stemplate=#<unspecified>\n" + "# p2g_String2string: failed to convert pobj=\"()\" using stemplate=#<unspecified>\n" + "# Entered g2p_apply: sobj=2.73 stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=2.73; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: 2.73 is not null\n" + "# g2p_list2List: unsuccessful conversion: 2.73 is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: 2.73 is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: 2.73 is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: 2.73 is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: 2.73 is not a bignum value\n" + "# g2p_real2Float: successful conversion of 2.73 into a Python Float value\n" + "# Leaving g2p_apply: with non-null result\n" + "# p2g_apply: pobj=\"6.8300000000000001\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"6.8300000000000001\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"6.8300000000000001\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"6.8300000000000001\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"6.8300000000000001\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"6.8300000000000001\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: successful conversion of \"6.8300000000000001\" into SCM\n" + "# python_apply: decoded results:\n" + "# Python: \"6.8300000000000001\"\n" + "# Scheme: 6.83\n"))) +(define saved-verbosity (pyguile-verbosity-set! (+ 3 64 128 256))) +(define gggggggg-transform-cdr ; perform substitute-hex-addresses-for-gggggggg only on cdr of the argument + (lambda (arg) + (cons (car arg) + (substitute-hex-addresses-for-gggggggg (cdr arg))))) +(is-ok 3 "pysmob func, using argumentless guile procedure" + (cons 6.83 expres3_cdr) + (gggggggg-transform-cdr + (capture-result-output-catch + python-apply pyfunc0-smob (list guilefunc0) '() + (list g2p_list2Tuple (cons g2p_procedure2PySCMObject pyscm-default-template))))) +(pyguile-verbosity-set! saved-verbosity) + +(define guilefunc + (lambda (arg dummy) (+ 3.5 (car arg)))) +(define pyfunccode + (string-concatenate + '("def pyfunc(argu,func):\n" + " return(4.2 + func(argu))\n"))) +(python-eval pyfunccode) +(define pyfunc-smob (python-eval "pyfunc" #t)) + +(is-ok 4 "show guile procedure with argument and Python function" + "#<procedure guilefunc (arg dummy)>\n(python-eval <function pyfunc at 0xgggggggg> #t)\n" + (substitute-hex-addresses-for-gggggggg + (cdr + (capture-result-output-catch + (lambda () + (display guilefunc) + (newline) + (display pyfunc-smob) + (newline)))))) + + +(is-ok 5 "pysmob func, using guile procedure with argument" + 67.7 + (python-apply pyfunc-smob (list 60.0 guilefunc) '() + (list g2p_list2Tuple g2p_real2Float (cons g2p_procedure2PySCMObject pyscm-default-template)))) + +(define expres6_cdr + (string-concatenate + (list + "# guile2python: entry: seeking to convert sobj=(python-eval <function pyfunc at 0xgggggggg> #t); unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not null\n" + "# g2p_list2List: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a bignum value\n" + "# g2p_real2Float: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a real value\n" + "# g2p_complex2Complex: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a complex value\n" + "# g2p_string2String: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a string value\n" + "# g2p_symbol2String: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a symbol value\n" + "# g2p_keyword2String: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a keyword value\n" + "# g2p_procedure2PySCMObject: unsuccessful conversion: (python-eval <function pyfunc at 0xgggggggg> #t) is not a procedure\n" + "# g2p_opaque2Object: the Python object inside opaque pysmob (python-eval <function pyfunc at 0xgggggggg> #t) is unwrapped\n" + "# python_apply: decoded pfunc \"<function pyfunc at 0xgggggggg>\"\n" + "# python_apply: decoded function actually to be invoked: \"<function pyfunc at 0xgggggggg>\"\n" + "# Entered g2p_apply: sobj=(60.0 #<procedure guilefunc (arg dummy)>) stemplate=('g2p_list2Tuple 'g2p_real2Float ('g2p_procedure2PySCMObject . #('python2guile 'python2guile 'guile2python #<procedure apply (fun . args)> #f)))\n" + "# g2p_list2Tuple - processing item CAR(sobj)=60.0 CAR(stemp)='g2p_real2Float\n" + "# Entered g2p_apply: sobj=60.0 stemplate='g2p_real2Float\n" + "# g2p_real2Float: successful conversion of 60.0 into a Python Float value\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple - processing item CAR(sobj)=#<procedure guilefunc (arg dummy)> CAR(stemp)=('g2p_procedure2PySCMObject . #('python2guile 'python2guile 'guile2python #<procedure apply (fun . args)> #f))\n" + "# Entered g2p_apply: sobj=#<procedure guilefunc (arg dummy)> stemplate=('g2p_procedure2PySCMObject . #('python2guile 'python2guile 'guile2python #<procedure apply (fun . args)> #f))\n" + "# wrap_scm: was called to wrap #<procedure guilefunc (arg dummy)>\n" + "# g2p_procedure2PySCMObject: successful conversion: #<procedure guilefunc (arg dummy)> has been wrapped\n" + "# Leaving g2p_apply: with non-null result\n" + "# g2p_list2Tuple: successful conversion of list () by template\n" + "# Leaving g2p_apply: with non-null result\n" + "# python_apply: decoded positional arguments \"(60.0, <pyscm.PySCM object at 0xgggggggg>)\"\n" + "# guileassoc2pythondict: entry: seeking to convert sobj=() using stemplate=#<undefined>\n" + "# guileassoc2pythondict: successful conversion of ()\n" + "# python_apply: decoded keyword arguments \"{}\"\n" + "# pyscm_PySCM_call: calling #<procedure guilefunc (arg dummy)> with args=\"(60.0,)\" and keywords=\"(null PyObject)\"; stemplate=#('python2guile 'python2guile 'guile2python #<procedure apply (fun . args)> #f)\n" + "# p2g_apply: pobj=\"(60.0,)\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"(60.0,)\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"(60.0,)\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"(60.0,)\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"(60.0,)\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"(60.0,)\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: failed to convert pobj=\"(60.0,)\" using stemplate=#<unspecified>\n" + "# p2g_Complex2complex: failed to convert pobj=\"(60.0,)\" using stemplate=#<unspecified>\n" + "# p2g_String2string: failed to convert pobj=\"(60.0,)\" using stemplate=#<unspecified>\n" + "# p2g_apply: pobj=\"60.0\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"60.0\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"60.0\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"60.0\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"60.0\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"60.0\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: successful conversion of \"60.0\" into SCM\n" + "# p2g_Tuple2list: 1. converted item pobj=0[\"60.0\"], stemplate='python2guile\n" + "# Entered g2p_apply: sobj=63.5 stemplate='guile2python\n" + "# guile2python: entry: seeking to convert sobj=63.5; unused stemplate=#<unspecified>\n" + "# g2p_null2PyNone: unsuccessful conversion: 63.5 is not null\n" + "# g2p_list2List: unsuccessful conversion: 63.5 is not a list\n" + "# g2p_pair2Tuple: unsuccessful conversion: 63.5 is not a pair\n" + "# g2p_bool2Bool: unsuccessful conversion: 63.5 is not a bool value\n" + "# g2p_num2Int: unsuccessful conversion: 63.5 is not a num value\n" + "# g2p_bignum2Long: unsuccessful conversion: 63.5 is not a bignum value\n" + "# g2p_real2Float: successful conversion of 63.5 into a Python Float value\n" + "# Leaving g2p_apply: with non-null result\n" + "# p2g_apply: pobj=\"67.700000000000003\" smob-stemplate='python2guile\n" + "# python2guile: trying to convert pobj=\"67.700000000000003\" using stemplate=#<unspecified>\n" + "# p2g_None2SCM_EOL: pobj=\"67.700000000000003\" stemplate=#<unspecified>\n" + "# p2g_Bool2SCM_BOOL: failed to convert pobj=\"67.700000000000003\" using stemplate=#<unspecified>\n" + "# p2g_Int2num: failed to convert pobj=\"67.700000000000003\" using stemplate=#<unspecified>\n" + "# p2g_Long2bignum: failed to convert pobj=\"67.700000000000003\" using stemplate=#<unspecified>\n" + "# p2g_Float2real: successful conversion of \"67.700000000000003\" into SCM\n" + "# python_apply: decoded results:\n" + "# Python: \"67.700000000000003\"\n" + "# Scheme: 67.7\n"))) +(set! saved-verbosity (pyguile-verbosity-set! (+ 3 64 128 256))) +(is-ok 6 "pysmob func, using guile procedure with argument" + (cons 67.7 expres6_cdr) + (gggggggg-transform-cdr + (capture-result-output-catch + python-apply pyfunc-smob (list 60.0 guilefunc) '() + (list g2p_list2Tuple g2p_real2Float (cons g2p_procedure2PySCMObject pyscm-default-template))))) +(pyguile-verbosity-set! saved-verbosity) + +(python-eval "def holdval(val):\n global heldval\n heldval=val\n") +(python-apply '("__main__" "holdval") (list guilefunc0) '()) +(like 7 "obtain python representation of a procedure value" + "^#<procedure guilefunc0 \\([-a-zA-Z0-9_]+ [-a-zA-Z0-9_]+\\)>$" + (object->string (python-eval "heldval" #t))) + +; End of 20_pyscm.t diff --git a/t/__init__.py b/t/__init__.py new file mode 100644 index 0000000..5dff9f3 --- /dev/null +++ b/t/__init__.py @@ -0,0 +1,2 @@ +# Dummy module - needed to keep happy import commands +# which import t/scripts/*.py modules diff --git a/t/scripts/RunGuileTests.pl b/t/scripts/RunGuileTests.pl new file mode 100755 index 0000000..a61a18a --- /dev/null +++ b/t/scripts/RunGuileTests.pl @@ -0,0 +1,36 @@ +#!/usr/bin/perl -w +# Run Guile tests - filenames are given as arguments to the script. +######################################################################## +# +# Copyright (C) 2008 Omer Zak. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library, in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA +# +# For licensing issues, contact <w1@zak.co.il>. +# +######################################################################## + +use strict; +use TAP::Harness; +my @tests = @ARGV; +my %args = ( + verbosity => 0, + timer => 1, + exec => ['/usr/bin/guile', '-s'], + ); +my $harness = TAP::Harness->new( \%args ); + $harness->runtests(@tests); + +# End of RunGuileTests.pl diff --git a/t/scripts/__init__.py b/t/scripts/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/t/scripts/__init__.py diff --git a/t/scripts/t2conv.py b/t/scripts/t2conv.py new file mode 100644 index 0000000..160c3b6 --- /dev/null +++ b/t/scripts/t2conv.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# Basic tests to validate conversions from Python to Guile and vice versa. +######################################################################## +# +# Copyright (C) 2008 Omer Zak. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library, in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA +# +# For licensing issues, contact <w1@zak.co.il>. +# +######################################################################## + +def return_None(): + return None + +def return_True(): + return True + +def return_False(): + return False + +def return_Int1(): + return 1 + +def return_Int_5(): + return(-5) + +def return_BigInt(): + return(1048576*1048576*1048576*32) + +def return_BigInt_neg(): + return(-1048576*1048576*1048576*32) + +def return_String1(): + return("abcdefghi") + +def return_String2(): + return("01abCD%^") + +def return_String_Zero(): + return("bef" + chr(0) + chr(163) + "ore") + +if (__name__ == '__main__'): + print "return_None: ",return_None() + print "return_True: ",return_True() + print "return_False: ",return_False() + print "return_Int1: ",return_Int1() + print "return_Int_5: ",return_Int_5() + print "return_BigInt: ",return_BigInt() + print "return_BigInt_neg: ",return_BigInt_neg() + print "String1: ",return_String1() + print "String2: ",return_String2() + print "String_Zero: ",return_String_Zero(),repr(return_String_Zero()) + +# End of t2conv.py diff --git a/t/scripts/t4apply.py b/t/scripts/t4apply.py new file mode 100644 index 0000000..a716462 --- /dev/null +++ b/t/scripts/t4apply.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +# Auxiliary functions for exercising python-apply. +######################################################################## +# +# Copyright (C) 2008 Omer Zak. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library, in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA +# +# For licensing issues, contact <w1@zak.co.il>. +# +######################################################################## + +def return_args(*args,**kw): + return("positional: %s keywords: %s" % (repr(args),repr(kw))) + +class cl1(object): + def __init__(self,num=1,str="2"): + self.num = num + self.str = str + + def myfunc(self,arg="x"): + return(arg + self.str) + +class cl2(object): + def __init__(self,num=3): + self.num2 = num + self.mycl1 = cl1(10,str="Twenty") + + def cl2func(self,argx="y"): + return(str(self.num2) + argx) + +mainobj = cl2(33) + +# End of t4apply.py diff --git a/t/scripts/t5smobs.py b/t/scripts/t5smobs.py new file mode 100644 index 0000000..2f4b4a5 --- /dev/null +++ b/t/scripts/t5smobs.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +# Auxiliary functions for exercising pysmobs. +######################################################################## +# +# Copyright (C) 2008 Omer Zak. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library, in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA +# +# For licensing issues, contact <w1@zak.co.il>. +# +######################################################################## + +class opaq(object): + def __init__(self,arg1): + self.arg1 = arg1 + def transform(self): + self.arg1 = self.arg1 + self.arg1 + def __repr__(self): + return("opaq(%s)" % repr(self.arg1)) + +# Work around temporary problem in PyGuile. +def genopaq(arg): + return(opaq(arg)) + +class noisydelete(object): + def __init__(self,id): + self.id = id + def __del__(self): + print "# Deleting class instance %s" % self.id + #object.__del__() + def __repr__(self): + return(repr(self.id)) + def __cmp__(self,other): + if ((self.id == "me") and (other.id == 42)): + # Want to prove that this function has indeed been exercised. + return(0) + return(cmp(self.id,other.id)) + +# End of t5smobs.py diff --git a/t/scripts/t7except.py b/t/scripts/t7except.py new file mode 100644 index 0000000..d2f6948 --- /dev/null +++ b/t/scripts/t7except.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# Raising exception inside my own code +######################################################################## +# +# Copyright (C) 2008 Omer Zak. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library, in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA +# +# For licensing issues, contact <w1@zak.co.il>. +# +######################################################################## + +import exceptions +class myexception(exceptions.Exception): + pass + +def raiser(arg="typical!"): + raise myexception("This is my exception",arg) + +# End of t7except.py diff --git a/t/scripts/test_auxiliary_functions.scm b/t/scripts/test_auxiliary_functions.scm new file mode 100644 index 0000000..f420fab --- /dev/null +++ b/t/scripts/test_auxiliary_functions.scm @@ -0,0 +1,177 @@ +#!/usr/bin/guile -s +!# +; Auxiliary functions, for use by test scripts +; Those functions are used by scripts from 20_* and on. +; Earlier scripts usually define their own versions of the +; functions. +; +; To use the functions, add +; (load "scripts/test_auxliary_functions.scm") +; at the beginning of your test script, after any (use-modules ...) +; calls. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; Copyright (C) 2008 Omer Zak. +; This library is free software; you can redistribute it and/or +; modify it under the terms of the GNU Lesser General Public +; License as published by the Free Software Foundation; either +; version 2.1 of the License, or (at your option) any later version. +; +; This library 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 +; Lesser General Public License for more details. +; +; You should have received a copy of the GNU Lesser General Public License +; along with this library, in a file named COPYING; if not, write to the +; Free Software Foundation, Inc., 59 Temple Place, Suite 330, +; Boston, MA 02111-1307 USA +; +; For licensing issues, contact <w1@zak.co.il>. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +(use-modules (ice-9 regex)) ; regexp-substitute + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Functions from modules, unlikely to be generally useful +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; from 03_guile2python.t +(define invoke-python-func + (lambda (module func arg) + (python-apply (list module func) (list arg) '()))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Functions likely to be generally useful +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Miscellaneous data manipulation ; +; functions ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Returns a 3-element list of booleans. +(define equalities3? + (lambda (obj1 obj2) + (list (eq? obj1 obj2) (eqv? obj1 obj2) (equal? obj1 obj2)))) + +; Does one alist include another alist. +; Inclusion means that all keys of the included alist are in the +; including one, and the corresponding values are equal. +; The equality criteria used here is equal? (for both key and value). +(define alist-properly-included? + (lambda (included includor) + (if (null? included) #t + (let ((key (caar included)) + (value (cdar included)) + (rest (cdr included))) + (let ((includor-ref (assoc key includor))) + (cond ((not includor-ref) #f) + ((not (equal? (cdr includor-ref) value)) #f) + (else (alist-properly-included? rest includor)))))))) + +; Are two alists equal? +(define alist-equal? + (lambda (alista alistb) + (and (alist-properly-included? alista alistb) + (alist-properly-included? alistb alista)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Modify actual results for easier ; +; comparison to expected results ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Replace all hex addresses appearing in a string +; by a specific literal. +(define substitute-hex-addresses-for-gggggggg + (lambda (strarg) + (regexp-substitute/global #f + "0x[0-9a-f]{8}" + strarg + 'pre "0xgggggggg" 'post))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Running inside catch harness ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Run a function, so that anything it writes to the current +; output port is captured, together with its return value. +; The return value to caller of capture-result-output is the +; pair of (return-value . output-string). + +(define capture-result-output + (lambda (func . args) + (let ((stdoutstr #f) + (retval #f)) + (set! stdoutstr + (with-output-to-string + (lambda () (set! retval + (apply func args))))) + (cons retval stdoutstr)))) + +; Run a function in an environment, in which any exceptions +; raised by it are caught; and anything it writes to the +; current output port is captured as well. +; The return value to caller of capture-result-output-catch +; is the pair of (return-value . output-string). +(define capture-result-output-catch + (lambda (func . args) + (let ((output-string #f) + (return-value #f)) + (set! output-string + (with-output-to-string + (lambda () (set! return-value + (catch #t + (lambda () (apply func args)) + (lambda (key . args2) + (object->string (list key args2)))))))) + (cons return-value output-string)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Functions specific to PyGuile ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Run python-eval under catch harness. +; template can be #f when no return value is expected, or #t when +; the default template is to be used. +(define catch-python-eval + (lambda (txt template) + (catch #t + (lambda () (python-eval txt template)) + (lambda (key . args) (object->string (list key args)))))) + +; Run python-import under catch harness. +(define catch-python-import + (lambda (arg) + (catch #t + (lambda () (python-import arg)) + (lambda (key . args) (object->string (list key args)))))) + +; Run python-apply under catch harness. +; The positional argument list must be supplied. +; The keyword argument list and the templates are optional. +(define catch-python-apply + (lambda (func posargs . kwargs-templates) + (catch #t + (lambda () (apply python-apply func posargs kwargs-templates)) + (lambda (key . args) (object->string (list key args)))))) + +; The following function is useful for checking how a SCM is +; actually converted into a PyObject using a template. +; The conversion is run under a catch harness. +(define catch-thunk-invoke-python-repr + (lambda (arg . template) + (catch #t + (lambda () + (if (null? template) + (python-apply '("__builtin__" "repr") arg '()) + (python-apply '("__builtin__" "repr") arg '() (car template)))) + (lambda (key . args2) (object->string (list key args2)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; End of test_axuliary_functions.scm diff --git a/verbose.c b/verbose.c new file mode 100644 index 0000000..e06b7d0 --- /dev/null +++ b/verbose.c @@ -0,0 +1,90 @@ +// verbosity control implementation +// +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// +// +// Implements verbosity control for PyGuile C code. + +#include "verbose.h" + +static int pyguile_verbosity_control = 0; + +//////////////////////////////////////////////////////////////////////// +// SCM side +//////////////////////////////////////////////////////////////////////// + +SCM +pyguile_verbosity_set(SCM snewsetting) +{ + if (SCM_INUMP(snewsetting)) { + SCM sprev_value = scm_long2num(pyguile_verbosity_control); + pyguile_verbosity_control = (int)scm_num2long(snewsetting,0,"pyguile-verbosity-set!"); + return(sprev_value); + } + else { + scm_wrong_type_arg("pyguile-verbosity-set!",SCM_ARG1,snewsetting); + } +} + +//////////////////////////////////////////////////////////////////////// +// C side +//////////////////////////////////////////////////////////////////////// + +int +pyguile_verbosity_test(int mask) +{ + return(pyguile_verbosity_control & mask); +} + +//////////////////////////////////////////////////////////////////////// +// Verbosity auxiliary functions +//////////////////////////////////////////////////////////////////////// + +// Create a SCM string whose contents are the Python-generated repr() +// of a PyObject. +// If PyObject==NULL, return "(null PyObject)". +SCM +verbosity_repr(PyObject *pobj) +{ + if (NULL == pobj) { + return(scm_makfrom0str("(null PyObject)")); + } + PyObject *pstr = PyObject_Repr(pobj); + if (NULL == pstr) { + PyObject *pexception = PyErr_Occurred(); // NOT COVERED BY TESTS + if (pexception) { // NOT COVERED BY TESTS + PyErr_Clear(); // NOT COVERED BY TESTS + scm_misc_error("verbosity_repr","cannot get repr of pobj", // NOT COVERED BY TESTS + SCM_UNDEFINED); + } + else { + scm_misc_error("verbosity_repr","unknown error during repr of pobj", // NOT COVERED BY TESTS + SCM_UNDEFINED); + } + } + SCM sstr = scm_makfrom0str(PyString_AsString(pstr)); + Py_DECREF(pstr); + return(sstr); +} + +//////////////////////////////////////////////////////////////////////// +// End of verbose.c diff --git a/verbose.h b/verbose.h new file mode 100644 index 0000000..55a48fa --- /dev/null +++ b/verbose.h @@ -0,0 +1,66 @@ +// verbose header file +// Functions for verbosity control of PyGuile functions. +//////////////////////////////////////////////////////////////////////// + +#ifndef VERBOSE_H +#define VERBOSE_H + +//////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2008 Omer Zak. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library, in a file named COPYING; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +// For licensing issues, contact <w1@zak.co.il>. +// +//////////////////////////////////////////////////////////////////////// + +#include <Python.h> // Must be first header file +#include <libguile.h> + +//////////////////////////////////////////////////////////////////////// +// Verbosity control +//////////////////////////////////////////////////////////////////////// + +// Function to be invoked from Scheme code. +// The previous setting is returned to the caller. +extern SCM pyguile_verbosity_set(SCM newsetting); + +// Function for accessing the verbosity control from C data. +// The return value is 0 if the caller is not to print the +// requested information. +extern int pyguile_verbosity_test(int mask); + +// Auxiliary function, for use in several verbosity messages. +extern SCM verbosity_repr(PyObject *pobj); + +// Bitmap settings for the aforementioned mask argument. +#define PYGUILE_VERBOSE_NONE 0 +#define PYGUILE_VERBOSE_G2P2G_SUCCESSFUL 1 +#define PYGUILE_VERBOSE_G2P2G_ALWAYS 2 // You should use 3 = 1+2 +#define PYGUILE_VERBOSE_GC 4 +#define PYGUILE_VERBOSE_GC_DETAILED 8 // You should use 12 = 4+8 +#define PYGUILE_VERBOSE_NEW_PYSMOB 16 +#define PYGUILE_VERBOSE_UNWRAP_PYSMOB 32 // You should use 48 = 16+32 +#define PYGUILE_VERBOSE_PYTHON_APPLY 64 +#define PYGUILE_VERBOSE_PYSCM 128 +#define PYGUILE_VERBOSE_GC_PYSCM 256 + +//////////////////////////////////////////////////////////////////////// + +#endif /* VERBOSE_H */ + +//////////////////////////////////////////////////////////////////////// +// End of verbose.h |