/* * 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 "FunctionDef.hpp" #include "Interpreter.hpp" #include "Parser.hpp" #include "Exception.hpp" #include #include #include "SymbolTable.hpp" #include "Types.hpp" #include "MexInterface.hpp" #include #include #if HAVE_AVCALL #include "avcall.h" #endif #define MSGBUFLEN 2048 QMutex functiondefmutex; stringVector IdentifierList(tree t) { stringVector retval; for (unsigned index=0;indexgetContext(); context->pushScope("anonymous"); eval->pushDebug("anonymous","anonymous"); stringVector workspaceVars(workspace.getCompletions("")); for (int i=0;iinsertVariableLocally(workspaceVars[i],*workspace.findSymbol(workspaceVars[i])); int minCount = (inputs.size() < arguments.size()) ? inputs.size() : arguments.size(); for (int i=0;iinsertVariableLocally(arguments[i],inputs[i]); try { try { eval->multiexpr(code,outputs); } catch (InterpreterBreakException& e) { } catch (InterpreterContinueException& e) { } catch (InterpreterReturnException& e) { } context->popScope(); eval->popDebug(); } catch (Exception& e) { context->popScope(); eval->popDebug(); throw; } catch (InterpreterRetallException& e) { context->popScope(); eval->popDebug(); throw; } return outputs; } void AnonymousFunctionDef::initialize(const tree &t, Interpreter *eval) { name = t.text(); arguments = IdentifierList(t.first()); code = t.second(); scriptFlag = false; temporaryFlag = false; graphicsFunction = false; stringVector vars; VariableReferencesList(t.second(),vars); for (int i=0;igetContext()->lookupVariable(vars[i])); if (ptr.valid()) { // cout << "Captured VAR: " << vars[i] << "\r\n"; workspace.insertSymbol(vars[i],*ptr); } } } MFunctionDef::MFunctionDef() { functionCompiled = false; timeStamp = 0; localFunction = false; pcodeFunction = false; #warning - check pcode nestedFunction = false; capturedFunction = false; } MFunctionDef::~MFunctionDef() { } int MFunctionDef::inputArgCount() { if (arguments.size() == 0) return 0; if (arguments[arguments.size()-1] == "varargin") return -1; else return arguments.size(); } int MFunctionDef::outputArgCount() { if (returnVals.size() == 0) return 0; if (returnVals[returnVals.size()-1] == "varargout") return -1; else return returnVals.size(); } void MFunctionDef::printMe(Interpreter*eval) { stringVector tmp; char msgBuffer[MSGBUFLEN]; snprintf(msgBuffer,MSGBUFLEN,"Function name:%s\n",name.c_str()); eval->outputMessage(msgBuffer); eval->outputMessage("Function class: Compiled M function\n"); eval->outputMessage("returnVals: "); tmp = returnVals; int i; for (i=0;ioutputMessage(msgBuffer); } eval->outputMessage("\n"); eval->outputMessage("arguments: "); tmp = arguments; for (i=0;ioutputMessage(msgBuffer); } eval->outputMessage("\ncode: \n"); code.print(); } #warning - This design causes an unneccesary copy - should use readonly pass first void CaptureFunctionPointer(FuncPtr &val, Interpreter *walker, MFunctionDef *parent, ScopePtr &workspace) { if (val->type() == FM_M_FUNCTION) { MFunctionDef* mptr = (MFunctionDef*) val; if (mptr->nestedFunction && !mptr->capturedFunction) { MFunctionDef* optr = new MFunctionDef; (*optr) = (*mptr); Context* context = walker->getContext(); string myScope = context->scopeName(); context->bypassScope(1); string parentScope = context->scopeName(); context->restoreScope(1); if (!Scope::nests(parentScope,myScope)) { // Now capture the variables in our current scope for (int i=0;ivariablesAccessed.size();i++) { ArrayReference ptr(context->lookupVariable(optr->variablesAccessed[i])); if (ptr.valid()) { if (!workspace) workspace = new Scope("captured",false); workspace->insertVariable(optr->variablesAccessed[i],*ptr); } } optr->workspace = workspace; optr->capturedFunction = true; } val = optr; } } } void CaptureFunctionPointers(ArrayVector& outputs, Interpreter *walker, MFunctionDef *parent) { ScopePtr workspace = NULL; // First check for any for (int i=0;igetContext(); context->pushScope(name,nestedFunction); context->setVariablesAccessed(variablesAccessed); context->setLocalVariablesList(returnVals); if (capturedFunction && workspace) { stringVector workspaceVars(workspace->getCompletions("")); for (int i=0;iinsertVariableLocally(workspaceVars[i],*workspace->lookupVariable(workspaceVars[i])); } walker->pushDebug(fileName,name); // When the function is called, the number of inputs is // at sometimes less than the number of arguments requested. // Check the argument count. If this is a non-varargin // argument function, then use the following logic: minCount = 0; if (inputArgCount() != -1) { minCount = (inputs.size() < arguments.size()) ? inputs.size() : arguments.size(); for (int i=0;iinsertVariableLocally(arg,inputs[i]); } context->insertVariableLocally("nargin", Array::int32Constructor(minCount)); } else { // Count the number of supplied arguments int inputCount = inputs.size(); context->insertVariableLocally("nargin", Array::int32Constructor(inputCount)); // Get the number of explicit arguments int explicitCount = arguments.size() - 1; // For each explicit argument (that we have an input for), // insert it into the scope. minCount = (explicitCount < inputCount) ? explicitCount : inputCount; int i; for (i=0;iinsertVariableLocally(arg,inputs[i]); } inputCount -= minCount; // Put minCount...inputCount Array varg(FM_CELL_ARRAY); varg.vectorResize(inputCount); Array* dp = (Array *) varg.getReadWriteDataPointer(); for (i=0;iinsertVariableLocally("varargin",varg); } context->insertVariableLocally("nargout", Array::int32Constructor(nargout)); try { try { walker->block(code); } catch (InterpreterBreakException& e) { } catch (InterpreterContinueException& e) { } catch (InterpreterReturnException& e) { } warningIssued = false; if (outputArgCount() != -1) { // special case - if nargout == 0, and none of the // outputs are predefined, we don't do anything bool nonpredefed = true; for (int i=0;ilookupVariableLocally(returnVals[i]); nonpredefed = nonpredefed && (!ptr); } if ((nargout > 0) || ((nargout == 0) && (!nonpredefed))) { outputs = ArrayVector(); for (int i=0;ilookupVariableLocally(returnVals[i]); if (!ptr) outputs[i] = Array::emptyConstructor(); else outputs[i] = *ptr; if (!ptr && (i < nargout)) if (!warningIssued) { walker->warningMessage("one or more outputs not assigned in call (1)"); warningIssued = true; } } } } else { outputs = ArrayVector(); for (int i=0;ilookupVariableLocally(returnVals[i]); if (!ptr) outputs[i] = Array::emptyConstructor(); else outputs[i] = *ptr; if (!ptr && (i < nargout)) if (!warningIssued) { walker->warningMessage("one or more outputs not assigned in call (2)"); warningIssued = true; } } // Are there any outputs not yet filled? if (nargout > explicitCount) { Array varargout, *ptr; // Yes, get a pointer to the "vargout" variable that should be defined ptr = context->lookupVariableLocally("varargout"); if (!ptr) throw Exception("The special variable 'varargout' was not defined as expected"); varargout = *ptr; if (varargout.dataClass() != FM_CELL_ARRAY) throw Exception("The special variable 'varargout' was not defined as a cell-array"); // Get the data pointer const Array *dp = ((const Array*) varargout.getDataPointer()); // Get the length int varlen = varargout.getLength(); int toFill = nargout - explicitCount; if (toFill > varlen) throw Exception("Not enough outputs in varargout to satisfy call"); for (int i=0;ilookupVariableLocally("varargout"); if (ptr) { varargout = *ptr; if (varargout.dataClass() != FM_CELL_ARRAY) throw Exception("The special variable 'varargout' was not defined as a cell-array"); // Get the data pointer const Array *dp = ((const Array*) varargout.getDataPointer()); if (varargout.getLength() > 0) outputs << dp[0]; } } } // Check for arguments that were passed by reference, and // update their values. for (int i=0;ilookupVariableLocally(arg); if (ptr) inputs[i] = *ptr; } // Check the outputs for function pointers CaptureFunctionPointers(outputs,walker,this); if (capturedFunction && workspace) { stringVector workspaceVars(workspace->getCompletions("")); for (int i=0;ilookupVariableLocally(workspaceVars[i]); workspace->insertVariable(workspaceVars[i],*ptr); } } context->popScope(); walker->popDebug(); return outputs; } catch (Exception& e) { if (capturedFunction && workspace) { stringVector workspaceVars(workspace->getCompletions("")); for (int i=0;ilookupVariableLocally(workspaceVars[i]); workspace->insertVariable(workspaceVars[i],*ptr); } } context->popScope(); walker->popDebug(); throw; } catch (InterpreterRetallException& e) { if (capturedFunction && workspace) { stringVector workspaceVars(workspace->getCompletions("")); for (int i=0;ilookupVariableLocally(workspaceVars[i]); workspace->insertVariable(workspaceVars[i],*ptr); } } context->popScope(); walker->popDebug(); throw; } } static string ReadFileIntoString(FILE *fp) { struct stat st; clearerr(fp); fstat(fileno(fp),&st); long cpos = st.st_size; // Allocate enough for the text, an extra newline, and null char *buffer = (char*) calloc(cpos+2,sizeof(char)); int n = fread(buffer,sizeof(char),cpos,fp); buffer[n]='\n'; buffer[n+1]=0; string retval(buffer); free(buffer); return retval; } //MFunctionDef* ConvertParseTreeToMFunctionDef(tree t, string fileName) { // MFunctionDef *fp = new MFunctionDef; // fp->returnVals = IdentifierList(t.first()); // fp->name = t.second().text(); // fp->arguments = IdentifierList(t.child(2)); // fp->code = t.child(3); // fp->fileName = fileName; // return fp; //} // //MFunctionDef* ConvertParseTreeToMFunctionDefs(treeVector t, // string fileName) { // MFunctionDef* last = NULL; // for (int index = t.size()-1;index>=0;index--) { // MFunctionDef* rp = ConvertParseTreeToMFunctionDef(t[index],fileName); // if (index>0) // rp->localFunction = true; // else // rp->localFunction = false; // rp->nextFunction = last; // if (last) // last->prevFunction = rp; // last = rp; // } // return last; //} void RegisterNested(const tree &t, Interpreter *m_eval, MFunctionDef *parent) { if (t.is(TOK_NEST_FUNC)) { MFunctionDef *fp = new MFunctionDef; fp->localFunction = parent->localFunction; fp->nestedFunction = true; fp->returnVals = IdentifierList(t.first()); fp->name = parent->name + "/" + t.second().text(); fp->arguments = IdentifierList(t.child(2)); fp->code = t.child(3); VariableReferencesList(fp->code,fp->variablesAccessed); fp->fileName = parent->fileName; // Register any nested functions for the local functions m_eval->getContext()->insertFunction(fp,false); RegisterNested(fp->code,m_eval,fp); } else for (int i=0;i 1) && (htext[htext.size()-1] != '\n')) helpText.push_back(htext + "\n"); else helpText.push_back(htext); } } if (helpText.size() == 0) helpText.push_back(buffer); rewind(fp); try { // Read the file into a string string fileText = ReadFileIntoString(fp); Scanner S(fileText,fileName); Parser P(S); tree pcode = P.Process(); fclose(fp); if (pcode.is(TOK_FUNCTION_DEFS)) { scriptFlag = false; // Get the main function.. tree MainFuncCode = pcode.first(); returnVals = IdentifierList(MainFuncCode.first()); // The name is mangled by the interpreter... We ignore the // name as parsed in the function. // name = MainFuncCode.second().text(); arguments = IdentifierList(MainFuncCode.child(2)); code = MainFuncCode.child(3); VariableReferencesList(code,variablesAccessed); // Register any nested functions RegisterNested(code,m_eval,this); localFunction = false; // Process the local functions for (int index = 1;index < pcode.numchildren();index++) { tree LocalFuncCode = pcode.child(index); MFunctionDef *fp = new MFunctionDef; fp->localFunction = true; fp->returnVals = IdentifierList(LocalFuncCode.first()); fp->name = name + "/" + LocalFuncCode.second().text(); fp->arguments = IdentifierList(LocalFuncCode.child(2)); fp->code = LocalFuncCode.child(3); VariableReferencesList(fp->code,fp->variablesAccessed); fp->fileName = fileName; // Register any nested functions for the local functions // local functions are not marked as temporary. This yields // clutter in the name space, but solves the troublesome // issue of local functions being flushed by the CD command. m_eval->getContext()->insertFunction(fp,false); // qDebug() << "Registering " << QString::fromStdString(fp->name); RegisterNested(fp->code,m_eval,this); } functionCompiled = true; } else { scriptFlag = true; functionCompiled = true; code = pcode.first(); } } catch (Exception &e) { if (fp) fclose(fp); functionCompiled = false; throw; } return true; } return false; } bool StatementTypeNode(tree t) { return (t.is('=') || t.is(TOK_MULTI) || t.is(TOK_SPECIAL) || t.is(TOK_FOR) || t.is(TOK_WHILE) || t.is(TOK_IF) || t.is(TOK_BREAK) || t.is(TOK_CONTINUE) || t.is(TOK_DBSTEP) || t.is(TOK_RETURN) || t.is(TOK_SWITCH) || t.is(TOK_TRY) || t.is(TOK_QUIT) || t.is(TOK_RETALL) || t.is(TOK_KEYBOARD) || t.is(TOK_GLOBAL) || t.is(TOK_PERSISTENT) || t.is(TOK_EXPR)); } // Find the smallest line number larger than the argument // if our line number is larger than the target, then we // void TreeLine(tree t, unsigned &bestLine, unsigned lineTarget) { if (!t.valid()) return; // Nested functions are tracked separately - so that we do not // check them for line numbers if (t.is(TOK_NEST_FUNC)) return; if (StatementTypeNode(t)) { unsigned myLine = (t.context() & 0xffff); if ((myLine >= lineTarget) && (myLine < bestLine)) bestLine = myLine; } for (int i=0;iputString(fptr->name.c_str()); s->putString(fptr->fileName.c_str()); s->putBool(fptr->scriptFlag); s->putStringVector(fptr->arguments); s->putStringVector(fptr->returnVals); s->putBool(fptr->functionCompiled); s->putBool(fptr->localFunction); s->putBool(fptr->nestedFunction); s->putBool(fptr->capturedFunction); s->putStringVector(fptr->helpText); s->putStringVector(fptr->variablesAccessed); FreezeScope(fptr->workspace,s); FreezeTree(fptr->code,s); } MFunctionDef* ThawMFunction(Serialize *s) { MFunctionDef *t = new MFunctionDef(); t->name = std::string(s->getString()); t->fileName = std::string(s->getString()); t->scriptFlag = s->getBool(); t->arguments = s->getStringVector(); t->returnVals = s->getStringVector(); t->functionCompiled = s->getBool(); t->localFunction = s->getBool(); t->nestedFunction = s->getBool(); t->capturedFunction = s->getBool(); t->helpText = s->getStringVector(); t->variablesAccessed = s->getStringVector(); t->workspace = ThawScope(s); t->code = ThawTree(s); return t; } BuiltInFunctionDef::BuiltInFunctionDef() { } BuiltInFunctionDef::~BuiltInFunctionDef() { } int BuiltInFunctionDef::inputArgCount() { return argCount; } int BuiltInFunctionDef::outputArgCount() { return retCount; } void BuiltInFunctionDef::printMe(Interpreter *eval) { stringVector tmp; char msgBuffer[MSGBUFLEN]; snprintf(msgBuffer,MSGBUFLEN," Function name:%s\n",name.c_str()); eval->outputMessage(msgBuffer); eval->outputMessage(" Function class: Built in\n"); snprintf(msgBuffer,MSGBUFLEN," Return count: %d\n",retCount); eval->outputMessage(msgBuffer); snprintf(msgBuffer,MSGBUFLEN," Argument count: %d\n",argCount); eval->outputMessage(msgBuffer); } ArrayVector BuiltInFunctionDef::evaluateFunction(Interpreter *walker, ArrayVector& inputs, int nargout) { ArrayVector outputs; int i; walker->pushDebug(name,"built in"); try { outputs = fptr(nargout,inputs); walker->popDebug(); return outputs; } catch(Exception& e) { walker->popDebug(); throw; } catch (InterpreterRetallException& e) { walker->popDebug(); throw; } } SpecialFunctionDef::SpecialFunctionDef() { } SpecialFunctionDef::~SpecialFunctionDef() { } ArrayVector SpecialFunctionDef::evaluateFunction(Interpreter *walker, ArrayVector& inputs, int nargout) { ArrayVector outputs; walker->pushDebug(name,"built in"); try { outputs = fptr(nargout,inputs,walker); walker->popDebug(); return outputs; } catch(Exception& e) { walker->popDebug(); throw; } catch (InterpreterRetallException& e) { walker->popDebug(); throw; } } void SpecialFunctionDef::printMe(Interpreter *eval) { } FunctionDef::FunctionDef() { scriptFlag = false; graphicsFunction = false; temporaryFlag = false; refcount = 0; } void FunctionDef::lock() { QMutexLocker lockit(&functiondefmutex); refcount++; } void FunctionDef::unlock() { QMutexLocker lockit(&functiondefmutex); refcount--; } bool FunctionDef::referenced() { QMutexLocker lockit(&functiondefmutex); return (refcount>0); } FunctionDef::~FunctionDef() { } ImportedFunctionDef::ImportedFunctionDef(GenericFuncPointer address_arg, stringVector types_arg, stringVector arguments_arg, treeVector sizeChecks, std::string retType_arg) { address = address_arg; types = types_arg; arguments = arguments_arg; sizeCheckExpressions = sizeChecks; retType = retType_arg; /* * Set up the cif... */ argCount = types_arg.size(); if (retType == "void") retCount = 0; else retCount = 1; } ImportedFunctionDef::~ImportedFunctionDef() { } void ImportedFunctionDef::printMe(Interpreter *) { } Class mapTypeNameToClass(std::string name) { if (name == "logical") return FM_LOGICAL; if (name == "uint8") return FM_UINT8; if (name == "int8") return FM_INT8; if (name == "uint16") return FM_UINT16; if (name == "int16") return FM_INT16; if (name == "uint32") return FM_UINT32; if (name == "int32") return FM_INT32; if (name == "uint64") return FM_UINT64; if (name == "int64") return FM_INT64; if (name == "float") return FM_FLOAT; if (name == "complex") return FM_COMPLEX; if (name == "double") return FM_DOUBLE; if (name == "dcomplex") return FM_DCOMPLEX; if (name == "string") return FM_STRING; if (name == "void") return FM_UINT32; throw Exception("unrecognized type " + name + " in imported function setup"); } ArrayVector ImportedFunctionDef::evaluateFunction(Interpreter *walker, ArrayVector& inputs, int nargout) { #if HAVE_AVCALL walker->pushDebug(name,"imported"); /** * To actually evaluate the function, we have to process each of * the arguments and get them into the right form. */ int i; for (i=0;igetContext(); context->pushScope("temp"); walker->pushDebug(name,"bounds check"); try { for (i=0;iinsertVariableLocally(arguments[i],inputs[i]); } else { string nme(arguments[i]); nme.erase(nme.begin()); context->insertVariableLocally(nme,inputs[i]); } } /* * Next, evaluate each size check expression */ for (i=0;iexpression(sizeCheckExpressions[i])); ret.promoteType(FM_INT32); int len; len = ret.getContentsAsIntegerScalar(); if (len != inputs[i].getLength()) { throw Exception("array input " + arguments[i] + " length different from computed bounds" + " check length"); } } } } catch (Exception& e) { context->popScope(); throw; } catch (InterpreterRetallException& e) { context->popScope(); throw; } context->popScope(); walker->popDebug(); } /** * Allocate an array of pointers to store for variables passed * by reference. */ void **refPointers; refPointers = (void**) malloc(sizeof(void*)*passByReference); /** * Allocate an array of value pointers... */ void **values; values = (void**) malloc(sizeof(void*)*inputs.size()); int ptr = 0; for (i=0;ipopDebug(); return singleArrayVector(retArray); #else throw Exception("Support for the import command requires that the avcall library be installed. FreeMat was compiled without this library being available, and hence imported functions are unavailable. To enable imported commands, please install avcall and recompile FreeMat."); #endif } MexFunctionDef::MexFunctionDef(std::string fullpathname) { fullname = fullpathname; importSuccess = false; lib = new DynLib(fullname); try { address = (mexFuncPtr) lib->GetSymbol("mexFunction"); importSuccess = true; } catch (Exception& e) { } } bool MexFunctionDef::LoadSuccessful() { return importSuccess; } MexFunctionDef::~MexFunctionDef() { } void MexFunctionDef::printMe(Interpreter *) { } ArrayVector MexFunctionDef::evaluateFunction(Interpreter *walker, ArrayVector& inputs, int nargout) { // Convert arguments to mxArray mxArray** args = new mxArray*[inputs.size()]; for (int i=0;i