tdlib bindings generator for Dart
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

557 lines
12 KiB

import 'dart:io';
import 'tl_scheme.dart';
import 'package:recase/recase.dart';
import 'package:path/path.dart' as path;
Future<void> main(List<String> arguments) async {
if (arguments.length != 2) {
print('The program must be run with 2 arguments:');
print(' path to .tl schema');
print(' path to Dart project source folder');
exit(1);
}
final schemeFileStr = arguments[0];
final srcFolderStr = arguments[1];
if (! await File.fromUri(Uri.file(schemeFileStr)).exists()) {
print("Schema file $schemeFileStr doesn't exist");
exit(1);
}
if (! await Directory.fromUri(Uri.directory(srcFolderStr)).exists()) {
print("Dart project source folder $srcFolderStr doesn't exist");
exit(1);
}
print('.tl schema folder: $schemeFileStr');
print('Dart project source folder: $srcFolderStr');
print('');
print('Reading .tl schema file...');
final schemeFileContents = await File.fromUri(Uri.file(schemeFileStr)).readAsString();
print('Parsing .tl schema file...');
final scheme = TlSchema.parse(schemeFileContents);
print('Generating...');
final baseFile = File.fromUri(Uri.file(path.join(srcFolderStr, 'base.dart')));
final abstractFile = File.fromUri(Uri.file(path.join(srcFolderStr, 'abstract.dart')));
final objFile = File.fromUri(Uri.file(path.join(srcFolderStr, 'obj.dart')));
final fnFile = File.fromUri(Uri.file(path.join(srcFolderStr, 'fn.dart')));
await baseFile.writeAsString(makeBaseFile(scheme));
await abstractFile.writeAsString(makeAbstractFile(scheme));
await objFile.writeAsString(makeObjFile(scheme));
await fnFile.writeAsString(makeFnFile(scheme));
print('Done!');
}
String findDartType(
String type,
TlSchema scheme,
{String abstractPrefix = 'a.',
String objectPrefix = 'o.',
String functionPrefix = 'f.',
bool noNullCheck = false}
) {
if (type.startsWith('vector<')) {
final tmp1 = type.replaceFirst('vector<', '');
final tmp2 = tmp1.substring(0, tmp1.length - 1);
final innerType = findDartType(
tmp2,
scheme,
abstractPrefix: abstractPrefix,
functionPrefix: functionPrefix,
objectPrefix: objectPrefix,
);
return 'List<$innerType>';
}
final predefined = {
'double': 'double',
'string': 'String',
'int32': 'int',
'int53': 'int',
'int64': 'int',
'bytes': 'Uint8List',
'Bool': 'bool',
};
if (predefined.containsKey(type)) {
return predefined[type]!;
}
final result = scheme.findType(type);
if (result == null) {
throw Exception("Couldn't find type: $type");
}
if (result is TlSchemeAbstractClass) {
final name = abstractPrefix + result.name.pascalCase;
if (noNullCheck) {
return name;
}
else {
return '$name?';
}
}
else if (result is TlSchemeObject) {
final name = objectPrefix + result.name.pascalCase;
if (noNullCheck) {
return name;
}
else {
return '$name?';
}
}
else if (result is TlSchemeFunction) {
final name = functionPrefix + result.name.pascalCase;
if (noNullCheck) {
return name;
}
else {
return '$name?';
}
}
else {
throw Exception('Unknown tl object: $result');
}
}
String findToJsonHandling(
String type,
String varName,
TlSchema scheme,
) {
if (type.startsWith('vector<')) {
final tmp1 = type.replaceFirst('vector<', '');
final tmp2 = tmp1.substring(0, tmp1.length - 1);
late String newVarName;
if (varName.startsWith('_e')) {
final num = int.parse(varName.substring(2));
newVarName = '_e${num + 1}';
}
else {
newVarName = '_e1';
}
final innerHandling = findToJsonHandling(
tmp2,
newVarName,
scheme,
);
return '$varName.map(($newVarName) => $innerHandling).toList(growable: false)';
}
final predefined = {
'double': 'double',
'string': 'String',
'int32': 'int',
'int53': 'int',
'Bool': 'bool',
};
if (predefined.containsKey(type)) {
return varName;
}
else if (type == 'int64') {
return '$varName.toString()';
}
else if (type == 'bytes') {
return 'base64.encode($varName)';
}
else {
return '$varName?.toJson()';
}
}
String findFromJsonHandling(
String type,
String keyName,
TlSchema scheme,
{String abstractPrefix = 'a.',
String objectPrefix = 'o.',
String functionPrefix = 'f.',
String? varNameInsteadOfKeyName}
) {
if (type.startsWith('vector<')) {
final tmp1 = type.replaceFirst('vector<', '');
final tmp2 = tmp1.substring(0, tmp1.length - 1);
// final innerType = findDartType(tmp2, scheme, abstractPrefix: abstractPrefix, functionPrefix: functionPrefix, objectPrefix: objectPrefix);
final innerHandler = findFromJsonHandling(
tmp2,
'',
scheme,
abstractPrefix: abstractPrefix,
functionPrefix: functionPrefix,
objectPrefix: objectPrefix,
varNameInsteadOfKeyName: 'e',
);
return "(json['$keyName'] as List<dynamic>).map((e) => ($innerHandler)).toList(growable: false)";
}
final varAccess = varNameInsteadOfKeyName ?? "json['$keyName']";
final predefined = {
'double': 'double',
'string': 'String',
'int32': 'int',
'int53': 'int',
'Bool': 'bool',
};
final predefinedDefault = {
'double': '0',
'string': "''",
'int32': '0',
'int53': '0',
'Bool': 'false',
};
if (predefined.containsKey(type)) {
return '($varAccess as ${predefined[type]}?) ?? ${predefinedDefault[type]}';
}
else if (type == 'int64') {
return "int.parse($varAccess ?? '0')";
}
else if (type == 'bytes') {
return 'base64.decode($varAccess)';
}
else {
return 'b.TdBase.fromJson($varAccess) as ${findDartType(type, scheme, abstractPrefix: abstractPrefix, functionPrefix: functionPrefix, objectPrefix: objectPrefix)}';
}
}
String makeBaseFile(TlSchema scheme) {
var result = r"""
import 'obj.dart' as o;
abstract class TdBase {
Map<String, dynamic> toJson();
@override
String toString() {
return 'td::TdBase()';
}
static TdBase? fromJson(Map<String, dynamic>? json) {
if (json == null) {
return null;
}
final type = json['@type'] as String;
final constructors = {
""";
for (final o in scheme.objects) {
final normName = o.name.pascalCase;
result += '''
'${o.name}': (json) => o.$normName.fromJson(json),
''';
}
result += '''
};
return constructors[type]!(json);
}
}
''';
return result;
}
String makeAbstractFile(TlSchema scheme) {
var result = r"""
import 'dart:core' as dc show Error;
import 'base.dart' as b;
import 'obj.dart' as o;
typedef Func1<T, TResult> = TResult Function(T);
class MatchError extends dc.Error {}
""";
for (final ac in scheme.abstractClasses) {
final normName = ac.name.pascalCase;
final implementors = scheme.objects.where((element) => element.baseType == ac.name).toList(growable: false);
result += '''
/// ${ac.doc}
abstract class $normName extends b.TdBase {
TResult match<TResult>({
''';
for (final impl in implementors) {
final iNormName = impl.name.pascalCase;
result += '''
Func1<o.$iNormName, TResult>? is$iNormName,
''';
}
result += '''
Func1<$normName, TResult>? otherwise,
}) {
if (false) {} // ignore: dead_code
''';
for (final impl in implementors) {
final iNormName = impl.name.pascalCase;
result += '''
else if (this is o.$iNormName) {
if (is$iNormName != null) {
return is$iNormName(this as o.$iNormName);
}
else if (otherwise != null) {
return otherwise(this);
}
}
''';
}
result += '''
else if (otherwise != null) {
otherwise(this);
}
else if (TResult == null.runtimeType) {
return null as TResult;
}
throw MatchError();
}
}
''';
}
return result;
}
String makeObjFile(TlSchema scheme) {
var result = r"""
import 'dart:convert';
import 'dart:typed_data';
import 'base.dart' as b;
import 'abstract.dart' as a;
""";
for (final o in scheme.objects) {
final normName = o.name.pascalCase;
final baseName = findDartType(o.baseType, scheme, objectPrefix: '', noNullCheck: true);
result += '''
/// ${o.doc}
class $normName extends $baseName {
''';
for (final param in o.parameters) {
final normParamName = param.name.camelCase;
final paramType = findDartType(param.type, scheme, objectPrefix: '');
result += '''
/// ${param.doc}
final $paramType $normParamName;
''';
}
// Constructor
if (o.parameters.isNotEmpty) {
result += '''
$normName({
''';
for (final param in o.parameters) {
final normParamName = param.name.camelCase;
result += '''
required this.$normParamName,
''';
}
result += '''
});
''';
}
else {
result += '''
$normName();
''';
}
// toString
result += '''
@override
String toString() {
var s = 'td::$normName(';
// Params
final params = <String>[];
''';
for (final param in o.parameters) {
final normParamName = param.name.camelCase;
result += '''
params.add($normParamName.toString());
''';
}
result += '''
s += params.join(', ');
s += ')';
return s;
}
''';
// toJson
result += '''
@override
Map<String, dynamic> toJson() => {
'@type': '${o.name}',
''';
for (final param in o.parameters) {
final normParamName = param.name.camelCase;
final jsonHandling = findToJsonHandling(param.type, normParamName, scheme);
result += '''
'${param.name}': $jsonHandling,
''';
}
result += '''
};
''';
// fromJson
result += '''
factory $normName.fromJson(Map<String, dynamic> json) => $normName(
''';
for (final param in o.parameters) {
final normParamName = param.name.camelCase;
final handle = findFromJsonHandling(param.type, param.name, scheme, objectPrefix: '');
result += '''
$normParamName: $handle,
''';
}
result += '''
);
''';
result += '''
}
''';
}
return result;
}
String makeFnFile(TlSchema scheme) {
var result = r"""
import 'dart:convert';
import 'dart:typed_data';
import 'base.dart' as b;
import 'abstract.dart' as a;
import 'obj.dart' as o;
abstract class TdFunction extends b.TdBase {}
""";
for (final f in scheme.functions) {
final normName = f.name.pascalCase;
result += '''
/// ${f.doc}
class $normName extends TdFunction {
''';
// Parameters
for (final param in f.parameters) {
final pNormName = param.name.camelCase;
final pType = findDartType(param.type, scheme, functionPrefix: '');
result += '''
/// ${param.doc}
final $pType $pNormName;
''';
}
// Constructor
if (f.parameters.isEmpty) {
result += '''
$normName();
''';
}
else {
result += '''
$normName({
''';
for (final param in f.parameters) {
final pNormName = param.name.camelCase;
result += '''
required this.$pNormName,
''';
}
result += '''
});
''';
}
// toString
result += '''
@override
String toString() {
var s = 'td::$normName(';
// Params
final params = <String>[];
''';
for (final param in f.parameters) {
final normParamName = param.name.camelCase;
result += '''
params.add($normParamName.toString());
''';
}
result += '''
s += params.join(', ');
s += ')';
return s;
}
''';
// toJson
result += '''
@override
Map<String, dynamic> toJson() => {
'@type': '${f.name}',
''';
for (final param in f.parameters) {
final pNormName = param.name.camelCase;
final jsonHandling = findToJsonHandling(param.type, pNormName, scheme);
result += '''
'${param.name}': $jsonHandling,
''';
}
result += '''
};
''';
// fromJson
result += '''
factory $normName.fromJson(Map<String, dynamic> json) => $normName(
''';
for (final param in f.parameters) {
final normParamName = param.name.camelCase;
final handle = findFromJsonHandling(param.type, param.name, scheme);
result += '''
$normParamName: $handle,
''';
}
result += '''
);
''';
result += '''
}
''';
}
return result;
}