mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
[c] null-analysis tool.
This commit is contained in:
parent
3e622605b3
commit
736f5148f1
@ -1,6 +1,6 @@
|
||||
# Spine C API Code Generator
|
||||
|
||||
This TypeScript-based code generator automatically creates a C wrapper API for the Spine C++ runtime. It parses the spine-cpp headers using Clang's AST and generates a complete C API with opaque types, following systematic type conversion rules.
|
||||
This TypeScript-based code generator automatically creates a C wrapper API for the Spine C++ runtime. It parses the spine-cpp headers using Clang's AST and generates a complete C API with opaque types, following systematic type conversion rules. The generator also builds inheritance maps and interface information for multi-language binding generation.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@ -15,7 +15,8 @@ This TypeScript-based code generator automatically creates a C wrapper API for t
|
||||
9. [Array Specializations](#array-specializations)
|
||||
10. [Generated Code Examples](#generated-code-examples)
|
||||
11. [Implementation Details](#implementation-details)
|
||||
12. [Troubleshooting](#troubleshooting)
|
||||
12. [Development Tools](#development-tools)
|
||||
13. [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Overview
|
||||
|
||||
@ -28,6 +29,7 @@ The code generator performs static analysis on the spine-cpp headers to automati
|
||||
- Array specializations for different element types
|
||||
- Field accessors (getters/setters) for public fields
|
||||
- Automatic validation and conflict detection
|
||||
- Inheritance analysis and interface detection for multi-language bindings
|
||||
|
||||
## Architecture
|
||||
|
||||
@ -67,7 +69,13 @@ The generator follows a multi-stage pipeline:
|
||||
- Writes header files with C function declarations
|
||||
- Writes implementation files with C++ wrapper code
|
||||
- Generates array specialization files
|
||||
- Creates main include files (`types.h`, `spine-c.h`)
|
||||
- Creates main include files (`types.h`)
|
||||
|
||||
7. **Inheritance Analysis**
|
||||
- Builds inheritance maps for single-inheritance languages (Dart, Swift, Java)
|
||||
- Identifies pure interfaces vs concrete classes
|
||||
- Detects multiple concrete inheritance (not supported)
|
||||
- Generates inheritance information for language binding generators
|
||||
|
||||
## Type System
|
||||
|
||||
@ -106,6 +114,7 @@ codegen/
|
||||
├── src/
|
||||
│ ├── index.ts # Main entry point and orchestration
|
||||
│ ├── type-extractor.ts # Clang AST parsing
|
||||
│ ├── cpp-check.ts # C++ nullability analysis tool
|
||||
│ ├── types.ts # Type definitions and conversion logic
|
||||
│ ├── c-types.ts # C IR type definitions
|
||||
│ ├── array-scanner.ts # Array specialization detection
|
||||
@ -114,18 +123,22 @@ codegen/
|
||||
│ ├── ir-generator.ts # C++ to C IR conversion
|
||||
│ ├── c-writer.ts # File generation
|
||||
│ └── warnings.ts # Warning collection
|
||||
├── dist/ # TypeScript compilation output
|
||||
├── exclusions.txt # Type/method exclusions
|
||||
├── spine-cpp-types.json # Extracted type information
|
||||
├── nullable.md # C++ nullability analysis results
|
||||
├── out.json # Debug output file
|
||||
├── package.json # Node.js configuration
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
└── generated/ # Output directory (temporary)
|
||||
├── tsfmt.json # TypeScript formatter configuration
|
||||
├── biome.json # Biome linter configuration
|
||||
└── node_modules/ # Dependencies
|
||||
```
|
||||
|
||||
Generated files are output to `../src/generated/`:
|
||||
- Individual files per type (e.g., `skeleton.h`, `skeleton.cpp`)
|
||||
- `types.h` - Forward declarations for all types
|
||||
- `arrays.h/cpp` - Array specializations
|
||||
- `spine-c.h` - Main include file
|
||||
|
||||
## Usage
|
||||
|
||||
@ -133,14 +146,36 @@ Generated files are output to `../src/generated/`:
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
npx -y tsx src/index.ts
|
||||
# Run the code generator
|
||||
npx tsx src/index.ts
|
||||
|
||||
# Or export JSON for debugging
|
||||
npx tsx src/index.ts --export-json
|
||||
|
||||
# The generated files will be in ../src/generated/
|
||||
```
|
||||
|
||||
### C++ Nullability Analysis Tool
|
||||
|
||||
The codegen includes a tool to analyze spine-cpp for nullability patterns:
|
||||
|
||||
```bash
|
||||
# Generate nullable.md with clickable links to methods with nullable inputs/outputs
|
||||
npm run cpp-check
|
||||
```
|
||||
|
||||
This tool identifies all methods that either:
|
||||
- Return pointer types (nullable return values)
|
||||
- Take pointer parameters (nullable inputs)
|
||||
|
||||
The output `nullable.md` contains clickable markdown links for easy navigation in VS Code. This is useful for cleaning up the spine-cpp API to use references vs pointers appropriately to signal nullability.
|
||||
|
||||
The generator automatically:
|
||||
- Detects when spine-cpp headers have changed
|
||||
- Regenerates only when necessary
|
||||
- Reports warnings and errors during generation
|
||||
- Formats the generated C++ code using the project's formatter
|
||||
- Builds inheritance maps for multi-language binding generation
|
||||
|
||||
## Type Conversion Rules
|
||||
|
||||
@ -320,14 +355,14 @@ Array<PropertyId> → spine_array_property_id
|
||||
// Header: skeleton.h
|
||||
typedef struct spine_skeleton* spine_skeleton;
|
||||
|
||||
spine_skeleton spine_skeleton_new(spine_skeleton_data data);
|
||||
spine_skeleton spine_skeleton_create(spine_skeleton_data data);
|
||||
void spine_skeleton_dispose(spine_skeleton self);
|
||||
void spine_skeleton_update_cache(spine_skeleton self);
|
||||
float spine_skeleton_get_x(const spine_skeleton self);
|
||||
void spine_skeleton_set_x(spine_skeleton self, float value);
|
||||
|
||||
// Implementation: skeleton.cpp
|
||||
spine_skeleton spine_skeleton_new(spine_skeleton_data data) {
|
||||
spine_skeleton spine_skeleton_create(spine_skeleton_data data) {
|
||||
return (spine_skeleton) new (__FILE__, __LINE__) Skeleton((SkeletonData*)data);
|
||||
}
|
||||
|
||||
@ -339,46 +374,83 @@ void spine_skeleton_update_cache(spine_skeleton self) {
|
||||
### Enum Wrapper
|
||||
```c
|
||||
// Header: blend_mode.h
|
||||
#ifndef SPINE_SPINE_BLEND_MODE_H
|
||||
#define SPINE_SPINE_BLEND_MODE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum spine_blend_mode {
|
||||
SPINE_BLEND_MODE_NORMAL = 0,
|
||||
SPINE_BLEND_MODE_ADDITIVE = 1,
|
||||
SPINE_BLEND_MODE_MULTIPLY = 2,
|
||||
SPINE_BLEND_MODE_SCREEN = 3
|
||||
SPINE_BLEND_MODE_ADDITIVE,
|
||||
SPINE_BLEND_MODE_MULTIPLY,
|
||||
SPINE_BLEND_MODE_SCREEN
|
||||
} spine_blend_mode;
|
||||
|
||||
// Implementation: blend_mode.cpp
|
||||
spine_blend_mode spine_blend_mode_from_cpp(BlendMode value) {
|
||||
return (spine_blend_mode)value;
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
BlendMode spine_blend_mode_to_cpp(spine_blend_mode value) {
|
||||
return (BlendMode)value;
|
||||
}
|
||||
#endif /* SPINE_SPINE_BLEND_MODE_H */
|
||||
```
|
||||
|
||||
### Array Specialization
|
||||
Arrays are generated as opaque types with complete CRUD operations. All arrays are consolidated into `arrays.h` and `arrays.cpp`.
|
||||
|
||||
```c
|
||||
// Header: array_float.h
|
||||
typedef struct spine_array_float* spine_array_float;
|
||||
// Header: arrays.h
|
||||
SPINE_OPAQUE_TYPE(spine_array_float)
|
||||
|
||||
spine_array_float spine_array_float_new(int32_t capacity);
|
||||
void spine_array_float_dispose(spine_array_float self);
|
||||
int32_t spine_array_float_get_size(const spine_array_float self);
|
||||
float spine_array_float_get(const spine_array_float self, int32_t index);
|
||||
void spine_array_float_set(spine_array_float self, int32_t index, float value);
|
||||
// Creation functions
|
||||
spine_array_float spine_array_float_create(void);
|
||||
spine_array_float spine_array_float_create_with_capacity(size_t initialCapacity);
|
||||
|
||||
// Implementation: array_float.cpp
|
||||
struct spine_array_float {
|
||||
Array<float> data;
|
||||
};
|
||||
// Memory management
|
||||
void spine_array_float_dispose(spine_array_float array);
|
||||
void spine_array_float_clear(spine_array_float array);
|
||||
|
||||
spine_array_float spine_array_float_new(int32_t capacity) {
|
||||
auto* arr = new (__FILE__, __LINE__) spine_array_float();
|
||||
arr->data.setCapacity(capacity);
|
||||
return arr;
|
||||
// Size and capacity operations
|
||||
size_t spine_array_float_get_capacity(spine_array_float array);
|
||||
size_t spine_array_float_size(spine_array_float array);
|
||||
spine_array_float spine_array_float_set_size(spine_array_float array, size_t newSize, float defaultValue);
|
||||
void spine_array_float_ensure_capacity(spine_array_float array, size_t newCapacity);
|
||||
|
||||
// Element operations
|
||||
void spine_array_float_add(spine_array_float array, float inValue);
|
||||
void spine_array_float_add_all(spine_array_float array, spine_array_float inValue);
|
||||
void spine_array_float_clear_and_add_all(spine_array_float array, spine_array_float inValue);
|
||||
void spine_array_float_remove_at(spine_array_float array, size_t inIndex);
|
||||
|
||||
// Search operations
|
||||
bool spine_array_float_contains(spine_array_float array, float inValue);
|
||||
int spine_array_float_index_of(spine_array_float array, float inValue);
|
||||
|
||||
// Direct buffer access
|
||||
float *spine_array_float_buffer(spine_array_float array);
|
||||
|
||||
// Implementation: arrays.cpp
|
||||
spine_array_float spine_array_float_create(void) {
|
||||
return (spine_array_float) new (__FILE__, __LINE__) Array<float>();
|
||||
}
|
||||
|
||||
void spine_array_float_dispose(spine_array_float array) {
|
||||
delete (Array<float> *) array;
|
||||
}
|
||||
|
||||
void spine_array_float_add(spine_array_float array, float inValue) {
|
||||
Array<float> *_array = (Array<float> *) array;
|
||||
_array->add(inValue);
|
||||
}
|
||||
|
||||
float *spine_array_float_buffer(spine_array_float array) {
|
||||
Array<float> *_array = (Array<float> *) array;
|
||||
return _array->buffer();
|
||||
}
|
||||
```
|
||||
|
||||
Arrays are generated for all basic types (`float`, `int`, `unsigned_short`, `property_id`) and all object types used in collections throughout the API. The implementation directly casts the opaque handle to the underlying `Array<T>*` type.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Memory Management
|
||||
@ -391,7 +463,7 @@ spine_array_float spine_array_float_new(int32_t capacity) {
|
||||
- Only generates constructors for non-abstract classes
|
||||
- Only generates constructors for classes inheriting from `SpineObject`
|
||||
- Requires at least one public constructor or explicit exclusion
|
||||
- Constructor overloads are numbered: `_new`, `_new2`, `_new3`
|
||||
- Constructor overloads are numbered: `_create`, `_create2`, `_create3`
|
||||
|
||||
### Field Accessor Generation
|
||||
- Generates getters for all non-static public fields
|
||||
@ -400,8 +472,9 @@ spine_array_float spine_array_float_new(int32_t capacity) {
|
||||
- Handles nested field access (e.g., `obj.field.x`)
|
||||
|
||||
### Method Overloading
|
||||
- Constructor overloads are numbered: `_new`, `_new2`, `_new3`
|
||||
- Other overloads must be excluded (C doesn't support overloading)
|
||||
- Constructor overloads are numbered: `_create`, `_create2`, `_create3`, etc.
|
||||
- Method overloads are numbered with suffixes: `_1`, `_2`, `_3`, etc.
|
||||
- Methods named "create" get `_method` suffix to avoid constructor conflicts
|
||||
- Const/non-const conflicts are detected and reported
|
||||
|
||||
### RTTI Handling
|
||||
@ -410,10 +483,21 @@ spine_array_float spine_array_float_new(int32_t capacity) {
|
||||
- RTTI checks are performed in generated code where needed
|
||||
|
||||
### Warning System
|
||||
- Collects non-fatal issues during generation
|
||||
- Collects non-fatal issues during generation using `WarningsCollector`
|
||||
- Reports abstract classes, missing constructors, etc.
|
||||
- Groups warnings by pattern to avoid repetition
|
||||
- Warnings don't stop generation but are reported at the end
|
||||
|
||||
### Interface Detection
|
||||
- Automatically identifies pure interfaces (classes with only pure virtual methods)
|
||||
- Distinguishes between concrete classes and interfaces for inheritance mapping
|
||||
- Used to determine extends vs implements relationships for target languages
|
||||
|
||||
### Multiple Inheritance Handling
|
||||
- Detects multiple concrete inheritance scenarios
|
||||
- Fails generation with clear error messages when unsupported patterns are found
|
||||
- Provides guidance on converting concrete classes to interfaces
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Errors
|
||||
@ -438,6 +522,11 @@ spine_array_float spine_array_float_new(int32_t capacity) {
|
||||
- Generated function name collides with a type name
|
||||
- Solution: Rename method or exclude
|
||||
|
||||
6. **"Multiple concrete inheritance detected"**
|
||||
- A class inherits from multiple concrete (non-interface) classes
|
||||
- Solution: Convert one of the parent classes to a pure interface
|
||||
- Check the error message for specific guidance on which classes to modify
|
||||
|
||||
### Debugging Tips
|
||||
|
||||
1. Check `spine-cpp-types.json` for extracted type information
|
||||
@ -445,6 +534,9 @@ spine_array_float spine_array_float_new(int32_t capacity) {
|
||||
3. Verify inheritance with "inherits from SpineObject" messages
|
||||
4. Array specializations are listed with element type mapping
|
||||
5. Check warnings at the end of generation for issues
|
||||
6. Use `--export-json` flag to export inheritance and type information as JSON
|
||||
7. Check `out.json` for debug output when troubleshooting
|
||||
8. Review console output for inheritance mapping information (extends/mixins)
|
||||
|
||||
### Adding New Types
|
||||
|
||||
@ -461,3 +553,33 @@ spine_array_float spine_array_float_new(int32_t capacity) {
|
||||
- Array scanning happens after type filtering for efficiency
|
||||
- Validation checks run before generation to fail fast
|
||||
- Incremental generation avoids regenerating unchanged files
|
||||
|
||||
## Development Tools
|
||||
|
||||
The codegen project includes several development tools and configurations:
|
||||
|
||||
### Biome Configuration (`biome.json`)
|
||||
- Linting enabled with recommended rules
|
||||
- Formatting disabled (uses external formatter)
|
||||
- Helps maintain code quality during development
|
||||
|
||||
### TypeScript Formatter (`tsfmt.json`)
|
||||
- Comprehensive formatting rules for TypeScript code
|
||||
- Configures indentation, spacing, and code style
|
||||
- Used for consistent code formatting across the project
|
||||
|
||||
### Build Output (`dist/`)
|
||||
- Contains compiled TypeScript files
|
||||
- Generated JavaScript and declaration files
|
||||
- Source maps for debugging
|
||||
|
||||
### Debug Output (`out.json`)
|
||||
- Contains debug information from the generation process
|
||||
- Useful for troubleshooting and understanding the generated data structure
|
||||
|
||||
### Dependencies
|
||||
The project uses minimal dependencies for maximum compatibility:
|
||||
- `@types/node` - Node.js type definitions
|
||||
- `tsx` - TypeScript execution engine
|
||||
- `typescript-formatter` - Code formatting
|
||||
- `@biomejs/biome` - Fast linter for code quality
|
||||
@ -1,6 +1,10 @@
|
||||
{
|
||||
"name": "spine-c-codegen",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"generate": "tsx src/index.ts",
|
||||
"null-analysis": "tsx src/null-analysis.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"tsx": "^4.0.0",
|
||||
|
||||
205
spine-c/codegen/src/null-analysis.ts
Normal file
205
spine-c/codegen/src/null-analysis.ts
Normal file
@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env node
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { extractTypes } from './type-extractor';
|
||||
import type { ClassOrStruct, Method, Type } from './types';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* Checks if a type string represents a pointer to a class instance
|
||||
*/
|
||||
function isPointerToClass(typeStr: string, allTypes: Type[]): boolean {
|
||||
// Remove const, references, and whitespace
|
||||
const cleanType = typeStr.replace(/\bconst\b/g, '').replace(/&/g, '').trim();
|
||||
|
||||
// Check if it ends with * (pointer)
|
||||
if (!cleanType.endsWith('*')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the base type (remove the *)
|
||||
const baseType = cleanType.replace(/\*+$/, '').trim();
|
||||
|
||||
// Check if the base type is a class/struct in our type list
|
||||
return allTypes.some(type =>
|
||||
type.kind !== 'enum' && type.name === baseType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a type string represents a class instance (for return types)
|
||||
*/
|
||||
function isClassType(typeStr: string, allTypes: Type[]): boolean {
|
||||
// Remove const, references, and whitespace
|
||||
const cleanType = typeStr.replace(/\bconst\b/g, '').replace(/&/g, '').replace(/\*/g, '').trim();
|
||||
|
||||
// Check if the base type is a class/struct in our type list
|
||||
return allTypes.some(type =>
|
||||
type.kind !== 'enum' && type.name === cleanType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes all methods to find those with nullable inputs or outputs
|
||||
*/
|
||||
function analyzeNullableMethods(): void {
|
||||
console.log('Extracting type information...');
|
||||
const allTypes = extractTypes();
|
||||
|
||||
const nullableMethods: Array<{
|
||||
filename: string;
|
||||
line: number;
|
||||
signature: string;
|
||||
reason: string;
|
||||
}> = [];
|
||||
|
||||
// Process each type
|
||||
for (const type of allTypes) {
|
||||
if (type.kind === 'enum') continue;
|
||||
|
||||
const classType = type as ClassOrStruct;
|
||||
if (!classType.members) continue;
|
||||
|
||||
// Get the source file name relative to the nullable file location
|
||||
const filename = `../../spine-cpp/include/spine/${classType.name}.h`;
|
||||
|
||||
// Process each method
|
||||
for (const member of classType.members) {
|
||||
if (member.kind !== 'method') continue;
|
||||
|
||||
const method = member as Method;
|
||||
const signature = buildMethodSignature(classType.name, method);
|
||||
|
||||
// Check return type - if it returns a pointer to a class
|
||||
if (method.returnType) {
|
||||
const cleanReturnType = method.returnType.replace(/\bconst\b/g, '').trim();
|
||||
if (isPointerToClass(cleanReturnType, allTypes)) {
|
||||
nullableMethods.push({
|
||||
filename,
|
||||
line: method.loc.line,
|
||||
signature,
|
||||
reason: `returns nullable pointer: ${method.returnType}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check parameters - if any parameter is a pointer to a class
|
||||
if (method.parameters) {
|
||||
for (const param of method.parameters) {
|
||||
if (isPointerToClass(param.type, allTypes)) {
|
||||
nullableMethods.push({
|
||||
filename,
|
||||
line: method.loc.line,
|
||||
signature,
|
||||
reason: `takes nullable parameter '${param.name}': ${param.type}`
|
||||
});
|
||||
break; // Only report once per method
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by filename and line
|
||||
nullableMethods.sort((a, b) => {
|
||||
if (a.filename !== b.filename) {
|
||||
return a.filename.localeCompare(b.filename);
|
||||
}
|
||||
return a.line - b.line;
|
||||
});
|
||||
|
||||
// Write results to nullable.md file
|
||||
const outputPath = path.join(__dirname, '../nullable.md');
|
||||
|
||||
const instructions = `# Spine C++ Nullability Cleanup
|
||||
|
||||
## Instructions
|
||||
|
||||
**Phase 1: Enrich nullable.md (if implementations not yet inlined)**
|
||||
If checkboxes don't contain concrete implementations:
|
||||
1. Use parallel Task agents to find implementations (agents do NOT write to file)
|
||||
2. Each agent researches 10-15 methods and returns structured data:
|
||||
\`\`\`
|
||||
METHOD: [method signature]
|
||||
CPP_HEADER: [file:line] [declaration]
|
||||
CPP_IMPL: [file:line] [implementation code]
|
||||
JAVA_IMPL: [file:line] [java method code]
|
||||
---
|
||||
\`\`\`
|
||||
3. Collect all agent results and do ONE MultiEdit to update nullable.md
|
||||
4. Inline implementations BELOW each existing checkbox (keep original checkbox text):
|
||||
\`\`\`
|
||||
- [ ] [keep original checkbox line exactly as is]
|
||||
**C++ Implementation:**
|
||||
\`\`\`cpp
|
||||
// Header: [file:line]
|
||||
[declaration]
|
||||
// Implementation: [file:line]
|
||||
[implementation body]
|
||||
\`\`\`
|
||||
**Java Implementation:**
|
||||
\`\`\`java
|
||||
// [file:line]
|
||||
[java method body]
|
||||
\`\`\`
|
||||
\`\`\`
|
||||
|
||||
**Phase 2: Review and Update**
|
||||
For each unchecked checkbox (now with implementations inlined):
|
||||
1. **Present both implementations** from the checkbox
|
||||
2. **Ask if we need to change the C++ signature** based on Java nullability patterns (y/n)
|
||||
3. **Make changes if needed**
|
||||
- Change the signature in the header file
|
||||
- Update the implementation in the corresponding .cpp file
|
||||
- Run \`../../spine-cpp/build.sh\` to confirm the changes compile successfully
|
||||
4. **Confirm changes**
|
||||
- Summarize what was changed
|
||||
- Ask for confirmation that the changes are correct (y/n)
|
||||
- If yes, check the checkbox and move to the next unchecked item
|
||||
|
||||
## Methods to Review
|
||||
|
||||
`;
|
||||
|
||||
const methodsList = nullableMethods.map(m =>
|
||||
`- [ ] [${m.filename}:${m.line}](${m.filename}#L${m.line}) ${m.signature} // ${m.reason}`
|
||||
).join('\n');
|
||||
|
||||
fs.writeFileSync(outputPath, instructions + methodsList + '\n');
|
||||
|
||||
console.log(`Found ${nullableMethods.length} methods with nullable inputs/outputs`);
|
||||
console.log(`Results written to: ${outputPath}`);
|
||||
|
||||
// Print summary statistics
|
||||
const byReason = new Map<string, number>();
|
||||
for (const method of nullableMethods) {
|
||||
const reasonType = method.reason.startsWith('returns') ? 'nullable return' : 'nullable parameter';
|
||||
byReason.set(reasonType, (byReason.get(reasonType) || 0) + 1);
|
||||
}
|
||||
|
||||
console.log('\nSummary:');
|
||||
for (const [reason, count] of byReason) {
|
||||
console.log(` ${reason}: ${count} methods`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a method signature string
|
||||
*/
|
||||
function buildMethodSignature(className: string, method: Method): string {
|
||||
const params = method.parameters?.map(p => `${p.type} ${p.name}`).join(', ') || '';
|
||||
const constStr = method.isConst ? ' const' : '';
|
||||
return `${method.returnType || 'void'} ${className}::${method.name}(${params})${constStr}`;
|
||||
}
|
||||
|
||||
// Main execution
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
try {
|
||||
analyzeNullableMethods();
|
||||
} catch (error) {
|
||||
console.error('Error during analysis:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@ -122,12 +122,19 @@ function extractMember(inner: any, parent: any): Member & { access?: 'public' |
|
||||
|
||||
switch (inner.kind) {
|
||||
case 'FieldDecl': {
|
||||
if (!inner.loc) {
|
||||
throw new Error(`Failed to extract location for field '${inner.name || 'unknown'}' in ${parent.name || 'unknown'}`);
|
||||
}
|
||||
const field: Field & { access?: 'public' | 'protected' } = {
|
||||
kind: 'field',
|
||||
name: inner.name || '',
|
||||
type: inner.type?.qualType || '',
|
||||
isStatic: inner.storageClass === 'static',
|
||||
access: 'public' // Will be set correctly later
|
||||
access: 'public', // Will be set correctly later
|
||||
loc: {
|
||||
line: inner.loc.line || 0,
|
||||
col: inner.loc.col || 0
|
||||
}
|
||||
};
|
||||
return field;
|
||||
}
|
||||
@ -136,6 +143,9 @@ function extractMember(inner: any, parent: any): Member & { access?: 'public' |
|
||||
// Skip operators - not needed for C wrapper generation
|
||||
if (inner.name.startsWith('operator')) return null;
|
||||
|
||||
if (!inner.loc) {
|
||||
throw new Error(`Failed to extract location for method '${inner.name}' in ${parent.name || 'unknown'}`);
|
||||
}
|
||||
const method: Method & { access?: 'public' | 'protected' } = {
|
||||
kind: 'method',
|
||||
name: inner.name,
|
||||
@ -145,27 +155,45 @@ function extractMember(inner: any, parent: any): Member & { access?: 'public' |
|
||||
isVirtual: inner.virtual || false,
|
||||
isPure: inner.pure || false,
|
||||
isConst: inner.constQualifier || false,
|
||||
access: 'public' // Will be set correctly later
|
||||
access: 'public', // Will be set correctly later
|
||||
loc: {
|
||||
line: inner.loc.line || 0,
|
||||
col: inner.loc.col || 0
|
||||
}
|
||||
};
|
||||
return method;
|
||||
}
|
||||
case 'CXXConstructorDecl': {
|
||||
if (!inner.loc) {
|
||||
throw new Error(`Failed to extract location for constructor '${inner.name || parent.name || 'unknown'}' in ${parent.name || 'unknown'}`);
|
||||
}
|
||||
const constr: Constructor & { access?: 'public' | 'protected' } = {
|
||||
kind: 'constructor',
|
||||
name: inner.name || parent.name || '',
|
||||
parameters: extractParameters(inner),
|
||||
access: 'public' // Will be set correctly later
|
||||
access: 'public', // Will be set correctly later
|
||||
loc: {
|
||||
line: inner.loc.line || 0,
|
||||
col: inner.loc.col || 0
|
||||
}
|
||||
};
|
||||
return constr;
|
||||
}
|
||||
case 'CXXDestructorDecl': {
|
||||
// Include destructors for completeness
|
||||
if (!inner.loc) {
|
||||
throw new Error(`Failed to extract location for destructor '${inner.name || `~${parent.name}`}' in ${parent.name || 'unknown'}`);
|
||||
}
|
||||
const destructor: Destructor & { access?: 'public' | 'protected' } = {
|
||||
kind: 'destructor',
|
||||
name: inner.name || `~${parent.name}`,
|
||||
isVirtual: inner.virtual || false,
|
||||
isPure: inner.pure || false,
|
||||
access: 'public' // Will be set correctly later
|
||||
access: 'public', // Will be set correctly later
|
||||
loc: {
|
||||
line: inner.loc.line || 0,
|
||||
col: inner.loc.col || 0
|
||||
}
|
||||
};
|
||||
return destructor;
|
||||
}
|
||||
|
||||
@ -9,6 +9,10 @@ export type Field = {
|
||||
type: string;
|
||||
isStatic?: boolean;
|
||||
fromSupertype?: string;
|
||||
loc: {
|
||||
line: number;
|
||||
col: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type Method = {
|
||||
@ -21,6 +25,10 @@ export type Method = {
|
||||
isPure?: boolean;
|
||||
isConst?: boolean;
|
||||
fromSupertype?: string;
|
||||
loc: {
|
||||
line: number;
|
||||
col: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type Constructor = {
|
||||
@ -28,6 +36,10 @@ export type Constructor = {
|
||||
name: string;
|
||||
parameters?: Parameter[];
|
||||
fromSupertype?: string;
|
||||
loc: {
|
||||
line: number;
|
||||
col: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type Destructor = {
|
||||
@ -36,6 +48,10 @@ export type Destructor = {
|
||||
isVirtual?: boolean;
|
||||
isPure?: boolean;
|
||||
fromSupertype?: string;
|
||||
loc: {
|
||||
line: number;
|
||||
col: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type Member =
|
||||
|
||||
@ -9,58 +9,7 @@ import { DartWriter } from './dart-writer.js';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
async function generateFFIBindings(spineCDir: string): Promise<void> {
|
||||
console.log('Finding all header files...');
|
||||
const generatedDir = path.join(spineCDir, 'src/generated');
|
||||
const headerFiles = fs.readdirSync(generatedDir)
|
||||
.filter(f => f.endsWith('.h'))
|
||||
.map(f => path.join('src/spine-c/src/generated', f))
|
||||
.sort();
|
||||
|
||||
console.log(`Found ${headerFiles.length} header files`);
|
||||
|
||||
// Generate ffigen.yaml configuration
|
||||
console.log('Generating ffigen.yaml configuration...');
|
||||
const ffigenConfig = `# Run with \`dart run ffigen --config ffigen.yaml\`.
|
||||
name: SpineDartBindings
|
||||
description: |
|
||||
Bindings for Spine C headers.
|
||||
|
||||
Regenerate bindings with \`dart run ffigen --config ffigen.yaml\`.
|
||||
output: 'lib/generated/spine_dart_bindings_generated.dart'
|
||||
llvm-path:
|
||||
- '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/'
|
||||
headers:
|
||||
entry-points:
|
||||
- 'src/spine-c/include/spine-c.h'
|
||||
compiler-opts:
|
||||
- '-Isrc/spine-c/include'
|
||||
- '-Isrc/spine-c/src'
|
||||
- '-Isrc/spine-c/src/generated'
|
||||
- '-xc'
|
||||
- '-std=c99'
|
||||
functions:
|
||||
include:
|
||||
- 'spine_.*'
|
||||
structs:
|
||||
include:
|
||||
- 'spine_.*'
|
||||
enums:
|
||||
include:
|
||||
- 'spine_.*'
|
||||
typedefs:
|
||||
include:
|
||||
- 'spine_.*'
|
||||
preamble: |
|
||||
// ignore_for_file: always_specify_types, constant_identifier_names
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
comments:
|
||||
style: any
|
||||
length: full
|
||||
`;
|
||||
|
||||
const ffigenPath = path.join(__dirname, '../../ffigen.yaml');
|
||||
fs.writeFileSync(ffigenPath, ffigenConfig);
|
||||
const ffigenPath = await generateFFigenYaml(spineCDir);
|
||||
|
||||
// Run ffigen to generate bindings
|
||||
console.log('Running ffigen...');
|
||||
@ -93,7 +42,7 @@ comments:
|
||||
console.log('✅ FFI bindings generated successfully!');
|
||||
}
|
||||
|
||||
async function generateFFigenYamlOnly(spineCDir: string): Promise<void> {
|
||||
async function generateFFigenYaml(spineCDir: string): Promise<string> {
|
||||
console.log('Finding all header files...');
|
||||
const generatedDir = path.join(spineCDir, 'src/generated');
|
||||
const headerFiles = fs.readdirSync(generatedDir)
|
||||
@ -147,6 +96,7 @@ comments:
|
||||
const ffigenPath = path.join(__dirname, '../../ffigen.yaml');
|
||||
fs.writeFileSync(ffigenPath, ffigenConfig);
|
||||
console.log(`FFigen config written to: ${ffigenPath}`);
|
||||
return ffigenPath;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
@ -158,7 +108,7 @@ async function main() {
|
||||
|
||||
// Generate FFI bindings YAML config only
|
||||
const spineCDir = path.join(__dirname, '../../src/spine-c');
|
||||
await generateFFigenYamlOnly(spineCDir);
|
||||
await generateFFigenYaml(spineCDir);
|
||||
console.log('✅ ffigen.yaml generated successfully!');
|
||||
return;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user