diff --git a/spine-c/codegen/src/index.ts b/spine-c/codegen/src/index.ts index 4b0831799..e7785a627 100644 --- a/spine-c/codegen/src/index.ts +++ b/spine-c/codegen/src/index.ts @@ -7,6 +7,7 @@ import { isTypeExcluded, loadExclusions } from './exclusions'; import { generateArrays, generateTypes } from './ir-generator'; import { extractTypes } from './type-extractor'; import type { ClassOrStruct } from './types'; +import { toSnakeCase } from './types'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -69,12 +70,123 @@ export async function generate() { // Generate C intermediate representation for classes, enums and arrays const { cTypes, cEnums } = await generateTypes(types, exclusions, allExtractedTypes); const cArrayTypes = await generateArrays(types, arrayType, exclusions); - return { cTypes, cEnums, cArrayTypes }; + + // Build inheritance relationships including template classes + const supertypes = buildSupertypesMap(allExtractedTypes.filter(t => t.kind !== 'enum') as ClassOrStruct[], types.filter(t => t.kind !== 'enum') as ClassOrStruct[]); + + console.log('Built supertypes map with', Object.keys(supertypes).length, 'entries'); + for (const [child, supertypeList] of Object.entries(supertypes)) { + if (child.includes('constraint')) { + console.log(` ${child} -> [${supertypeList.join(', ')}]`); + } + } + + // Build subtypes map (opposite of supertypes) + const subtypes = buildSubtypesMap(supertypes); + + return { cTypes, cEnums, cArrayTypes, supertypes, subtypes }; +} + +/** Build supertypes map for inheritance relationships, including template classes */ +function buildSupertypesMap(allTypes: (ClassOrStruct)[], nonTemplateTypes: (ClassOrStruct)[]): Record { + const supertypes: Record = {}; + const typeMap = new Map(); + + // Build type lookup map from all types (including templates) + for (const type of allTypes) { + typeMap.set(type.name, type); + } + + // Build non-template type lookup for C names + const nonTemplateMap = new Map(); + for (const type of nonTemplateTypes) { + nonTemplateMap.set(type.name, type); + } + + // For each non-template type, find all non-template ancestors + for (const type of nonTemplateTypes) { + const classType = type; + const cName = `spine_${toSnakeCase(classType.name)}`; + const supertypeList = findNonTemplateSupertypes(classType, typeMap, nonTemplateMap); + + if (supertypeList.length > 0) { + supertypes[cName] = supertypeList; + } + } + + return supertypes; +} + +/** Find all non-template supertypes for a given type */ +function findNonTemplateSupertypes(type: ClassOrStruct, typeMap: Map, nonTemplateMap: Map): string[] { + const visited = new Set(); + const supertypes: string[] = []; + + function traverse(currentType: ClassOrStruct) { + if (visited.has(currentType.name)) return; + visited.add(currentType.name); + + if (!currentType.superTypes) return; + + for (const superTypeName of currentType.superTypes) { + // Extract base type name from templated types like "ConstraintGeneric<...>" + const baseTypeName = superTypeName.split('<')[0]; + const superType = typeMap.get(baseTypeName); + if (!superType) continue; + + // If this supertype is not a template and has bindings, add it to supertypes + if (!superType.isTemplate && nonTemplateMap.has(superType.name)) { + const cName = `spine_${toSnakeCase(superType.name)}`; + if (!supertypes.includes(cName)) { + supertypes.push(cName); + } + } + + // Continue traversing up the chain (through templates too) + traverse(superType); + } + } + + traverse(type); + return supertypes; +} + +/** Build subtypes map from supertypes data */ +function buildSubtypesMap(supertypes: Record): Record { + const subtypes: Record = {}; + + // For each type and its supertypes, add this type to each supertype's subtypes list + for (const [childType, supertypeList] of Object.entries(supertypes)) { + for (const supertype of supertypeList) { + if (!subtypes[supertype]) { + subtypes[supertype] = []; + } + if (!subtypes[supertype].includes(childType)) { + subtypes[supertype].push(childType); + } + } + } + + return subtypes; } async function main() { + // Check if we should just export JSON for debugging + if (process.argv.includes('--export-json')) { + // Suppress console output during generation + const originalLog = console.log; + console.log = () => {}; + + const { cTypes, cEnums, cArrayTypes, supertypes, subtypes } = await generate(); + + // Restore console.log and output JSON + console.log = originalLog; + console.log(JSON.stringify({ cTypes, cEnums, cArrayTypes, supertypes, subtypes }, null, 2)); + return; + } + // Generate C types and enums - const { cTypes, cEnums, cArrayTypes } = await generate(); + const { cTypes, cEnums, cArrayTypes, supertypes, subtypes } = await generate(); // Write all files to disk const cWriter = new CWriter(path.join(__dirname, '../../src/generated'));