Files
oldboy/scripts/csound-parser/generator.ts
2025-10-15 15:05:23 +02:00

208 lines
6.2 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import type { CsoundReference } from './types';
export class TypeScriptGenerator {
private outputDir: string;
constructor(outputDir: string) {
this.outputDir = outputDir;
}
sanitizeCategoryName(category: string): string {
return category
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
sanitizeVariableName(category: string): string {
return category
.replace(/[^a-zA-Z0-9]+/g, ' ')
.split(' ')
.map((word, index) =>
index === 0
? word.toLowerCase()
: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
)
.join('')
.replace(/^[0-9]/, '_$&');
}
escapeString(str: string): string {
return str
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
}
generateReferenceObject(ref: CsoundReference, indent: string = ' '): string {
const lines: string[] = [];
lines.push(`{`);
lines.push(`${indent}name: '${this.escapeString(ref.name)}',`);
lines.push(`${indent}type: '${ref.type}',`);
lines.push(`${indent}category: '${this.escapeString(ref.category)}',`);
lines.push(`${indent}description: '${this.escapeString(ref.description)}',`);
if (ref.syntax) {
lines.push(`${indent}syntax: '${this.escapeString(ref.syntax)}',`);
}
if (ref.example) {
const escapedExample = this.escapeString(ref.example);
lines.push(`${indent}example: '${escapedExample}',`);
}
if (ref.rates && ref.rates.length > 0) {
lines.push(`${indent}rates: [${ref.rates.map(r => `'${r}'`).join(', ')}],`);
}
if (ref.parameters && ref.parameters.length > 0) {
lines.push(`${indent}parameters: [`);
for (const param of ref.parameters) {
lines.push(`${indent} {`);
lines.push(`${indent} name: '${this.escapeString(param.name)}',`);
lines.push(`${indent} description: '${this.escapeString(param.description)}',`);
lines.push(`${indent} type: '${param.type}'`);
lines.push(`${indent} },`);
}
lines.push(`${indent}],`);
}
if (ref.seeAlso && ref.seeAlso.length > 0) {
lines.push(`${indent}seeAlso: [${ref.seeAlso.map(s => `'${this.escapeString(s)}'`).join(', ')}]`);
}
lines.push(`}`);
return lines.join('\n');
}
generateCategoryFile(
category: string,
references: CsoundReference[],
fileName: string
): string {
const varName = this.sanitizeVariableName(category);
const lines: string[] = [];
lines.push(`import type { CsoundReference } from './types'`);
lines.push(``);
lines.push(`// ${category}`);
lines.push(`export const ${varName}: CsoundReference[] = [`);
for (const ref of references) {
const refLines = this.generateReferenceObject(ref, ' ');
lines.push(refLines + ',');
}
lines.push(`]`);
lines.push(``);
return lines.join('\n');
}
generateMainFile(categories: Map<string, CsoundReference[]>): string {
const lines: string[] = [];
lines.push(`import type { CsoundReference } from './types'`);
const imports: string[] = [];
const varNames: string[] = [];
for (const [category] of categories) {
const fileName = this.sanitizeCategoryName(category);
const varName = this.sanitizeVariableName(category);
imports.push(`import { ${varName} } from './${fileName}'`);
varNames.push(varName);
}
lines.push(...imports);
lines.push(``);
lines.push(`export type { CsoundReference }`);
lines.push(``);
lines.push(`export const allCsoundReferences: CsoundReference[] = [`);
for (const varName of varNames) {
lines.push(` ...${varName},`);
}
lines.push(`]`);
lines.push(``);
lines.push(`export const csoundReferenceMap = new Map<string, CsoundReference>()`);
lines.push(`allCsoundReferences.forEach(ref => {`);
lines.push(` csoundReferenceMap.set(ref.name, ref)`);
lines.push(`})`);
lines.push(``);
lines.push(`export function getCsoundReference(name: string): CsoundReference | undefined {`);
lines.push(` return csoundReferenceMap.get(name)`);
lines.push(`}`);
lines.push(``);
lines.push(`export function getCsoundReferencesByCategory(category: string): CsoundReference[] {`);
lines.push(` return allCsoundReferences.filter(ref => ref.category === category)`);
lines.push(`}`);
lines.push(``);
lines.push(`export {`);
for (const varName of varNames) {
lines.push(` ${varName},`);
}
lines.push(`}`);
lines.push(``);
return lines.join('\n');
}
writeCategoryFiles(categories: Map<string, CsoundReference[]>): void {
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, { recursive: true });
}
let fileCount = 0;
for (const [category, references] of categories) {
const fileName = this.sanitizeCategoryName(category);
const filePath = path.join(this.outputDir, `${fileName}.ts`);
const content = this.generateCategoryFile(category, references, fileName);
fs.writeFileSync(filePath, content, 'utf-8');
fileCount++;
console.log(`Generated: ${fileName}.ts (${references.length} opcodes)`);
}
console.log(`\nGenerated ${fileCount} category files`);
}
writeMainFile(categories: Map<string, CsoundReference[]>): void {
const filePath = path.join(this.outputDir, 'csoundReference.ts');
const content = this.generateMainFile(categories);
fs.writeFileSync(filePath, content, 'utf-8');
console.log(`Generated: csoundReference.ts`);
}
copyTypes(): void {
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, { recursive: true });
}
const sourceTypes = path.join(__dirname, 'types.ts');
const destTypes = path.join(this.outputDir, 'types.ts');
if (fs.existsSync(sourceTypes)) {
fs.copyFileSync(sourceTypes, destTypes);
console.log(`Copied: types.ts`);
}
}
generateAll(categories: Map<string, CsoundReference[]>): void {
console.log(`\nGenerating TypeScript files...`);
this.copyTypes();
this.writeCategoryFiles(categories);
this.writeMainFile(categories);
console.log(`\nDone!`);
}
}