PLearn 0.1
|
00001 // -*- C++ -*- 00002 00003 // PythonExtension.h 00004 // Copyright (C) 2007 Xavier Saint-Mleux, ApSTAT Technologies inc. 00005 00006 // Redistribution and use in source and binary forms, with or without 00007 // modification, are permitted provided that the following conditions are met: 00008 // 00009 // 1. Redistributions of source code must retain the above copyright 00010 // notice, this list of conditions and the following disclaimer. 00011 // 00012 // 2. Redistributions in binary form must reproduce the above copyright 00013 // notice, this list of conditions and the following disclaimer in the 00014 // documentation and/or other materials provided with the distribution. 00015 // 00016 // 3. The name of the authors may not be used to endorse or promote 00017 // products derived from this software without specific prior written 00018 // permission. 00019 // 00020 // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 00021 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 00022 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 00023 // NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 00024 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 00025 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 00026 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 00027 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 00028 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 00029 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00030 // 00031 // This file is part of the PLearn library. For more information on the PLearn 00032 // library, go to the PLearn Web site at www.plearn.org 00033 00034 #define PL_LOG_MODULE_NAME "PythonExtension" 00035 00036 #include <plearn/python/PythonExtension.h> 00037 #include <plearn/python/PythonObjectWrapper.h> 00038 #include <plearn/base/RemoteDeclareMethod.h> 00039 #include <plearn/base/HelpSystem.h> 00040 #include <plearn/base/TypeFactory.h> 00041 #include <plearn/base/PMemPool.h> 00042 #include <plearn/base/stringutils.h> 00043 #include <plearn/vmat/VMatrix.h> 00044 #include <plearn/io/pl_log.h> 00045 #include <plearn/sys/procinfo.h> 00046 00047 namespace PLearn { 00048 00049 // Trampoline for global PLearn 'remote' functions 00050 PyObject* pythonGlobalFuncTramp(PyObject* self, PyObject* args) 00051 { 00052 DBG_MODULE_LOG << "pythonGlobalFuncTramp(" << PythonObjectWrapper(self) 00053 << ", " << PythonObjectWrapper(args) << ')' << endl; 00054 RemoteTrampoline* t= 00055 static_cast<RemoteTrampoline*>(PyCObject_AsVoidPtr(self)); 00056 int nas= PyTuple_GET_SIZE(args); 00057 TVec<PythonObjectWrapper> as; 00058 for(int i= 0; i < nas; ++i) 00059 as.push_back(PythonObjectWrapper(PyTuple_GET_ITEM(args, i))); 00060 00061 PythonObjectWrapper::gc_collect1(); 00062 00063 try 00064 { 00065 PythonObjectWrapper returned_value= t->call(0, as); 00066 PyObject* to_return= returned_value.getPyObject(); 00067 Py_XINCREF(to_return); 00068 return to_return; 00069 } 00070 catch(const PLearnError& e) 00071 { 00072 PyErr_SetString(the_PLearn_python_exception, e.message().c_str()); 00073 return 0; 00074 } 00075 catch(const std::exception& e) 00076 { 00077 PyErr_SetString(PyExc_Exception, e.what()); 00078 return 0; 00079 } 00080 catch(...) 00081 { 00082 PyErr_SetString(PyExc_Exception, 00083 "Caught unknown C++ exception"); 00084 return 0; 00085 } 00086 } 00087 00088 // the global funcs (storage never reclaimed) 00089 static PObjectPool<PyMethodDef> pyfuncs(50); 00090 static TVec<string> funcs_help; 00091 00092 void injectPLearnGlobalFunctions(PyObject* env) 00093 { 00094 const RemoteMethodMap::MethodMap& global_funcs= 00095 getGlobalFunctionMap().getMap(); 00096 00097 for(RemoteMethodMap::MethodMap::const_iterator it= 00098 global_funcs.begin(); 00099 it != global_funcs.end(); ++it) 00100 { 00101 PyObject* self= 00102 PyCObject_FromVoidPtr(it->second, NULL); 00103 00104 PyMethodDef* py_method= pyfuncs.allocate(); 00105 py_method->ml_name= 00106 const_cast<char*>(it->first.first.c_str()); 00107 py_method->ml_meth= pythonGlobalFuncTramp; 00108 py_method->ml_flags= METH_VARARGS; 00109 funcs_help.push_back( 00110 HelpSystem::helpOnFunction(it->first.first.c_str(), 00111 it->first.second)); 00112 py_method->ml_doc= const_cast<char*>(funcs_help.last().c_str()); 00113 00114 /* module= env if env is a module; NULL otherwise */ 00115 PyObject* module= 0; 00116 if(PyModule_Check(env)) 00117 module= env; 00118 00119 // N.B.: module == NULL works on python2.3, 2.4 and 2.5, but is not 00120 // documented 00121 PyObject* pyfunc= 00122 PyCFunction_NewEx(py_method, self, module); 00123 00124 if(pyfunc) 00125 PyObject_SetAttrString(env, 00126 py_method->ml_name, 00127 pyfunc); 00128 else 00129 PLERROR("Cannot inject global function " 00130 "'%s' into python.", 00131 py_method->ml_name); 00132 } 00133 } 00134 00135 00136 void injectPLearnClasses(PyObject* module) 00137 { 00138 PythonGlobalInterpreterLock gil; // For thread-safety 00139 PythonObjectWrapper::initializePython(); 00140 00141 if(!PyModule_Check(module)) 00142 PLERROR("In injectPLearnClasses : " 00143 "module param. should be a python module."); 00144 // import python class for wrapping PLearn objects 00145 string importcode= "\nfrom plearn.pybridge.wrapped_plearn_object " 00146 "import *\n"; 00147 PyObject_SetAttrString(module, const_cast<char*>("__builtins__"), PyEval_GetBuiltins()); 00148 PyObject* res= PyRun_String(importcode.c_str(), Py_file_input, 00149 PyModule_GetDict(module), PyModule_GetDict(module)); 00150 if(!res) 00151 { 00152 Py_DECREF(module); 00153 if(PyErr_Occurred()) PyErr_Print(); 00154 PLERROR("in injectPLearnClasses : cannot import plearn.pybridge.wrapped_plearn_object."); 00155 } 00156 Py_DECREF(res); 00157 00158 string wrapper_name= "WrappedPLearnObject"; 00159 // now find the class in the env. 00160 typedef map<string, PyObject*> env_t; 00161 env_t env= PythonObjectWrapper( 00162 PyModule_GetDict(module), 00163 PythonObjectWrapper::transfer_ownership).as<env_t>(); 00164 env_t::iterator clit= env.find(wrapper_name); 00165 if(clit == env.end()) 00166 PLERROR("in injectPLearnClasses : " 00167 "class %s not defined " 00168 "in plearn.pybridge.wrapped_plearn_object", 00169 wrapper_name.c_str()); 00170 PyObject* wrapper= clit->second; 00171 00172 //inject unref and newCPPObj methods 00173 PyMethodDef* py_method= &PythonObjectWrapper::m_unref_method_def; 00174 py_method->ml_name = const_cast<char*>("_unref"); 00175 py_method->ml_meth = PythonObjectWrapper::python_del; 00176 py_method->ml_flags = METH_VARARGS; 00177 py_method->ml_doc = const_cast<char*>("Injected unref function from PythonObjectWrapper; " 00178 "DO NOT USE THIS FUNCTION! IT MAY DEALLOCATE THE PLEARN OBJECT!"); 00179 PyObject* py_funcobj= PyCFunction_NewEx(py_method, NULL, NULL); 00180 PyObject* py_methobj= PyMethod_New(py_funcobj, NULL, wrapper); 00181 Py_XDECREF(py_funcobj); 00182 if(!py_funcobj || !py_methobj) 00183 { 00184 Py_DECREF(module); 00185 Py_XDECREF(py_methobj); 00186 PLERROR("in injectPLearnClasses : " 00187 "can't inject method '%s' (i.e. __del__)", 00188 py_method->ml_name); 00189 } 00190 PyObject_SetAttrString(wrapper, py_method->ml_name, py_methobj); 00191 Py_DECREF(py_methobj); 00192 00193 // inject 'newCPPObj' and 'refCPPObj' class methods 00194 TVec<PyMethodDef*> classmethods(2); 00195 classmethods[0]= &PythonObjectWrapper::m_newCPPObj_method_def; 00196 classmethods[0]->ml_name = const_cast<char*>("_newCPPObj"); 00197 classmethods[0]->ml_meth = PythonObjectWrapper::newCPPObj; 00198 classmethods[0]->ml_flags = METH_VARARGS; 00199 classmethods[0]->ml_doc = const_cast<char*>("Injected new function from PythonObjectWrapper; " 00200 "DO NOT USE THIS FUNCTION!"); 00201 classmethods[1]= &PythonObjectWrapper::m_refCPPObj_method_def; 00202 classmethods[1]->ml_name = const_cast<char*>("_refCPPObj"); 00203 classmethods[1]->ml_meth = PythonObjectWrapper::refCPPObj; 00204 classmethods[1]->ml_flags = METH_VARARGS; 00205 classmethods[1]->ml_doc = const_cast<char*>("Injected new function from PythonObjectWrapper; " 00206 "DO NOT USE THIS FUNCTION!"); 00207 00208 for(TVec<PyMethodDef*>::iterator mit= classmethods.begin(); 00209 mit != classmethods.end(); ++mit) 00210 { 00211 py_method= *mit; 00212 py_funcobj= PyCFunction_NewEx(py_method, NULL, NULL); 00213 py_methobj= PyMethod_New(py_funcobj, NULL, wrapper); 00214 Py_XDECREF(py_funcobj); 00215 if(!py_funcobj || !py_methobj) 00216 { 00217 Py_DECREF(module); 00218 Py_XDECREF(py_methobj); 00219 PLERROR("in injectPLearnClasses : " 00220 "can't inject method '%s' (i.e. C++'s new)", 00221 py_method->ml_name); 00222 } 00223 PyObject_SetAttrString(wrapper, py_method->ml_name, py_methobj); 00224 Py_DECREF(py_methobj); 00225 00226 string classmethodname= wrapper_name+"."+py_method->ml_name; 00227 res= PyRun_String((classmethodname 00228 +"= classmethod("+classmethodname+".im_func)").c_str(), 00229 Py_file_input, 00230 PyModule_GetDict(module), PyModule_GetDict(module)); 00231 Py_DECREF(res); 00232 if (PyErr_Occurred()) 00233 { 00234 Py_DECREF(module); 00235 PyErr_Print(); 00236 PLERROR("in injectPLearnClasses : error making " 00237 "newCPPObj a classmethod."); 00238 } 00239 } 00240 00241 if(0 != PyType_Ready(reinterpret_cast<PyTypeObject*>(wrapper))) 00242 PLERROR("in injectPLearnClasses : " 00243 "failed PyType_Ready on wrapper class."); 00244 00245 if(!PyCallable_Check(wrapper)) 00246 PLERROR("in injectPLearnClasses : " 00247 "%s is not callable [not a class?]", 00248 wrapper_name.c_str()); 00249 00250 PyObject* the_pyclass= 0; 00251 const TypeMap& tp_map= TypeFactory::instance().getTypeMap(); 00252 for(TypeMap::const_iterator tit= tp_map.begin(); 00253 tit != tp_map.end(); ++tit) 00254 { 00255 if(!tit->second.constructor) 00256 continue; //skip abstract classes 00257 00258 string actual_wrapper_name= wrapper_name; 00259 Object* temp_obj= tit->second.constructor(); 00260 // use different wrapper for VMats (w/ len, getitem, etc.) 00261 if(dynamic_cast<VMatrix*>(temp_obj)) 00262 actual_wrapper_name= "WrappedPLearnVMat"; 00263 delete temp_obj; 00264 00265 // create new python type deriving from WrappedPLearnObject 00266 string classname= tit->first; 00267 string class_help_text= HelpSystem::helpOnClass(classname); 00268 search_replace(class_help_text, "\"\"\"", "\\\"\\\"\\\""); 00269 string pyclassname= classname; 00270 search_replace(pyclassname, " ", "_"); 00271 search_replace(pyclassname, "<", "_"); 00272 search_replace(pyclassname, ">", "_"); 00273 string derivcode= string("\nclass ") 00274 + pyclassname + "(" + actual_wrapper_name + "):\n" 00275 " \"\"\" \n" + class_help_text + "\n \"\"\"\n" 00276 " def __new__(cls,*args,**kwargs):\n" 00277 " #get_plearn_module().loggingControl(500, ['__ALL__'])" 00278 " #print '** "+pyclassname+".__new__',args,kwargs\n" 00279 " #import sys; sys.stdout.flush()\n" 00280 " obj= object.__new__(cls)\n" 00281 " if '_cptr' not in kwargs:\n" 00282 " obj._cptr= cls._newCPPObj('"+classname+"')\n" 00283 " cls._refCPPObj(obj)\n" 00284 " return obj\n"; 00285 PyObject* res= PyRun_String(derivcode.c_str(), 00286 Py_file_input, 00287 PyModule_GetDict(module), PyModule_GetDict(module)); 00288 00289 Py_XDECREF(res); 00290 env= PythonObjectWrapper( 00291 PyModule_GetDict(module), 00292 PythonObjectWrapper::transfer_ownership).as<env_t>(); 00293 clit= env.find(pyclassname); 00294 if(clit == env.end()) 00295 PLERROR("in injectPLearnClasses : " 00296 "Cannot create new python class deriving from " 00297 "%s (%s).", 00298 actual_wrapper_name.c_str(), 00299 classname.c_str()); 00300 00301 //set option names 00302 OptionList& options= tit->second.getoptionlist_method(); 00303 unsigned int nopts= options.size(); 00304 //set<string> optionnames; 00305 map<string, vector<string> > optionnames; 00306 for(unsigned int i= 0; i < nopts; ++i) 00307 //optionnames.insert(options[i]->optionname()); 00308 optionnames[options[i]->optionname()]= options[i]->flagStrings(); 00309 the_pyclass= clit->second; 00310 if(-1==PyObject_SetAttrString(the_pyclass, const_cast<char*>("_optionnames"), 00311 PythonObjectWrapper(optionnames).getPyObject())) 00312 { 00313 Py_DECREF(module); 00314 if (PyErr_Occurred()) PyErr_Print(); 00315 PLERROR("in injectPLearnClasses : " 00316 "cannot set attr _optionnames for class %s", 00317 classname.c_str()); 00318 } 00319 00320 // inject all declared methods 00321 const RemoteMethodMap* methods= &tit->second.get_remote_methods(); 00322 00323 PP<PObjectPool<PyMethodDef> > meth_def_pool= 00324 new PObjectPool<PyMethodDef>(methods->size()+1); 00325 00326 PythonObjectWrapper::m_pypl_classes.insert( 00327 make_pair(classname, PLPyClass(the_pyclass, meth_def_pool))); 00328 TVec<string>& methods_help= 00329 PythonObjectWrapper::m_pypl_classes.find(classname)->second.methods_help; 00330 00331 set<pair<string, int> > methods_done; 00332 while(methods) 00333 { 00334 for(RemoteMethodMap::MethodMap::const_iterator it= methods->begin(); 00335 it != methods->end(); ++it) 00336 { 00337 //skip methods already injected, or not to inject 00338 if(methods_done.find(it->first) != methods_done.end()) continue; 00339 methods_done.insert(it->first); 00340 if(it->second->flags() & RemoteTrampoline::nopython) continue; 00341 00342 //get the RemoteTrampoline 00343 PyObject* tramp= PyCObject_FromVoidPtr(it->second, NULL); 00344 00345 // Create a Python Function Object 00346 PyMethodDef* py_method= meth_def_pool->allocate(); 00347 py_method->ml_name = const_cast<char*>(it->first.first.c_str()); 00348 py_method->ml_meth = PythonObjectWrapper::trampoline; 00349 py_method->ml_flags = METH_VARARGS; 00350 methods_help.push_back(HelpSystem::helpOnMethod(classname, 00351 it->first.first.c_str(), 00352 it->first.second)); 00353 py_method->ml_doc = const_cast<char*>(methods_help.last().c_str()); 00354 00355 PyObject* py_funcobj= PyCFunction_NewEx(py_method, tramp, module); 00356 00357 // create an unbound method from the function 00358 PyObject* py_methobj= PyMethod_New(py_funcobj, NULL, the_pyclass); 00359 00360 Py_DECREF(tramp); 00361 Py_XDECREF(py_funcobj); 00362 if(!py_funcobj || !py_methobj) 00363 { 00364 Py_DECREF(module); 00365 Py_XDECREF(py_methobj); 00366 PLERROR("in injectPLearnClasses : " 00367 "can't inject method '%s'", py_method->ml_name); 00368 } 00369 00370 if(-1==PyObject_SetAttrString(the_pyclass, py_method->ml_name, py_methobj)) 00371 { 00372 Py_DECREF(py_methobj); 00373 if (PyErr_Occurred()) PyErr_Print(); 00374 PLERROR("in injectPLearnClasses : " 00375 "cannot set attr %s for class %s", 00376 py_method->ml_name, 00377 classname.c_str()); 00378 } 00379 Py_DECREF(py_methobj); 00380 } 00381 methods= methods->inheritedMethods();//get parent class methods 00382 } 00383 if(0 != PyType_Ready(reinterpret_cast<PyTypeObject*>(the_pyclass))) 00384 PLERROR("in injectPLearnClasses : " 00385 "failed PyType_Ready on class %s.",classname.c_str()); 00386 if(module) 00387 if(-1==PyObject_SetAttrString(module, 00388 const_cast<char*>(pyclassname.c_str()), 00389 the_pyclass)) 00390 { 00391 if (PyErr_Occurred()) PyErr_Print(); 00392 PLERROR("in injectPLearnClasses : " 00393 "cannot inject class %s.", 00394 classname.c_str()); 00395 } 00396 } 00397 } 00398 00399 PyObject* the_PLearn_python_exception= 0; 00400 void injectPLearnException(PyObject* module) 00401 { 00402 PyObject* res= PyRun_String("class PLearnError(Exception):\n\tpass\n", 00403 Py_file_input, 00404 PyModule_GetDict(module), 00405 PyModule_GetDict(module)); 00406 if(!res) 00407 { 00408 if(PyErr_Occurred()) PyErr_Print(); 00409 PLERROR("in injectPLearnException : cannot create PLearnException class."); 00410 } 00411 Py_DECREF(res); 00412 00413 the_PLearn_python_exception= PyObject_GetAttrString(module, "PLearnError"); 00414 if(!the_PLearn_python_exception) 00415 { 00416 if(PyErr_Occurred()) PyErr_Print(); 00417 PLERROR("in injectPLearnException : cannot retrieve PLearnException class."); 00418 } 00419 //keep ref. to PLearnError forever. 00420 } 00421 00422 00423 void createWrappedObjectsSet(PyObject* module) 00424 { 00425 /* can't set logging before this gets called 00426 perr << "[pid=" << getPid() << "] " 00427 << "createWrappedObjectsSet for module: " << PythonObjectWrapper(module) << endl; 00428 */ 00429 00430 string code= ""; 00431 #if PL_PYTHON_VERSION <= 230 00432 code+= "\nfrom sets import Set as set\n"; 00433 #endif // PL_PYTHON_VERSION <= 230 00434 code+= "\nwrapped_PLearn_instances= set()\n"; 00435 PyObject* res= PyRun_String(code.c_str(), Py_file_input, 00436 PyModule_GetDict(module), PyModule_GetDict(module)); 00437 if(!res) 00438 { 00439 if(PyErr_Occurred()) PyErr_Print(); 00440 PLERROR("in createWrappedObjectsSet : cannot create set."); 00441 } 00442 Py_DECREF(res); 00443 } 00444 00445 void addToWrappedObjectsSet(PyObject* o) 00446 { 00447 DBG_MODULE_LOG << "addToWrappedObjectsSet for module: " << PythonObjectWrapper(the_PLearn_python_module) 00448 << "\tadding object: " << PythonObjectWrapper(o) << endl; 00449 PLASSERT(the_PLearn_python_module); 00450 if(-1 == PyObject_SetAttrString(the_PLearn_python_module, const_cast<char*>("_tmp_wrapped_instance"), o)) 00451 PLERROR("in addToWrappedObjectsSet : cannot add wrapped object to module."); 00452 PyObject* res= PyRun_String("\nwrapped_PLearn_instances.add(_tmp_wrapped_instance)" 00453 "\ndel _tmp_wrapped_instance\n", 00454 Py_file_input, 00455 PyModule_GetDict(the_PLearn_python_module), 00456 PyModule_GetDict(the_PLearn_python_module)); 00457 if(!res) 00458 { 00459 if(PyErr_Occurred()) PyErr_Print(); 00460 PLERROR("in addToWrappedObjectsSet : cannot add wrapped object to set."); 00461 } 00462 Py_DECREF(res); 00463 } 00464 void removeFromWrappedObjectsSet(PyObject* o) 00465 { 00466 DBG_MODULE_LOG << "removeFromWrappedObjectsSet for module: " << PythonObjectWrapper(the_PLearn_python_module) 00467 << "\tremoving object: " << PythonObjectWrapper(o) << endl; 00468 PLASSERT(the_PLearn_python_module); 00469 if(-1 == PyObject_SetAttrString(the_PLearn_python_module, const_cast<char*>("_tmp_wrapped_instance"), o)) 00470 PLERROR("in removeFromWrappedObjectsSet : cannot add wrapped object to module."); 00471 PyObject* res= PyRun_String("\nwrapped_PLearn_instances.remove(_tmp_wrapped_instance)" 00472 "\ndel _tmp_wrapped_instance\n", 00473 Py_file_input, 00474 PyModule_GetDict(the_PLearn_python_module), 00475 PyModule_GetDict(the_PLearn_python_module)); 00476 if(!res) 00477 { 00478 if(PyErr_Occurred()) PyErr_Print(); 00479 PLERROR("in removeFromWrappedObjectsSet : cannot remove wrapped object from set."); 00480 } 00481 Py_DECREF(res); 00482 } 00483 00484 // Init func for python module. 00485 // init module, then inject global funcs 00486 void initPythonExtensionModule(char const * module_name) 00487 { 00488 /* // can't set logging before this gets called 00489 perr << "[pid=" << getPid() << "] " 00490 << "initPythonExtensionModule name=" << module_name << endl; 00491 */ 00492 PythonObjectWrapper::initializePython(); 00493 PyObject* plext= Py_InitModule(const_cast<char*>(module_name), NULL); 00494 setPythonModuleAndInject(plext); 00495 } 00496 00497 void setPythonModuleAndInject(PyObject* module) 00498 { 00499 /* //can't set logging before this gets called 00500 perr << "[pid=" << getPid() << "] " 00501 << "setPythonModuleAndInject for module: " << PythonObjectWrapper(module) << "\tat " << (void*)module << endl; 00502 */ 00503 injectPLearnGlobalFunctions(module); 00504 injectPLearnClasses(module); 00505 injectPLearnException(module); 00506 createWrappedObjectsSet(module); 00507 the_PLearn_python_module= module; 00508 } 00509 00510 PyObject* the_PLearn_python_module= 0; 00511 00512 00513 00514 00515 void setNullPout() 00516 { 00517 pout= pnull; 00518 } 00519 void setPoutToPerr() 00520 { 00521 pout= perr; 00522 } 00523 00524 BEGIN_DECLARE_REMOTE_FUNCTIONS 00525 00526 declareFunction("setNullPout", &setNullPout, 00527 (BodyDoc("Sets the pout output stream to be null.\n"))); 00528 00529 declareFunction("setPoutToPerr", &setPoutToPerr, 00530 (BodyDoc("Sets the pout output stream to be perr.\n"))); 00531 00532 END_DECLARE_REMOTE_FUNCTIONS 00533 00534 00535 00536 00537 } // end of namespace PLearn 00538 00539 00540 /* 00541 Local Variables: 00542 mode:c++ 00543 c-basic-offset:4 00544 c-file-style:"stroustrup" 00545 c-file-offsets:((innamespace . 0)(inline-open . 0)) 00546 indent-tabs-mode:nil 00547 fill-column:79 00548 End: 00549 */ 00550 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=79 :