mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-09 00:30:12 +08:00
362 lines
12 KiB
TypeScript
362 lines
12 KiB
TypeScript
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import type { CClassOrStruct, CEnum, CMethod, CParameter } from './c-types';
|
|
import { toSnakeCase } from './types';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const LICENSE_HEADER = fs.readFileSync(path.join(__dirname, '../../../spine-cpp/src/spine/Skeleton.cpp'), 'utf8').split('\n').slice(0, 28).join('\n');
|
|
|
|
/** Generates strings for CClassOrStruct and CEnum, and writes them to files. */
|
|
export class CWriter {
|
|
constructor(private outputDir: string) {
|
|
// Clean and recreate output directory
|
|
this.cleanOutputDirectory();
|
|
}
|
|
|
|
private cleanOutputDirectory(): void {
|
|
// Remove existing generated directory if it exists
|
|
if (fs.existsSync(this.outputDir)) {
|
|
console.log(`Cleaning ${this.outputDir}...`);
|
|
fs.rmSync(this.outputDir, { recursive: true, force: true });
|
|
}
|
|
|
|
// Recreate the directory
|
|
fs.mkdirSync(this.outputDir, { recursive: true });
|
|
}
|
|
|
|
writeClassHeader(type: CClassOrStruct): string {
|
|
const lines: string[] = [];
|
|
|
|
// Add header guard
|
|
const guardName = `SPINE_${type.name.toUpperCase()}_H`;
|
|
lines.push(`#ifndef ${guardName}`);
|
|
lines.push(`#define ${guardName}`);
|
|
lines.push('');
|
|
|
|
// Add includes
|
|
lines.push('#include "../base.h"');
|
|
lines.push('#include "types.h"');
|
|
lines.push('#include "arrays.h"');
|
|
lines.push('');
|
|
|
|
// Add extern C
|
|
lines.push('#ifdef __cplusplus');
|
|
lines.push('extern "C" {');
|
|
lines.push('#endif');
|
|
lines.push('');
|
|
|
|
// Add all method declarations
|
|
for (const constr of type.constructors) {
|
|
lines.push(this.writeMethodDeclaration(constr));
|
|
}
|
|
|
|
if (type.constructors.length > 0) {
|
|
lines.push('');
|
|
}
|
|
|
|
if (type.destructor) {
|
|
lines.push(this.writeMethodDeclaration(type.destructor));
|
|
lines.push('');
|
|
}
|
|
|
|
for (const method of type.methods) {
|
|
lines.push(this.writeMethodDeclaration(method));
|
|
}
|
|
|
|
// Close extern C
|
|
lines.push('');
|
|
lines.push('#ifdef __cplusplus');
|
|
lines.push('}');
|
|
lines.push('#endif');
|
|
lines.push('');
|
|
lines.push(`#endif /* ${guardName} */`);
|
|
lines.push('');
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
writeClassSource(type: CClassOrStruct): string {
|
|
const lines: string[] = [];
|
|
|
|
// Add includes
|
|
lines.push(`#include "${type.name.replace("spine_", "")}.h"`);
|
|
lines.push('#include <spine/spine.h>');
|
|
lines.push('');
|
|
lines.push('using namespace spine;');
|
|
lines.push('');
|
|
|
|
// Add all method implementations
|
|
for (const constr of type.constructors) {
|
|
lines.push(this.writeMethodImplementation(constr));
|
|
lines.push('');
|
|
}
|
|
|
|
if (type.destructor) {
|
|
lines.push(this.writeMethodImplementation(type.destructor));
|
|
lines.push('');
|
|
}
|
|
|
|
for (const method of type.methods) {
|
|
lines.push(this.writeMethodImplementation(method));
|
|
lines.push('');
|
|
}
|
|
|
|
return `${lines.join('\n').trim()}\n`;
|
|
}
|
|
|
|
writeEnumHeader(enumType: CEnum): string {
|
|
const lines: string[] = [];
|
|
|
|
// Add header guard
|
|
const guardName = `SPINE_${enumType.name.toUpperCase()}_H`;
|
|
lines.push(`#ifndef ${guardName}`);
|
|
lines.push(`#define ${guardName}`);
|
|
lines.push('');
|
|
|
|
// Add extern C
|
|
lines.push('#ifdef __cplusplus');
|
|
lines.push('extern "C" {');
|
|
lines.push('#endif');
|
|
lines.push('');
|
|
|
|
// Add enum declaration
|
|
lines.push(`typedef enum ${enumType.name} {`);
|
|
|
|
for (let i = 0; i < enumType.values.length; i++) {
|
|
const value = enumType.values[i];
|
|
const comma = i < enumType.values.length - 1 ? ',' : '';
|
|
|
|
if (value.value) {
|
|
lines.push(` ${value.name} = ${value.value}${comma}`);
|
|
} else {
|
|
lines.push(` ${value.name}${comma}`);
|
|
}
|
|
}
|
|
|
|
lines.push(`} ${enumType.name};`);
|
|
|
|
// Close extern C
|
|
lines.push('');
|
|
lines.push('#ifdef __cplusplus');
|
|
lines.push('}');
|
|
lines.push('#endif');
|
|
lines.push('');
|
|
lines.push(`#endif /* ${guardName} */`);
|
|
lines.push('');
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
private writeMethodDeclaration(method: CMethod): string {
|
|
const params = this.formatParameters(method.parameters);
|
|
return `SPINE_C_API ${method.returnType} ${method.name}(${params});`;
|
|
}
|
|
|
|
private writeMethodImplementation(method: CMethod): string {
|
|
const params = this.formatParameters(method.parameters);
|
|
const signature = `${method.returnType} ${method.name}(${params})`;
|
|
|
|
return `${signature} {
|
|
${method.body}
|
|
}`;
|
|
}
|
|
|
|
private formatParameters(parameters: CParameter[]): string {
|
|
if (parameters.length === 0) {
|
|
return 'void';
|
|
}
|
|
|
|
return parameters
|
|
.map(p => `${p.cType} ${p.name}`)
|
|
.join(', ');
|
|
}
|
|
|
|
async writeType(typeName: string, headerContent: string, sourceContent?: string): Promise<void> {
|
|
const fileName = toSnakeCase(typeName);
|
|
|
|
// Write header file
|
|
const headerPath = path.join(this.outputDir, `${fileName}.h`);
|
|
fs.writeFileSync(headerPath, headerContent);
|
|
|
|
// Write source file (as .cpp since it contains C++ code)
|
|
if (sourceContent) {
|
|
const sourcePath = path.join(this.outputDir, `${fileName}.cpp`);
|
|
fs.writeFileSync(sourcePath, sourceContent);
|
|
}
|
|
}
|
|
|
|
async writeMainHeader(cClasses: CClassOrStruct[]): Promise<void> {
|
|
const mainHeaderPath = path.join(this.outputDir, '..', '..', 'include', 'spine-c.h');
|
|
|
|
// Ensure include directory exists
|
|
const includeDir = path.dirname(mainHeaderPath);
|
|
if (!fs.existsSync(includeDir)) {
|
|
fs.mkdirSync(includeDir, { recursive: true });
|
|
}
|
|
|
|
const lines = [
|
|
LICENSE_HEADER,
|
|
'',
|
|
'#ifndef SPINE_C_H',
|
|
'#define SPINE_C_H',
|
|
'',
|
|
'// Base definitions',
|
|
'#include "../src/base.h"',
|
|
'',
|
|
'// All type declarations and enum includes',
|
|
'#include "../src/generated/types.h"',
|
|
'',
|
|
'// Extension types & functions',
|
|
'#include "../src/extensions.h"',
|
|
'',
|
|
'// Generated class types'
|
|
];
|
|
|
|
// Add class includes (they contain the actual function declarations)
|
|
for (const classType of cClasses) {
|
|
lines.push(`#include "../src/generated/${classType.name.replace("spine_", "")}.h"`);
|
|
}
|
|
|
|
lines.push('');
|
|
lines.push('#endif // SPINE_C_H');
|
|
lines.push('');
|
|
|
|
fs.writeFileSync(mainHeaderPath, lines.join('\n'));
|
|
}
|
|
|
|
async writeArrays(cArrayTypes: CClassOrStruct[]): Promise<void> {
|
|
console.log('\nGenerating arrays.h/arrays.cpp...');
|
|
|
|
// Generate header
|
|
const arrayHeaderLines: string[] = [];
|
|
|
|
arrayHeaderLines.push(LICENSE_HEADER);
|
|
arrayHeaderLines.push('');
|
|
arrayHeaderLines.push('#ifndef SPINE_C_ARRAYS_H');
|
|
arrayHeaderLines.push('#define SPINE_C_ARRAYS_H');
|
|
arrayHeaderLines.push('');
|
|
arrayHeaderLines.push('#include "../base.h"');
|
|
arrayHeaderLines.push('#include "types.h"');
|
|
arrayHeaderLines.push('');
|
|
arrayHeaderLines.push('#ifdef __cplusplus');
|
|
arrayHeaderLines.push('extern "C" {');
|
|
arrayHeaderLines.push('#endif');
|
|
arrayHeaderLines.push('');
|
|
|
|
// Add opaque type declarations
|
|
for (const arrayType of cArrayTypes) {
|
|
arrayHeaderLines.push(`SPINE_OPAQUE_TYPE(${arrayType.name})`);
|
|
}
|
|
|
|
arrayHeaderLines.push('');
|
|
|
|
// Add all method declarations
|
|
for (const arrayType of cArrayTypes) {
|
|
arrayHeaderLines.push(arrayType.constructors.map(c => this.writeMethodDeclaration(c)).join('\n\n'));
|
|
if (arrayType.destructor) {
|
|
arrayHeaderLines.push(this.writeMethodDeclaration(arrayType.destructor));
|
|
}
|
|
arrayHeaderLines.push(arrayType.methods.map(c => this.writeMethodDeclaration(c)).join('\n\n'));
|
|
arrayHeaderLines.push('');
|
|
}
|
|
|
|
// Close extern C
|
|
arrayHeaderLines.push('#ifdef __cplusplus');
|
|
arrayHeaderLines.push('}');
|
|
arrayHeaderLines.push('#endif');
|
|
arrayHeaderLines.push('');
|
|
arrayHeaderLines.push('#endif /* SPINE_C_ARRAYS_H */');
|
|
arrayHeaderLines.push('');
|
|
|
|
|
|
// Generate source
|
|
const arraySourceLines: string[] = [];
|
|
|
|
arraySourceLines.push(LICENSE_HEADER);
|
|
arraySourceLines.push('');
|
|
arraySourceLines.push('#include "arrays.h"');
|
|
arraySourceLines.push('#include <spine/spine.h>');
|
|
arraySourceLines.push('');
|
|
arraySourceLines.push('using namespace spine;');
|
|
arraySourceLines.push('');
|
|
|
|
// Add all method implementations
|
|
for (const arrayType of cArrayTypes) {
|
|
arraySourceLines.push(arrayType.constructors.map(c => this.writeMethodImplementation(c)).join('\n\n'));
|
|
if (arrayType.destructor) {
|
|
arraySourceLines.push(this.writeMethodImplementation(arrayType.destructor));
|
|
}
|
|
arraySourceLines.push(arrayType.methods.map(c => this.writeMethodImplementation(c)).join('\n\n'));
|
|
arraySourceLines.push('');
|
|
}
|
|
|
|
|
|
const headerPath = path.join(this.outputDir, 'arrays.h');
|
|
const sourcePath = path.join(this.outputDir, 'arrays.cpp');
|
|
|
|
fs.writeFileSync(headerPath, arrayHeaderLines.join('\n'));
|
|
fs.writeFileSync(sourcePath, arraySourceLines.join('\n'));
|
|
}
|
|
|
|
/** Writes the types.h file, which includes all class forward declarations and enum types. */
|
|
async writeTypesHeader(cClasses: CClassOrStruct[], cEnums: CEnum[]): Promise<void> {
|
|
const headerPath = path.join(this.outputDir, 'types.h');
|
|
const lines: string[] = [
|
|
LICENSE_HEADER,
|
|
'',
|
|
'#ifndef SPINE_C_TYPES_H',
|
|
'#define SPINE_C_TYPES_H',
|
|
'',
|
|
'#ifdef __cplusplus',
|
|
'extern "C" {',
|
|
'#endif',
|
|
'',
|
|
'#include "../base.h"',
|
|
'',
|
|
'// Forward declarations for all non-enum types'
|
|
];
|
|
|
|
// Forward declare all class types
|
|
for (const classType of cClasses) {
|
|
lines.push(`SPINE_OPAQUE_TYPE(${classType.name})`);
|
|
}
|
|
|
|
lines.push('');
|
|
lines.push('// Include all enum types (cannot be forward declared)');
|
|
|
|
// Include all enum headers
|
|
for (const enumType of cEnums) {
|
|
lines.push(`#include "${enumType.name.replace("spine_", "")}.h"`);
|
|
}
|
|
|
|
lines.push('');
|
|
lines.push('#ifdef __cplusplus');
|
|
lines.push('}');
|
|
lines.push('#endif');
|
|
lines.push('');
|
|
lines.push('#endif // SPINE_C_TYPES_H');
|
|
lines.push('');
|
|
|
|
fs.writeFileSync(headerPath, lines.join('\n'));
|
|
}
|
|
|
|
async writeAll(cClasses: CClassOrStruct[], cEnums: CEnum[], cArrayTypes: CClassOrStruct[]): Promise<void> {
|
|
await this.writeTypesHeader(cClasses, cEnums);
|
|
await this.writeMainHeader(cClasses);
|
|
await this.writeArrays(cArrayTypes);
|
|
|
|
// Write all class files
|
|
for (const classType of cClasses) {
|
|
const headerContent = this.writeClassHeader(classType);
|
|
const sourceContent = this.writeClassSource(classType);
|
|
await this.writeType(classType.name.replace("spine_", ""), headerContent, sourceContent);
|
|
}
|
|
|
|
// Write all enum headers
|
|
for (const enumType of cEnums) {
|
|
const headerContent = this.writeEnumHeader(enumType);
|
|
await this.writeType(enumType.name.replace("spine_", ""), headerContent);
|
|
}
|
|
}
|
|
} |