[ts] Use correct typescript-formatter package

This commit is contained in:
Mario Zechner 2025-07-16 00:02:20 +02:00
parent 8972ba5dc8
commit 46e38c0356
8 changed files with 1796 additions and 1796 deletions

View File

@ -1,18 +1,18 @@
#!/bin/bash #!/bin/bash
set -e set -e
# Format TypeScript files with Biome # Format TypeScript files with tsfmt
echo "Formatting TypeScript files..." echo "Formatting TypeScript files..."
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
# Check if biome.json files match # Check if tsfmt.json files match
if ! cmp -s ../spine-ts/biome.json ../tests/biome.json; then if ! cmp -s ../spine-ts/tsfmt.json ../tests/tsfmt.json; then
echo -e "\033[1;31mERROR: spine-ts/biome.json and tests/biome.json differ!\033[0m" echo -e "\033[1;31mERROR: spine-ts/tsfmt.json and tests/tsfmt.json differ!\033[0m"
echo -e "\033[1;31mPlease sync them to ensure consistent formatting.\033[0m" echo -e "\033[1;31mPlease sync them to ensure consistent formatting.\033[0m"
exit 1 exit 1
fi fi
# Format TypeScript files # Format TypeScript files
cd ../spine-ts && npx biome format --write . && cd ../formatters cd ../spine-ts && npm run format && cd ../formatters
cd ../tests && npx biome format --write --config-path ../spine-ts . && cd ../formatters cd ../tests && npm run format -r && cd ../formatters

View File

@ -2,37 +2,37 @@ import { execSync } from 'node:child_process';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as path from 'node:path'; import * as path from 'node:path';
function findTypeScriptFiles(dir: string, files: string[] = []): string[] { function findTypeScriptFiles (dir: string, files: string[] = []): string[] {
if (!fs.existsSync(dir)) return files; if (!fs.existsSync(dir)) return files;
fs.readdirSync(dir).forEach(name => { fs.readdirSync(dir).forEach(name => {
const filePath = path.join(dir, name); const filePath = path.join(dir, name);
const stat = fs.statSync(filePath); const stat = fs.statSync(filePath);
if (stat.isDirectory()) { if (stat.isDirectory()) {
// Skip node_modules and dist directories // Skip node_modules and dist directories
if (name !== 'node_modules' && name !== 'dist') { if (name !== 'node_modules' && name !== 'dist') {
findTypeScriptFiles(filePath, files); findTypeScriptFiles(filePath, files);
} }
} else if (name.endsWith('.ts') && !name.endsWith('.d.ts')) { } else if (name.endsWith('.ts') && !name.endsWith('.d.ts')) {
files.push(filePath); files.push(filePath);
} }
}); });
return files; return files;
} }
// Find all TypeScript files in spine-* directories // Find all TypeScript files in spine-* directories
const allFiles: string[] = []; const allFiles: string[] = [];
fs.readdirSync('.').forEach(name => { fs.readdirSync('.').forEach(name => {
if (name.startsWith('spine-') && fs.statSync(name).isDirectory()) { if (name.startsWith('spine-') && fs.statSync(name).isDirectory()) {
findTypeScriptFiles(name, allFiles); findTypeScriptFiles(name, allFiles);
} }
}); });
if (allFiles.length > 0) { if (allFiles.length > 0) {
console.log(`Formatting ${allFiles.length} TypeScript files...`); console.log(`Formatting ${allFiles.length} TypeScript files...`);
execSync(`npx tsfmt -r ${allFiles.join(' ')}`, { stdio: 'inherit' }); execSync(`npx -y typescript-formatter -r ${allFiles.join(' ')}`, { stdio: 'inherit' });
} else { } else {
console.log('No TypeScript files found to format.'); console.log('No TypeScript files found to format.');
} }

View File

@ -4,7 +4,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"compare": "tsx compare-with-reference-impl.ts", "compare": "tsx compare-with-reference-impl.ts",
"format": "npx tsfmt -r ./**/*.ts", "format": "npx -y typescript-formatter -r ./**/*.ts",
"lint": "npx biome lint ." "lint": "npx biome lint ."
}, },
"devDependencies": { "devDependencies": {

File diff suppressed because it is too large Load Diff

View File

@ -7,454 +7,454 @@ import type { Property, SerializerIR } from './generate-serializer-ir';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
function transformType(javaType: string): string { function transformType (javaType: string): string {
// Remove package prefixes // Remove package prefixes
const simpleName = javaType.includes('.') ? javaType.split('.').pop()! : javaType; const simpleName = javaType.includes('.') ? javaType.split('.').pop()! : javaType;
// Handle primitive types // Handle primitive types
if (simpleName === 'String') return 'const String&'; if (simpleName === 'String') return 'const String&';
if (simpleName === 'int') return 'int'; if (simpleName === 'int') return 'int';
if (simpleName === 'float') return 'float'; if (simpleName === 'float') return 'float';
if (simpleName === 'boolean') return 'bool'; if (simpleName === 'boolean') return 'bool';
if (simpleName === 'short') return 'short'; if (simpleName === 'short') return 'short';
// Handle arrays // Handle arrays
if (simpleName.endsWith('[]')) { if (simpleName.endsWith('[]')) {
const baseType = simpleName.slice(0, -2); const baseType = simpleName.slice(0, -2);
return `Array<${transformType(baseType)}>`; return `Array<${transformType(baseType)}>`;
} }
// Object types become pointers // Object types become pointers
return simpleName; return simpleName;
} }
function generatePropertyCode(property: Property, indent: string, enumMappings: { [enumName: string]: { [javaValue: string]: string } }): string[] { function generatePropertyCode (property: Property, indent: string, enumMappings: { [enumName: string]: { [javaValue: string]: string } }): string[] {
const lines: string[] = []; const lines: string[] = [];
// Transform field access for C++: add _ prefix except for Color fields // Transform field access for C++: add _ prefix except for Color fields
let accessor = `obj->${property.getter}`; let accessor = `obj->${property.getter}`;
if (!property.getter.includes('()')) { if (!property.getter.includes('()')) {
// This is a field access, not a method call // This is a field access, not a method call
const fieldName = property.getter; const fieldName = property.getter;
// Color fields (r, g, b, a) don't get _ prefix, all others do // Color fields (r, g, b, a) don't get _ prefix, all others do
const isColorField = ['r', 'g', 'b', 'a'].includes(fieldName); const isColorField = ['r', 'g', 'b', 'a'].includes(fieldName);
if (!isColorField) { if (!isColorField) {
accessor = `obj->_${fieldName}`; accessor = `obj->_${fieldName}`;
} else { } else {
accessor = `obj->${fieldName}`; accessor = `obj->${fieldName}`;
} }
} }
// C++-specific: darkColor specifically has hasDarkColor() method // C++-specific: darkColor specifically has hasDarkColor() method
const isDarkColor = property.kind === "object" && const isDarkColor = property.kind === "object" &&
property.valueType === "Color" && property.valueType === "Color" &&
property.getter === "getDarkColor()"; property.getter === "getDarkColor()";
if (isDarkColor) { if (isDarkColor) {
const colorAccessor = `&${accessor}`; const colorAccessor = `&${accessor}`;
lines.push(`${indent}if (obj->hasDarkColor()) {`); lines.push(`${indent}if (obj->hasDarkColor()) {`);
lines.push(`${indent} ${property.writeMethodCall}(${colorAccessor});`); lines.push(`${indent} ${property.writeMethodCall}(${colorAccessor});`);
lines.push(`${indent}} else {`); lines.push(`${indent}} else {`);
lines.push(`${indent} _json.writeNull();`); lines.push(`${indent} _json.writeNull();`);
lines.push(`${indent}}`); lines.push(`${indent}}`);
return lines; return lines;
} }
switch (property.kind) { switch (property.kind) {
case "primitive": case "primitive":
lines.push(`${indent}_json.writeValue(${accessor});`); lines.push(`${indent}_json.writeValue(${accessor});`);
break; break;
case "object": case "object":
if (property.isNullable) { if (property.isNullable) {
lines.push(`${indent}if (${accessor} == nullptr) {`); lines.push(`${indent}if (${accessor} == nullptr) {`);
lines.push(`${indent} _json.writeNull();`); lines.push(`${indent} _json.writeNull();`);
lines.push(`${indent}} else {`); lines.push(`${indent}} else {`);
lines.push(`${indent} ${property.writeMethodCall}(${accessor});`); lines.push(`${indent} ${property.writeMethodCall}(${accessor});`);
lines.push(`${indent}}`); lines.push(`${indent}}`);
} else { } else {
lines.push(`${indent}${property.writeMethodCall}(${accessor});`); lines.push(`${indent}${property.writeMethodCall}(${accessor});`);
} }
break; break;
case "enum": case "enum":
const enumName = property.enumName; const enumName = property.enumName;
const enumMap = enumMappings[enumName]; const enumMap = enumMappings[enumName];
if (enumMap && Object.keys(enumMap).length > 0) { if (enumMap && Object.keys(enumMap).length > 0) {
// Generate switch statement for enum // Generate switch statement for enum
lines.push(`${indent}_json.writeValue([&]() -> String {`); lines.push(`${indent}_json.writeValue([&]() -> String {`);
lines.push(`${indent} switch(${accessor}) {`); lines.push(`${indent} switch(${accessor}) {`);
for (const [javaValue, cppValue] of Object.entries(enumMap)) { for (const [javaValue, cppValue] of Object.entries(enumMap)) {
lines.push(`${indent} case ${cppValue}: return "${javaValue}";`); lines.push(`${indent} case ${cppValue}: return "${javaValue}";`);
} }
lines.push(`${indent} default: return "unknown";`); lines.push(`${indent} default: return "unknown";`);
lines.push(`${indent} }`); lines.push(`${indent} }`);
lines.push(`${indent}}());`); lines.push(`${indent}}());`);
} else { } else {
// Fallback if no enum mapping // Fallback if no enum mapping
lines.push(`${indent}_json.writeValue(String::valueOf((int)${accessor}));`); lines.push(`${indent}_json.writeValue(String::valueOf((int)${accessor}));`);
} }
break; break;
case "array": case "array":
// In C++, arrays are never null - empty arrays (size() == 0) are equivalent to Java null // In C++, arrays are never null - empty arrays (size() == 0) are equivalent to Java null
lines.push(`${indent}_json.writeArrayStart();`); lines.push(`${indent}_json.writeArrayStart();`);
lines.push(`${indent}for (size_t i = 0; i < ${accessor}.size(); i++) {`); lines.push(`${indent}for (size_t i = 0; i < ${accessor}.size(); i++) {`);
const elementAccess = `${accessor}[i]`; const elementAccess = `${accessor}[i]`;
if (property.elementKind === "primitive") { if (property.elementKind === "primitive") {
lines.push(`${indent} _json.writeValue(${elementAccess});`); lines.push(`${indent} _json.writeValue(${elementAccess});`);
} else { } else {
lines.push(`${indent} ${property.writeMethodCall}(${elementAccess});`); lines.push(`${indent} ${property.writeMethodCall}(${elementAccess});`);
} }
lines.push(`${indent}}`); lines.push(`${indent}}`);
lines.push(`${indent}_json.writeArrayEnd();`); lines.push(`${indent}_json.writeArrayEnd();`);
break; break;
case "nestedArray": case "nestedArray":
// Nested arrays are always considered non-null in both Java and C++ // Nested arrays are always considered non-null in both Java and C++
lines.push(`${indent}_json.writeArrayStart();`); lines.push(`${indent}_json.writeArrayStart();`);
lines.push(`${indent}for (size_t i = 0; i < ${accessor}.size(); i++) {`); lines.push(`${indent}for (size_t i = 0; i < ${accessor}.size(); i++) {`);
lines.push(`${indent} Array<${property.elementType}>& nestedArray = ${accessor}[i];`); lines.push(`${indent} Array<${property.elementType}>& nestedArray = ${accessor}[i];`);
lines.push(`${indent} _json.writeArrayStart();`); lines.push(`${indent} _json.writeArrayStart();`);
lines.push(`${indent} for (size_t j = 0; j < nestedArray.size(); j++) {`); lines.push(`${indent} for (size_t j = 0; j < nestedArray.size(); j++) {`);
lines.push(`${indent} _json.writeValue(nestedArray[j]);`); lines.push(`${indent} _json.writeValue(nestedArray[j]);`);
lines.push(`${indent} }`); lines.push(`${indent} }`);
lines.push(`${indent} _json.writeArrayEnd();`); lines.push(`${indent} _json.writeArrayEnd();`);
lines.push(`${indent}}`); lines.push(`${indent}}`);
lines.push(`${indent}_json.writeArrayEnd();`); lines.push(`${indent}_json.writeArrayEnd();`);
break; break;
} }
return lines; return lines;
} }
function generateCppFromIR(ir: SerializerIR): string { function generateCppFromIR (ir: SerializerIR): string {
const cppOutput: string[] = []; const cppOutput: string[] = [];
// Generate C++ file header // Generate C++ file header
cppOutput.push('#ifndef Spine_SkeletonSerializer_h'); cppOutput.push('#ifndef Spine_SkeletonSerializer_h');
cppOutput.push('#define Spine_SkeletonSerializer_h'); cppOutput.push('#define Spine_SkeletonSerializer_h');
cppOutput.push(''); cppOutput.push('');
cppOutput.push('#include <spine/spine.h>'); cppOutput.push('#include <spine/spine.h>');
cppOutput.push('#include "JsonWriter.h"'); cppOutput.push('#include "JsonWriter.h"');
cppOutput.push('#include <stdio.h>'); cppOutput.push('#include <stdio.h>');
cppOutput.push('#include <stdlib.h>'); cppOutput.push('#include <stdlib.h>');
cppOutput.push(''); cppOutput.push('');
cppOutput.push('namespace spine {'); cppOutput.push('namespace spine {');
cppOutput.push(''); cppOutput.push('');
cppOutput.push('class SkeletonSerializer {'); cppOutput.push('class SkeletonSerializer {');
cppOutput.push('private:'); cppOutput.push('private:');
cppOutput.push(' HashMap<void*, bool> _visitedObjects;'); cppOutput.push(' HashMap<void*, bool> _visitedObjects;');
cppOutput.push(' JsonWriter _json;'); cppOutput.push(' JsonWriter _json;');
cppOutput.push(''); cppOutput.push('');
cppOutput.push('public:'); cppOutput.push('public:');
cppOutput.push(' SkeletonSerializer() {}'); cppOutput.push(' SkeletonSerializer() {}');
cppOutput.push(' ~SkeletonSerializer() {}'); cppOutput.push(' ~SkeletonSerializer() {}');
cppOutput.push(''); cppOutput.push('');
// Generate public methods // Generate public methods
for (const method of ir.publicMethods) { for (const method of ir.publicMethods) {
const cppParamType = transformType(method.paramType); const cppParamType = transformType(method.paramType);
cppOutput.push(` String ${method.name}(${cppParamType}* ${method.paramName}) {`); cppOutput.push(` String ${method.name}(${cppParamType}* ${method.paramName}) {`);
cppOutput.push(' _visitedObjects.clear();'); cppOutput.push(' _visitedObjects.clear();');
cppOutput.push(' _json = JsonWriter();'); cppOutput.push(' _json = JsonWriter();');
cppOutput.push(` ${method.writeMethodCall}(${method.paramName});`); cppOutput.push(` ${method.writeMethodCall}(${method.paramName});`);
cppOutput.push(' return _json.getString();'); cppOutput.push(' return _json.getString();');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
} }
cppOutput.push('private:'); cppOutput.push('private:');
// Generate write methods // Generate write methods
for (const method of ir.writeMethods) { for (const method of ir.writeMethods) {
const shortName = method.paramType.split('.').pop()!; const shortName = method.paramType.split('.').pop()!;
const cppType = transformType(method.paramType); const cppType = transformType(method.paramType);
// Custom writeSkin and writeSkinEntry implementations // Custom writeSkin and writeSkinEntry implementations
if (method.name === 'writeSkin') { if (method.name === 'writeSkin') {
cppOutput.push(' void writeSkin(Skin* obj) {'); cppOutput.push(' void writeSkin(Skin* obj) {');
cppOutput.push(' if (_visitedObjects.containsKey(obj)) {'); cppOutput.push(' if (_visitedObjects.containsKey(obj)) {');
cppOutput.push(' _json.writeValue("<circular>");'); cppOutput.push(' _json.writeValue("<circular>");');
cppOutput.push(' return;'); cppOutput.push(' return;');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(' _visitedObjects.put(obj, true);'); cppOutput.push(' _visitedObjects.put(obj, true);');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' _json.writeObjectStart();'); cppOutput.push(' _json.writeObjectStart();');
cppOutput.push(' _json.writeName("type");'); cppOutput.push(' _json.writeName("type");');
cppOutput.push(' _json.writeValue("Skin");'); cppOutput.push(' _json.writeValue("Skin");');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' _json.writeName("attachments");'); cppOutput.push(' _json.writeName("attachments");');
cppOutput.push(' _json.writeArrayStart();'); cppOutput.push(' _json.writeArrayStart();');
cppOutput.push(' Skin::AttachmentMap::Entries entries = obj->getAttachments();'); cppOutput.push(' Skin::AttachmentMap::Entries entries = obj->getAttachments();');
cppOutput.push(' while (entries.hasNext()) {'); cppOutput.push(' while (entries.hasNext()) {');
cppOutput.push(' Skin::AttachmentMap::Entry& entry = entries.next();'); cppOutput.push(' Skin::AttachmentMap::Entry& entry = entries.next();');
cppOutput.push(' writeSkinEntry(&entry);'); cppOutput.push(' writeSkinEntry(&entry);');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(' _json.writeArrayEnd();'); cppOutput.push(' _json.writeArrayEnd();');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' _json.writeName("bones");'); cppOutput.push(' _json.writeName("bones");');
cppOutput.push(' _json.writeArrayStart();'); cppOutput.push(' _json.writeArrayStart();');
cppOutput.push(' for (size_t i = 0; i < obj->getBones().size(); i++) {'); cppOutput.push(' for (size_t i = 0; i < obj->getBones().size(); i++) {');
cppOutput.push(' BoneData* item = obj->getBones()[i];'); cppOutput.push(' BoneData* item = obj->getBones()[i];');
cppOutput.push(' writeBoneData(item);'); cppOutput.push(' writeBoneData(item);');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(' _json.writeArrayEnd();'); cppOutput.push(' _json.writeArrayEnd();');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' _json.writeName("constraints");'); cppOutput.push(' _json.writeName("constraints");');
cppOutput.push(' _json.writeArrayStart();'); cppOutput.push(' _json.writeArrayStart();');
cppOutput.push(' for (size_t i = 0; i < obj->getConstraints().size(); i++) {'); cppOutput.push(' for (size_t i = 0; i < obj->getConstraints().size(); i++) {');
cppOutput.push(' ConstraintData* item = obj->getConstraints()[i];'); cppOutput.push(' ConstraintData* item = obj->getConstraints()[i];');
cppOutput.push(' writeConstraintData(item);'); cppOutput.push(' writeConstraintData(item);');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(' _json.writeArrayEnd();'); cppOutput.push(' _json.writeArrayEnd();');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' _json.writeName("name");'); cppOutput.push(' _json.writeName("name");');
cppOutput.push(' _json.writeValue(obj->getName());'); cppOutput.push(' _json.writeValue(obj->getName());');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' _json.writeName("color");'); cppOutput.push(' _json.writeName("color");');
cppOutput.push(' writeColor(&obj->getColor());'); cppOutput.push(' writeColor(&obj->getColor());');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' _json.writeObjectEnd();'); cppOutput.push(' _json.writeObjectEnd();');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
continue; continue;
} }
// Custom writeSkinEntry // Custom writeSkinEntry
if (method.name === 'writeSkinEntry') { if (method.name === 'writeSkinEntry') {
// Custom writeSkinEntry implementation // Custom writeSkinEntry implementation
cppOutput.push(' void writeSkinEntry(Skin::AttachmentMap::Entry* obj) {'); cppOutput.push(' void writeSkinEntry(Skin::AttachmentMap::Entry* obj) {');
cppOutput.push(' _json.writeObjectStart();'); cppOutput.push(' _json.writeObjectStart();');
cppOutput.push(' _json.writeName("type");'); cppOutput.push(' _json.writeName("type");');
cppOutput.push(' _json.writeValue("SkinEntry");'); cppOutput.push(' _json.writeValue("SkinEntry");');
cppOutput.push(' _json.writeName("slotIndex");'); cppOutput.push(' _json.writeName("slotIndex");');
cppOutput.push(' _json.writeValue((int)obj->_slotIndex);'); cppOutput.push(' _json.writeValue((int)obj->_slotIndex);');
cppOutput.push(' _json.writeName("name");'); cppOutput.push(' _json.writeName("name");');
cppOutput.push(' _json.writeValue(obj->_name);'); cppOutput.push(' _json.writeValue(obj->_name);');
cppOutput.push(' _json.writeName("attachment");'); cppOutput.push(' _json.writeName("attachment");');
cppOutput.push(' writeAttachment(obj->_attachment);'); cppOutput.push(' writeAttachment(obj->_attachment);');
cppOutput.push(' _json.writeObjectEnd();'); cppOutput.push(' _json.writeObjectEnd();');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
continue; continue;
} }
cppOutput.push(` void ${method.name}(${cppType}* obj) {`); cppOutput.push(` void ${method.name}(${cppType}* obj) {`);
if (method.isAbstractType) { if (method.isAbstractType) {
// Handle abstract types with instanceof chain // Handle abstract types with instanceof chain
if (method.subtypeChecks && method.subtypeChecks.length > 0) { if (method.subtypeChecks && method.subtypeChecks.length > 0) {
let first = true; let first = true;
for (const subtype of method.subtypeChecks) { for (const subtype of method.subtypeChecks) {
const subtypeShortName = subtype.typeName.split('.').pop()!; const subtypeShortName = subtype.typeName.split('.').pop()!;
if (first) { if (first) {
cppOutput.push(` if (obj->getRTTI().instanceOf(${subtypeShortName}::rtti)) {`); cppOutput.push(` if (obj->getRTTI().instanceOf(${subtypeShortName}::rtti)) {`);
first = false; first = false;
} else { } else {
cppOutput.push(` } else if (obj->getRTTI().instanceOf(${subtypeShortName}::rtti)) {`); cppOutput.push(` } else if (obj->getRTTI().instanceOf(${subtypeShortName}::rtti)) {`);
} }
cppOutput.push(` ${subtype.writeMethodCall}((${subtypeShortName}*)obj);`); cppOutput.push(` ${subtype.writeMethodCall}((${subtypeShortName}*)obj);`);
} }
cppOutput.push(' } else {'); cppOutput.push(' } else {');
cppOutput.push(` fprintf(stderr, "Error: Unknown ${shortName} type\\n"); exit(1);`); cppOutput.push(` fprintf(stderr, "Error: Unknown ${shortName} type\\n"); exit(1);`);
cppOutput.push(' }'); cppOutput.push(' }');
} else { } else {
cppOutput.push(' _json.writeNull(); // No concrete implementations after filtering exclusions'); cppOutput.push(' _json.writeNull(); // No concrete implementations after filtering exclusions');
} }
} else { } else {
// Handle concrete types // Handle concrete types
// Add cycle detection // Add cycle detection
cppOutput.push(' if (_visitedObjects.containsKey(obj)) {'); cppOutput.push(' if (_visitedObjects.containsKey(obj)) {');
cppOutput.push(' _json.writeValue("<circular>");'); cppOutput.push(' _json.writeValue("<circular>");');
cppOutput.push(' return;'); cppOutput.push(' return;');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(' _visitedObjects.put(obj, true);'); cppOutput.push(' _visitedObjects.put(obj, true);');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' _json.writeObjectStart();'); cppOutput.push(' _json.writeObjectStart();');
// Write type field // Write type field
cppOutput.push(' _json.writeName("type");'); cppOutput.push(' _json.writeName("type");');
cppOutput.push(` _json.writeValue("${shortName}");`); cppOutput.push(` _json.writeValue("${shortName}");`);
// Write properties // Write properties
for (const property of method.properties) { for (const property of method.properties) {
cppOutput.push(''); cppOutput.push('');
cppOutput.push(` _json.writeName("${property.name}");`); cppOutput.push(` _json.writeName("${property.name}");`);
const propertyLines = generatePropertyCode(property, ' ', ir.enumMappings); const propertyLines = generatePropertyCode(property, ' ', ir.enumMappings);
cppOutput.push(...propertyLines); cppOutput.push(...propertyLines);
} }
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' _json.writeObjectEnd();'); cppOutput.push(' _json.writeObjectEnd();');
} }
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
} }
// Add custom helper methods for special types // Add custom helper methods for special types
cppOutput.push(' // Custom helper methods'); cppOutput.push(' // Custom helper methods');
cppOutput.push(' void writeColor(Color* obj) {'); cppOutput.push(' void writeColor(Color* obj) {');
cppOutput.push(' if (obj == nullptr) {'); cppOutput.push(' if (obj == nullptr) {');
cppOutput.push(' _json.writeNull();'); cppOutput.push(' _json.writeNull();');
cppOutput.push(' } else {'); cppOutput.push(' } else {');
cppOutput.push(' _json.writeObjectStart();'); cppOutput.push(' _json.writeObjectStart();');
cppOutput.push(' _json.writeName("r");'); cppOutput.push(' _json.writeName("r");');
cppOutput.push(' _json.writeValue(obj->r);'); cppOutput.push(' _json.writeValue(obj->r);');
cppOutput.push(' _json.writeName("g");'); cppOutput.push(' _json.writeName("g");');
cppOutput.push(' _json.writeValue(obj->g);'); cppOutput.push(' _json.writeValue(obj->g);');
cppOutput.push(' _json.writeName("b");'); cppOutput.push(' _json.writeName("b");');
cppOutput.push(' _json.writeValue(obj->b);'); cppOutput.push(' _json.writeValue(obj->b);');
cppOutput.push(' _json.writeName("a");'); cppOutput.push(' _json.writeName("a");');
cppOutput.push(' _json.writeValue(obj->a);'); cppOutput.push(' _json.writeValue(obj->a);');
cppOutput.push(' _json.writeObjectEnd();'); cppOutput.push(' _json.writeObjectEnd();');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' void writeColor(const Color& obj) {'); cppOutput.push(' void writeColor(const Color& obj) {');
cppOutput.push(' _json.writeObjectStart();'); cppOutput.push(' _json.writeObjectStart();');
cppOutput.push(' _json.writeName("r");'); cppOutput.push(' _json.writeName("r");');
cppOutput.push(' _json.writeValue(obj.r);'); cppOutput.push(' _json.writeValue(obj.r);');
cppOutput.push(' _json.writeName("g");'); cppOutput.push(' _json.writeName("g");');
cppOutput.push(' _json.writeValue(obj.g);'); cppOutput.push(' _json.writeValue(obj.g);');
cppOutput.push(' _json.writeName("b");'); cppOutput.push(' _json.writeName("b");');
cppOutput.push(' _json.writeValue(obj.b);'); cppOutput.push(' _json.writeValue(obj.b);');
cppOutput.push(' _json.writeName("a");'); cppOutput.push(' _json.writeName("a");');
cppOutput.push(' _json.writeValue(obj.a);'); cppOutput.push(' _json.writeValue(obj.a);');
cppOutput.push(' _json.writeObjectEnd();'); cppOutput.push(' _json.writeObjectEnd();');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' void writeTextureRegion(TextureRegion* obj) {'); cppOutput.push(' void writeTextureRegion(TextureRegion* obj) {');
cppOutput.push(' if (obj == nullptr) {'); cppOutput.push(' if (obj == nullptr) {');
cppOutput.push(' _json.writeNull();'); cppOutput.push(' _json.writeNull();');
cppOutput.push(' } else {'); cppOutput.push(' } else {');
cppOutput.push(' _json.writeObjectStart();'); cppOutput.push(' _json.writeObjectStart();');
cppOutput.push(' _json.writeName("u");'); cppOutput.push(' _json.writeName("u");');
cppOutput.push(' _json.writeValue(obj->getU());'); cppOutput.push(' _json.writeValue(obj->getU());');
cppOutput.push(' _json.writeName("v");'); cppOutput.push(' _json.writeName("v");');
cppOutput.push(' _json.writeValue(obj->getV());'); cppOutput.push(' _json.writeValue(obj->getV());');
cppOutput.push(' _json.writeName("u2");'); cppOutput.push(' _json.writeName("u2");');
cppOutput.push(' _json.writeValue(obj->getU2());'); cppOutput.push(' _json.writeValue(obj->getU2());');
cppOutput.push(' _json.writeName("v2");'); cppOutput.push(' _json.writeName("v2");');
cppOutput.push(' _json.writeValue(obj->getV2());'); cppOutput.push(' _json.writeValue(obj->getV2());');
cppOutput.push(' _json.writeName("width");'); cppOutput.push(' _json.writeName("width");');
cppOutput.push(' _json.writeValue(obj->getRegionWidth());'); cppOutput.push(' _json.writeValue(obj->getRegionWidth());');
cppOutput.push(' _json.writeName("height");'); cppOutput.push(' _json.writeName("height");');
cppOutput.push(' _json.writeValue(obj->getRegionHeight());'); cppOutput.push(' _json.writeValue(obj->getRegionHeight());');
cppOutput.push(' _json.writeObjectEnd();'); cppOutput.push(' _json.writeObjectEnd();');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' void writeTextureRegion(const TextureRegion& obj) {'); cppOutput.push(' void writeTextureRegion(const TextureRegion& obj) {');
cppOutput.push(' _json.writeObjectStart();'); cppOutput.push(' _json.writeObjectStart();');
cppOutput.push(' _json.writeName("u");'); cppOutput.push(' _json.writeName("u");');
cppOutput.push(' _json.writeValue(obj.getU());'); cppOutput.push(' _json.writeValue(obj.getU());');
cppOutput.push(' _json.writeName("v");'); cppOutput.push(' _json.writeName("v");');
cppOutput.push(' _json.writeValue(obj.getV());'); cppOutput.push(' _json.writeValue(obj.getV());');
cppOutput.push(' _json.writeName("u2");'); cppOutput.push(' _json.writeName("u2");');
cppOutput.push(' _json.writeValue(obj.getU2());'); cppOutput.push(' _json.writeValue(obj.getU2());');
cppOutput.push(' _json.writeName("v2");'); cppOutput.push(' _json.writeName("v2");');
cppOutput.push(' _json.writeValue(obj.getV2());'); cppOutput.push(' _json.writeValue(obj.getV2());');
cppOutput.push(' _json.writeName("width");'); cppOutput.push(' _json.writeName("width");');
cppOutput.push(' _json.writeValue(obj.getRegionWidth());'); cppOutput.push(' _json.writeValue(obj.getRegionWidth());');
cppOutput.push(' _json.writeName("height");'); cppOutput.push(' _json.writeName("height");');
cppOutput.push(' _json.writeValue(obj.getRegionHeight());'); cppOutput.push(' _json.writeValue(obj.getRegionHeight());');
cppOutput.push(' _json.writeObjectEnd();'); cppOutput.push(' _json.writeObjectEnd();');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' void writeIntArray(const Array<int>& obj) {'); cppOutput.push(' void writeIntArray(const Array<int>& obj) {');
cppOutput.push(' _json.writeArrayStart();'); cppOutput.push(' _json.writeArrayStart();');
cppOutput.push(' for (size_t i = 0; i < obj.size(); i++) {'); cppOutput.push(' for (size_t i = 0; i < obj.size(); i++) {');
cppOutput.push(' _json.writeValue(obj[i]);'); cppOutput.push(' _json.writeValue(obj[i]);');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(' _json.writeArrayEnd();'); cppOutput.push(' _json.writeArrayEnd();');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
cppOutput.push(' void writeFloatArray(const Array<float>& obj) {'); cppOutput.push(' void writeFloatArray(const Array<float>& obj) {');
cppOutput.push(' _json.writeArrayStart();'); cppOutput.push(' _json.writeArrayStart();');
cppOutput.push(' for (size_t i = 0; i < obj.size(); i++) {'); cppOutput.push(' for (size_t i = 0; i < obj.size(); i++) {');
cppOutput.push(' _json.writeValue(obj[i]);'); cppOutput.push(' _json.writeValue(obj[i]);');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(' _json.writeArrayEnd();'); cppOutput.push(' _json.writeArrayEnd();');
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
// Add reference versions for write methods (excluding custom implementations) // Add reference versions for write methods (excluding custom implementations)
cppOutput.push(' // Reference versions of write methods'); cppOutput.push(' // Reference versions of write methods');
const writeMethods = ir.writeMethods.filter(m => const writeMethods = ir.writeMethods.filter(m =>
!m.isAbstractType && !m.isAbstractType &&
m.name !== 'writeSkin' && m.name !== 'writeSkin' &&
m.name !== 'writeSkinEntry' m.name !== 'writeSkinEntry'
); );
for (const method of writeMethods) { for (const method of writeMethods) {
const cppType = transformType(method.paramType); const cppType = transformType(method.paramType);
cppOutput.push(` void ${method.name}(const ${cppType}& obj) {`); cppOutput.push(` void ${method.name}(const ${cppType}& obj) {`);
cppOutput.push(` ${method.name}(const_cast<${cppType}*>(&obj));`); cppOutput.push(` ${method.name}(const_cast<${cppType}*>(&obj));`);
cppOutput.push(' }'); cppOutput.push(' }');
cppOutput.push(''); cppOutput.push('');
} }
// C++ footer // C++ footer
cppOutput.push('};'); cppOutput.push('};');
cppOutput.push(''); cppOutput.push('');
cppOutput.push('} // namespace spine'); cppOutput.push('} // namespace spine');
cppOutput.push(''); cppOutput.push('');
cppOutput.push('#endif'); cppOutput.push('#endif');
return cppOutput.join('\n'); return cppOutput.join('\n');
} }
async function main() { async function main () {
try { try {
// Read the IR file // Read the IR file
const irFile = path.resolve(__dirname, '../output/serializer-ir.json'); const irFile = path.resolve(__dirname, '../output/serializer-ir.json');
if (!fs.existsSync(irFile)) { if (!fs.existsSync(irFile)) {
console.error('Serializer IR not found. Run generate-serializer-ir.ts first.'); console.error('Serializer IR not found. Run generate-serializer-ir.ts first.');
process.exit(1); process.exit(1);
} }
const ir: SerializerIR = JSON.parse(fs.readFileSync(irFile, 'utf8')); const ir: SerializerIR = JSON.parse(fs.readFileSync(irFile, 'utf8'));
// Generate C++ serializer from IR // Generate C++ serializer from IR
const cppCode = generateCppFromIR(ir); const cppCode = generateCppFromIR(ir);
// Write the C++ file // Write the C++ file
const cppFile = path.resolve( const cppFile = path.resolve(
__dirname, __dirname,
'../../spine-cpp/tests/SkeletonSerializer.h' '../../spine-cpp/tests/SkeletonSerializer.h'
); );
fs.mkdirSync(path.dirname(cppFile), { recursive: true }); fs.mkdirSync(path.dirname(cppFile), { recursive: true });
fs.writeFileSync(cppFile, cppCode); fs.writeFileSync(cppFile, cppCode);
console.log(`Generated C++ serializer from IR: ${cppFile}`); console.log(`Generated C++ serializer from IR: ${cppFile}`);
console.log(`- ${ir.publicMethods.length} public methods`); console.log(`- ${ir.publicMethods.length} public methods`);
console.log(`- ${ir.writeMethods.length} write methods`); console.log(`- ${ir.writeMethods.length} write methods`);
console.log(`- ${Object.keys(ir.enumMappings).length} enum mappings`); console.log(`- ${Object.keys(ir.enumMappings).length} enum mappings`);
} catch (error: any) { } catch (error: any) {
console.error('Error:', error.message); console.error('Error:', error.message);
console.error('Stack:', error.stack); console.error('Stack:', error.stack);
process.exit(1); process.exit(1);
} }
} }
// Allow running as a script or importing the function // Allow running as a script or importing the function
if (import.meta.url === `file://${process.argv[1]}`) { if (import.meta.url === `file://${process.argv[1]}`) {
main(); main();
} }
export { generateCppFromIR }; export { generateCppFromIR };

View File

@ -7,323 +7,323 @@ import type { Property, SerializerIR, WriteMethod } from './generate-serializer-
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
function generatePropertyCode(property: Property, indent: string, method?: WriteMethod): string[] { function generatePropertyCode (property: Property, indent: string, method?: WriteMethod): string[] {
const lines: string[] = []; const lines: string[] = [];
const accessor = `obj.${property.getter}`; const accessor = `obj.${property.getter}`;
switch (property.kind) { switch (property.kind) {
case "primitive": case "primitive":
lines.push(`${indent}json.writeValue(${accessor});`); lines.push(`${indent}json.writeValue(${accessor});`);
break; break;
case "object": case "object":
if (property.isNullable) { if (property.isNullable) {
lines.push(`${indent}if (${accessor} == null) {`); lines.push(`${indent}if (${accessor} == null) {`);
lines.push(`${indent} json.writeNull();`); lines.push(`${indent} json.writeNull();`);
lines.push(`${indent}} else {`); lines.push(`${indent}} else {`);
lines.push(`${indent} ${property.writeMethodCall}(${accessor});`); lines.push(`${indent} ${property.writeMethodCall}(${accessor});`);
lines.push(`${indent}}`); lines.push(`${indent}}`);
} else { } else {
lines.push(`${indent}${property.writeMethodCall}(${accessor});`); lines.push(`${indent}${property.writeMethodCall}(${accessor});`);
} }
break; break;
case "enum": case "enum":
if (property.isNullable) { if (property.isNullable) {
lines.push(`${indent}if (${accessor} == null) {`); lines.push(`${indent}if (${accessor} == null) {`);
lines.push(`${indent} json.writeNull();`); lines.push(`${indent} json.writeNull();`);
lines.push(`${indent}} else {`); lines.push(`${indent}} else {`);
lines.push(`${indent} json.writeValue(${accessor}.name());`); lines.push(`${indent} json.writeValue(${accessor}.name());`);
lines.push(`${indent}}`); lines.push(`${indent}}`);
} else { } else {
lines.push(`${indent}json.writeValue(${accessor}.name());`); lines.push(`${indent}json.writeValue(${accessor}.name());`);
} }
break; break;
case "array": case "array":
// Special handling for Skin attachments - sort by slot index // Special handling for Skin attachments - sort by slot index
const isSkinAttachments = method?.paramType === 'Skin' && property.name === 'attachments' && property.elementType === 'SkinEntry'; const isSkinAttachments = method?.paramType === 'Skin' && property.name === 'attachments' && property.elementType === 'SkinEntry';
const sortedAccessor = isSkinAttachments ? 'sortedAttachments' : accessor; const sortedAccessor = isSkinAttachments ? 'sortedAttachments' : accessor;
if (isSkinAttachments) { if (isSkinAttachments) {
lines.push(`${indent}Array<${property.elementType}> sortedAttachments = new Array<>(${accessor});`); lines.push(`${indent}Array<${property.elementType}> sortedAttachments = new Array<>(${accessor});`);
lines.push(`${indent}sortedAttachments.sort((a, b) -> Integer.compare(a.getSlotIndex(), b.getSlotIndex()));`); lines.push(`${indent}sortedAttachments.sort((a, b) -> Integer.compare(a.getSlotIndex(), b.getSlotIndex()));`);
} }
if (property.isNullable) { if (property.isNullable) {
lines.push(`${indent}if (${accessor} == null) {`); lines.push(`${indent}if (${accessor} == null) {`);
lines.push(`${indent} json.writeNull();`); lines.push(`${indent} json.writeNull();`);
lines.push(`${indent}} else {`); lines.push(`${indent}} else {`);
lines.push(`${indent} json.writeArrayStart();`); lines.push(`${indent} json.writeArrayStart();`);
lines.push(`${indent} for (${property.elementType} item : ${sortedAccessor}) {`); lines.push(`${indent} for (${property.elementType} item : ${sortedAccessor}) {`);
if (property.elementKind === "primitive") { if (property.elementKind === "primitive") {
lines.push(`${indent} json.writeValue(item);`); lines.push(`${indent} json.writeValue(item);`);
} else { } else {
lines.push(`${indent} ${property.writeMethodCall}(item);`); lines.push(`${indent} ${property.writeMethodCall}(item);`);
} }
lines.push(`${indent} }`); lines.push(`${indent} }`);
lines.push(`${indent} json.writeArrayEnd();`); lines.push(`${indent} json.writeArrayEnd();`);
lines.push(`${indent}}`); lines.push(`${indent}}`);
} else { } else {
lines.push(`${indent}json.writeArrayStart();`); lines.push(`${indent}json.writeArrayStart();`);
lines.push(`${indent}for (${property.elementType} item : ${sortedAccessor}) {`); lines.push(`${indent}for (${property.elementType} item : ${sortedAccessor}) {`);
if (property.elementKind === "primitive") { if (property.elementKind === "primitive") {
lines.push(`${indent} json.writeValue(item);`); lines.push(`${indent} json.writeValue(item);`);
} else { } else {
lines.push(`${indent} ${property.writeMethodCall}(item);`); lines.push(`${indent} ${property.writeMethodCall}(item);`);
} }
lines.push(`${indent}}`); lines.push(`${indent}}`);
lines.push(`${indent}json.writeArrayEnd();`); lines.push(`${indent}json.writeArrayEnd();`);
} }
break; break;
case "nestedArray": case "nestedArray":
if (property.isNullable) { if (property.isNullable) {
lines.push(`${indent}if (${accessor} == null) {`); lines.push(`${indent}if (${accessor} == null) {`);
lines.push(`${indent} json.writeNull();`); lines.push(`${indent} json.writeNull();`);
lines.push(`${indent}} else {`); lines.push(`${indent}} else {`);
lines.push(`${indent} json.writeArrayStart();`); lines.push(`${indent} json.writeArrayStart();`);
lines.push(`${indent} for (${property.elementType}[] nestedArray : ${accessor}) {`); lines.push(`${indent} for (${property.elementType}[] nestedArray : ${accessor}) {`);
lines.push(`${indent} if (nestedArray == null) {`); lines.push(`${indent} if (nestedArray == null) {`);
lines.push(`${indent} json.writeNull();`); lines.push(`${indent} json.writeNull();`);
lines.push(`${indent} } else {`); lines.push(`${indent} } else {`);
lines.push(`${indent} json.writeArrayStart();`); lines.push(`${indent} json.writeArrayStart();`);
lines.push(`${indent} for (${property.elementType} elem : nestedArray) {`); lines.push(`${indent} for (${property.elementType} elem : nestedArray) {`);
lines.push(`${indent} json.writeValue(elem);`); lines.push(`${indent} json.writeValue(elem);`);
lines.push(`${indent} }`); lines.push(`${indent} }`);
lines.push(`${indent} json.writeArrayEnd();`); lines.push(`${indent} json.writeArrayEnd();`);
lines.push(`${indent} }`); lines.push(`${indent} }`);
lines.push(`${indent} }`); lines.push(`${indent} }`);
lines.push(`${indent} json.writeArrayEnd();`); lines.push(`${indent} json.writeArrayEnd();`);
lines.push(`${indent}}`); lines.push(`${indent}}`);
} else { } else {
lines.push(`${indent}json.writeArrayStart();`); lines.push(`${indent}json.writeArrayStart();`);
lines.push(`${indent}for (${property.elementType}[] nestedArray : ${accessor}) {`); lines.push(`${indent}for (${property.elementType}[] nestedArray : ${accessor}) {`);
lines.push(`${indent} json.writeArrayStart();`); lines.push(`${indent} json.writeArrayStart();`);
lines.push(`${indent} for (${property.elementType} elem : nestedArray) {`); lines.push(`${indent} for (${property.elementType} elem : nestedArray) {`);
lines.push(`${indent} json.writeValue(elem);`); lines.push(`${indent} json.writeValue(elem);`);
lines.push(`${indent} }`); lines.push(`${indent} }`);
lines.push(`${indent} json.writeArrayEnd();`); lines.push(`${indent} json.writeArrayEnd();`);
lines.push(`${indent}}`); lines.push(`${indent}}`);
lines.push(`${indent}json.writeArrayEnd();`); lines.push(`${indent}json.writeArrayEnd();`);
} }
break; break;
} }
return lines; return lines;
} }
function generateJavaFromIR(ir: SerializerIR): string { function generateJavaFromIR (ir: SerializerIR): string {
const javaOutput: string[] = []; const javaOutput: string[] = [];
// Generate Java file header // Generate Java file header
javaOutput.push('package com.esotericsoftware.spine.utils;'); javaOutput.push('package com.esotericsoftware.spine.utils;');
javaOutput.push(''); javaOutput.push('');
javaOutput.push('import com.esotericsoftware.spine.*;'); javaOutput.push('import com.esotericsoftware.spine.*;');
javaOutput.push('import com.esotericsoftware.spine.Animation.*;'); javaOutput.push('import com.esotericsoftware.spine.Animation.*;');
javaOutput.push('import com.esotericsoftware.spine.AnimationState.*;'); javaOutput.push('import com.esotericsoftware.spine.AnimationState.*;');
javaOutput.push('import com.esotericsoftware.spine.BoneData.Inherit;'); javaOutput.push('import com.esotericsoftware.spine.BoneData.Inherit;');
javaOutput.push('import com.esotericsoftware.spine.Skin.SkinEntry;'); javaOutput.push('import com.esotericsoftware.spine.Skin.SkinEntry;');
javaOutput.push('import com.esotericsoftware.spine.PathConstraintData.*;'); javaOutput.push('import com.esotericsoftware.spine.PathConstraintData.*;');
javaOutput.push('import com.esotericsoftware.spine.TransformConstraintData.*;'); javaOutput.push('import com.esotericsoftware.spine.TransformConstraintData.*;');
javaOutput.push('import com.esotericsoftware.spine.attachments.*;'); javaOutput.push('import com.esotericsoftware.spine.attachments.*;');
javaOutput.push('import com.badlogic.gdx.graphics.Color;'); javaOutput.push('import com.badlogic.gdx.graphics.Color;');
javaOutput.push('import com.badlogic.gdx.graphics.g2d.TextureRegion;'); javaOutput.push('import com.badlogic.gdx.graphics.g2d.TextureRegion;');
javaOutput.push('import com.badlogic.gdx.utils.Array;'); javaOutput.push('import com.badlogic.gdx.utils.Array;');
javaOutput.push('import com.badlogic.gdx.utils.IntArray;'); javaOutput.push('import com.badlogic.gdx.utils.IntArray;');
javaOutput.push('import com.badlogic.gdx.utils.FloatArray;'); javaOutput.push('import com.badlogic.gdx.utils.FloatArray;');
javaOutput.push(''); javaOutput.push('');
javaOutput.push('import java.util.Locale;'); javaOutput.push('import java.util.Locale;');
javaOutput.push('import java.util.Set;'); javaOutput.push('import java.util.Set;');
javaOutput.push('import java.util.HashSet;'); javaOutput.push('import java.util.HashSet;');
javaOutput.push(''); javaOutput.push('');
javaOutput.push('public class SkeletonSerializer {'); javaOutput.push('public class SkeletonSerializer {');
javaOutput.push(' private final Set<Object> visitedObjects = new HashSet<>();'); javaOutput.push(' private final Set<Object> visitedObjects = new HashSet<>();');
javaOutput.push(' private JsonWriter json;'); javaOutput.push(' private JsonWriter json;');
javaOutput.push(''); javaOutput.push('');
// Generate public methods // Generate public methods
for (const method of ir.publicMethods) { for (const method of ir.publicMethods) {
javaOutput.push(` public String ${method.name}(${method.paramType} ${method.paramName}) {`); javaOutput.push(` public String ${method.name}(${method.paramType} ${method.paramName}) {`);
javaOutput.push(' visitedObjects.clear();'); javaOutput.push(' visitedObjects.clear();');
javaOutput.push(' json = new JsonWriter();'); javaOutput.push(' json = new JsonWriter();');
javaOutput.push(` ${method.writeMethodCall}(${method.paramName});`); javaOutput.push(` ${method.writeMethodCall}(${method.paramName});`);
javaOutput.push(' json.close();'); javaOutput.push(' json.close();');
javaOutput.push(' return json.getString();'); javaOutput.push(' return json.getString();');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(''); javaOutput.push('');
} }
// Generate write methods // Generate write methods
for (const method of ir.writeMethods) { for (const method of ir.writeMethods) {
const shortName = method.paramType.split('.').pop()!; const shortName = method.paramType.split('.').pop()!;
const className = method.paramType.includes('.') ? method.paramType : shortName; const className = method.paramType.includes('.') ? method.paramType : shortName;
javaOutput.push(` private void ${method.name}(${className} obj) {`); javaOutput.push(` private void ${method.name}(${className} obj) {`);
if (method.isAbstractType) { if (method.isAbstractType) {
// Handle abstract types with instanceof chain // Handle abstract types with instanceof chain
if (method.subtypeChecks && method.subtypeChecks.length > 0) { if (method.subtypeChecks && method.subtypeChecks.length > 0) {
let first = true; let first = true;
for (const subtype of method.subtypeChecks) { for (const subtype of method.subtypeChecks) {
const subtypeShortName = subtype.typeName.split('.').pop()!; const subtypeShortName = subtype.typeName.split('.').pop()!;
const subtypeClassName = subtype.typeName.includes('.') ? subtype.typeName : subtypeShortName; const subtypeClassName = subtype.typeName.includes('.') ? subtype.typeName : subtypeShortName;
if (first) { if (first) {
javaOutput.push(` if (obj instanceof ${subtypeClassName}) {`); javaOutput.push(` if (obj instanceof ${subtypeClassName}) {`);
first = false; first = false;
} else { } else {
javaOutput.push(` } else if (obj instanceof ${subtypeClassName}) {`); javaOutput.push(` } else if (obj instanceof ${subtypeClassName}) {`);
} }
javaOutput.push(` ${subtype.writeMethodCall}((${subtypeClassName}) obj);`); javaOutput.push(` ${subtype.writeMethodCall}((${subtypeClassName}) obj);`);
} }
javaOutput.push(' } else {'); javaOutput.push(' } else {');
javaOutput.push(` throw new RuntimeException("Unknown ${shortName} type: " + obj.getClass().getName());`); javaOutput.push(` throw new RuntimeException("Unknown ${shortName} type: " + obj.getClass().getName());`);
javaOutput.push(' }'); javaOutput.push(' }');
} else { } else {
javaOutput.push(' json.writeNull(); // No concrete implementations after filtering exclusions'); javaOutput.push(' json.writeNull(); // No concrete implementations after filtering exclusions');
} }
} else { } else {
// Handle concrete types // Handle concrete types
// Add cycle detection // Add cycle detection
javaOutput.push(' if (visitedObjects.contains(obj)) {'); javaOutput.push(' if (visitedObjects.contains(obj)) {');
javaOutput.push(' json.writeValue("<circular>");'); javaOutput.push(' json.writeValue("<circular>");');
javaOutput.push(' return;'); javaOutput.push(' return;');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(' visitedObjects.add(obj);'); javaOutput.push(' visitedObjects.add(obj);');
javaOutput.push(''); javaOutput.push('');
javaOutput.push(' json.writeObjectStart();'); javaOutput.push(' json.writeObjectStart();');
// Write type field // Write type field
javaOutput.push(' json.writeName("type");'); javaOutput.push(' json.writeName("type");');
javaOutput.push(` json.writeValue("${shortName}");`); javaOutput.push(` json.writeValue("${shortName}");`);
// Write properties // Write properties
for (const property of method.properties) { for (const property of method.properties) {
javaOutput.push(''); javaOutput.push('');
javaOutput.push(` json.writeName("${property.name}");`); javaOutput.push(` json.writeName("${property.name}");`);
const propertyLines = generatePropertyCode(property, ' ', method); const propertyLines = generatePropertyCode(property, ' ', method);
javaOutput.push(...propertyLines); javaOutput.push(...propertyLines);
} }
javaOutput.push(''); javaOutput.push('');
javaOutput.push(' json.writeObjectEnd();'); javaOutput.push(' json.writeObjectEnd();');
} }
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(''); javaOutput.push('');
} }
// Add helper methods for special types // Add helper methods for special types
javaOutput.push(' private void writeColor(Color obj) {'); javaOutput.push(' private void writeColor(Color obj) {');
javaOutput.push(' if (obj == null) {'); javaOutput.push(' if (obj == null) {');
javaOutput.push(' json.writeNull();'); javaOutput.push(' json.writeNull();');
javaOutput.push(' } else {'); javaOutput.push(' } else {');
javaOutput.push(' json.writeObjectStart();'); javaOutput.push(' json.writeObjectStart();');
javaOutput.push(' json.writeName("r");'); javaOutput.push(' json.writeName("r");');
javaOutput.push(' json.writeValue(obj.r);'); javaOutput.push(' json.writeValue(obj.r);');
javaOutput.push(' json.writeName("g");'); javaOutput.push(' json.writeName("g");');
javaOutput.push(' json.writeValue(obj.g);'); javaOutput.push(' json.writeValue(obj.g);');
javaOutput.push(' json.writeName("b");'); javaOutput.push(' json.writeName("b");');
javaOutput.push(' json.writeValue(obj.b);'); javaOutput.push(' json.writeValue(obj.b);');
javaOutput.push(' json.writeName("a");'); javaOutput.push(' json.writeName("a");');
javaOutput.push(' json.writeValue(obj.a);'); javaOutput.push(' json.writeValue(obj.a);');
javaOutput.push(' json.writeObjectEnd();'); javaOutput.push(' json.writeObjectEnd();');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(''); javaOutput.push('');
javaOutput.push(' private void writeTextureRegion(TextureRegion obj) {'); javaOutput.push(' private void writeTextureRegion(TextureRegion obj) {');
javaOutput.push(' if (obj == null) {'); javaOutput.push(' if (obj == null) {');
javaOutput.push(' json.writeNull();'); javaOutput.push(' json.writeNull();');
javaOutput.push(' } else {'); javaOutput.push(' } else {');
javaOutput.push(' json.writeObjectStart();'); javaOutput.push(' json.writeObjectStart();');
javaOutput.push(' json.writeName("u");'); javaOutput.push(' json.writeName("u");');
javaOutput.push(' json.writeValue(obj.getU());'); javaOutput.push(' json.writeValue(obj.getU());');
javaOutput.push(' json.writeName("v");'); javaOutput.push(' json.writeName("v");');
javaOutput.push(' json.writeValue(obj.getV());'); javaOutput.push(' json.writeValue(obj.getV());');
javaOutput.push(' json.writeName("u2");'); javaOutput.push(' json.writeName("u2");');
javaOutput.push(' json.writeValue(obj.getU2());'); javaOutput.push(' json.writeValue(obj.getU2());');
javaOutput.push(' json.writeName("v2");'); javaOutput.push(' json.writeName("v2");');
javaOutput.push(' json.writeValue(obj.getV2());'); javaOutput.push(' json.writeValue(obj.getV2());');
javaOutput.push(' json.writeName("width");'); javaOutput.push(' json.writeName("width");');
javaOutput.push(' json.writeValue(obj.getRegionWidth());'); javaOutput.push(' json.writeValue(obj.getRegionWidth());');
javaOutput.push(' json.writeName("height");'); javaOutput.push(' json.writeName("height");');
javaOutput.push(' json.writeValue(obj.getRegionHeight());'); javaOutput.push(' json.writeValue(obj.getRegionHeight());');
javaOutput.push(' json.writeObjectEnd();'); javaOutput.push(' json.writeObjectEnd();');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(' }'); javaOutput.push(' }');
// Add IntArray and FloatArray helper methods // Add IntArray and FloatArray helper methods
javaOutput.push(''); javaOutput.push('');
javaOutput.push(' private void writeIntArray(IntArray obj) {'); javaOutput.push(' private void writeIntArray(IntArray obj) {');
javaOutput.push(' if (obj == null) {'); javaOutput.push(' if (obj == null) {');
javaOutput.push(' json.writeNull();'); javaOutput.push(' json.writeNull();');
javaOutput.push(' } else {'); javaOutput.push(' } else {');
javaOutput.push(' json.writeArrayStart();'); javaOutput.push(' json.writeArrayStart();');
javaOutput.push(' for (int i = 0; i < obj.size; i++) {'); javaOutput.push(' for (int i = 0; i < obj.size; i++) {');
javaOutput.push(' json.writeValue(obj.get(i));'); javaOutput.push(' json.writeValue(obj.get(i));');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(' json.writeArrayEnd();'); javaOutput.push(' json.writeArrayEnd();');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(''); javaOutput.push('');
javaOutput.push(' private void writeFloatArray(FloatArray obj) {'); javaOutput.push(' private void writeFloatArray(FloatArray obj) {');
javaOutput.push(' if (obj == null) {'); javaOutput.push(' if (obj == null) {');
javaOutput.push(' json.writeNull();'); javaOutput.push(' json.writeNull();');
javaOutput.push(' } else {'); javaOutput.push(' } else {');
javaOutput.push(' json.writeArrayStart();'); javaOutput.push(' json.writeArrayStart();');
javaOutput.push(' for (int i = 0; i < obj.size; i++) {'); javaOutput.push(' for (int i = 0; i < obj.size; i++) {');
javaOutput.push(' json.writeValue(obj.get(i));'); javaOutput.push(' json.writeValue(obj.get(i));');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(' json.writeArrayEnd();'); javaOutput.push(' json.writeArrayEnd();');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push(' }'); javaOutput.push(' }');
javaOutput.push('}'); javaOutput.push('}');
return javaOutput.join('\n'); return javaOutput.join('\n');
} }
async function main() { async function main () {
try { try {
// Read the IR file // Read the IR file
const irFile = path.resolve(__dirname, '../output/serializer-ir.json'); const irFile = path.resolve(__dirname, '../output/serializer-ir.json');
if (!fs.existsSync(irFile)) { if (!fs.existsSync(irFile)) {
console.error('Serializer IR not found. Run generate-serializer-ir.ts first.'); console.error('Serializer IR not found. Run generate-serializer-ir.ts first.');
process.exit(1); process.exit(1);
} }
const ir: SerializerIR = JSON.parse(fs.readFileSync(irFile, 'utf8')); const ir: SerializerIR = JSON.parse(fs.readFileSync(irFile, 'utf8'));
// Generate Java serializer from IR // Generate Java serializer from IR
const javaCode = generateJavaFromIR(ir); const javaCode = generateJavaFromIR(ir);
// Write the Java file // Write the Java file
const javaFile = path.resolve( const javaFile = path.resolve(
__dirname, __dirname,
'../../spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java' '../../spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java'
); );
fs.mkdirSync(path.dirname(javaFile), { recursive: true }); fs.mkdirSync(path.dirname(javaFile), { recursive: true });
fs.writeFileSync(javaFile, javaCode); fs.writeFileSync(javaFile, javaCode);
console.log(`Generated Java serializer from IR: ${javaFile}`); console.log(`Generated Java serializer from IR: ${javaFile}`);
console.log(`- ${ir.publicMethods.length} public methods`); console.log(`- ${ir.publicMethods.length} public methods`);
console.log(`- ${ir.writeMethods.length} write methods`); console.log(`- ${ir.writeMethods.length} write methods`);
} catch (error: any) { } catch (error: any) {
console.error('Error:', error.message); console.error('Error:', error.message);
console.error('Stack:', error.stack); console.error('Stack:', error.stack);
process.exit(1); process.exit(1);
} }
} }
// Allow running as a script or importing the function // Allow running as a script or importing the function
if (import.meta.url === `file://${process.argv[1]}`) { if (import.meta.url === `file://${process.argv[1]}`) {
main(); main();
} }
export { generateJavaFromIR }; export { generateJavaFromIR };

File diff suppressed because it is too large Load Diff

View File

@ -2,44 +2,44 @@ import { Supertype } from '@mariozechner/lsp-cli';
// Shared types for the Spine serializer generator // Shared types for the Spine serializer generator
export interface ClassInfo { export interface ClassInfo {
className: string; className: string;
superTypes: string[]; // Just the names for backward compatibility superTypes: string[]; // Just the names for backward compatibility
superTypeDetails?: Supertype[]; // Full details with type arguments superTypeDetails?: Supertype[]; // Full details with type arguments
getters: GetterInfo[]; getters: GetterInfo[];
fields: FieldInfo[]; fields: FieldInfo[];
file: string; file: string;
isAbstract: boolean; isAbstract: boolean;
isInterface: boolean; isInterface: boolean;
isEnum: boolean; isEnum: boolean;
typeParameters?: string[]; // The class's own type parameters typeParameters?: string[]; // The class's own type parameters
enumValues?: string[]; // For enums enumValues?: string[]; // For enums
concreteImplementations?: string[]; // For abstract classes/interfaces - only leaf concrete types concreteImplementations?: string[]; // For abstract classes/interfaces - only leaf concrete types
allImplementations?: string[]; // For abstract classes/interfaces - includes intermediate abstract types allImplementations?: string[]; // For abstract classes/interfaces - includes intermediate abstract types
} }
export interface GetterInfo { export interface GetterInfo {
methodName: string; methodName: string;
returnType: string; returnType: string;
} }
export interface FieldInfo { export interface FieldInfo {
fieldName: string; fieldName: string;
fieldType: string; fieldType: string;
isFinal: boolean; isFinal: boolean;
} }
export interface PropertyInfo { export interface PropertyInfo {
name: string; name: string;
type: string; type: string;
isGetter: boolean; isGetter: boolean;
inheritedFrom?: string; // Which class this property was inherited from inheritedFrom?: string; // Which class this property was inherited from
excluded: boolean; // Whether this property should be excluded from serialization excluded: boolean; // Whether this property should be excluded from serialization
} }
export interface AnalysisResult { export interface AnalysisResult {
classMap: Map<string, ClassInfo>; classMap: Map<string, ClassInfo>;
accessibleTypes: Set<string>; accessibleTypes: Set<string>;
abstractTypes: Map<string, string[]>; // abstract type -> concrete implementations abstractTypes: Map<string, string[]>; // abstract type -> concrete implementations
allTypesToGenerate: Set<string>; // all types that need write methods allTypesToGenerate: Set<string>; // all types that need write methods
typeProperties: Map<string, PropertyInfo[]>; // type -> all properties (including inherited) typeProperties: Map<string, PropertyInfo[]>; // type -> all properties (including inherited)
} }