/* * Copyright (c) 2002-2006 Samit Basu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "Class.hpp" #include "Context.hpp" // some behavioral observations on classes. // The first call to "class" is the definitive one. // The exact order of the structure fieldnames must be the same // for all objects // The list of parent objects must also be the same for all objects // So, classes are stored as the following: // class UserClass { // stringVector fieldNames; // stringVector parentClasses; // } // Also, somewhere we require a table that // tracks the hierarchy relationship of the classes. // // To Do: // change grepping code to look for classes // change function eval code to handle classes // // These are both done. Next is the issue of parent classes. // What does it mean when we have one or more parent classes? // The structure is simple enough (simply add a new field with // the name of the parent class). But what about methods? // When we call a method of the parent class on the current class // what does it get passed? // // The answer: // Suppose g is of class1, and inherits class2, and class3. // Then g has fields class2 and class3. // When we call // method(g), where method is a member function of class2, then // effectively, the output is the same as // method(g.class2) // p = g // p.class2 = method(g.class2) // Odd - that's not quite right... it must be more complicated than that // Class related issues // rhs subscripting // assignment // // What about function pointers? - done // // Need overload capability for // And // Or // Uplus // a(s1,s2,...,sn) - subsref // a(s1, ..., sn) - subsasgn // b(a) - subsindex // [a b] - horzcat // [a; b] - vertcat // Colon // end (!) // // More ideas on overloading classes... // // What happens when the parent classes are different sizes - resolved // force parent classes to be the same size as the created object // // In c++, polymorphism is done through the notion of a pointer and // type casting. But we can't do exactly the same thing... Because // when we type-cast, only methods and fields from the type-cast // object are present... // // What we want is // a.class1.class2.foo = 32 // In this case, a is of some class (e.g., class3). But we want to // call some method on a that belongs to class2. now, inside the // method, we want something like // x.foo = 32 // but _x_ has to be tagged with prefix information, because _x_ is // really of class class3. The tag has to be on the object because // if there are multiple arguments to the function, they can be // typecast at different levels. Also, it tracks only the _instance_ // of the array, not the core array itself. So the information has // to be tagged on the array somehow. // // One idea is to replace the class name with the class path. So if // a is of type class3, but we want to access it as a type class2, // we "cast" it to type class3:class1:class2. Then, when accessing // members of "a", we use the class list to determine the indexing // sequence. This casting operation can be done at the dispatch // level. Because the "struct" operation simply strips the class name // from the object, it will still return the intact data array. // // To track precedence... // 1. Assume that inheritance and precedence do not interact (only the // outermost class determines precedence). // 2. For each class, a list of superior classes is provided. // 3. A inf B <--> B sup A // Precedence is then a simple question of testing interactions. UserClass::UserClass() { } UserClass::UserClass(rvstring fields, rvstring parents) : fieldNames(fields), parentClasses(parents) { } bool UserClass::matchClass(UserClass test) { return (((fieldNames) == (test.fieldNames)) && ((parentClasses) == (test.parentClasses))); } UserClass::~UserClass() { } rvstring UserClass::getParentClasses() { return parentClasses; } Array ClassAux(Array s, std::string classname, rvstring parentNames, ArrayVector parents, Interpreter* eval) { UserClass newclass(s.fieldNames(),parentNames); if (s.dataClass() != FM_STRUCT_ARRAY) throw Exception("first argument to 'class' function must be a structure"); // Check to see if this class has already been registered if (!eval->isUserClassDefined(classname)) { // new class... register it eval->registerUserClass(classname,newclass); } else { // existing class... make sure we match it UserClass eclass(eval->lookupUserClass(classname)); if (!eclass.matchClass(newclass)) throw Exception("fieldnames, and parent objects must match registered class. Use 'clear classes' to reset this information."); } // Set up the new structure array. We do this by constructing a set of fieldnames // that includes fields for the parent classes... To resolve - what happens // if the parent arrays are different sizes than the current class. rvstring newfields(s.fieldNames()); // We should check for duplicates! for (int i=0;i b //@] //! //! //@Module LE Overloaded Less-Than-Equals Comparison Operator //@@Section CLASS //@@Usage //This is a method that is invoked to compare two variables using a //less than or equals comparison operator, and is invoked when you call //@[ // c = le(a,b) //@] //or for //@[ // c = a <= b //@] //! //! //@Module GE Overloaded Greater-Than-Equals Comparison Operator //@@Section CLASS //@@Usage //This is a method that is invoked to combine two variables using a //greater than or equals comparison operator, and is invoked when you call //@[ // c = ge(a,b) //@] //or for //@[ // c = a >= b //@] //! //! //@Module EQ Overloaded Equals Comparison Operator //@@Section CLASS //@@Usage //This is a method that is invoked to combine two variables using an //equals comparison operator, and is invoked when you call //@[ // c = eq(a,b) //@] //or for //@[ // c = a == b //@] //! //! //@Module NE Overloaded Not-Equals Comparison Operator //@@Section CLASS //@@Usage //This is a method that is invoked to combine two variables using a //not-equals comparison operator, and is invoked when you call //@[ // c = ne(a,b) //@] //or for //@[ // c = a != b //@] //! //! //@Module PLUS Overloaded Addition Operator //@@Section CLASS //@@Usage //This is a method that is invoked when two variables are added //and is invoked when you call //@[ // c = plus(a,b) //@] //or for //@[ // c = a + b //@] //! //! //@Module MINUS Overloaded Addition Operator //@@Section CLASS //@@Usage //This is a method that is invoked when two variables are subtracted //and is invoked when you call //@[ // c = minus(a,b) //@] //or for //@[ // c = a - b //@] //! //! //@Module MTIMES Overloaded Matrix Multiplication Operator //@@Section CLASS //@@Usage //This is a method that is invoked when two variables are multiplied //using the matrix operator and is invoked when you call //@[ // c = mtimes(a,b) //@] //or for //@[ // c = a * b //@] //! //! //@Module TIMES Overloaded Multiplication Operator //@@Section CLASS //@@Usage //This is a method that is invoked when two variables are multiplied //and is invoked when you call //@[ // c = times(a,b) //@] //or for //@[ // c = a .* b //@] //! //! //@Module RDIVIDE Overloaded Right Divide Operator //@@Section CLASS //@@Usage //This is a method that is invoked when two variables are divided //and is invoked when you call //@[ // c = rdivide(a,b) //@] //or for //@[ // c = a ./ b //@] //! //! //@Module LDIVIDE Overloaded Left Divide Operator //@@Section CLASS //@@Usage //This is a method that is invoked when two variables are divided //and is invoked when you call //@[ // c = ldivide(a,b) //@] //or for //@[ // c = a .\ b //@] //! //! //@Module MRDIVIDE Overloaded Matrix Right Divide Operator //@@Section CLASS //@@Usage //This is a method that is invoked when two variables are divided //using the matrix divide operator, and is invoked when you call //@[ // c = mrdivide(a,b) //@] //or for //@[ // c = a / b //@] //! //! //@Module MLDIVIDE Overloaded Matrix Left Divide Operator //@@Section CLASS //@@Usage //This is a method that is invoked when two variables are divided //using the matrix (left) divide operator, and is invoked when //you call //@[ // c = mldivide(a,b) //@] //or for //@[ // c = a \ b //@] //! //! //@Module UMINUS Overloaded Unary Minus Operator //@@Section CLASS //@@Usage //This is a method that is invoked when a variable is negated, //and is invoked when you call //@[ // c = uminus(a) //@] //or for //@[ // c = -a //@] //! //! //@Module UPLUS Overloaded Unary Plus Operator //@@Section CLASS //@@Usage //This is a method that is invoked when a variable is preceeded by a "+", //and is invoked when you call //@[ // c = uplus(a) //@] //or for //@[ // c = +a //@] //! //! //@Module NOT Overloaded Logical Not Operator //@@Section CLASS //@@Usage //This is a method that is invoked when a variable is logically //inverted, and is invoked when you call //@[ // c = not(a) //@] //or for //@[ // c = ~a //@] //! //! //@Module MPOWER Overloaded Matrix Power Operator //@@Section CLASS //@@Usage //This is a method that is invoked when one variable is raised //to another variable using the matrix power operator, and //is invoked when you call //@[ // c = mpower(a,b) //@] //or //@[ // c = a^b //@] //! //! //@Module POWER Overloaded Power Operator //@@Section CLASS //@@Usage //This is a method that is invoked when one variable is raised //to another variable using the dot-power operator, and is //invoked when you call //@[ // c = power(a,b) //@] //or //@[ // c = a.^b //@] //! //! //@Module CTRANSPOSE Overloaded Conjugate Transpose Operator //@@Section CLASS //@@Usage //This is a method that is invoked when a variable has the //conjugate transpose operator method applied, and is invoked //when you call //@[ // c = ctranspose(a) //@] //or //@[ /// c = a' //@] //! //! //@Module TRANSPOSE Overloaded Transpose Operator //@@Section CLASS //@@Usage //This is a method that is invoked when a variable has the //transpose operator method applied, and is invoked //when you call //@[ // c = transpose(a) //@] //or //@[ /// c = a.' //@] //! //! //@Module COLON Overloaded Colon Operator //@@Section CLASS //@@Usage //This is a method that is invoked in one of two forms, either //the two argument version //@[ // c = colon(a,b) //@] //which is also called using the notation //@[ // c = a:b //@] //and the three argument version //@[ // d = colon(a,b,c) //@] //which is also called using the notation //@[ // d = a:b:c //@] //! //! //@Module SUBSREF Overloaded Class Indexing //@@Section CLASS //@@Usage //This method is called for expressions of the form //@[ // c = a(b), c = a{b}, c = a.b //@] //and overloading the @|subsref| method allows you //to define the meaning of these expressions for //objects of class @|a|. These expressions are //mapped to a call of the form //@[ // b = subsref(a,s) //@] //where @|s| is a structure array with two fields. The //first field is //\begin{itemize} //\item @|type| is a string containing either @|'()'| or // @|'{}'| or @|'.'| depending on the form of the call. //\item @|subs| is a cell array or string containing the // the subscript information. //\end{itemize} //When multiple indexing experssions are combined together //such as @|b = a(5).foo{:}|, the @|s| array contains //the following entries //@[ // s(1).type = '()' s(1).subs = {5} // s(2).type = '.' s(2).subs = 'foo' // s(3).type = '{}' s(3).subs = ':' //@] //! //! //@Module SUBSASGN Overloaded Class Assignment //@@Section CLASS //@@Usage //This method is called for expressions of the form //@[ // a(b) = c, a{b} = c, a.b = c //@] //and overloading the @|subsasgn| method can allow you //to define the meaning of these expressions for //objects of class @|a|. These expressions are mapped //to a call of the form //@[ // a = subsasgn(a,s,b) //@] //where @|s| is a structure array with two fields. The //first field is //\begin{itemize} //\item @|type| is a string containing either @|'()'| or // @|'{}'| or @|'.'| depending on the form of the call. //\item @|subs| is a cell array or string containing the // the subscript information. //\end{itemize} //When multiple indexing experssions are combined together //such as @|a(5).foo{:} = b|, the @|s| array contains //the following entries //@[ // s(1).type = '()' s(1).subs = {5} // s(2).type = '.' s(2).subs = 'foo' // s(3).type = '{}' s(3).subs = ':' //@] //! //! //@Module SUBSINDEX Overloaded Class Indexing //@@Section CLASS //@@Usage //This method is called for classes in the expressions //of the form //@[ // c = subsindex(a) //@] //where @|a| is an object, and @|c| is an index vector. //It is also called for //@[ // c = b(a) //@] //in which case @|subsindex(a)| must return a vector containing //integers between @|0| and @|N-1| where @|N| is the number //of elements in the vector @|b|. //! ArrayVector ClassFunction(int nargout, const ArrayVector& arg, Interpreter* eval) { if (arg.size() == 0) throw Exception("class function requires at least one argument"); if (arg.size() == 1) return singleArrayVector(ClassOneArgFunction(arg[0])); ArrayVector parents; rvstring parentNames; for (int i=2;iretCount = 1; sfdef->argCount = -1; sfdef->name = "class"; sfdef->fptr = ClassFunction; context->insertFunction(sfdef,false); } std::vector MarkUserClasses(ArrayVector t) { std::vector set; for (int j=0;j userset, FuncPtr &val, std::string name) { bool overload = false; int k = 0; while (kgetContext()->lookupFunction(ClassMangleName(t[userset[k]].className().back(),name),val); if (!overload) k++; } if (!overload) throw Exception(std::string("Unable to find overloaded '") + name + "' for user defined classes"); } Array ClassMatrixConstructor(ArrayMatrix m, Interpreter* eval) { // Process the rows... // If a row has no user defined classes, then // use the standard matrixConstructor ArrayVector rows; for (int i=0;i userset(MarkUserClasses(m[i])); if (userset.empty()) { ArrayMatrix n; n.push_back(m[i]); rows.push_back(Array::matrixConstructor(n)); } else { FuncPtr val; bool horzcat_overload = ClassSearchOverload(eval,m[i],userset, val,"horzcat"); // scan through the list of user defined classes - look // for one that has "horzcat" overloaded val->updateCode(eval); ArrayVector p; p = val->evaluateFunction(eval,m[i],1); if (!p.empty()) rows.push_back(p[0]); else { eval->warningMessage("'horzcat' called for user defined class and it returned nothing. Substituting empty array for result."); rows.push_back(Array::emptyConstructor()); } } } // Check for a singleton - handle with special case if (rows.size() == 1) return rows[0]; // At this point we have a vector arrays that have to vertically // concatenated. There may not be any objects in it, so we have // to rescan. std::vector userset(MarkUserClasses(rows)); if (userset.empty()) { // OK - we don't have any user-defined classes anymore, // so we want to call matrix constructor, which needs // an ArrayMatrix instead of an ArrayVector. ArrayMatrix ref; for (int i=0;iupdateCode(eval); ArrayVector p; p = val->evaluateFunction(eval,rows,1); if (!p.empty()) return p[0]; else return Array::emptyConstructor(); } return Array::emptyConstructor(); } Array ClassUnaryOperator(Array a, std::string funcname, Interpreter* eval) { FuncPtr val; ArrayVector m, n; if (eval->getContext()->lookupFunction(ClassMangleName(a.className().back(),funcname),val)) { val->updateCode(eval); m.push_back(a); n = val->evaluateFunction(eval,m,1); if (!n.empty()) return n[0]; else return Array::emptyConstructor(); } throw Exception("Unable to find a definition of " + funcname + " for arguments of class " + a.className().back()); } bool ClassResolveFunction(Interpreter* eval, Array& args, std::string funcName, FuncPtr& val) { Context *context = eval->getContext(); // First try to resolve to a method of the base class if (context->lookupFunction(ClassMangleName(args.className().back(),funcName),val)) { return true; } UserClass eclass(eval->lookupUserClass(args.className().back())); rvstring parentClasses(eclass.getParentClasses()); // Now check the parent classes for (int i=0;ilookupFunction(ClassMangleName(parentClasses.at(i),funcName),val)) { rvstring argClass(args.className()); argClass.push_back(parentClasses.at(i)); args.setClassName(argClass); return true; } } // Nothing matched, return return false; } void printClassNames(Array a) { rvstring classname(a.className()); for (int i=0;iupdateCode(eval); ArrayVector m, n; m.push_back(a); m.push_back(b); n = val->evaluateFunction(eval,m,1); if (!n.empty()) return n[0]; else return Array::emptyConstructor(); } Array ClassTriOp(Array a, Array b, Array c, FuncPtr val, Interpreter *eval) { val->updateCode(eval); ArrayVector m, n; m.push_back(a); m.push_back(b); m.push_back(c); n = val->evaluateFunction(eval,m,1); if (!n.empty()) return n[0]; else return Array::emptyConstructor(); } Array ClassTrinaryOperator(Array a, Array b, Array c, std::string funcname, Interpreter* eval) { FuncPtr val; if (a.isUserClass()) { if (eval->getContext()->lookupFunction(ClassMangleName(a.className().back(),funcname),val)) return ClassTriOp(a,b,c,val,eval); throw Exception("Unable to find a definition of " + funcname + " for arguments of class " + a.className().back()); } else if (b.isUserClass()) { if (eval->getContext()->lookupFunction(ClassMangleName(b.className().back(),funcname),val)) return ClassTriOp(a,b,c,val,eval); throw Exception("Unable to find a definition of " + funcname + " for arguments of class " + b.className().back()); } else if (c.isUserClass()) { if (eval->getContext()->lookupFunction(ClassMangleName(c.className().back(),funcname),val)) return ClassTriOp(a,b,c,val,eval); throw Exception("Unable to find a definition of " + funcname + " for arguments of class " + b.className().back()); } } Array ClassBinaryOperator(Array a, Array b, std::string funcname, Interpreter* eval) { FuncPtr val; if (a.isUserClass()) { if (eval->getContext()->lookupFunction(ClassMangleName(a.className().back(),funcname),val)) return ClassBiOp(a,b,val,eval); throw Exception("Unable to find a definition of " + funcname + " for arguments of class " + a.className().back()); } else if (b.isUserClass()) { if (eval->getContext()->lookupFunction(ClassMangleName(b.className().back(),funcname),val)) return ClassBiOp(a,b,val,eval); throw Exception("Unable to find a definition of " + funcname + " for arguments of class " + b.className().back()); } } // void AdjustColonCalls(ArrayVector& m, treeVector t) { // for (unsigned index=0;index < t.size();index++) // if (t[index].is(':')) // m[index] = Array::stringConstructor(":"); // } Array IndexExpressionToStruct(Interpreter* eval, const tree &t, Array r) { ArrayVector struct_args; ArrayVector rv; Array rsave(r); rvstring fNames; fNames.push_back("type"); fNames.push_back("subs"); for (unsigned index=1;index < t.numchildren();index++) { if (!rv.empty()) throw Exception("Cannot reindex an expression that returns multiple values."); if (t.child(index).is(TOK_PARENS)) { ArrayVector m; const tree &s(t.child(index)); for (unsigned p=0;pmultiexpr(s.child(p),m); eval->subsindex(m); // m = eval->varExpressionList(t[index].children(),r); // // Scan through the expressions... adjust for "colon" calls // AdjustColonCalls(m,t[index].children()); if (m.size() == 0) throw Exception("Expected indexing expression!"); // Take the arguments and push them into a cell array... ArrayMatrix q; q.push_back(m); struct_args.push_back(Array::stringConstructor("()")); struct_args.push_back(Array::cellConstructor(q)); } if (t.child(index).is(TOK_BRACES)) { ArrayVector m; const tree &s(t.child(index)); for (unsigned p=0;pmultiexpr(s.child(p),m); eval->subsindex(m); // m = eval->varExpressionList(t[index].children(),r); // AdjustColonCalls(m,t[index].children()); if (m.size() == 0) throw Exception("Expected indexing expression!"); // Take the arguments and push them into a cell array... ArrayMatrix q; q.push_back(m); struct_args.push_back(Array::stringConstructor("{}")); struct_args.push_back(Array::cellConstructor(q)); } if (t.child(index).is('.')) { struct_args.push_back(Array::stringConstructor(".")); struct_args.push_back(Array::stringConstructor(t.child(index).first().text())); } } int cnt = struct_args.size()/2; Array *cp = (Array*) Array::allocateArray(FM_STRUCT_ARRAY,cnt,fNames); for (int i=0;i<2*cnt;i++) cp[i] = struct_args[i]; return Array(FM_STRUCT_ARRAY,Dimensions(cnt,1),cp,false,fNames); } ArrayVector ClassSubsrefCall(Interpreter* eval, const tree &t, Array r, FuncPtr val) { ArrayVector p; p.push_back(r); p.push_back(IndexExpressionToStruct(eval,t, r)); val->updateCode(eval); ArrayVector n = val->evaluateFunction(eval,p,1); return n; } // What is special here... Need to be able to do field indexing // ArrayVector ClassRHSExpression(Array r, const tree &t, Interpreter* eval) { tree s; Array q; Array n, p; int peerCnt; int dims; bool isVar; bool isFun; FuncPtr val; // Try and look up subsref, _unless_ we are already in a method // of this class if (!eval->inMethodCall(r.className().back())) if (ClassResolveFunction(eval,r,"subsref",val)) { // Overloaded subsref case return ClassSubsrefCall(eval,t,r,val); } ArrayVector rv; for (unsigned index=1;index < t.numchildren();index++) { if (!rv.empty()) throw Exception("Cannot reindex an expression that returns multiple values."); if (t.child(index).is(TOK_PARENS)) { ArrayVector m; const tree &s(t.child(index)); for (unsigned p=0;pmultiexpr(s.child(p),m); eval->subsindex(m); // m = eval->varExpressionList(t.child(index).children(),r); if (m.size() == 0) throw Exception("Expected indexing expression!"); else if (m.size() == 1) { q = r.getVectorSubset(m[0],eval); r = q; } else { q = r.getNDimSubset(m,eval); r = q; } } if (t.child(index).is(TOK_BRACES)) { ArrayVector m; const tree &s(t.child(index)); for (unsigned p=0;pmultiexpr(s.child(p),m); eval->subsindex(m); // m = eval->varExpressionList(t.child(index).children(),r); if (m.size() == 0) throw Exception("Expected indexing expression!"); else if (m.size() == 1) rv = r.getVectorContentsAsList(m[0],eval); else rv = r.getNDimContentsAsList(m,eval); if (rv.size() == 1) { r = rv[0]; rv = ArrayVector(); } else if (rv.size() == 0) { throw Exception("Empty expression!"); r = Array::emptyConstructor(); } } if (t.child(index).is('.')) { // This is where the classname chain comes into being. rvstring className(r.className()); for (int i=1;iexpression(t.child(index).first())); field = fname.getContentsAsString(); } catch (Exception &e) { throw Exception("dynamic field reference to structure requires a string argument"); } rv = r.getFieldAsList(field); if (rv.size() <= 1) { r = rv[0]; rv = ArrayVector(); } } } if (rv.empty()) rv.push_back(r); return rv; } void ClassAssignExpression(ArrayReference dst, const tree &t, const Array& value, Interpreter* eval) { FuncPtr val; if (!ClassResolveFunction(eval,*dst,"subsasgn",val)) throw Exception("The method 'subsasgn' is not defined for objects of class " + dst->className().back()); ArrayVector p; p.push_back(*dst); p.push_back(IndexExpressionToStruct(eval,t, *dst)); p.push_back(value); val->updateCode(eval); bool overload(eval->getStopOverload()); eval->setStopOverload(true); ArrayVector n = val->evaluateFunction(eval,p,1); eval->setStopOverload(overload); if (!n.empty()) *dst = n[0]; else eval->warningMessage(std::string("'subsasgn' for class ") + dst->className().back() + std::string(" did not return a value... operation has no effect.")); } // Ideally, this would be the only place where the class name is mangled. // However, currently, the same op is repeated in the Interface implementation // code. std::string ClassMangleName(std::string className, std::string funcName) { return "@" + className + ":" + funcName; }