rokojori-godot/create-cpp.js

304 lines
6.7 KiB
JavaScript

const fs = require( "node:fs/promises" );
const path = require( "path" );
const { loadJSON, getFiles, saveUTF8, getMatches, loadUTF8, insertText } = require( "./library.js" );
const { CppClassMember } = require( "./cpp-class-member.js" );
function createCppHeader( className, extendingClassName, headerDefine, protectedMembers,
publicMembers, includes )
{
let cppHeader =
`
/* ${className}.h */
#ifndef ${headerDefine}
#define ${headerDefine}
${includes}
class ${className} : public ${extendingClassName}
{
GDCLASS(${className}, ${extendingClassName});
protected:
static void _bind_methods();
${protectedMembers}
public:
${publicMembers}
${className}();
~${className}();
};
#endif // ${headerDefine}
`
return cppHeader;
}
function createCppImplementation( className, boundMethods, constructorExpressions, destructorExpressions, methodImplementations )
{
let cppImplementation =
`
/* ${className}.cpp */
#include "${className}.h"
void ${className}::_bind_methods()
{
${boundMethods}
}
${className}::${className}()
{
${constructorExpressions}
}
${className}::~${className}()
{
${destructorExpressions}
}
${methodImplementations}
`
return cppImplementation;
}
let definitionsPath = __dirname + "/rokojori-action-library-definitions";
let outputPath = __dirname + "/rokojori-action-library";
let registerTypesPath = outputPath + "/register_types.cpp";
let classRegistrationRegex = /ClassDB\:\:register_class\<(\w+)\>\(\)\;/;
function createTypeRegistration( className )
{
return `ClassDB::register_class<${className}>();`;
}
function createIncludes( className )
{
console.log( "Create include", className );
let godotClasses =
{
"Node" : "scene/main/node.h",
"Node2D" : "scene/2d/node_2d.h",
"Node3D" : "scene/3d/node_3d.h",
"Resource" : "core/io/resource.h"
}
if ( godotClasses[ className ] )
{
return `#include "${godotClasses[ className ]}"`;
}
return `#include "./${className}.h"`;
}
async function main()
{
let files = await getFiles( definitionsPath );
files = files.filter( f => ! f.startsWith( "_" ) );
let typesFile = await loadUTF8( registerTypesPath );
let types = getRegistratedTypes( typesFile );
let missingTypes = [];
for ( let file of files )
{
await createCppFiles( file, types, missingTypes );
}
if ( missingTypes.length === 0 )
{
return;
}
console.log( "Registrating missing types:", missingTypes );
let additions = missingTypes.map( mt => createTypeRegistration( mt ) );
let additionsBlock = additions.join( "\n " );
let lastType = types[ types.length -1 ];
let insertPosition = lastType.index + lastType.match.length;
let updatedTypesFile = insertText( typesFile, "\n " + additionsBlock, insertPosition );
let includeMatches = getMatches( updatedTypesFile, /#include "(\w|\.|\/|\\)+.h"/ );
let lastInclude = includeMatches[ includeMatches.length - 1 ];
let includes = missingTypes.map( mt => createIncludes( mt ) ).join( "\n" );
insertPosition = lastInclude.index + lastInclude.match.length;
updatedTypesFile = insertText( updatedTypesFile, "\n" + includes, insertPosition );
saveUTF8( registerTypesPath, updatedTypesFile );
}
function grabMembers( membersData )
{
let members = [];
if ( ! membersData )
{
return members;
}
for ( let it in membersData )
{
if ( ! membersData.hasOwnProperty( it ) )
{
continue;
}
let nameDefinition = it;
let memberData = membersData[ it ];
let classMember = new CppClassMember( nameDefinition, memberData );
members.push( classMember );
}
return members;
}
function getHeaderDefine( className )
{
className = className.replace( /^RJ/, "" );
let output = [ className[ 0 ] ];
for ( let i = 1; i < className.length; i++ )
{
let character = className[ i ];
let upperCaseCharacter = character.toUpperCase();
if ( character === upperCaseCharacter )
{
output.push( "_" );
}
output.push( upperCaseCharacter );
}
className = output.join( "" );
return "ROKOJORI__" + className + "_H";
}
function getRegistratedTypes( text )
{
let matches = getMatches( text, classRegistrationRegex );
let types = matches.map(
m =>
{
return { match: m.match, index: m.index, type: classRegistrationRegex.exec( m.match )[ 1 ] }
}
);
return types;
}
async function createCppFiles( definitionPath, types, missingTypes )
{
console.log( "Creating: ", definitionPath );
let jsonPath = path.join( definitionsPath, definitionPath );
let data = await loadJSON( jsonPath );
//console.log( "data:", jsonPath, data );
let classNameResult = /(\w+)(?:\:(\w+))?/.exec( data.class );
let className = classNameResult[ 1 ];
let extendingClass = classNameResult[ 2 ];
let headerDefine = getHeaderDefine( className );
let protectedMembers = grabMembers( data.protected );
let publicMembers = grabMembers( data.public );
let allMembers = [].concat( protectedMembers ).concat( publicMembers );
let protectedHeader = protectedMembers.map( m => m.getHeaderDefinition() ).join( "\n " );
let publicHeader = publicMembers.map( m => m.getHeaderDefinition() ).join( "\n " );
let properties = publicMembers.filter( m => ! m.isMethod );
let propertyDefinitions = properties.map( p => p.getPropertyHeaderDefinition() ).join( "\n " );
protectedHeader += "\n\n " + propertyDefinitions;
let methodBindings = allMembers.map( m => m.getMethodBinding( className ) ).join( "\n " );
let initializers = allMembers.filter( m => m.isMemberWithInitialValue ).map( m => m.getMemberInitializer() ).join( "\n ");
let includes = "";
if ( data.includes )
{
includes = data.includes.map( inc => `#include "${inc}"` );
}
let extendingClassInclude = createIncludes( extendingClass );
includes += "\n" + extendingClassInclude;
let header = createCppHeader( className, extendingClass, headerDefine, protectedHeader, publicHeader, includes );
let constructorExpressions = initializers;
let destructorExpressions = "";
let methodImplementations = "";
let propertyImplementations = properties.map( p => p.getPropertyImplementation( className ) ).join( "\n" );
methodImplementations += propertyImplementations;
let implementation = createCppImplementation( className, methodBindings, constructorExpressions, destructorExpressions, methodImplementations );
//console.log( header );
//console.log( implementation );
let hasType = types.find( t => t.type === className );
if ( ! hasType )
{
missingTypes.push( className );
}
let rawFilePath = path.join( outputPath, className );
await saveUTF8( rawFilePath + ".h", header );
await saveUTF8( rawFilePath + ".cpp", implementation );
}
main();