|
@@ -6,7 +6,7 @@ import { checkConstNonConstConflicts, checkFieldAccessorConflicts, checkMethodTy
|
|
|
import { isTypeExcluded, loadExclusions } from './exclusions';
|
|
|
import { generateArrays, generateTypes } from './ir-generator';
|
|
|
import { extractTypes } from './type-extractor';
|
|
|
-import type { ClassOrStruct } from './types';
|
|
|
+import type { ClassOrStruct, Method } from './types';
|
|
|
import { toSnakeCase } from './types';
|
|
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -71,20 +71,30 @@ export async function generate() {
|
|
|
const { cTypes, cEnums } = await generateTypes(types, exclusions, allExtractedTypes);
|
|
|
const cArrayTypes = await generateArrays(types, arrayType, exclusions);
|
|
|
|
|
|
- // Build inheritance relationships including template classes
|
|
|
+ // Build interface/pure type information first
|
|
|
+ const isInterface = buildInterfaceMap(allExtractedTypes.filter(t => t.kind !== 'enum') as ClassOrStruct[]);
|
|
|
+
|
|
|
+ // Build inheritance structure
|
|
|
+ const inheritance = buildInheritanceMap(
|
|
|
+ allExtractedTypes.filter(t => t.kind !== 'enum') as ClassOrStruct[],
|
|
|
+ types.filter(t => t.kind !== 'enum') as ClassOrStruct[],
|
|
|
+ isInterface
|
|
|
+ );
|
|
|
+
|
|
|
+ // Build legacy maps for RTTI switching (still needed)
|
|
|
const supertypes = buildSupertypesMap(allExtractedTypes.filter(t => t.kind !== 'enum') as ClassOrStruct[], types.filter(t => t.kind !== 'enum') as ClassOrStruct[]);
|
|
|
+ const subtypes = buildSubtypesMap(supertypes);
|
|
|
|
|
|
- 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(', ')}]`);
|
|
|
+ console.log('Built dart inheritance map with', Object.keys(inheritance).length, 'entries');
|
|
|
+ for (const [child, info] of Object.entries(inheritance)) {
|
|
|
+ if (child.includes('constraint') && (info.extends || info.mixins.length > 0)) {
|
|
|
+ const extendsStr = info.extends ? `extends ${info.extends}` : '';
|
|
|
+ const mixinsStr = info.mixins.length > 0 ? `with ${info.mixins.join(', ')}` : '';
|
|
|
+ console.log(` ${child} ${extendsStr} ${mixinsStr}`.trim());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Build subtypes map (opposite of supertypes)
|
|
|
- const subtypes = buildSubtypesMap(supertypes);
|
|
|
-
|
|
|
- return { cTypes, cEnums, cArrayTypes, supertypes, subtypes };
|
|
|
+ return { cTypes, cEnums, cArrayTypes, inheritance, supertypes, subtypes, isInterface };
|
|
|
}
|
|
|
|
|
|
/** Build supertypes map for inheritance relationships, including template classes */
|
|
@@ -154,7 +164,7 @@ function findNonTemplateSupertypes(type: ClassOrStruct, typeMap: Map<string, Cla
|
|
|
/** Build subtypes map from supertypes data */
|
|
|
function buildSubtypesMap(supertypes: Record<string, string[]>): Record<string, string[]> {
|
|
|
const subtypes: Record<string, string[]> = {};
|
|
|
-
|
|
|
+
|
|
|
// 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) {
|
|
@@ -166,10 +176,129 @@ function buildSubtypesMap(supertypes: Record<string, string[]>): Record<string,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return subtypes;
|
|
|
}
|
|
|
|
|
|
+/** Build interface map to identify pure/interface types */
|
|
|
+function buildInterfaceMap(allTypes: ClassOrStruct[]): Record<string, boolean> {
|
|
|
+ const interfaces: Record<string, boolean> = {};
|
|
|
+
|
|
|
+ for (const type of allTypes) {
|
|
|
+ const cName = `spine_${toSnakeCase(type.name)}`;
|
|
|
+ interfaces[cName] = isPureInterface(type);
|
|
|
+ }
|
|
|
+
|
|
|
+ return interfaces;
|
|
|
+}
|
|
|
+
|
|
|
+/** Build inheritance map with extends/implements structure */
|
|
|
+function buildInheritanceMap(
|
|
|
+ allTypes: ClassOrStruct[],
|
|
|
+ nonTemplateTypes: ClassOrStruct[],
|
|
|
+ isInterface: Record<string, boolean>
|
|
|
+): Record<string, { extends?: string, mixins: string[] }> {
|
|
|
+ const inheritance: Record<string, { extends?: string, mixins: string[] }> = {};
|
|
|
+ const typeMap = new Map<string, ClassOrStruct>();
|
|
|
+ const nonTemplateMap = new Map<string, ClassOrStruct>();
|
|
|
+
|
|
|
+ // Build type lookup maps
|
|
|
+ for (const type of allTypes) {
|
|
|
+ typeMap.set(type.name, type);
|
|
|
+ }
|
|
|
+ for (const type of nonTemplateTypes) {
|
|
|
+ nonTemplateMap.set(type.name, type);
|
|
|
+ }
|
|
|
+
|
|
|
+ // For each non-template type, determine its extends/implements
|
|
|
+ for (const type of nonTemplateTypes) {
|
|
|
+ const cName = `spine_${toSnakeCase(type.name)}`;
|
|
|
+ const directParents = findDirectNonTemplateParents(type, typeMap, nonTemplateMap);
|
|
|
+
|
|
|
+ const concreteParents = directParents.filter(parent => !isInterface[parent]);
|
|
|
+ const interfaceParents = directParents.filter(parent => isInterface[parent]);
|
|
|
+
|
|
|
+ inheritance[cName] = {
|
|
|
+ extends: concreteParents.length > 0 ? concreteParents[0] : undefined,
|
|
|
+ mixins: interfaceParents
|
|
|
+ };
|
|
|
+
|
|
|
+ // Fail hard if multiple concrete parents (can't represent in single inheritance languages)
|
|
|
+ if (concreteParents.length > 1) {
|
|
|
+ console.error(`\nERROR: ${cName} has multiple concrete parents: ${concreteParents.join(', ')}`);
|
|
|
+ console.error(`This cannot be represented in single inheritance languages like Dart, Swift, Java, etc.`);
|
|
|
+ console.error(`\nTo fix this, convert one of the concrete parent classes to a pure interface:`);
|
|
|
+ for (const parent of concreteParents) {
|
|
|
+ const parentTypeName = parent.replace('spine_', '').replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()).replace(/ /g, '');
|
|
|
+ console.error(` - Make ${parentTypeName} a pure interface (all methods = 0)`);
|
|
|
+ }
|
|
|
+ console.error(`\nThen update the C++ class hierarchy and rebuild.`);
|
|
|
+ throw new Error(`Multiple concrete inheritance detected in ${cName}. Cannot generate bindings for single inheritance languages.`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return inheritance;
|
|
|
+}
|
|
|
+
|
|
|
+/** Find direct non-template parents for a type */
|
|
|
+function findDirectNonTemplateParents(
|
|
|
+ type: ClassOrStruct,
|
|
|
+ typeMap: Map<string, ClassOrStruct>,
|
|
|
+ nonTemplateMap: Map<string, ClassOrStruct>
|
|
|
+): string[] {
|
|
|
+ if (!type.superTypes) return [];
|
|
|
+
|
|
|
+ const directParents: string[] = [];
|
|
|
+
|
|
|
+ for (const superTypeName of type.superTypes) {
|
|
|
+ // Extract base type name from templated types
|
|
|
+ const baseTypeName = superTypeName.split('<')[0];
|
|
|
+ const superType = typeMap.get(baseTypeName);
|
|
|
+
|
|
|
+ if (!superType) continue;
|
|
|
+
|
|
|
+ if (!superType.isTemplate && nonTemplateMap.has(superType.name)) {
|
|
|
+ // Direct non-template parent
|
|
|
+ const cName = `spine_${toSnakeCase(superType.name)}`;
|
|
|
+ directParents.push(cName);
|
|
|
+ } else if (superType.isTemplate) {
|
|
|
+ // Template parent - recurse to find its non-template parents
|
|
|
+ const inheritedParents = findDirectNonTemplateParents(superType, typeMap, nonTemplateMap);
|
|
|
+ directParents.push(...inheritedParents);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return [...new Set(directParents)]; // Remove duplicates
|
|
|
+}
|
|
|
+
|
|
|
+/** Check if a class/struct is a pure interface (all methods are pure virtual or only has constructors/destructors) */
|
|
|
+function isPureInterface(type: ClassOrStruct): boolean {
|
|
|
+ if (!type.members || type.members.length === 0) {
|
|
|
+ return true; // No members = pure interface
|
|
|
+ }
|
|
|
+
|
|
|
+ const methods = type.members.filter(m => m.kind === 'method') as Method[];
|
|
|
+ const fields = type.members.filter(m => m.kind === 'field');
|
|
|
+
|
|
|
+ // If it has fields, it's not a pure interface
|
|
|
+ if (fields.length > 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Filter out RTTI methods (like getRTTI) which are infrastructure methods
|
|
|
+ const nonRttiMethods = methods.filter(method =>
|
|
|
+ !method.name.toLowerCase().includes('rtti')
|
|
|
+ );
|
|
|
+
|
|
|
+ // If no non-RTTI methods, only constructors/destructors/RTTI = pure interface
|
|
|
+ if (nonRttiMethods.length === 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if all non-RTTI methods are pure virtual
|
|
|
+ return nonRttiMethods.every(method => method.isPure === true);
|
|
|
+}
|
|
|
+
|
|
|
async function main() {
|
|
|
// Check if we should just export JSON for debugging
|
|
|
if (process.argv.includes('--export-json')) {
|
|
@@ -177,21 +306,33 @@ async function main() {
|
|
|
const originalLog = console.log;
|
|
|
console.log = () => {};
|
|
|
|
|
|
- const { cTypes, cEnums, cArrayTypes, supertypes, subtypes } = await generate();
|
|
|
+ const { cTypes, cEnums, cArrayTypes, inheritance, supertypes, subtypes, isInterface } = await generate();
|
|
|
|
|
|
// Restore console.log and output JSON
|
|
|
console.log = originalLog;
|
|
|
- console.log(JSON.stringify({ cTypes, cEnums, cArrayTypes, supertypes, subtypes }, null, 2));
|
|
|
+ console.log(JSON.stringify({ cTypes, cEnums, cArrayTypes, inheritance, supertypes, subtypes, isInterface }, null, 2));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Generate C types and enums
|
|
|
- const { cTypes, cEnums, cArrayTypes, supertypes, subtypes } = await generate();
|
|
|
+ const { cTypes, cEnums, cArrayTypes, inheritance, supertypes, subtypes, isInterface } = await generate();
|
|
|
|
|
|
// Write all files to disk
|
|
|
const cWriter = new CWriter(path.join(__dirname, '../../src/generated'));
|
|
|
await cWriter.writeAll(cTypes, cEnums, cArrayTypes);
|
|
|
|
|
|
+ // Format the generated code
|
|
|
+ console.log('Formatting generated code...');
|
|
|
+ try {
|
|
|
+ const { execSync } = await import('child_process');
|
|
|
+ const formatterPath = path.join(__dirname, '../../../formatters/format-cpp.sh');
|
|
|
+ execSync(formatterPath, { stdio: 'inherit' });
|
|
|
+ console.log('Code formatting complete!');
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('Warning: Code formatting failed:', error);
|
|
|
+ console.warn('You may need to run the formatter manually');
|
|
|
+ }
|
|
|
+
|
|
|
console.log('Code generation complete!');
|
|
|
}
|
|
|
|