ruby / rdoc

RDoc produces HTML and online documentation for Ruby projects.
https://ruby.github.io/rdoc/
Other
844 stars 440 forks source link

Needs a parse tree for C files #1168

Open tribusonz-2 opened 2 months ago

tribusonz-2 commented 2 months ago

In the current version of RDoc, when searching for class definition APIs such as rb_define_class and rb_define_method in a C file, if a match is found in that file, it seems to only accept the class and method definitions in that file. This means that no matter how the method is defined in other C files, it will be ignored.

A mechanism will needed that to first gather elements from the C file and determine whether they are properly defined as classes. In this case, the parse tree would be desirable. rb_xYYY variables used as object entities are basically the global variables, so they must be declared as extern when defining classes at the C level. However, this way does not work in outer method definitions (#pack/#unpack, etc.) because these "global variables" are not defined as classes.

In C, global variables must be initialized. A popular solution is to do the initialization in a single C file.

// global.h
#ifdef GLOBAL_VARIABLE_DEFINE
#define GLOBAL
#define GLOBAL_VAL(v) = (v)
#else
#define GLOBAL extern
#define GLOBAL_VAL(v)
#endif

This allows you to initialize variables as needed.

#define GLOBAL_VARIABLE_DEFINE // without extern
#include "global.h"

In Ruby, C's global variables are definitions, which means they have a different meaning than initialization. Now below, definition in single file, RDoc is well parse here:

/*
 * call-seq:
 *   bar -> nil
 *
 * It is a test.
 */
static VALUE
foo_bar(VALUE self)
{
    return Qnil;
}

void
Init_foo()
{
    rb_cFoo = rb_define_class("Foo", rb_cObject);

    rb_define_method(rb_cFoo, "bar", foo_bar, 0);
}

However, a library that could become a small framework (such as image processing) is unlikely to be a single C file. That is, it needs to be improved.

Below is a typical way I write it, and it is an example of an extension library that uses Professor Oura's FFT in Ruby. The compiler passes it, but RDoc doesn't.

// ---(ruby/ext_extern.h)
#ifndef RUBY_EXT_EXTERN_H_INCLUDED
#define RUBY_EXT_EXTERN_H_INCLUDED

#if defined(__cplusplus)
extern "C" {
#endif

#ifdef     USE_GLOBAL_VARIABLE
# define   RUBY_EXT_EXTERN
#else
# define   RUBY_EXT_EXTERN    extern
#endif

#if defined(__cplusplus)
}
#endif

#endif /* RUBY_EXT_EXTERN_H_INCLUDED */
// ---

// ---(ruby/ooura_fft/globals.h)
#ifndef RUBY_OOURAFFT_GLOBALS_H_INCLUDED
#define RUBY_OOURAFFT_GLOBALS_H_INCLUDED

#include <ruby/internal/value.h> // VALUE
#include "ruby/ext_extern.h"

RUBY_EXT_EXTERN VALUE rb_mOouraFFT;

#endif /* RUBY_OOURAFFT_GLOBALS_H_INCLUDED */
// ---

// --- (ooura_fft.c)
#include <ruby.h>
#define  USE_GLOBAL_VARIABLE
#include "ruby/ooura_fft/globals.h"

void
Init_ooura_fft(void)
{
    rb_mOouraFFT = rb_define_module("OouraFFT");

    InitVM(FFT);
}
// ---

// --- (fft.c)
#include <ruby.h>
#include "ruby/ooura_fft/globals.h"

// :
// :

static void InitVM_FFTMain(void);
void
InitVM_FFT(void)
{
    InitVM(FFTMain);
}

// :
// :

static void
InitVM_FFTMain(void)
{
    rb_define_module_function(rb_mOouraFFT, "cdft", fft_cdft, -1);
    rb_define_module_function(rb_mOouraFFT, "rdft", fft_rdft, -1);
    rb_define_module_function(rb_mOouraFFT, "ddct", fft_ddct, -1);
    rb_define_module_function(rb_mOouraFFT, "ddst", fft_ddst, -1);
    rb_define_module_function(rb_mOouraFFT, "dfct", fft_dfct, -1);
    rb_define_module_function(rb_mOouraFFT, "dfst", fft_dfst, -1);
    rb_define_const(rb_mOouraFFT, "USING_THREAD", rb_str_new_cstr((const char *)USING_THREAD));
}
// ---

It is expected that more extension libraries with this syntax will be available in the future. You may want to reconsider RDoc as well.

tribusonz-2 commented 2 months ago

Additional information.

It is expected that more extension libraries with this syntax will be available in the future.

I stated this definitively, but this is the only syntax that will work. It's also a "de facto bug fix."

tribusonz-2 commented 2 months ago

The RDoc parser only checks words, not C syntax. Therefore, it seems possible to write code that is passed by the compiler but ignored by the RDoc parser. If it's readable, it's a bit "fashionable".

A sample is shown below:

// (foo.c)
#define RDocDummy 0

void
Init_foo(void)
{
    rb_cFoo = rb_define_class("Foo", rb_cObject);

#if RDocDummy
    rb_define_method(rb_cFoo, "bar", foo_bar, 0); // in define.c
#else
    InitVM(Define);
#endif
}
// (define.c)

/*
 * call-seq:
 *   bar -> String
 * 
 * This is a test.
 */
static VALUE
foo_bar(VALUE self)
{
    return rb_str_new_cstr("hello!!");
}

void
InitVM_Define(void)
{
    rb_define_method(rb_cFoo, "bar", foo_bar, 0);
}