Mercurial > octave-nkf
diff src/DLD-FUNCTIONS/dispatch.cc @ 5164:57077d0ddc8e
[project @ 2005-02-25 19:55:24 by jwe]
author | jwe |
---|---|
date | Fri, 25 Feb 2005 19:55:28 +0000 |
parents | |
children | 240ed0328925 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/DLD-FUNCTIONS/dispatch.cc Fri Feb 25 19:55:28 2005 +0000 @@ -0,0 +1,599 @@ +/* + +Copyright (C) 2001 John W. Eaton and Paul Kienzle + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with Octave; see the file COPYING. If not, write to the Free +Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <list> +#include <map> +#include <string> + +#include "defun-dld.h" +#include "ov.h" +#include "ov-fcn.h" +#include "ov-typeinfo.h" +#include "pager.h" +#include "parse.h" +#include "symtab.h" +#include "variables.h" + +// XXX FIXME XXX should be using a map from type_id->name, rather +// than type_name->name + +template class std::map<std::string,std::string>; + +typedef std::map<std::string,std::string> Table; + +class +octave_dispatch : public octave_function +{ +public: + + // XXX FIXME XXX need to handle doc strings of dispatched functions, for + // example, by appending "for <f>(<type>,...) see <name>" for each + // time dispatch(f,type,name) is called. + octave_dispatch (const std::string &nm) + : octave_function (nm, "Overloaded function"), tab (), base (nm), + has_alias (false) + { } + + // XXX FIXME XXX if we get deleted, we should restore the original + // symbol_record from base before dying. + ~octave_dispatch (void) { } + + bool is_builtin_function (void) const { return true; } + + octave_function *function_value (bool) { return this; } + + octave_value do_index_op (const octave_value_list&, int) + { + error ("dispatch: do_index_op"); + return octave_value (); + } + + octave_value subsref (const std::string&, + const std::list<octave_value_list>&) + { + error ("dispatch: subsref (str, list)"); + panic_impossible (); + return octave_value (); + } + + octave_value_list subsref (const std::string& type, + const std::list<octave_value_list>& idx, + int nargout); + + octave_value_list do_multi_index_op (int, const octave_value_list&); + + void add (const std::string t, const std::string n); + + void clear (const std::string t); + + void print (std::ostream& os, bool pr_as_read=false) const; + +private: + + Table tab; + std::string base; + bool has_alias; + + octave_dispatch (void) + : octave_function (), tab (), base (), has_alias (false) { } + + DECLARE_OV_TYPEID_FUNCTIONS_AND_DATA + + DECLARE_OCTAVE_ALLOCATOR +}; + +DEFINE_OCTAVE_ALLOCATOR (octave_dispatch); + +DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_dispatch, + "overloaded function", "function"); + +void +octave_dispatch::add (const std::string t, const std::string n) +{ + if (tab.count (t) > 0 && tab[t] != n) + warning ("replacing %s(%s,...)->%s with %s", + base.c_str (), t.c_str (), tab[t].c_str (), n.c_str ()); + + tab[t] = n; + + if (t == "any") + has_alias = true; +} + +void +octave_dispatch::clear (const std::string t) +{ + tab.erase (t); + + if (t == "any") + has_alias = false; +} + +octave_value_list +octave_dispatch::subsref (const std::string& type, + const std::list<octave_value_list>& idx, + int nargout) +{ + octave_value_list retval; + + switch (type[0]) + { + case '(': + retval = do_multi_index_op (nargout, idx.front ()); + break; + + case '{': + case '.': + { + const std::string nm = type_name (); + error ("%s cannot be indexed with %c", nm.c_str (), type[0]); + } + break; + + default: + panic_impossible (); + } + + if (idx.size () > 1) + retval = retval(0).next_subsref (type, idx); + + return retval; +} + +static octave_function* +builtin (const std::string& base) +{ + octave_function *fcn = 0; + + // Check if we are overriding a builtin function. This is the + // case if builtin is protected. + symbol_record *builtin = fbi_sym_tab->lookup ("builtin:" + base, 0); + + if (! builtin) + error ("builtin record has gone missing"); + + if (error_state) + return fcn; + + if (builtin->is_read_only ()) + { + // builtin is read only, so checking for updates is pointless + if (builtin->is_function ()) + fcn = builtin->def().function_value (); + else + error ("builtin %s is not a function", base.c_str ()); + } + else + { + // Check that builtin is up to date. + + // Don't try to fight octave's function name handling + // mechanism. Instead, move dispatch record out of the way, + // and restore the builtin to its original name. + symbol_record *dispatch = fbi_sym_tab->lookup (base, 0); + if (! dispatch) + error ("dispatch record has gone missing"); + + dispatch->unprotect (); + + fbi_sym_tab->rename (base, "dispatch:" + base); + + fbi_sym_tab->rename ("builtin:" + base, base); + + // check for updates to builtin function; ignore errors that + // appear (they interfere with renaming), and remove the updated + // name from the current symbol table. XXX FIXME XXX check that + // updating a function updates it in all contexts --- it may be + // that it is updated only in the current symbol table, and not + // the caller. I believe this won't be a problem because the + // caller will go through the same logic and end up with the + // newer version. + fcn = is_valid_function (base, "dispatch", 1); + int cache_error = error_state; + error_state = 0; + curr_sym_tab->clear_function (base); + + // Move the builtin function out of the way and restore the + // dispatch fuction. + // XXX FIXME XXX what if builtin wants to protect itself? + symbol_record *found=fbi_sym_tab->lookup (base, 0); + bool readonly = found->is_read_only (); + found->unprotect (); + fbi_sym_tab->rename (base, "builtin:" + base); + fbi_sym_tab->rename ("dispatch:" + base, base); + if (readonly) + found->protect (); + dispatch->protect (); + + // remember if there were any errors. + error_state = cache_error; + } + + return fcn; +} + +static bool +any_arg_is_magic_colon (const octave_value_list& args) +{ + int nargin = args.length (); + + for (int i = 0; i < nargin; i++) + if (args(i).is_magic_colon ()) + return true; + + return false; +} + + +octave_value_list +octave_dispatch::do_multi_index_op (int nargout, const octave_value_list& args) +{ + octave_value_list retval; + + if (error_state) return retval; + + if (any_arg_is_magic_colon (args)) + { + ::error ("invalid use of colon in function argument list"); + return retval; + } + + // If more than one argument, check if argument template matches any + // overloaded functions. Also provide a catch-all '*' type to provide + // single level pseudo rename and replace functionality. + if (args.length () > 0 && tab.count (args(0).type_name ()) > 0) + retval = feval (tab[args(0).type_name()], args, nargout); + else if (has_alias) + retval = feval (tab["any"], args, nargout); + else + { + octave_function *fcn = builtin (base); + if (! error_state && fcn) + retval = fcn->do_multi_index_op (nargout, args); + } + + return retval; +} + +void +octave_dispatch::print (std::ostream& os, bool) const +{ + os << "Overloaded function " << base << std::endl; + + for (Table::const_iterator it = tab.begin (); it != tab.end (); it++) + os << base << "(" << it->first << ",...)->" + << it->second << "(" << it->first << ",...)" + << std::endl; +} + +DEFUN_DLD (builtin, args, nargout, + "-*- texinfo -*-\n\ +@deftypefn {Loadable Function} {[@dots{}]} builtin (@var{f}, @dots{})\n\ +Call the base function @var{f} even if @var{f} is overloaded to\n\ +some other function for the given type signature.\n\ +@end deftypefn\n\ +@seealso{dispatch}") +{ + octave_value_list retval; + + int nargin = args.length (); + + if (nargin > 0) + { + const std::string name (args(0).string_value ()); + + if (error_state) + return retval; + + symbol_record *sr = fbi_sym_tab->lookup (name, 0); + if (sr->def().type_id () == octave_dispatch::static_type_id ()) + { + octave_function *fcn = builtin (name); + + if (!error_state && fcn) + retval = fcn->do_multi_index_op (nargout, + args.splice (0, 1, retval)); + } + else + retval = feval (name, args, nargout); + } + else + print_usage ("builtin"); + + return retval; +} + +DEFUN_DLD (dispatch_help, args, nargout, + "-*- texinfo -*-\n\ +@deftypefn {Loadable Function} {} dispatch_help (@var{name}, @dots{})\n\ +Delayed loading of help messages for dispatched functions.\n\ +@end deftypefn\n\ +@seealso{builtin, dispatch}") +{ + octave_value_list retval; + + int nargin = args.length (); + + for (int i = 0; i < nargin; i++) + { + if (args(i).is_string ()) + { + const std::string name (args(i).string_value ()); + + if (error_state) + return retval; + + symbol_record *sr = fbi_sym_tab->lookup (name, false); + + if (sr) + { + std::string help = sr->help (); + + if (help[0] == '<' && help[1] == '>' + && sr->def().type_id () == octave_dispatch::static_type_id ()) + { + builtin (name); + + symbol_record *builtin_record + = fbi_sym_tab->lookup ("builtin:" + name, 0); + + help.replace (0, 2, builtin_record->help ()); + + sr->document (help); + } + } + } + } + + return feval ("builtin:help", args, nargout); +} + +static void +dispatch_record (const std::string &f, const std::string &n, + const std::string &t) +{ + // find the base function in the symbol table, loading it if it + // is not already there; if it is already a dispatch, then bonus + + symbol_record *sr = fbi_sym_tab->lookup (f, true); + + if (sr->def().type_id () != octave_dispatch::static_type_id ()) + { + // Preserve mark_as_command status + bool iscommand = sr->is_command (); + + // Not an overloaded name, so if only display or clear then we are done + if (t.empty ()) + return; + + // sr is the base symbol; rename it to keep it safe. When we need + // it we will rename it back again. + if (sr->is_read_only ()) + { + sr->unprotect (); + fbi_sym_tab->rename (f, "builtin:" + f); + sr = fbi_sym_tab->lookup (f, true); + sr->protect (); + } + else + fbi_sym_tab->rename (f, "builtin:" + f); + + std::string basedoc ("<>"); + + if (! sr->help().empty ()) + basedoc = sr->help (); + + // Problem: when a function is first called a new record + // is created for it in the current symbol table, so calling + // dispatch on a function that has already been called, we + // should also clear it from all existing symbol tables. + // This is too much work, so we will only do it for the + // top level symbol table. We can't use the clear_function() + // method, because it won't clear builtin functions. Instead + // we check if the symbol is a function and clear it then. This + // won't properly clear shadowed functions, or functions in + // other namespaces (such as the current, if called from a + // function). + symbol_record *local = top_level_sym_tab->lookup (f, false); + if (local && local->is_function ()) + local->clear (); + + // Build a new dispatch object based on the function definition + octave_dispatch *dispatch = new octave_dispatch (f); + + // Create a symbol record for the dispatch object. + sr = fbi_sym_tab->lookup (f, true); + sr->unprotect (); + sr->define (octave_value (dispatch), symbol_record::BUILTIN_FUNCTION); + // std::cout << "iscommand('"<<f<<"')=" << iscommand << std::endl; + if (iscommand) + sr->mark_as_command(); + sr->document (basedoc + "\n\nOverloaded function\n"); + sr->make_eternal (); // XXX FIXME XXX why?? + sr->mark_as_static (); + sr->protect (); + } + + // clear/replace/extend the map with the new type-function pair + const octave_dispatch& rep + = reinterpret_cast<const octave_dispatch&> (sr->def().get_rep ()); + + if (t.empty ()) + // XXX FIXME XXX should return the list if nargout > 1 + rep.print (octave_stdout); + else if (n.empty ()) + { + // XXX FIXME XXX should we eliminate the dispatch function if + // there are no more elements? + // XXX FIXME XXX should clear the " $t:\w+" from the help string. + // XXX FIXME XXX -- seems bad to cast away const here... + octave_dispatch& xrep = const_cast<octave_dispatch&> (rep); + + xrep.clear (t); + } + else + { + // XXX FIXME XXX -- seems bad to cast away const here... + octave_dispatch& xrep = const_cast<octave_dispatch&> (rep); + + xrep.add (t, n); + + if (! sr->help().empty ()) + sr->document (sr->help() + "\n " + n + "(" + t + ",...)"); + } +} + +DEFUN_DLD (dispatch, args, , + "-*- texinfo -*-\n\ +@deftypefn {Loadable Function} {} dispatch (@var{f}, @var{r}, @var{type})\n\ +\n\ +Replace the function @var{f} with a dispatch so that function @var{r}\n\ +is called when @var{f} is called with the first argument of the named\n\ +@var{type}. If the type is @var{any} then call @var{r} if no other type\n\ +matches. The original function @var{f} is accessible using\n\ +@code{builtin (@var{f}, @dots{}).\n\ +\n\ +If @var{r} is omitted, clear dispatch function associated with @var{type}.\n\ +\n\ +If both @var{r} and @var{type} are omitted, list dispatch functions\n\ +for @var{f}\n\ +@end deftypefn\n\ +@seealso{builtin}") +{ + octave_value retval; + int nargin = args.length (); + + if (nargin < 1 || nargin > 3) + { + print_usage ("dispatch"); + return retval; + } + + std::string f, t, n; + if (nargin > 0) + f = args(0).string_value (); + + if (nargin == 2) + t = args(1).string_value (); + else if (nargin > 2) + { + n = args(1).string_value (); + t = args(2).string_value (); + } + + if (error_state) + return retval; + + static bool register_type = true; + + // register dispatch function type if you have not already done so + if (register_type) + { + octave_dispatch::register_type (); + register_type = false; + fbi_sym_tab->lookup("dispatch")->mark_as_static (); + dispatch_record ("help", "dispatch_help", "string"); + } + + dispatch_record (f, n, t); + + return retval; +} + +/* + +%!test # builtin function replacement +%! dispatch('sin','length','string') +%! assert(sin('abc'),3) +%! assert(sin(0),0,10*eps); +%!test # 'any' function +%! dispatch('sin','exp','any') +%! assert(sin(0),1,eps); +%! assert(sin('abc'),3); +%!test # 'builtin' function +%! assert(builtin('sin',0),0,eps); +%! builtin('eval','x=1;'); +%! assert(x,1); +%!test # clear function mapping +%! dispatch('sin','string') +%! dispatch('sin','any') +%! assert(sin(0),0,10*eps); +%!test # oct-file replacement +%! dispatch('fft','length','string') +%! assert(fft([1,1]),[2,0]); +%! assert(fft('abc'),3) +%! dispatch('fft','string'); +%!test # m-file replacement +%! dispatch('hamming','length','string') +%! assert(hamming(1),1) +%! assert(hamming('abc'),3) +%! dispatch('hamming','string') + +%!test # override preloaded builtin +%! evalin('base','cos(1);'); +%! dispatch('cos','length','string') +%! evalin('base',"assert(cos('abc'),3)"); +%! evalin('base',"assert(cos(0),1,eps)"); +%! dispatch('cos','string') +%!test # override pre-loaded oct-file +%! evalin('base','qr(1);'); +%! dispatch('qr','length','string') +%! evalin('base',"assert(qr('abc'),3)"); +%! evalin('base',"assert(qr(1),1)"); +%! dispatch('qr','string'); +%!test # override pre-loaded m-file +%! evalin('base','hanning(1);'); +%! dispatch('hanning','length','string') +%! evalin('base','assert(hanning("abc"),3)'); +%! evalin('base','assert(hanning(1),1)'); +%! dispatch('hanning','string'); + +XXX FIXME XXX I would rather not create dispatch_x/dispatch_y +in the current directory! I don't want them installed accidentally. + +%!test # replace base m-file +%! system("echo 'function a=dispatch_x(a)'>dispatch_x.m"); +%! dispatch('dispatch_x','length','string') +%! assert(dispatch_x(3),3) +%! assert(dispatch_x('a'),1) +%! pause(1); +%! system("echo 'function a=dispatch_x(a),++a;'>dispatch_x.m"); +%! assert(dispatch_x(3),4) +%! assert(dispatch_x('a'),1) +%!test +%! system("rm dispatch_x.m"); + +%!test # replace dispatch m-file +%! system("echo 'function a=dispatch_y(a)'>dispatch_y.m"); +%! dispatch('hello','dispatch_y','complex scalar') +%! assert(hello(3i),3i) +%! pause(1); +%! system("echo 'function a=dispatch_y(a),++a;'>dispatch_y.m"); +%! assert(hello(3i),1+3i) +%!test +%! system("rm dispatch_y.m"); + +XXX FIXME XXX add tests for preservation of mark_as_command status. + +*/