From a683a58db22899a7565becf3e5f05a064a25953d Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Sun, 12 Dec 2021 06:13:12 +0200 Subject: [PATCH] Initial commit --- .gitignore | 11 + .vscode/launch.json | 24 ++ CHANGELOG.md | 3 + README.md | 1 + analysis_options.yaml | 16 ++ bin/tdlib_gen.dart | 549 ++++++++++++++++++++++++++++++++++++++ bin/tdlib_gen.dart.backup | 425 +++++++++++++++++++++++++++++ bin/tl_scheme.dart | 266 ++++++++++++++++++ bin/tl_token.dart | 139 ++++++++++ pubspec.lock | 26 ++ pubspec.yaml | 14 + 11 files changed, 1474 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 bin/tdlib_gen.dart create mode 100644 bin/tdlib_gen.dart.backup create mode 100644 bin/tl_scheme.dart create mode 100644 bin/tl_token.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2df022 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build output. +build/ +# Dart default executable location +bin/tdlib_gen.exe + +# tl files +*.tl \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..31f15e3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "tdlib_gen", + "request": "launch", + "type": "dart", + "program": "bin/tdlib_gen.dart", + "args": [ + "${workspaceFolder}/td_api.tl", + "${workspaceFolder}/../tdlib_types/lib" + ], + "windows": { + "args": [ + "${workspaceFolder}/td_api.tl", + "${workspaceFolder}\\..\\tdlib_types\\lib" + ] + } + } + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a307539 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +A simple command-line application. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..18b40b8 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,16 @@ +# Defines a default set of lint rules enforced for projects at Google. For +# details and rationale, see +# https://github.com/dart-lang/pedantic#enabled-lints. + +include: package:pedantic/analysis_options.yaml + +# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. + +# Uncomment to specify additional rules. +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** diff --git a/bin/tdlib_gen.dart b/bin/tdlib_gen.dart new file mode 100644 index 0000000..630f098 --- /dev/null +++ b/bin/tdlib_gen.dart @@ -0,0 +1,549 @@ +import 'dart:io'; + +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} + ) { + 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).map((e) => ($innerHandler)).toList(growable: false)"; + } + + final varAccess = varNameInsteadOfKeyName ?? "json['$keyName']"; + final predefined = { + 'double': 'double', + 'string': 'String', + 'int32': 'int', + 'int53': 'int', + 'Bool': 'bool', + }; + if (predefined.containsKey(type)) { + return '$varAccess as ${predefined[type]}'; + } + else if (type == 'int64') { + return 'int.parse($varAccess)'; + } + 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 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; +} + diff --git a/bin/tdlib_gen.dart.backup b/bin/tdlib_gen.dart.backup new file mode 100644 index 0000000..8629921 --- /dev/null +++ b/bin/tdlib_gen.dart.backup @@ -0,0 +1,425 @@ +import 'dart:io'; + +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.'} +) { + 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) { + return abstractPrefix + result.name.pascalCase; + } + else if (result is TlSchemeObject) { + return objectPrefix + result.name.pascalCase; + } + else if (result is TlSchemeFunction) { + return functionPrefix + result.name.pascalCase; + } + 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.'} + ) { + 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 "(json['$keyName'] as List).map((e) => b.TdBase.fromJson(e) as $innerType).toList(growable: false)"; + } + + final predefined = { + 'double': 'double', + 'string': 'String', + 'int32': 'int', + 'int53': 'int', + 'Bool': 'bool', + }; + if (predefined.containsKey(type)) { + return "json['$keyName']"; + } + else if (type == 'int64') { + return "int.parse(json['$keyName'])"; + } + else if (type == 'bytes') { + return "base64.decode(json['$keyName'])"; + } + else { + return "b.TdBase.fromJson(json['$keyName']) as ${findDartType(type, scheme, abstractPrefix: abstractPrefix, functionPrefix: functionPrefix, objectPrefix: objectPrefix)}"; + } +} + +String makeBaseFile(TlSchema scheme) { + var result = r""" +import 'dart:convert'; +import 'dart:typed_data'; + +import 'abstract.dart' as a; +import 'obj.dart' as o; +import 'fn.dart' as f; + +abstract class TdBase { + Map toJson(); + + static TdBase fromJson(Map json) { + final type = json['@type'] as String; + if (false) {} +"""; + for (final o in scheme.objects) { + final normName = o.name.pascalCase; + result += ''' + else if (type == '${o.name}') { + return o.$normName.fromJson(json); + } +'''; + } + + result += ''' + else { + throw Exception('Unknown type: \$type'); + } + } +} +'''; + return result; +} + +String makeAbstractFile(TlSchema scheme) { + var result = r""" +import 'base.dart' as b; + +"""; + + for (final ac in scheme.abstractClasses) { + final normName = ac.name.pascalCase; + result += ''' +/// ${ac.doc} +abstract class $normName extends b.TdBase {} + +'''; + } + + 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: ''); + 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(); + +'''; + } + + // toJson + result += ''' + @override + Map toJson() => { + '@type': '${o.name}', +'''; + for (final param in o.parameters) { + final normParamName = param.name.camelCase; + result += ''' + '${param.name}': $normParamName, +'''; + } + 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 += ''' + }); + +'''; + } + + // 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; +} + diff --git a/bin/tl_scheme.dart b/bin/tl_scheme.dart new file mode 100644 index 0000000..1fac3a0 --- /dev/null +++ b/bin/tl_scheme.dart @@ -0,0 +1,266 @@ +import 'tl_token.dart'; + +class TlSchema { + final List abstractClasses; + final List objects; + final List functions; + + TlSchema({ + required this.abstractClasses, + required this.objects, + required this.functions, + }); + + TlSchemeItem? findType(String type) { + TlSchemeItem? find(List list) { + for (final item in list) { + if (item.name == type) { + return item; + } + } + } + + return find(abstractClasses) ?? find(objects) ?? find(functions); + } + + factory TlSchema.parse(String file) { + var abstractClasses = {}; + var objects = {}; + var functions = {}; + + var comments = []; + + void finishBuilder(_TlSchemeItemBuilder builder) { + comments.clear(); + if (builder is _TlSchemeAbstractClassBuilder) { + final ac = builder.build(); + abstractClasses[ac.name] = ac; + } + else if (builder is _TlSchemeObjectBuilder) { + final obj = builder.build(); + objects[obj.name] = obj; + if (!abstractClasses.containsKey(obj.baseType)) { + abstractClasses[obj.baseType] = TlSchemeAbstractClass( + name: obj.baseType, + doc: '' + ); + } + } + else if (builder is _TlSchemeFunctionBuilder) { + final fn = builder.build(); + functions[fn.name] = fn; + } + else { + throw Exception('Unknown builder: $builder'); + } + } + + var buildingFunctions = false; + _TlSchemeItemBuilder? builder; + + for (final token in TlToken.tokenize(file)) { + if (token is TlTokenNone) { + if (builder != null) { + finishBuilder(builder); + } + builder = null; + } + else if (token is TlTokenFunctionsDelimiter) { + buildingFunctions = true; + } + else if (token is TlTokenCommentTag) { + if (token.name == 'class' && comments.isEmpty && builder == null) { + builder = _TlSchemeAbstractClassBuilder( + name: token.value, + ); + } + else if (builder is _TlSchemeAbstractClassBuilder && token.name == 'description') { + builder.doc = token.value; + } + else { + comments.add(token); + } + } + else if (token is TlTokenClassTag) { + // Check for skippable + final skippable = [ + 'double', + 'string', + 'int32', + 'int53', + 'int64', + 'bytes', + 'boolFalse', + 'boolTrue', + 'vector', + ]; + if (skippable.contains(token.name)) { + comments.clear(); + builder = null; + continue; + } + + var typeDoc = ''; + var paramDoc = {}; + + for (final comment in comments) { + if (comment.name == 'description') { + typeDoc = comment.value; + } + else if (comment.name == 'param_description') { + paramDoc['description'] = comment.value; + } + else { + paramDoc[comment.name] = comment.value; + } + } + + if (buildingFunctions) { + builder = _TlSchemeFunctionBuilder( + returnType: token.baseType, + name: token.name, + doc: typeDoc, + parameters: token.parameters.map((t) => _TlSchemeParamBuilder( + name: t.name, + type: t.type, + doc: paramDoc[t.name]!, + )).toList(growable: false), + ); + } + else { + builder = _TlSchemeObjectBuilder( + baseType: token.baseType, + name: token.name, + doc: typeDoc, + parameters: token.parameters.map((t) => _TlSchemeParamBuilder( + name: t.name, + type: t.type, + doc: paramDoc[t.name]!, + )).toList(growable: false), + ); + } + finishBuilder(builder); + builder = null; + } + } + + return TlSchema( + abstractClasses: abstractClasses.values.toList(growable: false), + objects: objects.values.toList(growable: false), + functions: functions.values.toList(growable: false), + ); + } +} + +abstract class TlSchemeItem { + final String name; + final String doc; + TlSchemeItem({required this.name, required this.doc}); +} +abstract class _TlSchemeItemBuilder { + T build(); +} + +class TlSchemeAbstractClass extends TlSchemeItem { + TlSchemeAbstractClass({required String name, required String doc}) + : super(name: name, doc: doc); + + @override + String toString() { + return 'abstract $name'; + } +} +class _TlSchemeAbstractClassBuilder extends _TlSchemeItemBuilder { + String name; + String doc; + _TlSchemeAbstractClassBuilder({this.name = '', this.doc = ''}); + @override + TlSchemeAbstractClass build() => TlSchemeAbstractClass(name: name, doc: doc); +} + +class TlSchemeObject extends TlSchemeItem { + final String baseType; + final List parameters; + TlSchemeObject({ + required this.baseType, + required String name, + required String doc, + required this.parameters, + }) : super(name: name, doc: doc); + + @override + String toString() { + return 'class $name(${parameters.join(', ')}) : $baseType'; + } +} +class _TlSchemeObjectBuilder extends _TlSchemeItemBuilder { + String baseType; + String name; + String doc; + List<_TlSchemeParamBuilder> parameters; + _TlSchemeObjectBuilder({ + this.baseType = '', + this.name = '', + this.doc = '', + this.parameters = const <_TlSchemeParamBuilder>[] + }); + @override + TlSchemeObject build() => TlSchemeObject( + baseType: baseType, + name: name, + doc: doc, + parameters: parameters.map((b) => b.build()).toList(growable: false), + ); +} + +class TlSchemeFunction extends TlSchemeItem { + final String returnType; + final List parameters; + TlSchemeFunction({ + required this.returnType, + required String name, + required String doc, + required this.parameters, + }) : super(name: name, doc: doc); + @override + String toString() { + return 'fn $name(${parameters.join(', ')}) -> $returnType'; + } +} +class _TlSchemeFunctionBuilder extends _TlSchemeItemBuilder { + String returnType; + String name; + String doc; + List<_TlSchemeParamBuilder> parameters; + _TlSchemeFunctionBuilder({ + this.returnType = '', + this.name = '', + this.doc = '', + this.parameters = const <_TlSchemeParamBuilder>[] + }); + @override + TlSchemeFunction build() => TlSchemeFunction( + returnType: returnType, + name: name, + doc: doc, + parameters: parameters.map((b) => b.build()).toList(growable: false), + ); +} + +class TlSchemeParam { + final String name; + final String type; + final String doc; + TlSchemeParam({required this.name, required this.type, required this.doc}); + @override + String toString() { + return '$name: $type'; + } +} +class _TlSchemeParamBuilder { + String name; + String type; + String doc; + _TlSchemeParamBuilder({this.name = '', this.type = '', this.doc = ''}); + TlSchemeParam build() => TlSchemeParam(name: name, type: type, doc: doc); +} diff --git a/bin/tl_token.dart b/bin/tl_token.dart new file mode 100644 index 0000000..cec1df5 --- /dev/null +++ b/bin/tl_token.dart @@ -0,0 +1,139 @@ +abstract class TlToken { + static Iterable tokenize(String file) sync* { + var lastWasNone = false; + for (final untrimmedLine in file.split(RegExp(r'[\r\n]'))) { + final line = untrimmedLine.trim(); + if (line.isEmpty) { + if (!lastWasNone) { + yield TlTokenNone(); + lastWasNone = true; + } + continue; + } + else { + lastWasNone = false; + if (line.startsWith('//')) { + // Comment + if (!line.contains('@')) { + yield TlTokenCommentLine(line); + continue; + } + // Skip to first @tag + final interestLine = line.substring(line.indexOf('@')); + for (final unTrimmedTaggedChunk in interestLine.split('@')) { + final taggedChunk = unTrimmedTaggedChunk.trim(); + if (taggedChunk.isEmpty) { + continue; + } + final firstWs = taggedChunk.indexOf(RegExp(r'\s')); + final tagName = taggedChunk.substring(0, firstWs); + final tagValue = taggedChunk.substring(firstWs).trim(); + yield TlTokenCommentTag(name: tagName, value: tagValue); + } + } + else if (line == '---functions---') { + yield TlTokenFunctionsDelimiter(); + } + else { + // class + // Split by space + final chunksIt = line.split(RegExp(r'\s+')).iterator; + // First is name + chunksIt.moveNext(); + final name = chunksIt.current; + // Then there are parameters until = + final parameters = []; + do { + chunksIt.moveNext(); + if (chunksIt.current == '=') { + break; + } + // Params are defined as name:type + // Ignore oddities + if (!chunksIt.current.contains(':')) { + continue; + } + final splitted = chunksIt.current.split(':'); + parameters.add(TlTokenClassParam( + name: splitted[0], + type: splitted[1] + )); + } while (true); + // Finally, there is the base type (abstract class) + chunksIt.moveNext(); + String removeSemicolon(String input) { + while (input.codeUnits.last == ';'.codeUnits[0]) { + input = input.substring(0, input.length - 1); + } + return input; + } + final baseType = removeSemicolon(chunksIt.current); + + yield TlTokenClassTag( + name: name, + parameters: parameters, + baseType: baseType, + ); + } + } + } + } +} +class TlTokenFunctionsDelimiter extends TlToken { + @override + String toString() { + return 'TlToken: ---functions---'; + } +} +class TlTokenNone extends TlToken { + @override + String toString() { + return 'TlToken.'; + } +} +class TlTokenCommentLine extends TlToken { + /// The content of the comment including the leading slashes + final String content; + TlTokenCommentLine(this.content); + + @override + String toString() { + return 'TlToken: //$content'; + } +} +class TlTokenCommentTag extends TlToken { + /// The name of the tag, excluding the ampersand: description + final String name; + /// The value of the tag: Provides ... + final String value; + + TlTokenCommentTag({required this.name, required this.value}); + + @override + String toString() { + return 'TlToken: //@$name $value'; + } +} +class TlTokenClassTag extends TlToken { + /// The name of the class + final String name; + final List parameters; + final String baseType; + TlTokenClassTag({required this.name, required this.parameters, required this.baseType}); + + @override + String toString() { + final params = parameters.join(', '); + return 'TlToken: $name($params) = $baseType'; + } +} +class TlTokenClassParam { + final String name; + final String type; + TlTokenClassParam({required this.name, required this.type}); + + @override + String toString() { + return '$name:$type'; + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..01b0eda --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,26 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + path: + dependency: "direct main" + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + pedantic: + dependency: "direct dev" + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.0" + recase: + dependency: "direct main" + description: + name: recase + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" +sdks: + dart: ">=2.12.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..c12239a --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,14 @@ +name: tdlib_gen +description: A simple command-line application. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.12.0 <3.0.0' + +dependencies: + recase: ^4.0.0 + path: ^1.8.0 + +dev_dependencies: + pedantic: ^1.10.0