const fs = require( "node:fs/promises" ); const path = require( "path" ); const { loadJSON, getFiles, saveUTF8, getMatches, loadUTF8, insertText, isDirectory } = 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( path.join( definitionsPath, 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, accessModifier ) { let members = []; if ( ! membersData ) { return members; } for ( let it in membersData ) { if ( ! membersData.hasOwnProperty( it ) ) { continue; } let nameDefinition = it; let memberData = membersData[ it ]; let actionRegex = /^\s*action\s+(\w+)\s*$/; if ( actionRegex.test( nameDefinition ) ) { let name = actionRegex.exec( nameDefinition )[ 1 ]; let upperCaseName = name[ 0 ].toUpperCase() + name.substring( 1 ); let actionName = `on${upperCaseName}`; let signal = `signal ${name}()`; let actionGetter = `virtual get_${actionName}():Ref`; let actionSetter = `virtual set_${actionName}()`; members.push( new CppClassMember( accessModifier, signal, {} ) ); members.push( new CppClassMember( "protected", "m_" + name, {} ) ); members.push( new CppClassMember( accessModifier, actionGetter, {} ) ); members.push( new CppClassMember( accessModifier, actionSetter, { "action":"Ref"} ) ); } else { let classMember = new CppClassMember( accessModifier, 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 = definitionPath; // path.join( definitionsPath, definitionPath ); let isDir = await isDirectory( jsonPath ); if ( isDir ) { let files = await getFiles( jsonPath ); for ( let file of files ) { await createCppFiles( path.join( jsonPath, file ), types, missingTypes ); } return Promise.resolve(); } 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 fromProtectedMembers = grabMembers( data.protected, "protected" ); let fromPublicMembers = grabMembers( data.public, "public" ); let protectedMembers = []; let publicMembers = []; let insertMember = m => { let container = m.accessModifier === "public" ? publicMembers : protectedMembers; container.push( m ); }; fromProtectedMembers.forEach( m => insertMember( m ) ); fromPublicMembers.forEach( m => insertMember( m ) ); 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 ); return Promise.resolve(); } main();