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