Rev 2 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
/** R : A Computer Langage for Statistical Data Analysis* Copyright (C) 1995, 1996 Robert Gentleman and Ross Ihaka** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 2 of the License, or* (at your option) any later version.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** You should have received a copy of the GNU General Public License* along with this program; if not, write to the Free Software* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.*//* This module contains support for S-style generic *//* functions and "class" support. Gag, barf ... */#include "Defn.h"static SEXP GetObject(RCNTXT *cptr){SEXP s;s = CAR(cptr->promargs);if( TYPEOF(s) == PROMSXP ) {if(PRVALUE(s) == R_UnboundValue ) {s = eval(PREXPR(s), PRENV(s));PRVALUE(CAR(cptr->promargs)) = s;}elses = PRVALUE(s);}return(s);}static SEXP applyMethod(SEXP call, SEXP op, SEXP args, SEXP rho, SEXP newrho){SEXP ans;if (TYPEOF(op) == SPECIALSXP) {int save = R_PPStackTop;R_Visible = 1 - PRIMPRINT(op);ans = PRIMFUN(op) (call, op, args, rho);if(save != R_PPStackTop) {Rprintf("stack imbalance in %s, %d then %d\n",PRIMNAME(op), save, R_PPStackTop);}}else if (TYPEOF(op) == BUILTINSXP) {int save = R_PPStackTop;PROTECT(args = evalList(args, rho));R_Visible = 1 - PRIMPRINT(op);ans = PRIMFUN(op) (call, op, args, rho);UNPROTECT(1);if(save != R_PPStackTop) {Rprintf("stack imbalance in %s, %d then %d\n",PRIMNAME(op), save, R_PPStackTop);}}else if (TYPEOF(op) == CLOSXP) {ans = applyClosure(call, op, args, rho, newrho);}return ans;}/* newintoold - a destructive matching of arguments;* newargs comes first; any element of oldargs with* a name that matches a named newarg is deleted; the* two resulting lists are appended and returned S* says they do this (white book) but doesn't seem to. */static SEXP newintoold(SEXP new, SEXP old){if( new==R_NilValue)return R_NilValue;CDR(new) = newintoold(CDR(new),old);while( old != R_NilValue ) {if( TAG(old) != R_NilValue && TAG(old) == TAG(new) ) {CAR(old) = CAR(new);return CDR(new);}old = CDR(old);}return new;}static SEXP matchmethargs(SEXP oldargs, SEXP newargs){newargs = newintoold(newargs,oldargs);return listAppend(oldargs,newargs);}/* usemethod - calling functions need to evaluate the object* (== 2nd argument). They also need to ensure that the* argument list is set up in the correct manner.** 1. find the context for the calling function (i.e. the generic)* this gives us the unevaluated arguments for the original call** 2. create an environment for evaluating the method and insert* a handful of variables (.Generic, .Class and .Method) into* that environment. Also copy any variables in the env of the* generic that are not formal (or actual) arguments.** 3. fix up the argument list; it should be the arguments to the* generic matched to the formals of the method to be invoked */int usemethod(char *generic, SEXP obj, SEXP call, SEXP args, SEXP rho, SEXP *ans){SEXP class, method, sxp, t, s, matchedarg, cloenv;SEXP op, formals, newrho;char buf[512];int i, j, nclass, matched;RCNTXT *cptr;/* Get the context which UseMethod was called from. */cptr = R_GlobalContext;if(cptr->callflag != CTXT_RETURN || cptr->cloenv != rho)error("UseMethod used in an inappropriate fashion\n");#ifdef oldwhile(cptr != NULL) {if(cptr->callflag == CTXT_RETURN && cptr->cloenv == rho)break;cptr = cptr->nextcontext;}if(cptr == NULL)error("UseMethod called outside a function\n");#endif/* Create a new environment without any *//* of the formals to the generic in it. */PROTECT(newrho=allocSExp(ENVSXP));PROTECT(op=findFun(CAR(cptr->call), cptr->sysparent));if(TYPEOF(op)==CLOSXP) {formals=FORMALS(op);for(s=FRAME(cptr->cloenv);s!=R_NilValue;s=CDR(s)) {matched=0;for(t=formals;t!=R_NilValue;t=CDR(t))if(TAG(t) == TAG(s))matched=1;if(!matched)defineVar(TAG(s),CAR(s),newrho);}}PROTECT(matchedarg= cptr->promargs);if(isObject(obj)) {class = getAttrib(obj, R_ClassSymbol);nclass = length(class);for(i=0 ; i<nclass ; i++) {sprintf(buf, "%s.%s", generic, CHAR(STRING(class)[i]));method = install(buf);sxp = findVar(method, rho);if(isFunction(sxp)) {defineVar(install(".Generic"), mkString(generic), newrho);if(i>0) {PROTECT(t=allocVector(STRSXP,nclass-i));for(j=0 ; j< length(t) ; j++, i++)STRING(t)[j] = STRING(class)[i];setAttrib(t,install("previous"),class);defineVar(install(".Class"), t, newrho);UNPROTECT(1);}elsedefineVar(install(".Class"),class, newrho);PROTECT(t=mkString(buf));defineVar(install(".Method"), t, newrho);UNPROTECT(1);PROTECT(t=LCONS(method,matchedarg));R_GlobalContext->callflag=CTXT_GENERIC;*ans = applyMethod(t,sxp,matchedarg,rho,newrho);R_GlobalContext->callflag=CTXT_RETURN;UNPROTECT(4);return 1;}}}sprintf(buf, "%s.default", generic);method = install(buf);sxp = findVar(method, rho);if(isFunction(sxp)) {defineVar(install(".Generic"), mkString(generic), newrho);defineVar(install(".Class"), R_NilValue, newrho);PROTECT(t=mkString(buf));defineVar(install(".Method"), t, newrho);UNPROTECT(1);PROTECT(t=LCONS(method,matchedarg));R_GlobalContext->callflag=CTXT_GENERIC;*ans = applyMethod(t, sxp, matchedarg, rho, newrho);R_GlobalContext->callflag=CTXT_RETURN;UNPROTECT(4);return 1;}UNPROTECT(3);cptr->callflag = CTXT_RETURN;return 0;}/* do_usemethod is not the only entry point to usemethod,* things like [ and [[ call usemethod directly, hence* do_usemethod should just be an interface to usemethod. */SEXP do_usemethod(SEXP call, SEXP op, SEXP args, SEXP env){char buf[128];SEXP ans, meth, obj;int nargs;RCNTXT *cptr;nargs = length(args);if( nargs )PROTECT(meth=eval(CAR(args), env));elsemeth=R_MissingArg;if( nargs >= 2 )PROTECT(obj = eval(CADR(args), env));else {cptr = R_GlobalContext;while(cptr != NULL) {if(cptr->callflag == CTXT_RETURN && cptr->cloenv == env)break;cptr = cptr->nextcontext;}if(cptr == NULL)error("UseMethod called from outside a closure\n");if( meth == R_MissingArg )PROTECT(meth = mkString(CHAR(PRINTNAME(CAR(cptr->call)))));PROTECT(obj = GetObject(cptr));}if(TYPEOF(meth) != STRSXP || LENGTH(meth) < 1 || strlen(CHAR(STRING(meth)[0])) == 0)errorcall(call, "first argument must be a method name\n");strcpy(buf, CHAR(STRING(meth)[0]));if(usemethod(buf, obj, call, CDR(args), env, &ans) == 1) {UNPROTECT(1);findcontext(CTXT_RETURN, ans);}elseerror("no applicable method for \"%s\"\n", buf);}/*if NextMethod has any arguments the first must be the genericthe second the object and any remaining are matched with theformals of the chosen method*/#define ARGUSED(x) LEVELS(x)SEXP do_nextmethod(SEXP call, SEXP op, SEXP args, SEXP env){char buf[128],*pt, *pb;SEXP ans, s, t, class, method, matchedarg, generic, nextfun;SEXP sysp, m, formals, actuals, tmp;RCNTXT *cptr;int nargs,i,j;cptr=R_GlobalContext;cptr->callflag = CTXT_GENERIC;/* get the env NextMethod was called from */sysp = R_GlobalContext->sysparent;while(cptr != NULL) {if(cptr->callflag == CTXT_RETURN && cptr->cloenv == sysp)break;cptr = cptr->nextcontext;}if(cptr == NULL)error("NextMethod called from outside a closure\n");/* set up the arglist */s=findFun(CAR(cptr->call), cptr->sysparent);if( TYPEOF(s) != CLOSXP )errorcall(cptr->call,"function is not a closure\n");formals = FORMALS(s);actuals = matchArgs(formals, cptr->promargs);/* we can't duplicate because it would force the promises *//* so we do our own duplication of the promargs */PROTECT(matchedarg = allocList(length(cptr->promargs)));for( t=matchedarg, s=cptr->promargs; t != R_NilValue; t=CDR(t), s=CDR(s)){CAR(t) = CAR(s);TAG(t) = TAG(s);}for(s=formals; s!=R_NilValue ; s=CDR(s) )ARGUSED(s) = 0;for( t=matchedarg ; t != R_NilValue ; t = CDR(t) ) {for (m = actuals, s= formals ; m != R_NilValue ; m = CDR(m),s=CDR(s) )if( (CAR(m) == CAR(t)) && !ARGUSED(s) ) {ARGUSED(s) = 1;if( CAR(m) == R_MissingArg ) {tmp = findVarInFrame(FRAME(cptr->cloenv), TAG(s));if (tmp == R_MissingArg)break;}CAR(t) = mkPROMISE(TAG(s), cptr->cloenv);break;}}/*Now see if there were any other arguments passed in.Class is used to determine the next method; if it doesn'texist the first argument to the current method is usedthe second argument to NextMethod is another option butisn't currently used).*/s = CADDR(args); /* this is ... and we need to see if it's bound */if( s == R_DotsSymbol ) {t = findVarInFrame(FRAME(env),s);if( t != R_NilValue && t != R_MissingArg ) {TYPEOF(t)= LISTSXP; /* a safe mutation */s =matchmethargs(matchedarg,t);UNPROTECT(1);PROTECT(matchedarg=s);}}elseerrorcall(call,"wrong argument ...\n");class = dynamicfindVar(install(".Class"),R_GlobalContext);if (class == R_UnboundValue) {s = GetObject(cptr);if( ! isObject(s) )errorcall(call, "object not specified\n");class = getAttrib(s, R_ClassSymbol);}generic = eval(CAR(args), env);if( generic == R_NilValue ) {generic = dynamicfindVar(install(".Generic"), R_GlobalContext);if( generic == R_UnboundValue)generic = mkString(CHAR(PRINTNAME(CAR(cptr->call))));}PROTECT(generic);if( !isString(generic) || length(generic) > 1 )errorcall(call,"invalid generic argument to NextMethod\n");if( strlen(CHAR(STRING(generic)[0])) == 0 )errorcall(call,"generic function not specified\n");/* we need the value of i on exit from the for loop to figure outhow many classes to drop*/nextfun=R_NilValue;/* jump over the current call */t=CAR(cptr->call);for(j = 0; j<length(class); j++) {sprintf(buf,"%s.%s",CHAR(STRING(generic)[0]),CHAR(STRING(class)[j]));if(install(buf) == t)break;}for(i = j+1; i<length(class); i++) {sprintf(buf,"%s.%s",CHAR(STRING(generic)[0]),CHAR(STRING(class)[i]));nextfun=findVar(install(buf),env);if(isFunction(nextfun))break;}if(!isFunction(nextfun)) {sprintf(buf,"%s.default",CHAR(STRING(generic)[0]));nextfun=findVar(install(buf),env);if(!isFunction(nextfun)) {t = install(CHAR(STRING(generic)[0]));nextfun=findVar(t,env);if( !isFunction(nextfun) )error("No method to invoke\n");if (TYPEOF(nextfun) == CLOSXP )if(INTERNAL(t) != R_NilValue )nextfun = INTERNAL(t);elseerror("No method to invoke\n");}}PROTECT(s = allocVector(STRSXP,length(class)-i));PROTECT(class = duplicate(class));PROTECT(m = allocSExp(ENVSXP));for(j = 0 ; j< length(s) ; j++ )STRING(s)[j] = duplicate(STRING(class)[i++]);setAttrib(s,install("previous"),class);defineVar(install(".Class"),s, m);PROTECT(method=mkString(buf));defineVar(install(".Method"), method, m);method=install(buf);defineVar(install(".Generic"), generic, m);PROTECT(t=LCONS(method,matchedarg));ans = applyMethod(t, nextfun, matchedarg, env, m);UNPROTECT(7);return(ans);}SEXP do_unclass(SEXP call, SEXP op, SEXP args, SEXP env){checkArity(op, args);if(isObject(CAR(args))) {CAR(args) = duplicate(CAR(args));setAttrib(CAR(args), R_ClassSymbol, R_NilValue);}return CAR(args);}/* InheritsClass - does an object inherit from a class */int InheritsClass(SEXP x, char *name){SEXP class;int i, nclass;if(isObject(x)) {class = getAttrib(x, R_ClassSymbol);nclass = length(class);for(i=0 ; i<nclass ; i++)if(!strcmp(CHAR(STRING(class)[i]), name))return 1;}return 0;}void RemoveClass(SEXP x, char *name){SEXP class, newclass;int i, j, nclass, nmatch;if(isObject(x)) {PROTECT(x);class = getAttrib(x, R_ClassSymbol);nclass = length(class);nmatch = 0;for(i=0 ; i<nclass ; i++)if(!strcmp(CHAR(STRING(class)[i]), name))nmatch++;if(nmatch == nclass) {setAttrib(x, R_ClassSymbol, R_NilValue);}else if(nmatch > 0) {PROTECT(newclass = allocVector(STRSXP, nclass-nmatch));for(i=0, j=0 ; i<nclass ; i++)if(strcmp(CHAR(STRING(class)[i]), name)) {STRING(newclass)[j++] = STRING(class)[i];}setAttrib(x, R_ClassSymbol, newclass);UNPROTECT(1);}UNPROTECT(1);}}