import 'dart:io'; import 'dart:typed_data'; import 'tl_scheme.dart'; import 'package:recase/recase.dart'; import 'package:path/path.dart' as path; Future main(List 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).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 toJson(); @override String toString() { return 'td::TdBase()'; } static TdBase? fromJson(Map? 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 = 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({ '''; for (final impl in implementors) { final iNormName = impl.name.pascalCase; result += ''' Func1? 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 = []; '''; 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 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 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 = []; '''; 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 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 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; }