/** * Fit a variable into an argument according to the argument rule. If necessary and * appropriate, we do type conversion or type promotion. * * * * @todo To conform to the old code we change the required type of the incoming * variable wrapper here. We need to change this so that we do not change * the wrapper here, but make sure that if the argument variable is inserted * in a member variable or container element slot, that the slot variable * wrapper, which should be unique (not the same as the incoming variable * wrapper), has the right required type. */ Argument ArgumentRule::fitArgument( Argument& arg, bool once ) const { RevPtr<RevVariable> theVar = arg.getVariable(); if ( evalType == BY_VALUE || theVar->isWorkspaceVariable() || theVar->getRevObject().isConstant() ) { once = true; } for ( std::vector<TypeSpec>::const_iterator it = argTypeSpecs.begin(); it != argTypeSpecs.end(); ++it ) { if ( evalType == BY_VALUE ) { if ( theVar->getRevObject().isType( *it ) ) { RevPtr<RevVariable> valueVar = RevPtr<RevVariable>( new RevVariable(theVar->getRevObject().clone(),arg.getLabel()) ); return Argument( valueVar, getArgumentLabel(), false ); } else if ( theVar->getRevObject().isConvertibleTo( *it, once ) != -1) { // Fit by type conversion. For now, we also modify the type of the incoming variable wrapper. RevObject* convertedObject = theVar->getRevObject().convertTo( *it ); RevPtr<RevVariable> valueVar = RevPtr<RevVariable>( new RevVariable(convertedObject,arg.getLabel()) ); return Argument( valueVar, getArgumentLabel(), false ); } } // if (by-value) else { if ( theVar->getRevObject().isType( *it ) ) { // For now, change the required type of the incoming variable wrapper theVar->setRequiredTypeSpec( *it ); if ( !isEllipsis() ) { return Argument( theVar, getArgumentLabel(), evalType == BY_CONSTANT_REFERENCE ); } else { return Argument( theVar, arg.getLabel(), true ); } } else if ( theVar->getRevObject().isConvertibleTo( *it, once ) != -1 && (*it).isDerivedOf( theVar->getRequiredTypeSpec() )) { // Fit by type conversion. For now, we also modify the type of the incoming variable wrapper. RevObject* convertedObject = theVar->getRevObject().convertTo( *it ); theVar->replaceRevObject( convertedObject ); theVar->setRequiredTypeSpec( *it ); if ( !isEllipsis() ) { return Argument( theVar, getArgumentLabel(), false ); } else { return Argument( theVar, arg.getLabel(), false ); } } else { // Fit by type conversion function const TypeSpec& typeFrom = theVar->getRevObject().getTypeSpec(); const TypeSpec& typeTo = *it; // create the function name std::string functionName = "_" + typeFrom.getType() + "2" + typeTo.getType(); // Package arguments std::vector<Argument> args; Argument theArg = Argument( theVar, "arg" ); args.push_back( theVar ); Environment& env = Workspace::globalWorkspace(); try { Function* func = env.getFunction(functionName, args, once).clone(); // Allow the function to process the arguments func->processArguments( args, once ); // Set the execution environment of the function func->setExecutionEnviroment( &env ); // Evaluate the function RevPtr<RevVariable> conversionVar = func->execute(); // free the memory delete func; conversionVar->setHiddenVariableState( true ); conversionVar->setRequiredTypeSpec( *it ); return Argument( conversionVar, getArgumentLabel(), evalType == BY_CONSTANT_REFERENCE ); } catch (RbException e) { // we do nothing here } } // else (type conversion function) } // else (not by-value) } std::cerr << "Once = " << (once ? "TRUE" : "FALSE") << std::endl; throw RbException( "Argument type mismatch fitting a " + theVar->getRevObject().getType() + " argument to formal " + getArgumentTypeSpec()[0].getType() + " " + getArgumentLabel() ); }
/** * @brief Process arguments * * This function processes arguments based on argument rules. First it deletes * any previously stored arguments. If the matching of the new arguments * succeeds, the processedArguments will be set to the new vector of processed * arguments and the function returns true. Any subsequent calls to execute() * will then use the processed arguments. You can also call the function with * the arguments directly, in which case processArguments will be called first * before the operation is actually performed. * * In matching arguments to argument rules, we use the same rules as in R with * the addition that types are also used in the matching process, after arguments * have been reordered as in R. The FunctionTable ensure that all argument rules * are distinct. However, several functions can nevertheless match the same * arguments because of the inheritance hierarchy. In these clases, the closest * match is chosen based on the first argument, then on the second, etc. * * The function returns a match score based on how closely the arguments match the * rules, with 0 being perfect match, 1 being a match to an immediate base class type, * 2 a match to a grandparent class, etc. A large number is used for arguments that * need type conversion. * * The evaluateOnce parameter MemberObject whether the function is to be evaluated once, * immediately after the call to processArguments, or whether the processed arguments * will be used in repeated function calls in a function node. Argument matching is * based on current values in the first case, but on the wrapper type in the second. * * @todo Labels need to be stored for ellipsis arguments. * * These are the argument matching rules: * * 1. If the last argument rule is an ellipsis, and it is the kth argument passed * in, then all arguments passed in, from position k to the end, are wrapped * in a single ContainerNode object. These arguments are not matched to any * rules. * 2. The remaining arguments are matched to labels using exact matching. If the * type does not match the type of the rule, it is an error. * 3. The remaining arguments are matched to any remaining slots using partial * matching. If there is ambiguity or the types do not match, it is an error. * 4. The remaining arguments are used for the empty slots in the order they were * passed in. If the types do not match, it is an error. * 5. Any remaining empty slots are filled with default values stored in the argument * rules (we use copies of the values, of course). * 6. If there are still empty slots, the arguments do not match the rules. * * @todo Fredrik: The code and the logic has been changed without changing the comments, so these * are out of date. Also note that the argument matching is problematic for unlabeled * arguments (order can be changed based on argument types, which may cause unintended * consequences). Furthermore, there is redundant code left from the old implementation. * Finally, the ellipsis arguments no longer have to be last among the rules, but they * are still the last arguments after processing. * * @todo Fredrik: Static and dynamic type conversion added, but partly hack-ish, so the implementation * needs to be revised */ void Function::processArguments( const std::vector<Argument>& passedArgs, bool once ) { /********************* 0. Initialization **********************/ /* Get my own copy of the argument vector */ std::vector<Argument> pArgs = passedArgs; /* Get the argument rules */ const ArgumentRules& theRules = getArgumentRules(); /* Get the number of argument rules */ size_t nRules = theRules.size(); /* Clear previously processed arguments */ args.clear(); /* Keep track of which arguments we have used, and which argument slots we have filled, and with what passed arguments */ std::vector<bool> taken = std::vector<bool>( passedArgs.size(), false ); std::vector<bool> filled = std::vector<bool>( nRules, false ); std::vector<size_t> passedArgIndex = std::vector<size_t>( nRules, 1000 ); std::vector<Argument> ellipsisArgs; /********************* 1. Do exact matching **********************/ /* Do exact matching of labels */ for(size_t i=0; i<passedArgs.size(); i++) { /* Test if swallowed by ellipsis; if so, we can quit because the remaining passedArgs will also be swallowed */ if ( taken[i] ) { break; } /* Skip if no label */ if ( passedArgs[i].getLabel().size() == 0 ) { continue; } /* Check for matches in all regular rules (we assume that all labels are unique; this is checked by FunctionTable) */ for (size_t j=0; j<nRules; j++) { std::vector<std::string> aliases = theRules[j].getArgumentAliases(); for(size_t k=0; k < aliases.size(); k++) { if ( passedArgs[i].getLabel() == aliases[k] ) { if ( filled[j] ) { throw RbException( "Duplicate argument labels '" + passedArgs[i].getLabel() ); } pArgs[i] = theRules[j].fitArgument( pArgs[i], once ); taken[i] = true; filled[j] = true; passedArgIndex[j] = static_cast<int>( i ); // We got an exact match -> we can skip the other labels for checking j = nRules; break; } } } } /********************* 2. Do partial matching **********************/ /* Do partial matching of labels */ for(size_t i=0; i<passedArgs.size(); i++) { /* Skip if already matched */ if ( taken[i] ) { continue; } /* Skip if no label */ if ( passedArgs[i].getLabel().size() == 0 ) { continue; } /* Initialize match index and number of matches */ int nMatches = 0; int matchRule = -1; std::string label; /* Try all rules */ for (size_t j=0; j<nRules; j++) { std::vector<std::string> aliases = theRules[j].getArgumentAliases(); for(size_t k=0; k < aliases.size(); k++) { if ( !filled[j] && aliases[k].compare(0, passedArgs[i].getLabel().size(), passedArgs[i].getLabel()) == 0 ) { ++nMatches; matchRule = static_cast<int>( j ); label = aliases[k]; } } } if (nMatches > 1) { throw RbException( "Argument label '" + passedArgs[i].getLabel() + "' matches mutliple parameter labels." ); } else if (nMatches < 1) { throw RbException( "Argument label '" + passedArgs[i].getLabel() + "' matches no untaken parameter labels." ); } if ( nMatches == 1) { pArgs[i].setLabel(label); pArgs[i] = theRules[matchRule].fitArgument( pArgs[i], once ); taken[i] = true; filled[matchRule] = true; passedArgIndex[matchRule] = static_cast<int>( i ); } } /********************* 3. Fill with unused passedArgs **********************/ /* Fill in empty slots using the remaining args in order */ for(size_t i=0; i<passedArgs.size(); i++) { /* Skip if already matched */ if ( taken[i] ) { continue; } /* Find first empty slot and try to fit argument there */ for (size_t j=0; j<nRules; j++) { if ( filled[j] == false && ( (!theRules[j].isEllipsis() && passedArgs[i].getLabel().size() == 0) || (theRules[j].isEllipsis()) ) ) { Argument &arg = const_cast<Argument&>(passedArgs[i]); double penalty = theRules[j].isArgumentValid( arg, once ); if ( penalty != -1 ) { if(theRules[j].getArgumentAliases().size() > 1) { throw RbException("Could not determine argument label for parameter '" + theRules[j].getArgumentLabel() + "'."); } pArgs[i].setLabel( theRules[j].getArgumentLabel() ); pArgs[i] = theRules[j].fitArgument( pArgs[i], once ); taken[i] = true; if ( !theRules[j].isEllipsis() ) { filled[j] = true; passedArgIndex[j] = static_cast<int>( i ); } else { ellipsisArgs.push_back( pArgs[i] ); } break; } } } /* Final test if we found a match */ if ( !taken[i] ) { throw RbException("Superfluous argument of type '" + passedArgs[i].getVariable()->getRevObject().getType() + "' and name '" + passedArgs[i].getLabel() + "' passed to function '" + getType() + "'."); } } /********************* 4. Fill with default values **********************/ /* Fill in empty slots using default values */ for(size_t i=0; i<nRules; i++) { if ( filled[i] == true || theRules[i].isEllipsis()) { continue; } // we just leave the optional arguments empty if ( !theRules[i].hasDefault() ) { throw RbException("No argument found for parameter '" + theRules[i].getArgumentLabel() + "'."); } const ArgumentRule& theRule = theRules[i]; RevPtr<RevVariable> theVar = theRule.getDefaultVariable().clone(); theVar->setName( "." + theRule.getArgumentLabel() ); theVar->setRequiredTypeSpec( theRule.getDefaultVariable().getRequiredTypeSpec() ); size_t idx = pArgs.size(); passedArgIndex[i] = idx; pArgs.push_back( Argument( theVar, theRule.getArgumentLabel(), theRule.getEvaluationType() != ArgumentRule::BY_CONSTANT_REFERENCE ) ); } argsProcessed = true; /********************* 5. Insert arguments into argument list **********************/ for (size_t j=0; j<nRules; j++) { if ( passedArgIndex[j] < 1000 ) { args.push_back( pArgs[ passedArgIndex[j] ] ); } } /********************* 6. Insert ellipsis arguments **********************/ for (std::vector<Argument>::iterator i = ellipsisArgs.begin(); i != ellipsisArgs.end(); i++) { args.push_back( *i ); } }