Keep full descriptor metadata as the default and add an opt-in structural profile for conversion-oriented late-bound API v2 users. Strip descriptive and constraint text while retaining the complete runtime descriptor graph. Pass the profile through CMake and correct the scanner's compact late-bound file lists. AP242e4 full-to-structural measurements: Generated output falls from 54,839 lines and 3,304,007 bytes to 29,993 lines and 1,525,897 bytes. The packed schema source shrinks 68.1%. The schema DSO falls from 2,178,296 to 720,120 bytes, and peak compile RSS drops 6.8%. File count and 49 ELF relocations are unchanged. The NIST AP242e4 sample still creates and validates all 10,609 instances with no errors or warnings and writes the same 949,685-byte semantic result. Twenty read/write runs improve from 4.76 to 4.71 seconds and peak RSS falls from 82,276 to 78,900 KiB.
430 lines
15 KiB
C++
430 lines
15 KiB
C++
|
|
/** \file schemaScanner.cc
|
|
* This file, along with part of libexpress, are compiled (at configure time)
|
|
* into a static executable. This executable is a schema scanner that is used
|
|
* by CMake to determine what files exp2cxx will create. Otherwise, we'd need
|
|
* to use a few huge files - there is no other way to tell CMake what the
|
|
* generated files will be called.
|
|
*/
|
|
|
|
/* WARNING
|
|
* If you modify this file, you must re-run cmake. It doesn't seem to be possible
|
|
* to re-run cmake automatically, as CMake's configure_file() gets confused by
|
|
* the '${' and '}' in writeLists() below.
|
|
*/
|
|
|
|
extern "C" {
|
|
#define _XOPEN_SOURCE /* for S_IFDIR */
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
|
|
#include "expparse.h"
|
|
#include "expscan.h"
|
|
#include "express/scope.h"
|
|
#include "genCxxFilenames.h"
|
|
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
# include <direct.h>
|
|
# define getcwd _getcwd
|
|
#else
|
|
# include <unistd.h>
|
|
#endif
|
|
}
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
|
|
int multiple_inheritance = 0;
|
|
|
|
using std::string;
|
|
using std::stringstream;
|
|
using std::endl;
|
|
using std::ofstream;
|
|
using std::cerr;
|
|
using std::cout;
|
|
|
|
static unsigned long entityChunkSize = 256;
|
|
static unsigned long typeChunkSize = 256;
|
|
|
|
static bool
|
|
parseChunkSizes( const char * value )
|
|
{
|
|
if( !value || !value[0] ) {
|
|
return false;
|
|
}
|
|
char * end = 0;
|
|
const unsigned long entities = strtoul( value, &end, 10 );
|
|
if( !end || entities == 0 ) {
|
|
return false;
|
|
}
|
|
unsigned long types = entities;
|
|
if( *end == ':' ) {
|
|
char * typeEnd = 0;
|
|
types = strtoul( end + 1, &typeEnd, 10 );
|
|
if( !end[1] || !typeEnd || *typeEnd || types == 0 ) {
|
|
return false;
|
|
}
|
|
} else if( *end ) {
|
|
return false;
|
|
}
|
|
entityChunkSize = entities;
|
|
typeChunkSize = types;
|
|
return true;
|
|
}
|
|
|
|
/** \return true for types that exp2cxx won't generate code for */
|
|
bool notGenerated( const Type t ) {
|
|
switch( TYPEget_body( t )->type ) {
|
|
case integer_:
|
|
case real_:
|
|
case string_:
|
|
case binary_:
|
|
case boolean_:
|
|
case number_:
|
|
case logical_:
|
|
case aggregate_:
|
|
case bag_:
|
|
case set_:
|
|
case list_:
|
|
case array_:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
/* from addRenameTypedefs() in multpass.c - check for renamed
|
|
* enums and selects - exp2cxx prints typedefs for them */
|
|
if( ( TYPEis_enumeration( t ) || TYPEis_select( t ) ) && ( TYPEget_head( t ) ) ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** \return a short name for the schema
|
|
* this name is intended to be unique, but no special care is put into its generation
|
|
* the official schema name seems most likely to not be unique, but it is generally the longest
|
|
*
|
|
* picks the shortest of:
|
|
* * the name of the dir containing the file (if in sc/data),
|
|
* * the name of the file containing the schema (minus extension),
|
|
* * the official name of the schema, passed in arg 'longName'
|
|
*
|
|
* TODO: maybe also consider an acronym, such that 'test_array_bounds_expr' becomes 'tabe', 'TABE', 'T_A_B_E', or similar
|
|
*/
|
|
string makeShortName( const char * longName ) {
|
|
//input_filename is path to file. we will extract dir name and file name from it.
|
|
string dirname = input_filename, filename = input_filename, schname = longName;
|
|
const char slash = '/';
|
|
|
|
//for filename, get rid of dir name(s), if any, as well as the extension
|
|
size_t sl = filename.rfind( slash );
|
|
if( sl != string::npos ) {
|
|
filename.erase( 0, sl + 1 );
|
|
}
|
|
size_t dot = filename.rfind( '.' );
|
|
if( dot != string::npos ) {
|
|
filename.erase( dot );
|
|
}
|
|
|
|
//for dirname, get rid of the filename and ensure it's in data/
|
|
sl = dirname.rfind( slash );
|
|
if( sl != string::npos ) {
|
|
dirname.erase( sl );
|
|
}
|
|
const char * dat = "data";
|
|
size_t data = dirname.find( dat );
|
|
if( ( data == string::npos ) || ( dirname[ data + strlen( dat ) ] != slash ) ) {
|
|
//doesn't contain 'data/'. clear it so it'll be ignored.
|
|
dirname.clear();
|
|
} else {
|
|
//get rid of all but last dir name
|
|
dirname.erase( 0, dirname.rfind( slash ) + 1 );
|
|
}
|
|
|
|
//use dir name if it's at least 3 chars and shorter than file name
|
|
//length requirement gets rid of short, undescriptive dir names - including '.' and '..'
|
|
if( ( dirname.size() > 2 ) && ( dirname.size() < filename.size() ) ) {
|
|
filename = dirname;
|
|
}
|
|
if( strlen( longName ) < filename.size() ) {
|
|
filename = longName;
|
|
}
|
|
filename.insert( 0, "sdai_" );
|
|
return filename;
|
|
}
|
|
|
|
/* cross-platform mkdir */
|
|
int sc_mkdir( const char * path ) {
|
|
#ifdef _WIN32
|
|
return mkdir( path );
|
|
#else
|
|
return mkdir( path, 0777 );
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
/* return -1 if error, 0 if created, 1 if dir existed already */
|
|
static int mkDirIfNone( const char * path ) {
|
|
struct stat s;
|
|
if( stat( path, &s ) != 0 ) {
|
|
if( errno == ENOENT ) {
|
|
return sc_mkdir( path );
|
|
}
|
|
} else if( s.st_mode & S_IFDIR ) {
|
|
return 1;
|
|
}
|
|
/* either stat returned an error other than ENOENT, or 'path' exists but isn't a dir */
|
|
return -1;
|
|
}
|
|
|
|
|
|
/** write a CMakeLists.txt file for the schema; print its directory to stdout for CMake's add_subdirectory() command */
|
|
void writeLists( const char * schemaName, stringstream & eh, stringstream & ei, int ecount,
|
|
stringstream & th, stringstream & ti, int tcount ) {
|
|
string shortName = makeShortName( schemaName );
|
|
if( mkDirIfNone( shortName.c_str() ) < 0 ) {
|
|
cerr << "Error creating directory " << shortName << " at " << __FILE__ << ":" << __LINE__;
|
|
perror( 0 );
|
|
exit( EXIT_FAILURE );
|
|
}
|
|
size_t nameLen = strlen( schemaName );
|
|
string schema_upper( nameLen, char() );
|
|
for( size_t i = 0; i < nameLen; ++i) {
|
|
schema_upper[i] = toupper(schemaName[i]);
|
|
}
|
|
string cmListsPath = shortName;
|
|
cmListsPath += "/CMakeLists.txt";
|
|
ofstream cmLists;
|
|
cmLists.open( cmListsPath.c_str() );
|
|
if( !cmLists.good() ) {
|
|
cerr << "error opening file " << cmListsPath << " - exiting." << endl;
|
|
exit( EXIT_FAILURE );
|
|
}
|
|
cmLists << "# ----- GENERATED FILE -----" << endl;
|
|
cmLists << "# ----- Do not edit! -----" << endl << endl;
|
|
|
|
cmLists << "# schema name: " << schemaName << endl;
|
|
cmLists << "# (short name: " << shortName << ")" << endl;
|
|
cmLists << "# " << ecount << " entities, " << tcount << " types" << endl << endl;
|
|
|
|
cmLists << "# targets, logic, etc are within a set of macros shared by all schemas" << endl;
|
|
cmLists << "include(${SC_CMAKE_DIR}/SC_CXX_schema_macros.cmake)" << endl;
|
|
|
|
const int entityChunks = std::max( 1, ( ecount + static_cast<int>( entityChunkSize ) - 1 ) / static_cast<int>( entityChunkSize ) );
|
|
const int typeChunks = std::max( 1, ( tcount + static_cast<int>( typeChunkSize ) - 1 ) / static_cast<int>( typeChunkSize ) );
|
|
cmLists << "if(SC_EXP2CXX_LATE_BOUND)" << endl;
|
|
cmLists << " set(" << shortName << "_file_count 10)" << endl;
|
|
cmLists << "else()" << endl;
|
|
cmLists << " set(" << shortName << "_file_count " <<
|
|
( ecount + tcount ) * 2 + entityChunks + typeChunks + 14 << ")" << endl;
|
|
cmLists << "endif()" << endl << endl;
|
|
|
|
|
|
cmLists << "PROJECT(" << shortName << ")" << endl;
|
|
cmLists << "# list headers so they can be installed - entity, type, misc" << endl;
|
|
|
|
cmLists << "if(SC_EXP2CXX_LATE_BOUND)" << endl;
|
|
cmLists << " set(" << shortName << "_entity_hdrs)" << endl;
|
|
cmLists << "else()" << endl;
|
|
cmLists << " set(" << shortName << "_entity_hdrs" << endl;
|
|
cmLists << eh.str();
|
|
cmLists << " )" << endl;
|
|
cmLists << "endif()" << endl << endl;
|
|
|
|
cmLists << "if(SC_EXP2CXX_LATE_BOUND)" << endl;
|
|
cmLists << " set(" << shortName << "_type_hdrs)" << endl;
|
|
cmLists << "else()" << endl;
|
|
cmLists << " set(" << shortName << "_type_hdrs" << endl;
|
|
cmLists << th.str();
|
|
cmLists << " )" << endl;
|
|
cmLists << "endif()" << endl << endl;
|
|
|
|
cmLists << "set(" << shortName << "_misc_hdrs" << endl;
|
|
cmLists << " Sdaiclasses.h schema.h" << endl;
|
|
cmLists << " Sdai" << schema_upper << "Names.h" << endl;
|
|
cmLists << " Sdai" << schema_upper << ".h" << endl;
|
|
cmLists << " )" << endl << endl;
|
|
|
|
cmLists << "# install all headers" << endl;
|
|
cmLists << "set(all_headers ${" << shortName << "_entity_hdrs} ${" << shortName << "_type_hdrs} ${" << shortName << "_misc_hdrs})" << endl;
|
|
cmLists << "foreach( header_file ${all_headers} )" << endl;
|
|
cmLists << " set(curr_dir)" << endl;
|
|
cmLists << " get_filename_component(curr_dir ${header_file} PATH)" << endl;
|
|
cmLists << " get_filename_component(curr_name ${header_file} NAME)" << endl;
|
|
cmLists << " if (curr_dir)" << endl;
|
|
cmLists << " install( FILES ${header_file} DESTINATION \"include/schemas/" << shortName << "/${curr_dir}\" )" << endl;
|
|
cmLists << " else (curr_dir)" << endl;
|
|
cmLists << " install( FILES ${header_file} DESTINATION \"include/schemas/" << shortName << "\" )" << endl;
|
|
cmLists << " endif (curr_dir)" << endl;
|
|
cmLists << "endforeach()" << endl;
|
|
|
|
cmLists << "# implementation files - 3 lists" << endl << endl;
|
|
|
|
cmLists << "# unity build: #include small .cc files to reduce the number" << endl;
|
|
cmLists << "# of translation units that must be compiled" << endl;
|
|
cmLists << "if(SC_EXP2CXX_LATE_BOUND)" << endl;
|
|
cmLists << " set(" << shortName << "_unity_build FALSE)" << endl;
|
|
cmLists << " set(" << shortName << "_entity_impls)" << endl;
|
|
cmLists << " set(" << shortName << "_type_impls)" << endl;
|
|
cmLists << "elseif(SC_UNITY_BUILD)" << endl;
|
|
cmLists << " # bounded compact chunks; definitions are target-local" << endl;
|
|
cmLists << " set(" << shortName << "_unity_build TRUE)" << endl;
|
|
cmLists << " set(" << shortName << "_entity_impls" << endl;
|
|
for( int chunk = 1; chunk <= entityChunks; ++chunk ) {
|
|
cmLists << " Sdai" << schema_upper << "_unity_entities_" <<
|
|
std::setfill( '0' ) << std::setw( 4 ) << chunk << ".cc" << endl;
|
|
}
|
|
cmLists << " )" << endl;
|
|
cmLists << " set(" << shortName << "_type_impls" << endl;
|
|
for( int chunk = 1; chunk <= typeChunks; ++chunk ) {
|
|
cmLists << " Sdai" << schema_upper << "_unity_types_" <<
|
|
std::setfill( '0' ) << std::setw( 4 ) << chunk << ".cc" << endl;
|
|
}
|
|
cmLists << " )" << endl;
|
|
cmLists << "else()" << endl;
|
|
cmLists << " set(" << shortName << "_entity_impls" << endl;
|
|
cmLists << ei.str();
|
|
cmLists << " )" << endl << endl;
|
|
|
|
cmLists << " set(" << shortName << "_type_impls" << endl;
|
|
cmLists << ti.str();
|
|
cmLists << " )" << endl;
|
|
cmLists << "endif()" << endl << endl;
|
|
|
|
cmLists << "set( " << shortName << "_misc_impls" << endl;
|
|
cmLists << " SdaiAll.cc compstructs.cc schema.cc" << endl;
|
|
cmLists << " Sdai" << schema_upper << ".cc" << endl;
|
|
cmLists << " Sdai" << schema_upper << ".init.cc" << endl;
|
|
cmLists << " Sdai" << schema_upper << ".sources.cmake )" << endl << endl;
|
|
|
|
cmLists << "set(schema_target_files ${" << shortName << "_entity_impls} " << "${" << shortName << "_type_impls} " << "${" << shortName << "_misc_impls})" << endl;
|
|
cmLists << "SCHEMA_TARGETS(\"" << input_filename << "\" \"" << schemaName << "\"" << endl;
|
|
cmLists << " \"${schema_target_files}\")" << endl;
|
|
|
|
cmLists.close();
|
|
|
|
char pwd[BUFSIZ+1] = {0};
|
|
if( getcwd( pwd, BUFSIZ ) ) {
|
|
cout << pwd << "/" << shortName << endl;
|
|
} else {
|
|
cerr << "Error encountered by getcwd() for " << shortName << " - exiting. Error was ";
|
|
perror( 0 );
|
|
exit( EXIT_FAILURE );
|
|
}
|
|
}
|
|
|
|
void printSchemaFilenames( Schema sch ){
|
|
const int numColumns = 2;
|
|
const int colWidth = 75;
|
|
const char * tab = " ";
|
|
|
|
stringstream typeHeaders, typeImpls, entityHeaders, entityImpls;
|
|
typeHeaders << tab;
|
|
typeImpls << tab;
|
|
entityHeaders << tab;
|
|
entityImpls << tab;
|
|
|
|
int ecount = 0, tcount = 0;
|
|
|
|
DictionaryEntry de;
|
|
void *x;
|
|
filenames_t fn;
|
|
DICTdo_init( sch->symbol_table, &de );
|
|
while( 0 != ( x = DICTdo( &de ) ) ) {
|
|
switch( DICT_type ) {
|
|
case OBJ_ENTITY:
|
|
fn = getEntityFilenames( ( Entity ) x );
|
|
entityHeaders << std::left << std::setw( colWidth ) << fn.header << " ";
|
|
entityImpls << std::left << std::setw( colWidth ) << fn.impl << " ";
|
|
++ecount;
|
|
if( ( ecount % numColumns ) == 0 ) {
|
|
// columns
|
|
entityHeaders << endl << tab;
|
|
entityImpls << endl << tab;
|
|
}
|
|
break;
|
|
case OBJ_TYPE: {
|
|
Type t = ( Type ) x;
|
|
if( TYPEis_enumeration( t ) && ( TYPEget_head( t ) ) ) {
|
|
/* t is a renamed enum type, for which exp2cxx
|
|
* will print a typedef in an existing file */
|
|
break;
|
|
}
|
|
if( notGenerated( t ) ) {
|
|
/* skip builtin types */
|
|
break;
|
|
}
|
|
fn = getTypeFilenames( t );
|
|
typeHeaders << std::left << std::setw( colWidth ) << fn.header << " ";
|
|
typeImpls << std::left << std::setw( colWidth ) << fn.impl << " ";
|
|
++tcount;
|
|
if( ( tcount % numColumns ) == 0 ) {
|
|
typeHeaders << endl << tab;
|
|
typeImpls << endl << tab;
|
|
}
|
|
break;
|
|
}
|
|
/* case OBJ_FUNCTION:
|
|
* case OBJ_PROCEDURE:
|
|
* case OBJ_RULE: */
|
|
default:
|
|
/* ignore everything else */
|
|
/* TODO: if DEBUG is defined, print the names of these to stderr */
|
|
break;
|
|
}
|
|
}
|
|
//write the CMakeLists.txt
|
|
writeLists( sch->symbol.name, entityHeaders, entityImpls, ecount, typeHeaders, typeImpls, tcount );
|
|
}
|
|
|
|
int main( int argc, char ** argv ) {
|
|
/* TODO init globals! */
|
|
|
|
Schema schema;
|
|
DictionaryEntry de;
|
|
/* copied from fedex.c */
|
|
Express model;
|
|
if( ( argc != 2 && argc != 3 ) || ( strlen( argv[1] ) < 1 ) ) {
|
|
fprintf( stderr, "\nUsage: %s file.exp [entity-chunk-size[:type-chunk-size]]\nOutput: a CMakeLists.txt to build the schema,", argv[0] );
|
|
fprintf( stderr, " containing file names for entities, types, etc\n" );
|
|
fprintf( stderr, "also prints (to stdout) the absolute path to the directory CMakeLists.txt was created in\n" );
|
|
exit( EXIT_FAILURE );
|
|
}
|
|
EXPRESSprogram_name = argv[0];
|
|
input_filename = argv[1];
|
|
if( argc == 3 && !parseChunkSizes( argv[2] ) ) {
|
|
fprintf( stderr, "chunk sizes must be positive integers in N or N:N form\n" );
|
|
exit( EXIT_FAILURE );
|
|
}
|
|
|
|
EXPRESSinitialize();
|
|
|
|
model = EXPRESScreate();
|
|
EXPRESSparse( model, ( FILE * )0, input_filename );
|
|
if( ERRORoccurred ) {
|
|
EXPRESSdestroy( model );
|
|
exit( EXIT_FAILURE );
|
|
}
|
|
EXPRESSresolve( model );
|
|
if( ERRORoccurred ) {
|
|
int result = EXPRESS_fail( model );
|
|
EXPRESScleanup();
|
|
EXPRESSdestroy( model );
|
|
exit( result );
|
|
}
|
|
|
|
DICTdo_type_init( model->symbol_table, &de, OBJ_SCHEMA );
|
|
while( 0 != ( schema = ( Schema )DICTdo( &de ) ) ) {
|
|
printSchemaFilenames( schema );
|
|
}
|
|
|
|
EXPRESSdestroy( model );
|
|
exit( EXIT_SUCCESS );
|
|
}
|