Rewrote backend in c++
This commit is contained in:
		
							
								
								
									
										306
									
								
								old_backend/tools/apigen.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								old_backend/tools/apigen.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,306 @@
 | 
			
		||||
import {
 | 
			
		||||
	AST_NODE_TYPES,
 | 
			
		||||
	createProgram,
 | 
			
		||||
	parse
 | 
			
		||||
} from '@typescript-eslint/typescript-estree';
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
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');
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
	}
 | 
			
		||||
	writeDecInc(data: string) {
 | 
			
		||||
		this.indent--;
 | 
			
		||||
		this.write(data);
 | 
			
		||||
		this.indent++;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Outputable {
 | 
			
		||||
	write(s: BetterWritable);
 | 
			
		||||
	writeJsonAs(ns: string, s: BetterWritable);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CppProp {
 | 
			
		||||
	constructor(entry: string) {
 | 
			
		||||
		this.entry = entry;
 | 
			
		||||
	}
 | 
			
		||||
	entry: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// template <> inline dto::Requests::Auth::LoginRequest Json::Value::as<dto::Requests::Auth::LoginRequest>() const { return asFloat(); }
 | 
			
		||||
 | 
			
		||||
class CppClass implements Outputable {
 | 
			
		||||
	constructor(name: string) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
	}
 | 
			
		||||
	type = 'class' as const;
 | 
			
		||||
	name: string;
 | 
			
		||||
	sub: string[] = [];
 | 
			
		||||
	entries: CppProp[] = [];
 | 
			
		||||
 | 
			
		||||
	write(s: BetterWritable) {
 | 
			
		||||
		let name = `struct ${this.name}`;
 | 
			
		||||
		if (this.sub.length > 0)
 | 
			
		||||
			name += ' : public ' + this.sub.join(', public ');
 | 
			
		||||
		s.writeInc(`${name} {`);
 | 
			
		||||
		if (this.sub.length == 0) {
 | 
			
		||||
			s.write(`${this.name}() = default;`);
 | 
			
		||||
			s.writeInc(`explicit ${this.name}(const Json::Value& j) {`);
 | 
			
		||||
		} else {
 | 
			
		||||
			s.writeInc(`${this.name}() :`);
 | 
			
		||||
			this.sub.forEach((sub) => {
 | 
			
		||||
				s.write(`${sub}()`);
 | 
			
		||||
			});
 | 
			
		||||
			s.writeDec('{}');
 | 
			
		||||
			s.writeInc(`explicit ${this.name}(const Json::Value& j) :`);
 | 
			
		||||
			this.sub.forEach((sub) => {
 | 
			
		||||
				s.write(`${sub}(j)`);
 | 
			
		||||
			});
 | 
			
		||||
			s.writeDecInc('{');
 | 
			
		||||
		}
 | 
			
		||||
		this.entries.forEach((e) => {
 | 
			
		||||
			const [type, name] = e.entry.split(' ');
 | 
			
		||||
			const opt_match = /std::optional<([a-z:<>]+)>/.exec(type);
 | 
			
		||||
			if (opt_match !== null)
 | 
			
		||||
				s.write(
 | 
			
		||||
					`this->${name} = json_get_optional<${opt_match[1]}>(j, "${name}");`
 | 
			
		||||
				);
 | 
			
		||||
			else s.write(`this->${name} = j["${name}"].as<${type}>();`);
 | 
			
		||||
		});
 | 
			
		||||
		s.writeDec('}');
 | 
			
		||||
		this.entries.forEach((e) => s.write(`${e.entry};`));
 | 
			
		||||
		s.writeDec('};');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	writeJsonAs(ns: string, s: BetterWritable) {
 | 
			
		||||
		s.write(
 | 
			
		||||
			`template <> inline ${ns}${this.name} Json::Value::as() const { return ${ns}${this.name}(*this); }`
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CppEnum implements Outputable {
 | 
			
		||||
	constructor(name: string) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
	}
 | 
			
		||||
	type = 'enum' as const;
 | 
			
		||||
	name: string;
 | 
			
		||||
	entries: CppProp[] = [];
 | 
			
		||||
 | 
			
		||||
	write(s: BetterWritable) {
 | 
			
		||||
		return;
 | 
			
		||||
		s.writeInc(`enum ${this.name} : int {`);
 | 
			
		||||
		this.entries.forEach((e, idx, arr) =>
 | 
			
		||||
			s.write(`${e.entry}${idx === arr.length - 1 ? '' : ','}`)
 | 
			
		||||
		);
 | 
			
		||||
		s.writeDec('};');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	writeJsonAs(ns: string, s: BetterWritable) {
 | 
			
		||||
		return;
 | 
			
		||||
		s.write(
 | 
			
		||||
			`template <> inline ${ns}${this.name} Json::Value::as() const { return (${ns}${this.name})(asInt()); }`
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CppNamespace implements Outputable {
 | 
			
		||||
	constructor(name: string) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
	}
 | 
			
		||||
	type = 'ns' as const;
 | 
			
		||||
	name: string;
 | 
			
		||||
	body: (CppNamespace | CppClass | CppEnum)[] = [];
 | 
			
		||||
 | 
			
		||||
	write(s: BetterWritable) {
 | 
			
		||||
		s.writeInc(`namespace ${this.name} {`);
 | 
			
		||||
		this.body.forEach((b) => b.write(s));
 | 
			
		||||
		s.writeDec('}');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	writeJsonAs(ns: string, s: BetterWritable) {
 | 
			
		||||
		ns += `${this.name}::`;
 | 
			
		||||
		this.body.forEach((b) => b.writeJsonAs(ns, s));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _getCppType(
 | 
			
		||||
	t: TypeNode
 | 
			
		||||
): null | 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 'int';
 | 
			
		||||
		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:
 | 
			
		||||
			return null;
 | 
			
		||||
		case AST_NODE_TYPES.TSUnionType:
 | 
			
		||||
			return _getCppType(t.types[0]);
 | 
			
		||||
		case AST_NODE_TYPES.TSArrayType:
 | 
			
		||||
			return `std::vector<${_getCppType(t.elementType)}>`;
 | 
			
		||||
		default:
 | 
			
		||||
			throw new Err(t);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getCppType(opt: boolean, name: string, t: TypeNode): null | string {
 | 
			
		||||
	let ret = _getCppType(t);
 | 
			
		||||
	if (typeof ret === 'function') return `${ret(opt, name)}`;
 | 
			
		||||
	if (typeof ret === 'string') {
 | 
			
		||||
		if (opt) ret = `std::optional<${ret}>`;
 | 
			
		||||
		return `${ret} ${name}`;
 | 
			
		||||
	} else return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleClassProperty(
 | 
			
		||||
	prop: a.PropertyDefinitionComputedName | a.PropertyDefinitionNonComputedName
 | 
			
		||||
): CppProp | null {
 | 
			
		||||
	if (prop.key.type != AST_NODE_TYPES.Identifier) throw new Err(prop);
 | 
			
		||||
	if (!prop.typeAnnotation) throw new Err(prop);
 | 
			
		||||
	const type = getCppType(
 | 
			
		||||
		prop.optional && prop.optional,
 | 
			
		||||
		prop.key.name,
 | 
			
		||||
		prop.typeAnnotation.typeAnnotation
 | 
			
		||||
	);
 | 
			
		||||
	return type === null ? null : new CppProp(type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
			const prop = handleClassProperty(inner);
 | 
			
		||||
			if (prop) cls.entries.push(prop);
 | 
			
		||||
		} else if (
 | 
			
		||||
			inner.type == AST_NODE_TYPES.MethodDefinition &&
 | 
			
		||||
			inner.key.type == AST_NODE_TYPES.Identifier &&
 | 
			
		||||
			inner.key.name === 'constructor'
 | 
			
		||||
		)
 | 
			
		||||
			return;
 | 
			
		||||
		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('../backend/src/dto.h');
 | 
			
		||||
output_stream.write(
 | 
			
		||||
	'#pragma once\n\n' +
 | 
			
		||||
		'#include <string>\n' +
 | 
			
		||||
		'#include <optional>\n' +
 | 
			
		||||
		'#include <vector>\n' +
 | 
			
		||||
		'#include <json/allocator.h>\n' +
 | 
			
		||||
		'#include "dto_extras.h"\n\n'
 | 
			
		||||
);
 | 
			
		||||
const output = new BetterWritable(output_stream);
 | 
			
		||||
globalNamespace.write(output);
 | 
			
		||||
globalNamespace.writeJsonAs('', output);
 | 
			
		||||
		Reference in New Issue
	
	Block a user