Improved apigen tool for cpp
This commit is contained in:
parent
2e165736ec
commit
520aeecb4e
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';
|
||||
|
||||
class Err extends Error {
|
||||
constructor(o: object) {
|
||||
super();
|
||||
console.error(o);
|
||||
}
|
||||
}
|
||||
|
||||
const program = createProgram('tsconfig.json');
|
||||
const ast = parse(fs.readFileSync('src/controller/filesystem.ts').toString(), {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function parseFunction(name: string, func: FunctionExpression) {
|
||||
const decs = func.params
|
||||
.map((p) => p.decorators)
|
||||
.map(parseDecorators)
|
||||
.filter((d) => d !== null);
|
||||
console.log(name, decs);
|
||||
}
|
||||
|
||||
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);
|
||||
.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));
|
||||
|
Loading…
Reference in New Issue
Block a user