Kenneth Bruen
3 years ago
commit
a683a58db2
11 changed files with 1474 additions and 0 deletions
@ -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 |
@ -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" |
||||
] |
||||
} |
||||
} |
||||
] |
||||
} |
@ -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/** |
@ -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; |
||||
} |
||||
|
@ -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; |
||||
} |
||||
|
@ -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); |
||||
} |
@ -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'; |
||||
} |
||||
} |
@ -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" |
Loading…
Reference in new issue