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
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; |
|
} |
|
|
|
|