602 lines
20 KiB
C++
602 lines
20 KiB
C++
// Filename: interrogate.cxx
|
|
// Created by: drose (31Jul00)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d-general@lists.sourceforge.net .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "interrogate.h"
|
|
#include "interrogateBuilder.h"
|
|
|
|
#include "interrogateDatabase.h"
|
|
#include "cppGlobals.h"
|
|
#include "pnotify.h"
|
|
#include <time.h>
|
|
|
|
// If our system getopt() doesn't come with getopt_long_only(), then use
|
|
// the GNU flavor that we've got in tool for this purpose.
|
|
#ifndef HAVE_GETOPT_LONG_ONLY
|
|
#include "gnu_getopt.h"
|
|
#else
|
|
#ifdef HAVE_GETOPT_H
|
|
#include <getopt.h>
|
|
#endif
|
|
#endif
|
|
|
|
CPPParser parser;
|
|
|
|
Filename output_code_filename;
|
|
Filename output_include_filename;
|
|
Filename output_data_filename;
|
|
Filename source_file_directory;
|
|
string output_data_basename;
|
|
bool output_module_specific = false;
|
|
bool output_function_pointers = false;
|
|
bool output_function_names = false;
|
|
bool convert_strings = false;
|
|
bool manage_reference_counts = false;
|
|
bool watch_asserts = false;
|
|
bool true_wrapper_names = false;
|
|
bool build_c_wrappers = false;
|
|
bool build_python_wrappers = false;
|
|
bool build_python_obj_wrappers = false;
|
|
bool build_python_native = false;
|
|
bool track_interpreter = false;
|
|
bool save_unique_names = false;
|
|
bool no_database = false;
|
|
bool generate_spam = false;
|
|
bool left_inheritance_requires_upcast = true;
|
|
CPPVisibility min_vis = V_published;
|
|
string library_name;
|
|
string module_name;
|
|
|
|
// Short command-line options.
|
|
static const char *short_options = "I:S:D:F:vh";
|
|
|
|
// Long command-line options.
|
|
enum CommandOptions {
|
|
CO_oc = 256,
|
|
CO_od,
|
|
CO_srcdir,
|
|
CO_module,
|
|
CO_library,
|
|
CO_do_module,
|
|
CO_fptrs,
|
|
CO_fnames,
|
|
CO_string,
|
|
CO_refcount,
|
|
CO_assert,
|
|
CO_true_names,
|
|
CO_c,
|
|
CO_python,
|
|
CO_python_obj,
|
|
CO_python_native,
|
|
CO_track_interpreter,
|
|
CO_unique_names,
|
|
CO_nodb,
|
|
CO_longlong,
|
|
CO_promiscuous,
|
|
CO_spam,
|
|
CO_help,
|
|
};
|
|
|
|
static struct option long_options[] = {
|
|
{ "oc", required_argument, NULL, CO_oc },
|
|
{ "od", required_argument, NULL, CO_od },
|
|
{ "srcdir", required_argument, NULL, CO_srcdir },
|
|
{ "module", required_argument, NULL, CO_module },
|
|
{ "library", required_argument, NULL, CO_library },
|
|
{ "do-module", no_argument, NULL, CO_do_module },
|
|
{ "fptrs", no_argument, NULL, CO_fptrs },
|
|
{ "fnames", no_argument, NULL, CO_fnames },
|
|
{ "string", no_argument, NULL, CO_string },
|
|
{ "refcount", no_argument, NULL, CO_refcount },
|
|
{ "assert", no_argument, NULL, CO_assert },
|
|
{ "true-names", no_argument, NULL, CO_true_names },
|
|
{ "c", no_argument, NULL, CO_c },
|
|
{ "python", no_argument, NULL, CO_python },
|
|
{ "python-obj", no_argument, NULL, CO_python_obj },
|
|
{ "python-native", no_argument, NULL, CO_python_native },
|
|
{ "track-interpreter", no_argument, NULL, CO_track_interpreter },
|
|
{ "unique-names", no_argument, NULL, CO_unique_names },
|
|
{ "nodb", no_argument, NULL, CO_nodb },
|
|
{ "longlong", required_argument, NULL, CO_longlong },
|
|
{ "promiscuous", no_argument, NULL, CO_promiscuous },
|
|
{ "spam", no_argument, NULL, CO_spam },
|
|
{ "help", no_argument, NULL, CO_help },
|
|
{ NULL }
|
|
};
|
|
|
|
void
|
|
show_usage() {
|
|
cerr
|
|
<< "\nUsage:\n"
|
|
<< " interrogate [opts] file.C [file.C ...]\n"
|
|
<< " interrogate -h\n\n";
|
|
}
|
|
|
|
void show_help() {
|
|
show_usage();
|
|
cerr
|
|
<< "Interrogate is a program to parse a body of C++ code and build up a table\n"
|
|
<< "of classes, methods, functions, and symbols found, for the purposes of\n"
|
|
<< "calling into the codebase via a non-C++ scripting language like Scheme,\n"
|
|
<< "Smalltalk, or Python.\n\n"
|
|
|
|
<< "In addition to identifying all the classes and their relationships,\n"
|
|
<< "interrogate will generate a wrapper function for each callable function.\n"
|
|
<< "The wrapper functions will be callable directly from the scripting language,\n"
|
|
<< "with no understanding of C++ necessary; these wrapper functions will in turn\n"
|
|
<< "call the actual C++ functions or methods.\n\n"
|
|
|
|
<< "Most exportable features of C++ are supported, including templates, default\n"
|
|
<< "parameters, and function overloading.\n\n"
|
|
|
|
<< "Options:\n\n"
|
|
|
|
<< " -oc output.C\n"
|
|
<< " Specify the name of the file to which generated code will be written.\n"
|
|
<< " This includes all of the function wrappers, as well as those tables\n"
|
|
<< " which must be compiled into the library.\n\n"
|
|
|
|
<< " -od output.in\n"
|
|
<< " Specify the name of the file to which the non-compiled data tables\n"
|
|
<< " will be written. This file describes the relationships between\n"
|
|
<< " all the types and the functions, and associates the function wrappers\n"
|
|
<< " above with this data. This file will be opened and read at runtime\n"
|
|
<< " when the scripting language first calls some interrogate query\n"
|
|
<< " function.\n\n"
|
|
|
|
<< " -srcdir directory\n"
|
|
<< " Specify the name of the directory to which the source filenames are\n"
|
|
<< " relative.\n\n"
|
|
|
|
<< " -module module_name\n"
|
|
<< " Defines the name of the module this data is associated with. This\n"
|
|
<< " is strictly a code-organizational tool. Conceptually, a module is\n"
|
|
<< " the highest level of grouping for interrogate data; a module may\n"
|
|
<< " contain several libraries. If this is omitted, no module name is\n"
|
|
<< " specified.\n\n"
|
|
|
|
<< " Sometimes, depending on the type of wrappers being generated, there\n"
|
|
<< " may be additional code that needs to be generated on the module\n"
|
|
<< " level, above that which was already generated at the library level.\n"
|
|
<< " Python, for instance, generates the table of python-callable function\n"
|
|
<< " wrappers at the module level. Use the program interrogate-module\n"
|
|
<< " to generate the appropriate code at the module level.\n\n"
|
|
|
|
<< " -library library_name\n"
|
|
<< " Defines the name of the library this data is associated with. This\n"
|
|
<< " is another code-organizational tool. Typically, there will be one\n"
|
|
<< " invocation of interrogate for each library, and there will be\n"
|
|
<< " multiple libraries per module. If this is omitted, no library name\n"
|
|
<< " is specified.\n\n"
|
|
|
|
<< " -do-module\n"
|
|
<< " Generate whatever module-level code should be generated immediately,\n"
|
|
<< " rather than waiting for a special interrogate-module pass.\n"
|
|
<< " This, of course, prohibits grouping several libraries together\n"
|
|
<< " into a single module.\n\n"
|
|
|
|
<< " -fptrs\n"
|
|
<< " Make void* pointers to the function wrappers directly available. A\n"
|
|
<< " scripting language will be able to call the interrogate functions\n"
|
|
<< " directly by pointer.\n\n"
|
|
|
|
<< " -fnames\n"
|
|
<< " Make the names of the function wrappers public symbols so that the\n"
|
|
<< " scripting language will be able to call the interrogate functions\n"
|
|
<< " by name.\n\n"
|
|
|
|
<< " Either or both of -fptrs and/or -fnames may be specified. If both are\n"
|
|
<< " omitted, the default is -fnames.\n\n"
|
|
|
|
<< " -string\n"
|
|
<< " Treat char* and basic_string<char> as special cases, and map\n"
|
|
<< " parameters of these types to type atomic string. The scripting\n"
|
|
<< " language will see only functions that receive and return strings,\n"
|
|
<< " not pointers to character or structures of basic_string<char>.\n"
|
|
<< " If C calling convention wrappers are being generated, the atomic\n"
|
|
<< " string type means type char*. In any other calling convention, the\n"
|
|
<< " atomic string type is whatever the native string type is.\n\n"
|
|
|
|
<< " -refcount\n"
|
|
<< " Treat classes that inherit from a class called ReferenceCount as a\n"
|
|
<< " special case. Any wrapper function that returns a pointer to\n"
|
|
<< " one of these classes will automatically increment the reference\n"
|
|
<< " count by calling ref() on the object first, and any destructors\n"
|
|
<< " that are generated will call unref_delete() on the object instead of\n"
|
|
<< " simply delete.\n\n"
|
|
<< " Furthermore, parameters of type PointerTo<N> or ConstPointerTo<N>\n"
|
|
<< " will automatically be mapped to N * and const N *, respectively.\n\n"
|
|
|
|
<< " -assert\n"
|
|
<< " Generate code in each wrapper that will check the state of the assert\n"
|
|
<< " flag and trigger an exception in the scripting language when a\n"
|
|
<< " C++ assertion fails. Presently, this only has meaning to the Python\n"
|
|
<< " wrappers.\n\n"
|
|
|
|
<< " -true-names\n"
|
|
<< " Use the actual name of the function being wrapped as the name of\n"
|
|
<< " the generated wrapper function, instead of an ugly hash name.\n"
|
|
<< " This means the wrapper functions may be called directly using a\n"
|
|
<< " meaningful name (especially if -fnames is also given), but it\n"
|
|
<< " also means that C++ function overloading (including default values\n"
|
|
<< " for parameters) cannot be used, as it will lead to multiple wrapper\n"
|
|
<< " functions with the same name.\n\n"
|
|
|
|
<< " -c\n"
|
|
<< " Generate function wrappers using the C calling convention. Any\n"
|
|
<< " scripting language that can call a C function should be able to\n"
|
|
<< " make advantage of the interrogate database.\n\n"
|
|
<< " -python\n"
|
|
<< " Generate function wrappers using the Python calling convention.\n"
|
|
<< " The shared library will be directly loadable as a Python module\n"
|
|
<< " (especially if the module definitions are made available either by\n"
|
|
<< " running interrogate-module later, or by specifying -do-module on\n"
|
|
<< " the command line now). However, C++ objects and methods will be\n"
|
|
<< " converted into an object handle and a list of independent Python\n"
|
|
<< " functions.\n\n"
|
|
<< " -python-obj\n"
|
|
<< " Generate Python function wrappers that convert C++ objects to true\n"
|
|
<< " python objects, with all methods converted to Python methods. This\n"
|
|
<< " is currently experimental.\n\n"
|
|
<< " -python-native\n"
|
|
<< " Generate Python function wrappers that convert C++ objects to true\n"
|
|
<< " python objects, with all methods converted to Python methods. This\n"
|
|
<< " is currently experimental.\n\n"
|
|
|
|
<< " Any combination of -c, -python, or -python-obj may be specified. If all\n"
|
|
<< " are omitted, the default is -c.\n\n"
|
|
|
|
<< " -track-interpreter\n"
|
|
<< " Generate code within each wrapper function to adjust the global\n"
|
|
<< " variable \"in_interpreter\" to indicated whether code is running\n"
|
|
<< " within the Panda C++ environment or within the high-level language.\n"
|
|
|
|
<< " -unique-names\n"
|
|
<< " Compile a table into the library (i.e. generate code into the -oc\n"
|
|
<< " file) that defines a lookup of each function wrapper by its unique\n"
|
|
<< " name. This makes it possible to consistently identify function\n"
|
|
<< " wrappers between sessions, at the cost of having this additional\n"
|
|
<< " table in memory.\n\n"
|
|
|
|
<< " -nodb\n"
|
|
<< " Do not build a full interrogate database, but just generate function\n"
|
|
<< " wrappers. It is assumed that the user will know how to call the\n"
|
|
<< " function wrappers already, from some external source. This is most\n"
|
|
<< " useful in conjunction with -true-names.\n\n"
|
|
|
|
<< " -longlong typename\n"
|
|
<< " Specify the name of the 64-bit integer type for the current compiler.\n"
|
|
<< " By default, this is \"long long\".\n\n"
|
|
|
|
<< " -promiscuous\n"
|
|
<< " Export *all* public symbols, functions, and classes seen, even those\n"
|
|
<< " not explicitly marked to be published.\n\n"
|
|
|
|
<< " -spam\n"
|
|
<< " Generate wrapper functions that report each invocation to Notify.\n"
|
|
<< " This can sometimes be useful for tracking down bugs.\n\n";
|
|
}
|
|
|
|
// handle commandline -D options
|
|
static void
|
|
predefine_macro(CPPParser& parser, const string& inoption) {
|
|
string macro_name, macro_def;
|
|
|
|
size_t eq = inoption.find('=');
|
|
if (eq != string::npos) {
|
|
macro_name = inoption.substr(0, eq);
|
|
macro_def = inoption.substr(eq + 1);
|
|
} else {
|
|
macro_name = inoption;
|
|
}
|
|
|
|
CPPManifest *macro = new CPPManifest(macro_name + " " + macro_def);
|
|
parser._manifests[macro->_name] = macro;
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[]) {
|
|
string command_line;
|
|
int i;
|
|
for (i = 0; i < argc; i++) {
|
|
command_line += string(argv[i]) + " ";
|
|
}
|
|
|
|
Filename fn;
|
|
extern char *optarg;
|
|
extern int optind;
|
|
int flag;
|
|
|
|
flag = getopt_long_only(argc, argv, short_options, long_options, NULL);
|
|
while (flag != EOF) {
|
|
switch (flag) {
|
|
case 'I':
|
|
fn = Filename::from_os_specific(optarg);
|
|
fn.make_absolute();
|
|
parser._include_path.append_directory(fn);
|
|
break;
|
|
|
|
case 'S':
|
|
fn = Filename::from_os_specific(optarg);
|
|
fn.make_absolute();
|
|
parser._system_include_path.append_directory(fn);
|
|
break;
|
|
|
|
case 'D':
|
|
predefine_macro(parser, optarg);
|
|
break;
|
|
|
|
case 'F':
|
|
// This is just a compile directive which we ignore.
|
|
break;
|
|
|
|
case 'v':
|
|
parser.set_verbose(parser.get_verbose() + 1);
|
|
break;
|
|
|
|
case CO_oc:
|
|
output_code_filename = Filename::from_os_specific(optarg);
|
|
output_code_filename.make_absolute();
|
|
break;
|
|
|
|
case CO_od:
|
|
output_data_filename = Filename::from_os_specific(optarg);
|
|
output_data_filename.make_absolute();
|
|
break;
|
|
|
|
case CO_srcdir:
|
|
source_file_directory = Filename::from_os_specific(optarg);
|
|
source_file_directory.make_absolute();
|
|
break;
|
|
|
|
case CO_module:
|
|
module_name = optarg;
|
|
break;
|
|
|
|
case CO_library:
|
|
library_name = optarg;
|
|
break;
|
|
|
|
case CO_do_module:
|
|
output_module_specific = true;
|
|
break;
|
|
|
|
case CO_fptrs:
|
|
output_function_pointers = true;
|
|
break;
|
|
|
|
case CO_fnames:
|
|
output_function_names = true;
|
|
break;
|
|
|
|
case CO_string:
|
|
convert_strings = true;
|
|
break;
|
|
|
|
case CO_refcount:
|
|
manage_reference_counts = true;
|
|
break;
|
|
|
|
case CO_assert:
|
|
watch_asserts = true;
|
|
break;
|
|
|
|
case CO_true_names:
|
|
true_wrapper_names = true;
|
|
break;
|
|
|
|
case CO_c:
|
|
build_c_wrappers = true;
|
|
break;
|
|
|
|
case CO_python:
|
|
build_python_wrappers = true;
|
|
break;
|
|
|
|
case CO_python_obj:
|
|
build_python_obj_wrappers = true;
|
|
break;
|
|
|
|
case CO_python_native:
|
|
build_python_native = true;
|
|
break;
|
|
|
|
case CO_track_interpreter:
|
|
track_interpreter = true;
|
|
break;
|
|
|
|
case CO_unique_names:
|
|
save_unique_names = true;
|
|
break;
|
|
|
|
case CO_nodb:
|
|
no_database = true;
|
|
break;
|
|
|
|
case CO_longlong:
|
|
cpp_longlong_keyword = optarg;
|
|
break;
|
|
|
|
case CO_promiscuous:
|
|
min_vis = V_public;
|
|
break;
|
|
|
|
case CO_spam:
|
|
generate_spam = true;
|
|
break;
|
|
|
|
case 'h':
|
|
case CO_help:
|
|
show_help();
|
|
exit(0);
|
|
|
|
default:
|
|
exit(1);
|
|
}
|
|
flag = getopt_long_only(argc, argv, short_options, long_options, NULL);
|
|
}
|
|
|
|
argc -= (optind-1);
|
|
argv += (optind-1);
|
|
|
|
if (argc < 2) {
|
|
show_usage();
|
|
exit(1);
|
|
}
|
|
|
|
// If requested, change directory to the source-file directory.
|
|
if (source_file_directory != "") {
|
|
if (!source_file_directory.chdir()) {
|
|
cerr << "Could not change directory to " << source_file_directory << "\n";
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
// if(!output_code_filename.empty())
|
|
// {
|
|
// output_include_filename = output_code_filename.get_fullpath_wo_extension() +".h";
|
|
// printf(" Include File Will be Set to %s \n",output_include_filename.c_str());
|
|
// }
|
|
|
|
output_code_filename.set_text();
|
|
output_data_filename.set_text();
|
|
// output_include_filename.set_text();
|
|
output_data_basename = output_data_filename.get_basename();
|
|
|
|
if (output_function_names && true_wrapper_names) {
|
|
cerr
|
|
<< "Cannot simultaneously export function names and report\n"
|
|
<< "true wrapper names--wrapper names will clash with the\n"
|
|
<< "wrapped functions!\n";
|
|
exit(1);
|
|
}
|
|
|
|
if (!build_c_wrappers && !build_python_wrappers &&
|
|
!build_python_obj_wrappers &&!build_python_native) {
|
|
build_c_wrappers = true;
|
|
}
|
|
|
|
// Get all of the .h files.
|
|
for (i = 1; i < argc; ++i)
|
|
{
|
|
Filename filename = Filename::from_os_specific(argv[i]);
|
|
if (!parser.parse_file(filename)) {
|
|
cerr << "Error parsing file: '" << argv[i] << "'\n";
|
|
exit(1);
|
|
}
|
|
builder.add_source_file(filename);
|
|
}
|
|
|
|
// Now that we've parsed all the source code, change the way things
|
|
// are output from now on so we can compile our generated code using
|
|
// VC++. Sheesh.
|
|
cppparser_output_class_keyword = false;
|
|
|
|
|
|
// Now look for the .N files.
|
|
for (i = 1; i < argc; ++i) {
|
|
Filename filename = Filename::from_os_specific(argv[i]);
|
|
Filename nfilename = filename;
|
|
nfilename.set_extension("N");
|
|
nfilename.set_text();
|
|
ifstream nfile;
|
|
if (nfilename.open_read(nfile)) {
|
|
builder.read_command_file(nfile);
|
|
}
|
|
}
|
|
|
|
builder.build();
|
|
|
|
// Make up a file identifier. This is just some bogus number that
|
|
// should be the same in both the compiled-in code and in the
|
|
// database, so we can check synchronicity at load time.
|
|
int file_identifier = time((time_t *)NULL);
|
|
InterrogateModuleDef *def = builder.make_module_def(file_identifier);
|
|
|
|
ofstream * the_output_include = NULL;
|
|
ofstream output_include;
|
|
|
|
|
|
if (1==2 && !output_include_filename.empty())
|
|
{
|
|
output_include_filename.open_write(output_include);
|
|
|
|
output_include << "#ifndef " << output_include_filename.get_basename_wo_extension() << "__HH__\n";
|
|
output_include << "#define " << output_include_filename.get_basename_wo_extension() << "__HH__\n";
|
|
|
|
output_include
|
|
<< "/*\n"
|
|
<< " * This file generated by:\n"
|
|
<< " * " << command_line << "\n"
|
|
<< " *\n"
|
|
<< " */\n\n";
|
|
|
|
|
|
if (output_include.fail())
|
|
{
|
|
nout << "Unable to write to " << output_include_filename << "\n";
|
|
exit(-1);
|
|
}
|
|
the_output_include = &output_include;
|
|
}
|
|
|
|
// Now output all of the wrapper functions.
|
|
if (!output_code_filename.empty())
|
|
{
|
|
ofstream output_code;
|
|
output_code_filename.open_write(output_code);
|
|
|
|
output_code
|
|
<< "/*\n"
|
|
<< " * This file generated by:\n"
|
|
<< " * " << command_line << "\n"
|
|
<< " *\n"
|
|
<< " */\n\n";
|
|
|
|
if(the_output_include != NULL)
|
|
{
|
|
output_code << "#include \""<<output_include_filename<<"\"\n";
|
|
*the_output_include << "#include \"" << output_include_filename.get_fullpath_wo_extension() << "_pynative.h\"\n";
|
|
}
|
|
|
|
if (output_code.fail()) {
|
|
nout << "Unable to write to " << output_code_filename << "\n";
|
|
} else {
|
|
builder.write_code(output_code,the_output_include, def);
|
|
}
|
|
}
|
|
|
|
|
|
if(the_output_include != NULL)
|
|
*the_output_include << "#endif // #define " << output_include_filename.get_basename_wo_extension() << "__HH__\n";
|
|
|
|
// And now output the bulk of the database.
|
|
if (!output_data_filename.empty()) {
|
|
ofstream output_data;
|
|
output_data_filename.open_write(output_data);
|
|
|
|
if (output_data.fail())
|
|
{
|
|
nout << "Unable to write to " << output_data_filename << "\n";
|
|
} else {
|
|
InterrogateDatabase::get_ptr()->write(output_data, def);
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|