diff --git a/CHANGES.current b/CHANGES.current index 2db5a6d6c..e10400c64 100644 --- a/CHANGES.current +++ b/CHANGES.current @@ -8,13 +8,12 @@ Version 1.3.32 (in progress) Added typename for raw lua_State* Added documentation on native functions. -05/07/2007: gga +05/02/2007: gga [Ruby] Docstrings are now supported. %feature("autodoc") and %feature("docstring") are now properly supported in Ruby. These features will generate a _wrap.cxx file with rdoc comments in them. - TODO: add doxygen -> rdoc conversion support. 05/05/2007: gga [Ruby] @@ -40,6 +39,14 @@ Version 1.3.32 (in progress) exceptions. Now, nil is returned instead, following ruby's standard Array behavior. +05/02/2007: gga + [Ruby] + Changed the value of SWIG_TYPECHECK_BOOL to be 10000 (ie. higher + than that of all integers). This is because Ruby allows typecasting + integers down to booleans which can make overloaded functions on + bools and integers to fail. + (bug# 1488142) + 05/02/2007: gga [Ruby] Fixed a subtle bug in multiple argouts that could get triggered if diff --git a/Examples/test-suite/li_std_stream.i b/Examples/test-suite/li_std_stream.i new file mode 100644 index 000000000..0a999ddbf --- /dev/null +++ b/Examples/test-suite/li_std_stream.i @@ -0,0 +1,59 @@ +%module li_std_stream + +%inline %{ + struct A; +%} + +%include +%include + + + +%callback(1) A::bar; + +%inline %{ + + struct B { + virtual ~B() + { + } + + }; + + struct A : B + { + void __add__(int a) + { + } + + void __add__(double a) + { + } + + static int bar(int a){ + return a; + } + + static int foo(int a, int (*pf)(int a)) + { + return pf(a); + } + + + std::ostream& __rlshift__(std::ostream& out) + { + out << "A class"; + return out; + } + }; +%} + +%extend std::basic_ostream{ + std::basic_ostream& + operator<<(const A& a) + { + *self << "A class"; + return *self; + } +} + diff --git a/Examples/test-suite/ruby/li_std_stream_runme.rb b/Examples/test-suite/ruby/li_std_stream_runme.rb new file mode 100755 index 000000000..feac3df6f --- /dev/null +++ b/Examples/test-suite/ruby/li_std_stream_runme.rb @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby +# +# Simple test of std::ostringstream. +# +# Author:: gga +# Copyright:: 2007 +# License:: Ruby +# + +require 'swig_assert' + +require 'li_std_stream' +include Li_std_stream + +swig_assert_each_line(<<'EOF', binding) + +a = A.new +o = Ostringstream.new +o << a << " " << 2345 << " " << 1.435 +o.str == "A class 2345 1.435" + +EOF diff --git a/Lib/ruby/rubytypemaps.swg b/Lib/ruby/rubytypemaps.swg index 9e5120f29..20eff330d 100644 --- a/Lib/ruby/rubytypemaps.swg +++ b/Lib/ruby/rubytypemaps.swg @@ -4,6 +4,9 @@ /* ------------------------------------------------------------ * Fragment section * ------------------------------------------------------------ */ +/* bool is dangerous in Ruby, change precedence */ +#undef SWIG_TYPECHECK_BOOL +%define SWIG_TYPECHECK_BOOL 10000 %enddef /* Include fundamental fragemt definitions */ %include diff --git a/Lib/ruby/std_basic_string.i b/Lib/ruby/std_basic_string.i new file mode 100644 index 000000000..1351177fc --- /dev/null +++ b/Lib/ruby/std_basic_string.i @@ -0,0 +1,97 @@ +#if !defined(SWIG_STD_STRING) +#define SWIG_STD_BASIC_STRING + +%include + +#define %swig_basic_string(Type...) %swig_sequence_methods_val(Type) + + +%traits_swigtype(std::basic_string); +%fragment(SWIG_Traits_frag(std::basic_string)); + + +%fragment(SWIG_AsPtr_frag(std::basic_string),"header", + fragment="SWIG_AsCharPtrAndSize") { +SWIGINTERN int +SWIG_AsPtr(std::basic_string)(VALUE obj, std::string **val) +{ + static swig_type_info* string_info = + SWIG_TypeQuery("std::basic_string *"); + std::string *vptr; + if (SWIG_ConvertPtr(obj, (void**)&vptr, string_info, 0) == SWIG_OK) { + if (val) *val = vptr; + return SWIG_OLDOBJ; + } else { + char* buf = 0 ; size_t size = 0; int alloc = 0; + if (SWIG_AsCharPtrAndSize(obj, &buf, &size, &alloc) == SWIG_OK) { + if (buf) { + if (val) *val = new std::string(buf, size - 1); + if (alloc == SWIG_NEWOBJ) %delete_array(buf); + return SWIG_NEWOBJ; + } + } + if (val) { + rb_raise( rb_eTypeError, "a string is expected"); + } + return 0; + } +} +} + +%fragment(SWIG_From_frag(std::basic_string),"header", + fragment="SWIG_FromCharPtrAndSize") { +SWIGINTERNINLINE VALUE + SWIG_From(std::basic_string)(const std::string& s) + { + return SWIG_FromCharPtrAndSize(s.data(), s.size()); + } +} + +%include +%typemaps_asptrfromn(%checkcode(STRING), std::basic_string); + +#endif + + +#if !defined(SWIG_STD_WSTRING) + +%fragment(SWIG_AsPtr_frag(std::basic_string),"header", + fragment="SWIG_AsWCharPtrAndSize") { +SWIGINTERN int + SWIG_AsPtr(std::basic_string)(VALUE obj, std::wstring **val) + { + static swig_type_info* string_info = + SWIG_TypeQuery("std::basic_string *"); + std::wstring *vptr; + if (SWIG_ConvertPtr(obj, (void**)&vptr, string_info, 0) == SWIG_OK) { + if (val) *val = vptr; + return SWIG_OLDOBJ; + } else { + wchar_t *buf = 0 ; size_t size = 0; int alloc = 0; + if (SWIG_AsWCharPtrAndSize(obj, &buf, &size, &alloc) == SWIG_OK) { + if (buf) { + if (val) *val = new std::wstring(buf, size - 1); + if (alloc == SWIG_NEWOBJ) %delete_array(buf); + return SWIG_NEWOBJ; + } + } + if (val) { + rb_raise( rb_eTypeError, "a string is expected"); + } + return 0; + } + } +} + +%fragment(SWIG_From_frag(std::basic_string),"header", + fragment="SWIG_FromWCharPtrAndSize") { +SWIGINTERNINLINE VALUE + SWIG_From(std::basic_string)(const std::wstring& s) + { + return SWIG_FromWCharPtrAndSize(s.data(), s.size()); + } +} + +%typemaps_asptrfromn(%checkcode(UNISTRING), std::basic_string); + +#endif diff --git a/Lib/ruby/std_char_traits.i b/Lib/ruby/std_char_traits.i new file mode 100644 index 000000000..bf4e6c47d --- /dev/null +++ b/Lib/ruby/std_char_traits.i @@ -0,0 +1 @@ +%include diff --git a/Lib/ruby/std_ios.i b/Lib/ruby/std_ios.i index aa6f0994d..937786f93 100644 --- a/Lib/ruby/std_ios.i +++ b/Lib/ruby/std_ios.i @@ -1,3 +1,6 @@ + +#pragma SWIG nowarn=801 + %rename(ios_base_in) std::ios_base::in; %include diff --git a/Lib/ruby/std_iostream.i b/Lib/ruby/std_iostream.i new file mode 100644 index 000000000..ee36bec40 --- /dev/null +++ b/Lib/ruby/std_iostream.i @@ -0,0 +1,12 @@ +namespace std +{ +%callback("%s") endl; +%callback("%s") ends; +%callback("%s") flush; +} + +%warnfilter(365) operator+=; +%warnfilter(802) std::basic_iostream; // turn off multiple inheritance warning + +%include + diff --git a/Lib/ruby/std_sstream.i b/Lib/ruby/std_sstream.i new file mode 100644 index 000000000..537a3ae52 --- /dev/null +++ b/Lib/ruby/std_sstream.i @@ -0,0 +1,2 @@ + +%include diff --git a/Lib/ruby/std_streambuf.i b/Lib/ruby/std_streambuf.i new file mode 100644 index 000000000..44b9bb4d7 --- /dev/null +++ b/Lib/ruby/std_streambuf.i @@ -0,0 +1 @@ +%include diff --git a/Lib/ruby/std_string.i b/Lib/ruby/std_string.i index dc1378ae6..1a715bc1a 100644 --- a/Lib/ruby/std_string.i +++ b/Lib/ruby/std_string.i @@ -1 +1,6 @@ + +%warnfilter(801) std::string; // wrong class name +%warnfilter(378) std::basic_string::operator!=; + %include + diff --git a/Source/Modules/ruby.cxx b/Source/Modules/ruby.cxx index 4221f3f11..be7ca207d 100644 --- a/Source/Modules/ruby.cxx +++ b/Source/Modules/ruby.cxx @@ -113,6 +113,16 @@ public: }; +/* flags for the make_autodoc function */ +enum autodoc_t { + AUTODOC_CLASS, + AUTODOC_CTOR, + AUTODOC_DTOR, + AUTODOC_STATICFUNC, + AUTODOC_FUNC, + AUTODOC_METHOD +}; + static const char *usage = "\ Ruby Options (available with -ruby)\n\ -globalmodule - Wrap everything into the global module\n\ @@ -166,6 +176,378 @@ private: STATIC_VAR }; + /* ------------------------------------------------------------ + * autodoc level declarations + * ------------------------------------------------------------ */ + + enum autodoc_l { + NO_AUTODOC = -2, // no autodoc + STRING_AUTODOC = -1, // use provided string + NAMES_AUTODOC = 0, // only parameter names + TYPES_AUTODOC = 1, // parameter names and types + EXTEND_AUTODOC = 2, // extended documentation and parameter names + EXTEND_TYPES_AUTODOC = 3 // extended documentation and parameter types + names + }; + + + autodoc_l autodoc_level(String *autodoc) { + autodoc_l dlevel = NO_AUTODOC; + if (autodoc) { + char *c = Char(autodoc); + if (c && isdigit(c[0])) { + dlevel = (autodoc_l) atoi(c); + } else { + if (strcmp(c, "extended") == 0) { + dlevel = EXTEND_AUTODOC; + } else { + dlevel = STRING_AUTODOC; + } + } + } + return dlevel; + } + + + + /* ------------------------------------------------------------ + * have_docstring() + * Check if there is a docstring directive and it has text, + * or there is an autodoc flag set + * ------------------------------------------------------------ */ + + bool have_docstring(Node *n) { + String *str = Getattr(n, "feature:docstring"); + return (str != NULL && Len(str) > 0) || (Getattr(n, "feature:autodoc") && !GetFlag(n, "feature:noautodoc")); + } + + /* ------------------------------------------------------------ + * docstring() + * Get the docstring text, stripping off {} if neccessary, + * and enclose in triple double quotes. If autodoc is also + * set then it will build a combined docstring. + * ------------------------------------------------------------ */ + + String *docstring(Node *n, autodoc_t ad_type) { + String *str = Getattr(n, "feature:docstring"); + bool have_ds = (str != NULL && Len(str) > 0); + bool have_auto = (Getattr(n, "feature:autodoc") && !GetFlag(n, "feature:noautodoc")); + String *autodoc = NULL; + String *doc = NULL; + + if (have_ds) { + char *t = Char(str); + if (*t == '{') { + Delitem(str, 0); + Delitem(str, DOH_END); + } + } + + if (have_auto) { + autodoc = make_autodoc(n, ad_type); + have_auto = (autodoc != NULL && Len(autodoc) > 0); + } + // If there is more than one line then make docstrings like this: + // + // This is line1 + // And here is line2 followed by the rest of them + // + // otherwise, put it all on a single line + // + if (have_auto && have_ds) { // Both autodoc and docstring are present + doc = NewString(""); + Printv(doc, "\n", autodoc, "\n", str, NIL); + } else if (!have_auto && have_ds) { // only docstring + if (Strchr(str, '\n') == NULL) { + doc = NewString(str); + } else { + doc = NewString(""); + Printv(doc, str, NIL); + } + } else if (have_auto && !have_ds) { // only autodoc + if (Strchr(autodoc, '\n') == NULL) { + doc = NewStringf("%s", autodoc); + } else { + doc = NewString(""); + Printv(doc, "\n", autodoc, NIL); + } + } else + doc = NewString(""); + + // Save the generated strings in the parse tree in case they are used later + // by post processing tools + Setattr(n, "ruby:docstring", doc); + Setattr(n, "ruby:autodoc", autodoc); + return doc; + } + + /* ------------------------------------------------------------ + * make_autodocParmList() + * Generate the documentation for the function parameters + * ------------------------------------------------------------ */ + + String *make_autodocParmList(Node *n, bool showTypes) { + String *doc = NewString(""); + String *pdocs = Copy(Getattr(n, "feature:pdocs")); + ParmList *plist = CopyParmList(Getattr(n, "parms")); + Parm *p; + Parm *pnext; + Node *lookup; + int lines = 0; + const int maxwidth = 50; + + if (pdocs) + Append(pdocs, "\n"); + + + Swig_typemap_attach_parms("in", plist, 0); + Swig_typemap_attach_parms("doc", plist, 0); + + for (p = plist; p; p = pnext) { + String *name = 0; + String *type = 0; + String *value = 0; + String *ptype = 0; + String *pdoc = Getattr(p, "tmap:doc"); + if (pdoc) { + name = Getattr(p, "tmap:doc:name"); + type = Getattr(p, "tmap:doc:type"); + value = Getattr(p, "tmap:doc:value"); + ptype = Getattr(p, "tmap:doc:pytype"); + } + + name = name ? name : Getattr(p, "name"); + type = type ? type : Getattr(p, "type"); + value = value ? value : Getattr(p, "value"); + + String *tm = Getattr(p, "tmap:in"); + if (tm) { + pnext = Getattr(p, "tmap:in:next"); + } else { + pnext = nextSibling(p); + } + + // Skip the 'self' parameter which in ruby is implicit + if ( Cmp(name, "self") == 0 ) + continue; + + if (Len(doc)) { + // add a comma to the previous one if any + Append(doc, ", "); + + // Do we need to wrap a long line? + if ((Len(doc) - lines * maxwidth) > maxwidth) { + Printf(doc, "\n%s", tab4); + lines += 1; + } + } + // Do the param type too? + if (showTypes) { + type = SwigType_base(type); + lookup = Swig_symbol_clookup(type, 0); + if (lookup) + type = Getattr(lookup, "sym:name"); + Printf(doc, "%s ", type); + } + + if (name) { + Append(doc, name); + if (pdoc) { + if (!pdocs) + pdocs = NewString("Parameters:\n"); + Printf(pdocs, " %s\n", pdoc); + } + } else { + Append(doc, "?"); + } + + if (value) { + if (Strcmp(value, "NULL") == 0) + value = NewString("nil"); + else if (Strcmp(value, "true") == 0 || Strcmp(value, "TRUE") == 0) + value = NewString("true"); + else if (Strcmp(value, "false") == 0 || Strcmp(value, "FALSE") == 0) + value = NewString("false"); + else { + lookup = Swig_symbol_clookup(value, 0); + if (lookup) + value = Getattr(lookup, "sym:name"); + } + Printf(doc, "=%s", value); + } + } + if (pdocs) + Setattr(n, "feature:pdocs", pdocs); + Delete(plist); + return doc; + } + + /* ------------------------------------------------------------ + * make_autodoc() + * Build a docstring for the node, using parameter and other + * info in the parse tree. If the value of the autodoc + * attribute is "0" then do not include parameter types, if + * it is "1" (the default) then do. If it has some other + * value then assume it is supplied by the extension writer + * and use it directly. + * ------------------------------------------------------------ */ + + String *make_autodoc(Node *n, autodoc_t ad_type) { + int extended = 0; + // If the function is overloaded then this funciton is called + // for the last one. Rewind to the first so the docstrings are + // in order. + while (Getattr(n, "sym:previousSibling")) + n = Getattr(n, "sym:previousSibling"); + + Node *pn = Swig_methodclass(n); + String* class_name = Getattr(pn, "sym:name"); + String* full_name; + if ( module ) { + full_name = NewString(module); + Append(full_name, "::"); + } + else + full_name = NewString(""); + if ( class_name ) + Append(full_name, class_name); + + String *doc = NewString("/*\n"); + int counter = 0; + for ( ; n; ++counter ) { + bool showTypes = false; + bool skipAuto = false; + String *autodoc = Getattr(n, "feature:autodoc"); + autodoc_l dlevel = autodoc_level(autodoc); + switch (dlevel) { + case NO_AUTODOC: + break; + case NAMES_AUTODOC: + showTypes = false; + break; + case TYPES_AUTODOC: + showTypes = true; + break; + case EXTEND_AUTODOC: + extended = 1; + showTypes = false; + break; + case EXTEND_TYPES_AUTODOC: + extended = 1; + showTypes = true; + break; + case STRING_AUTODOC: + Append(doc, autodoc); + skipAuto = true; + break; + } + + if (!skipAuto) { + String *symname = Getattr(n, "sym:name"); + if ( Getattr( special_methods, symname ) ) + symname = Getattr( special_methods, symname ); + + SwigType *type = Getattr(n, "type"); + + if (type) { + if (Strcmp(type, "void") == 0) + type = NULL; + else { + SwigType *qt = SwigType_typedef_resolve_all(type); + if (SwigType_isenum(qt)) + type = NewString("int"); + else { + type = SwigType_base(type); + Node *lookup = Swig_symbol_clookup(type, 0); + if (lookup) + type = Getattr(lookup, "sym:name"); + } + } + } + + switch (ad_type) { + case AUTODOC_CLASS: + { + // Only do the autodoc if there isn't a docstring for the class + String *str = Getattr(n, "feature:docstring"); + if (counter == 0 && (str == NULL || Len(str) == 0)) { + if (CPlusPlus) { + Printf(doc, " Document-class: %s\n\n Proxy of C++ %s class", + full_name, class_name); + } else { + Printf(doc, " Document-class: %s\n\n Proxy of C %s struct", + full_name, class_name); + } + } + } + break; + case AUTODOC_CTOR: + if (counter == 0) + Append(doc, " Document-method: new\n\n call-seq:\n"); + if (Strcmp(class_name, symname) == 0) { + String *paramList = make_autodocParmList(n, showTypes); + if (Len(paramList)) + Printf(doc, " %s.new(%s)", class_name, paramList); + else + Printf(doc, " %s.new", class_name); + } else + Printf(doc, " %s.new(%s)", class_name, + make_autodocParmList(n, showTypes)); + break; + + case AUTODOC_DTOR: + break; + + case AUTODOC_STATICFUNC: + if (counter == 0) + Printf(doc, " Document-method: %s\n\n call-seq:\n", full_name); + Printf(doc, " %s(%s)", full_name, + make_autodocParmList(n, showTypes)); + + if (type) + Printf(doc, " -> %s", type); + break; + + case AUTODOC_FUNC: + if (counter == 0) + Printf(doc, " Document-method: %s\n\n call-seq:\n", symname); + Printf(doc, " %s(%s)", symname, + make_autodocParmList(n, showTypes)); + if (type) + Printf(doc, " -> %s", type); + break; + + case AUTODOC_METHOD: + if (counter == 0) + Printf(doc, " Document-method: %s\n\n call-seq:\n", symname); + String *paramList = make_autodocParmList(n, showTypes); + if (Len(paramList)) + Printf(doc, " %s(%s)", symname, paramList); + else + Printf(doc, " %s", symname); + if (type) + Printf(doc, " -> %s", type); + break; + } + } + if (extended) { + String *pdocs = Getattr(n, "feature:pdocs"); + if (pdocs) { + Printv(doc, "\n", pdocs, NULL); + } + } + + // if it's overloaded then get the next decl and loop around again + n = Getattr(n, "sym:nextSibling"); + if (n) + Append(doc, "\n"); + } + + Append(doc, "\n\n*/\n"); + Delete(full_name); + + return doc; + } + public: /* --------------------------------------------------------------------- @@ -1064,6 +1446,9 @@ public: bool ctor_director = (current == CONSTRUCTOR_INITIALIZE && Swig_directorclass(n)); int start = (current == MEMBER_FUNC || current == MEMBER_VAR || ctor_director) ? 1 : 0; + + + /* Now write the wrapper function itself */ if (current == CONSTRUCTOR_ALLOCATE) { Printf(f->def, "#ifdef HAVE_RB_DEFINE_ALLOC_FUNC\n"); @@ -1110,7 +1495,7 @@ public: insertArgOutputCode(l, outarg, need_result); /* if the object is a director, and the method call originated from its - * underlying python object, resolve the call by going up the c++ + * underlying Ruby object, resolve the call by going up the c++ * inheritance chain. otherwise try to resolve the method in python. * without this check an infinite loop is set up between the director and * shadow class method calls. @@ -1751,6 +2136,9 @@ public: * ---------------------------------------------------------------------- */ virtual int classHandler(Node *n) { + String* docs = docstring(n, AUTODOC_CLASS); + Printf(f_wrappers, "%s", docs); + Delete(docs); String *name = Getattr(n, "name"); String *symname = Getattr(n, "sym:name"); @@ -1842,6 +2230,11 @@ public: virtual int memberfunctionHandler(Node *n) { current = MEMBER_FUNC; + + String* docs = docstring(n, AUTODOC_METHOD); + Printf(f_wrappers, "%s", docs); + Delete(docs); + Language::memberfunctionHandler(n); current = NO_CPP; return SWIG_OK; @@ -1908,7 +2301,13 @@ public: Delete(self); } + + /* Now do the instance initialize method */ + String* docs = docstring(n, AUTODOC_CTOR); + Printf(f_wrappers, "%s", docs); + Delete(docs); + current = CONSTRUCTOR_INITIALIZE; Swig_name_register((String_or_char *) "construct", (String_or_char *) "new_%c"); Language::constructorHandler(n); @@ -2005,6 +2404,10 @@ public: * -------------------------------------------------------------------- */ virtual int membervariableHandler(Node *n) { + String* docs = docstring(n, AUTODOC_METHOD); + Printf(f_wrappers, "%s", docs); + Delete(docs); + current = MEMBER_VAR; Language::membervariableHandler(n); current = NO_CPP; @@ -2018,6 +2421,10 @@ public: * ---------------------------------------------------------------------- */ virtual int staticmemberfunctionHandler(Node *n) { + String* docs = docstring(n, AUTODOC_STATICFUNC); + Printf(f_wrappers, "%s", docs); + Delete(docs); + current = STATIC_FUNC; Language::staticmemberfunctionHandler(n); current = NO_CPP; @@ -2031,6 +2438,10 @@ public: * --------------------------------------------------------------------- */ virtual int memberconstantHandler(Node *n) { + String* docs = docstring(n, AUTODOC_STATICFUNC); + Printf(f_wrappers, "%s", docs); + Delete(docs); + current = CLASS_CONST; Language::memberconstantHandler(n); current = NO_CPP; @@ -2042,6 +2453,10 @@ public: * --------------------------------------------------------------------- */ virtual int staticmembervariableHandler(Node *n) { + String* docs = docstring(n, AUTODOC_STATICFUNC); + Printf(f_wrappers, "%s", docs); + Delete(docs); + current = STATIC_VAR; Language::staticmembervariableHandler(n); current = NO_CPP;