567 lines
12 KiB
Dart
567 lines
12 KiB
Dart
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
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}
|
|
) {
|
|
final varAccess = varNameInsteadOfKeyName ?? "json['$keyName']";
|
|
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',
|
|
);
|
|
final innerType = findDartType(
|
|
tmp2,
|
|
scheme,
|
|
abstractPrefix: abstractPrefix,
|
|
functionPrefix: functionPrefix,
|
|
objectPrefix: objectPrefix,
|
|
);
|
|
return '$varAccess == null ? <$innerType>[] : ($varAccess as List<dynamic>).map((e) => ($innerHandler)).toList(growable: false)';
|
|
}
|
|
|
|
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 '$varAccess == null ? Uint8List(0) : 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 final constructors = {
|
|
""";
|
|
|
|
for (final o in scheme.objects) {
|
|
final normName = o.name.pascalCase;
|
|
result += '''
|
|
'${o.name}': (json) => o.$normName.fromJson(json),
|
|
''';
|
|
}
|
|
|
|
result += """
|
|
};
|
|
|
|
static TdBase? fromJson(Map<String, dynamic>? json) {
|
|
if (json == null) {
|
|
return null;
|
|
}
|
|
final type = json['@type'] as String;
|
|
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;
|
|
}
|
|
|