Browse Source

Initial commit

master
Kenneth Bruen 3 years ago
commit
a683a58db2
Signed by: kbruen
GPG Key ID: C1980A470C3EE5B1
  1. 11
      .gitignore
  2. 24
      .vscode/launch.json
  3. 3
      CHANGELOG.md
  4. 1
      README.md
  5. 16
      analysis_options.yaml
  6. 549
      bin/tdlib_gen.dart
  7. 425
      bin/tdlib_gen.dart.backup
  8. 266
      bin/tl_scheme.dart
  9. 139
      bin/tl_token.dart
  10. 26
      pubspec.lock
  11. 14
      pubspec.yaml

11
.gitignore vendored

@ -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

24
.vscode/launch.json vendored

@ -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"
]
}
}
]
}

3
CHANGELOG.md

@ -0,0 +1,3 @@
## 1.0.0
- Initial version.

1
README.md

@ -0,0 +1 @@
A simple command-line application.

16
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/**

549
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<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',
};
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<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;
}

425
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<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.'}
) {
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<dynamic>).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<String, dynamic> toJson();
static TdBase fromJson(Map<String, dynamic> 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<String, dynamic> 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<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 += '''
});
''';
}
// 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;
}

266
bin/tl_scheme.dart

@ -0,0 +1,266 @@
import 'tl_token.dart';
class TlSchema {
final List<TlSchemeAbstractClass> abstractClasses;
final List<TlSchemeObject> objects;
final List<TlSchemeFunction> functions;
TlSchema({
required this.abstractClasses,
required this.objects,
required this.functions,
});
TlSchemeItem? findType(String type) {
TlSchemeItem? find(List<TlSchemeItem> 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 = <String, TlSchemeAbstractClass>{};
var objects = <String, TlSchemeObject>{};
var functions = <String, TlSchemeFunction>{};
var comments = <TlTokenCommentTag>[];
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 = <String, String>{};
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 extends TlSchemeItem> {
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<TlSchemeAbstractClass> {
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<TlSchemeParam> 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<TlSchemeObject> {
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<TlSchemeParam> 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<TlSchemeFunction> {
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);
}

139
bin/tl_token.dart

@ -0,0 +1,139 @@
abstract class TlToken {
static Iterable<TlToken> 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 = <TlTokenClassParam>[];
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<TlTokenClassParam> 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';
}
}

26
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"

14
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
Loading…
Cancel
Save