Improved apigen tool for cpp
This commit is contained in:
		
							
								
								
									
										283
									
								
								tools/apigen.ts
									
									
									
									
									
								
							
							
						
						
									
										283
									
								
								tools/apigen.ts
									
									
									
									
									
								
							@@ -4,59 +4,244 @@ import {
 | 
			
		||||
	parse
 | 
			
		||||
} from '@typescript-eslint/typescript-estree';
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import {
 | 
			
		||||
	Decorator,
 | 
			
		||||
	ExportDefaultDeclaration,
 | 
			
		||||
	FunctionExpression,
 | 
			
		||||
	Literal
 | 
			
		||||
} from '@typescript-eslint/types/dist/generated/ast-spec';
 | 
			
		||||
import * as path from 'path';
 | 
			
		||||
import * as a from '@typescript-eslint/types/dist/generated/ast-spec';
 | 
			
		||||
import { TypeNode } from '@typescript-eslint/types/dist/generated/ast-spec';
 | 
			
		||||
import { Writable } from 'stream';
 | 
			
		||||
 | 
			
		||||
const program = createProgram('tsconfig.json');
 | 
			
		||||
const ast = parse(fs.readFileSync('src/controller/filesystem.ts').toString(), {
 | 
			
		||||
	programs: [program]
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function parseDecorators(decs: Decorator[]): object[] | null {
 | 
			
		||||
	try {
 | 
			
		||||
		return decs.map((dec) => {
 | 
			
		||||
			if (dec.expression.type !== AST_NODE_TYPES.CallExpression)
 | 
			
		||||
				throw null;
 | 
			
		||||
			if (dec.expression.callee.type != AST_NODE_TYPES.Identifier)
 | 
			
		||||
				throw null;
 | 
			
		||||
			const name = dec.expression.callee.name;
 | 
			
		||||
			if (name === 'Request') throw null;
 | 
			
		||||
			const prop = (
 | 
			
		||||
				dec.expression.arguments
 | 
			
		||||
					.filter((arg) => arg.type === AST_NODE_TYPES.Literal)
 | 
			
		||||
					.pop() as Literal
 | 
			
		||||
			).value;
 | 
			
		||||
			return { name: name, prop: prop };
 | 
			
		||||
		});
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		return null;
 | 
			
		||||
class Err extends Error {
 | 
			
		||||
	constructor(o: object) {
 | 
			
		||||
		super();
 | 
			
		||||
		console.error(o);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseFunction(name: string, func: FunctionExpression) {
 | 
			
		||||
	const decs = func.params
 | 
			
		||||
		.map((p) => p.decorators)
 | 
			
		||||
		.map(parseDecorators)
 | 
			
		||||
		.filter((d) => d !== null);
 | 
			
		||||
	console.log(name, decs);
 | 
			
		||||
const program = createProgram('tsconfig.json');
 | 
			
		||||
 | 
			
		||||
class BetterWritable {
 | 
			
		||||
	constructor(private w: Writable) {}
 | 
			
		||||
	private indent = 0;
 | 
			
		||||
 | 
			
		||||
	write(data: string) {
 | 
			
		||||
		this.w.write('\t'.repeat(this.indent));
 | 
			
		||||
		this.w.write(data);
 | 
			
		||||
		this.w.write('\n');
 | 
			
		||||
	}
 | 
			
		||||
	writeInc(data: string) {
 | 
			
		||||
		this.write(data);
 | 
			
		||||
		this.indent++;
 | 
			
		||||
	}
 | 
			
		||||
	writeDec(data: string) {
 | 
			
		||||
		this.indent--;
 | 
			
		||||
		this.write(data);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ast.body
 | 
			
		||||
	.filter((body) => body.type == AST_NODE_TYPES.ExportDefaultDeclaration)
 | 
			
		||||
	.forEach((body: ExportDefaultDeclaration) => {
 | 
			
		||||
		if (body.declaration.type !== AST_NODE_TYPES.ClassDeclaration) return;
 | 
			
		||||
		if (body.declaration.body.type !== AST_NODE_TYPES.ClassBody) return;
 | 
			
		||||
		body.declaration.body.body.forEach((def) => {
 | 
			
		||||
			if (
 | 
			
		||||
				def.type === AST_NODE_TYPES.MethodDefinition &&
 | 
			
		||||
				'name' in def.key &&
 | 
			
		||||
				def.key.name !== 'constructor' &&
 | 
			
		||||
				def.value.type === AST_NODE_TYPES.FunctionExpression
 | 
			
		||||
			)
 | 
			
		||||
				parseFunction(def.key.name, def.value);
 | 
			
		||||
		});
 | 
			
		||||
interface Outputable {
 | 
			
		||||
	write(s: BetterWritable);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CppProp {
 | 
			
		||||
	constructor(entry: string) {
 | 
			
		||||
		this.entry = entry;
 | 
			
		||||
	}
 | 
			
		||||
	entry: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CppClass implements Outputable {
 | 
			
		||||
	constructor(name: string) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
	}
 | 
			
		||||
	name: string;
 | 
			
		||||
	sub: string[] = [];
 | 
			
		||||
	entries: CppProp[] = [];
 | 
			
		||||
 | 
			
		||||
	write(s: BetterWritable) {
 | 
			
		||||
		let name = `struct ${this.name}`;
 | 
			
		||||
		if (this.sub.length > 0) name += ' : ' + this.sub.join(', ');
 | 
			
		||||
		if (this.entries.length == 0) s.write(`${name} {}`);
 | 
			
		||||
		else {
 | 
			
		||||
			s.writeInc(`${name} {`);
 | 
			
		||||
			this.entries.forEach((e) => s.write(`${e.entry};`));
 | 
			
		||||
			s.writeDec('};');
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CppEnum implements Outputable {
 | 
			
		||||
	constructor(name: string) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
	}
 | 
			
		||||
	name: string;
 | 
			
		||||
	entries: CppProp[] = [];
 | 
			
		||||
 | 
			
		||||
	write(s: BetterWritable) {
 | 
			
		||||
		s.writeInc(`enum ${this.name} {`);
 | 
			
		||||
		this.entries.forEach((e, idx, arr) =>
 | 
			
		||||
			s.write(`${e.entry}${idx === arr.length - 1 ? '' : ','}`)
 | 
			
		||||
		);
 | 
			
		||||
		s.writeDec('};');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CppNamespace implements Outputable {
 | 
			
		||||
	constructor(name: string) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
	}
 | 
			
		||||
	name: string;
 | 
			
		||||
	body: (CppNamespace | CppClass | CppEnum)[] = [];
 | 
			
		||||
 | 
			
		||||
	write(s: BetterWritable) {
 | 
			
		||||
		s.writeInc(`namespace ${this.name} {`);
 | 
			
		||||
		this.body.forEach((b) => b.write(s));
 | 
			
		||||
		s.writeDec('}');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _getCppType(
 | 
			
		||||
	t: TypeNode
 | 
			
		||||
): string | ((opt: boolean, name: string) => string) {
 | 
			
		||||
	switch (t.type) {
 | 
			
		||||
		case AST_NODE_TYPES.TSStringKeyword:
 | 
			
		||||
			return 'std::string';
 | 
			
		||||
		case AST_NODE_TYPES.TSBooleanKeyword:
 | 
			
		||||
			return 'bool';
 | 
			
		||||
		case AST_NODE_TYPES.TSNumberKeyword:
 | 
			
		||||
			return 'long';
 | 
			
		||||
		case AST_NODE_TYPES.TSTypeReference:
 | 
			
		||||
			if (t.typeName.type != AST_NODE_TYPES.Identifier) throw new Err(t);
 | 
			
		||||
			return t.typeName.name;
 | 
			
		||||
		case AST_NODE_TYPES.TSLiteralType:
 | 
			
		||||
			if (t.literal.type != AST_NODE_TYPES.Literal) throw new Err(t);
 | 
			
		||||
			return (opt, name) => `${name} = ${(t.literal as a.Literal).raw}`;
 | 
			
		||||
		case AST_NODE_TYPES.TSUnionType:
 | 
			
		||||
			return _getCppType(t.types[0]);
 | 
			
		||||
		case AST_NODE_TYPES.TSArrayType:
 | 
			
		||||
			return (opt, name) =>
 | 
			
		||||
				opt
 | 
			
		||||
					? `std::optional<${_getCppType(t.elementType)}[]> ${name}`
 | 
			
		||||
					: `${getCppType(false, name, t.elementType)}[]`;
 | 
			
		||||
		default:
 | 
			
		||||
			throw new Err(t);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getCppType(opt: boolean, name: string, t: TypeNode): string {
 | 
			
		||||
	let ret = _getCppType(t);
 | 
			
		||||
	if (typeof ret === 'string') {
 | 
			
		||||
		if (opt) ret = `std::optional<${ret}>`;
 | 
			
		||||
		return `${ret} ${name}`;
 | 
			
		||||
	} else return `${ret(opt, name)}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleClassProperty(
 | 
			
		||||
	prop: a.PropertyDefinitionComputedName | a.PropertyDefinitionNonComputedName
 | 
			
		||||
): CppProp {
 | 
			
		||||
	if (prop.key.type != AST_NODE_TYPES.Identifier) throw new Err(prop);
 | 
			
		||||
	if (!prop.typeAnnotation) throw new Err(prop);
 | 
			
		||||
	return new CppProp(
 | 
			
		||||
		getCppType(
 | 
			
		||||
			prop.optional && prop.optional,
 | 
			
		||||
			prop.key.name,
 | 
			
		||||
			prop.typeAnnotation.typeAnnotation
 | 
			
		||||
		)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleClass(
 | 
			
		||||
	dec: a.ClassDeclarationWithName | a.ClassDeclarationWithOptionalName
 | 
			
		||||
): CppClass {
 | 
			
		||||
	if (!dec.id) throw new Err(dec);
 | 
			
		||||
	const cls = new CppClass(dec.id.name);
 | 
			
		||||
	if (dec.superClass) {
 | 
			
		||||
		const subCls = dec.superClass;
 | 
			
		||||
		if (subCls.type == AST_NODE_TYPES.Identifier) cls.sub.push(subCls.name);
 | 
			
		||||
		else throw new Err(subCls);
 | 
			
		||||
	}
 | 
			
		||||
	dec.body.body.forEach((inner) => {
 | 
			
		||||
		if (inner.type == AST_NODE_TYPES.PropertyDefinition) {
 | 
			
		||||
			cls.entries.push(handleClassProperty(inner));
 | 
			
		||||
		} else if (
 | 
			
		||||
			inner.type == AST_NODE_TYPES.MethodDefinition &&
 | 
			
		||||
			inner.key.type == AST_NODE_TYPES.Identifier &&
 | 
			
		||||
			inner.key.name === 'constructor'
 | 
			
		||||
		)
 | 
			
		||||
			console.warn('Handle constructors?');
 | 
			
		||||
		else throw new Err(inner);
 | 
			
		||||
	});
 | 
			
		||||
	return cls;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleEnum(dec: a.TSEnumDeclaration): CppEnum {
 | 
			
		||||
	const enm = new CppEnum(dec.id.name);
 | 
			
		||||
	dec.members.forEach((member) => {
 | 
			
		||||
		if (
 | 
			
		||||
			member.id.type == AST_NODE_TYPES.Identifier &&
 | 
			
		||||
			member.initializer.type == AST_NODE_TYPES.Literal
 | 
			
		||||
		) {
 | 
			
		||||
			enm.entries.push(
 | 
			
		||||
				new CppProp(`${member.id.name} = ${member.initializer.raw}`)
 | 
			
		||||
			);
 | 
			
		||||
		} else throw new Err(member);
 | 
			
		||||
	});
 | 
			
		||||
	return enm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleNamedDeclaration(
 | 
			
		||||
	dec: a.ExportNamedDeclaration
 | 
			
		||||
): CppClass | CppEnum | undefined {
 | 
			
		||||
	if (!dec.declaration) throw new Err(dec);
 | 
			
		||||
	else if (dec.declaration.type == AST_NODE_TYPES.ClassDeclaration)
 | 
			
		||||
		return handleClass(dec.declaration);
 | 
			
		||||
	else if (dec.declaration.type == AST_NODE_TYPES.TSEnumDeclaration)
 | 
			
		||||
		return handleEnum(dec.declaration);
 | 
			
		||||
	else if (dec.declaration.type == AST_NODE_TYPES.FunctionDeclaration) return;
 | 
			
		||||
	else {
 | 
			
		||||
		throw new Err(dec);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleExport(
 | 
			
		||||
	ns: CppNamespace,
 | 
			
		||||
	file: string,
 | 
			
		||||
	body: a.ExportNamedDeclaration | a.ExportAllDeclaration
 | 
			
		||||
) {
 | 
			
		||||
	let newPath = path.join(path.dirname(file), body.source.value);
 | 
			
		||||
	if (fs.existsSync(newPath) && fs.statSync(newPath).isDirectory())
 | 
			
		||||
		newPath = path.join(newPath, 'index.ts');
 | 
			
		||||
	else if (!newPath.endsWith('.ts')) newPath = newPath + '.ts';
 | 
			
		||||
	console.log(`${file}: ${newPath}`);
 | 
			
		||||
	parseFile(ns, newPath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseFile(ns: CppNamespace, file: string) {
 | 
			
		||||
	const ast = parse(fs.readFileSync(file).toString(), {
 | 
			
		||||
		programs: [program]
 | 
			
		||||
	});
 | 
			
		||||
	ast.body
 | 
			
		||||
		.filter((body) => body.type == AST_NODE_TYPES.ExportNamedDeclaration)
 | 
			
		||||
		.forEach((body: a.ExportNamedDeclaration) => {
 | 
			
		||||
			if (!body.source) {
 | 
			
		||||
				const ret = handleNamedDeclaration(body);
 | 
			
		||||
				if (ret) ns.body.push(ret);
 | 
			
		||||
			} else handleExport(ns, file, body);
 | 
			
		||||
		});
 | 
			
		||||
	ast.body
 | 
			
		||||
		.filter((body) => body.type == AST_NODE_TYPES.ExportAllDeclaration)
 | 
			
		||||
		.forEach((body: a.ExportAllDeclaration) => {
 | 
			
		||||
			let usedNs = ns;
 | 
			
		||||
			if ('exported' in body && body.exported) {
 | 
			
		||||
				const newNs = new CppNamespace(body.exported.name);
 | 
			
		||||
				ns.body.push(newNs);
 | 
			
		||||
				usedNs = newNs;
 | 
			
		||||
			}
 | 
			
		||||
			handleExport(usedNs, file, body);
 | 
			
		||||
		});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const globalNamespace = new CppNamespace('dto');
 | 
			
		||||
parseFile(globalNamespace, 'dto/index.ts');
 | 
			
		||||
 | 
			
		||||
const output_stream = fs.createWriteStream('dto.h');
 | 
			
		||||
output_stream.write('#include <string>\n#include <optional>\n\n');
 | 
			
		||||
globalNamespace.write(new BetterWritable(output_stream));
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user