tdesktop/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py

1162 lines
46 KiB
Python

'''
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
'''
import glob, re, binascii, os, sys
input_files = []
output_path = ''
next_output_path = False
for arg in sys.argv[1:]:
if next_output_path:
next_output_path = False
output_path = arg
elif arg == '-o':
next_output_path = True
elif re.match(r'^-o(.+)', arg):
output_path = arg[2:]
else:
input_files.append(arg)
if len(input_files) == 0:
print('Input file required.')
sys.exit(1)
if output_path == '':
print('Output path required.')
sys.exit(1)
output_header = output_path + '/scheme.h'
output_source = output_path + '/scheme.cpp'
# define some checked flag conversions
# the key flag type should be a subset of the value flag type
# with exact the same names, then the key flag can be implicitly
# casted to the value flag type
parentFlags = {};
parentFlagsList = [];
def addChildParentFlags(child, parent):
parentFlagsList.append(child);
parentFlags[child] = parent;
addChildParentFlags('MTPDmessageService', 'MTPDmessage');
addChildParentFlags('MTPDupdateShortMessage', 'MTPDmessage');
addChildParentFlags('MTPDupdateShortChatMessage', 'MTPDmessage');
addChildParentFlags('MTPDupdateShortSentMessage', 'MTPDmessage');
addChildParentFlags('MTPDreplyKeyboardHide', 'MTPDreplyKeyboardMarkup');
addChildParentFlags('MTPDreplyKeyboardForceReply', 'MTPDreplyKeyboardMarkup');
addChildParentFlags('MTPDinputPeerNotifySettings', 'MTPDpeerNotifySettings');
addChildParentFlags('MTPDpeerNotifySettings', 'MTPDinputPeerNotifySettings');
addChildParentFlags('MTPDchannelForbidden', 'MTPDchannel');
addChildParentFlags('MTPDdialogFolder', 'MTPDdialog');
# this is a map (key flags -> map (flag name -> flag bit))
# each key flag of parentFlags should be a subset of the value flag here
parentFlagsCheck = {};
countedTypeIdExceptions = {};
countedTypeIdExceptions['channel#c88974ac'] = True
countedTypeIdExceptions['ipPortSecret#37982646'] = True
countedTypeIdExceptions['accessPointRule#4679b65f'] = True
countedTypeIdExceptions['help.configSimple#5a592a6c'] = True
renamedTypes = {};
renamedTypes['passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow'] = 'passwordKdfAlgoModPow';
lines = [];
layer = '';
layerIndex = 0;
funcs = 0
types = 0;
consts = 0
funcsNow = 0
enums = [];
funcsDict = {};
funcsList = [];
typesDict = {};
TypesDict = {};
typesList = [];
boxed = {};
funcsText = '';
typesText = '';
dataTexts = '';
creatorProxyText = '';
factories = '';
flagOperators = '';
methods = '';
inlineMethods = '';
visitorMethods = '';
textSerializeInit = '';
textSerializeMethods = '';
forwards = '';
forwTypedefs = '';
for input_file in input_files:
lines.append('---types---')
with open(input_file) as f:
for line in f:
layerline = re.match(r'// LAYER (\d+)', line)
if (layerline):
layerIndex = int(layerline.group(1));
layer = 'inline constexpr mtpPrime CurrentLayer = mtpPrime(' + str(layerIndex) + ');';
else:
lines.append(line);
for line in lines:
nocomment = re.match(r'^(.*?)//', line)
if (nocomment):
line = nocomment.group(1);
if (re.match(r'\-\-\-functions\-\-\-', line)):
funcsNow = 1;
continue;
if (re.match(r'\-\-\-types\-\-\-', line)):
funcsNow = 0;
continue;
if (re.match(r'^\s*$', line)):
continue;
nametype = re.match(r'([a-zA-Z\.0-9_]+)(#[0-9a-f]+)?([^=]*)=\s*([a-zA-Z\.<>0-9_]+);', line);
if (not nametype):
if (not re.match(r'vector#1cb5c415 \{t:Type\} # \[ t \] = Vector t;', line)):
print('Bad line found: ' + line);
sys.exit(1);
continue;
originalname = nametype.group(1);
name = originalname;
if (name in renamedTypes):
name = renamedTypes[name];
nameInd = name.find('.');
if (nameInd >= 0):
Name = name[0:nameInd] + '_' + name[nameInd + 1:nameInd + 2].upper() + name[nameInd + 2:];
name = name.replace('.', '_');
else:
Name = name[0:1].upper() + name[1:];
typeid = nametype.group(2);
if (typeid and len(typeid) > 0):
typeid = typeid[1:]; # Skip '#'
while (typeid and len(typeid) > 0 and typeid[0] == '0'):
typeid = typeid[1:];
cleanline = nametype.group(1) + nametype.group(3) + '= ' + nametype.group(4);
cleanline = re.sub(r' [a-zA-Z0-9_]+\:flags\.[0-9]+\?true', '', cleanline);
cleanline = cleanline.replace('<', ' ').replace('>', ' ').replace(' ', ' ');
cleanline = re.sub(r'^ ', '', cleanline);
cleanline = re.sub(r' $', '', cleanline);
cleanline = cleanline.replace(':bytes ', ':string ');
cleanline = cleanline.replace('?bytes ', '?string ');
cleanline = cleanline.replace('{', '');
cleanline = cleanline.replace('}', '');
countTypeId = binascii.crc32(binascii.a2b_qp(cleanline));
if (countTypeId < 0):
countTypeId += 2 ** 32;
countTypeId = re.sub(r'^0x|L$', '', hex(countTypeId));
if (typeid and len(typeid) > 0):
typeid = typeid;
if (typeid != countTypeId):
key = originalname + '#' + typeid;
if (not key in countedTypeIdExceptions):
print('Warning: counted ' + countTypeId + ' mismatch with provided ' + typeid + ' (' + key + ', ' + cleanline + ')');
continue;
else:
typeid = countTypeId;
typeid = '0x' + typeid;
params = nametype.group(3);
restype = nametype.group(4);
if (restype.find('<') >= 0):
templ = re.match(r'^([vV]ector<)([A-Za-z0-9\._]+)>$', restype);
if (templ):
vectemplate = templ.group(2);
if (re.match(r'^[A-Z]', vectemplate) or re.match(r'^[a-zA-Z0-9]+\.[A-Z]', vectemplate)):
restype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>';
elif (vectemplate == 'int' or vectemplate == 'long' or vectemplate == 'string' or vectemplate == 'bytes'):
restype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>';
else:
foundmeta = '';
for metatype in typesDict:
for typedata in typesDict[metatype]:
if (typedata[0] == vectemplate):
foundmeta = metatype;
break;
if (len(foundmeta) > 0):
break;
if (len(foundmeta) > 0):
ptype = templ.group(1) + 'MTP' + foundmeta.replace('.', '_') + '>';
else:
print('Bad vector param: ' + vectemplate);
sys.exit(1);
else:
print('Bad template type: ' + restype);
sys.exit(1);
resType = restype.replace('.', '_');
if (restype.find('.') >= 0):
parts = re.match(r'([a-z]+)\.([A-Z][A-Za-z0-9<>\._]+)', restype)
if (parts):
restype = parts.group(1) + '_' + parts.group(2)[0:1].lower() + parts.group(2)[1:];
else:
print('Bad result type name with dot: ' + restype);
sys.exit(1);
else:
if (re.match(r'^[A-Z]', restype)):
restype = restype[:1].lower() + restype[1:];
else:
print('Bad result type name: ' + restype);
sys.exit(1);
boxed[resType] = restype;
boxed[Name] = name;
enums.append('\tmtpc_' + name + ' = ' + typeid);
paramsList = params.strip().split(' ');
prms = {};
conditions = {};
trivialConditions = {}; # true type
prmsList = [];
conditionsList = [];
isTemplate = hasFlags = hasTemplate = '';
for param in paramsList:
if (re.match(r'^\s*$', param)):
continue;
templ = re.match(r'^{([A-Za-z]+):Type}$', param);
if (templ):
hasTemplate = templ.group(1);
continue;
pnametype = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*):([A-Za-z0-9<>\._]+|![a-zA-Z]+|\#|[a-z_][a-z0-9_]*\.[0-9]+\?[A-Za-z0-9<>\._]+)$', param);
if (not pnametype):
print('Bad param found: "' + param + '" in line: ' + line);
sys.exit(1);
pname = pnametype.group(1);
ptypewide = pnametype.group(2);
if (re.match(r'^!([a-zA-Z]+)$', ptypewide)):
if ('!' + hasTemplate == ptypewide):
isTemplate = pname;
ptype = 'TQueryType';
else:
print('Bad template param name: "' + param + '" in line: ' + line);
sys.exit(1);
elif (ptypewide == '#'):
hasFlags = pname;
if funcsNow:
ptype = 'flags<MTP' + name + '::Flags>';
else:
ptype = 'flags<MTPD' + name + '::Flags>';
else:
ptype = ptypewide;
if (ptype.find('?') >= 0):
pmasktype = re.match(r'([a-z_][a-z0-9_]*)\.([0-9]+)\?([A-Za-z0-9<>\._]+)', ptype);
if (not pmasktype or pmasktype.group(1) != hasFlags):
print('Bad param found: "' + param + '" in line: ' + line);
sys.exit(1);
ptype = pmasktype.group(3);
if (ptype.find('<') >= 0):
templ = re.match(r'^([vV]ector<)([A-Za-z0-9\._]+)>$', ptype);
if (templ):
vectemplate = templ.group(2);
if (re.match(r'^[A-Z]', vectemplate) or re.match(r'^[a-zA-Z0-9]+\.[A-Z]', vectemplate)):
ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>';
elif (vectemplate == 'int' or vectemplate == 'long' or vectemplate == 'string' or vectemplate == 'bytes'):
ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>';
else:
foundmeta = '';
for metatype in typesDict:
for typedata in typesDict[metatype]:
if (typedata[0] == vectemplate):
foundmeta = metatype;
break;
if (len(foundmeta) > 0):
break;
if (len(foundmeta) > 0):
ptype = templ.group(1) + 'MTP' + foundmeta.replace('.', '_') + '>';
else:
print('Bad vector param: ' + vectemplate);
sys.exit(1);
else:
print('Bad template type: ' + ptype);
sys.exit(1);
if (not pname in conditions):
conditionsList.append(pname);
conditions[pname] = pmasktype.group(2);
if (ptype == 'true'):
trivialConditions[pname] = 1;
elif (ptype.find('<') >= 0):
templ = re.match(r'^([vV]ector<)([A-Za-z0-9\._]+)>$', ptype);
if (templ):
vectemplate = templ.group(2);
if (re.match(r'^[A-Z]', vectemplate) or re.match(r'^[a-zA-Z0-9]+\.[A-Z]', vectemplate)):
ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>';
elif (vectemplate == 'int' or vectemplate == 'long' or vectemplate == 'string' or vectemplate == 'bytes'):
ptype = templ.group(1) + 'MTP' + vectemplate.replace('.', '_') + '>';
else:
foundmeta = '';
for metatype in typesDict:
for typedata in typesDict[metatype]:
if (typedata[0] == vectemplate):
foundmeta = metatype;
break;
if (len(foundmeta) > 0):
break;
if (len(foundmeta) > 0):
ptype = templ.group(1) + 'MTP' + foundmeta.replace('.', '_') + '>';
else:
print('Bad vector param: ' + vectemplate);
sys.exit(1);
else:
print('Bad template type: ' + ptype);
sys.exit(1);
prmsList.append(pname);
prms[pname] = ptype.replace('.', '_');
if (isTemplate == '' and resType == 'X'):
print('Bad response type "X" in "' + name +'" in line: ' + line);
sys.exit(1);
if funcsNow:
methodBodies = ''
if (isTemplate != ''):
funcsText += '\ntemplate <typename TQueryType>';
funcsText += '\nclass MTP' + name + ' { // RPC method \'' + nametype.group(1) + '\'\n'; # class
funcsText += 'public:\n';
prmsStr = [];
prmsInit = [];
prmsNames = [];
if (hasFlags != ''):
funcsText += '\tenum class Flag : uint32 {\n';
maxbit = 0;
parentFlagsCheck['MTP' + name] = {};
for paramName in conditionsList:
funcsText += '\t\tf_' + paramName + ' = (1U << ' + conditions[paramName] + '),\n';
parentFlagsCheck['MTP' + name][paramName] = conditions[paramName];
maxbit = max(maxbit, int(conditions[paramName]));
if (maxbit > 0):
funcsText += '\n';
funcsText += '\t\tMAX_FIELD = (1U << ' + str(maxbit) + '),\n';
funcsText += '\t};\n';
funcsText += '\tusing Flags = base::flags<Flag>;\n';
funcsText += '\tfriend inline constexpr bool is_flag_type(Flag) { return true; };\n';
funcsText += '\n';
if (len(prms) > len(trivialConditions)):
for paramName in prmsList:
if (paramName in trivialConditions):
continue;
paramType = prms[paramName];
prmsInit.append('_' + paramName + '(' + paramName + '_)');
prmsNames.append(paramName + '_');
if (paramName == isTemplate):
ptypeFull = paramType;
else:
ptypeFull = 'MTP' + paramType;
if (paramType in ['int', 'Int', 'bool', 'Bool', 'flags<Flags>']):
prmsStr.append(ptypeFull + ' ' + paramName + '_');
else:
prmsStr.append('const ' + ptypeFull + ' &' + paramName + '_');
funcsText += '\tMTP' + name + '();\n';# = default; # constructor
if (isTemplate != ''):
methodBodies += 'template <typename TQueryType>\n'
methodBodies += 'MTP' + name + '<TQueryType>::MTP' + name + '() = default;\n';
else:
methodBodies += 'MTP' + name + '::MTP' + name + '() = default;\n';
if (len(prms) > len(trivialConditions)):
funcsText += '\tMTP' + name + '(' + ', '.join(prmsStr) + ');\n';
if (isTemplate != ''):
methodBodies += 'template <typename TQueryType>\n'
methodBodies += 'MTP' + name + '<TQueryType>::MTP' + name + '(' + ', '.join(prmsStr) + ') : ' + ', '.join(prmsInit) + ' {\n}\n';
else:
methodBodies += 'MTP' + name + '::MTP' + name + '(' + ', '.join(prmsStr) + ') : ' + ', '.join(prmsInit) + ' {\n}\n';
funcsText += '\n';
funcsText += '\tuint32 innerLength() const;\n'; # count size
if (isTemplate != ''):
methodBodies += 'template <typename TQueryType>\n'
methodBodies += 'uint32 MTP' + name + '<TQueryType>::innerLength() const {\n';
else:
methodBodies += 'uint32 MTP' + name + '::innerLength() const {\n';
size = [];
for k in prmsList:
v = prms[k];
if (k in conditionsList):
if (not k in trivialConditions):
size.append('((_' + hasFlags + '.v & Flag::f_' + k + ') ? _' + k + '.innerLength() : 0)');
else:
size.append('_' + k + '.innerLength()');
if (not len(size)):
size.append('0');
methodBodies += '\treturn ' + ' + '.join(size) + ';\n';
methodBodies += '}\n';
funcsText += '\tmtpTypeId type() const {\n\t\treturn mtpc_' + name + ';\n\t}\n'; # type id
funcsText += '\t[[nodiscard]] bool read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_' + name + ');\n'; # read method
if (isTemplate != ''):
methodBodies += 'template <typename TQueryType>\n'
methodBodies += 'bool MTP' + name + '<TQueryType>::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {\n';
else:
methodBodies += 'bool MTP' + name + '::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {\n';
readFunc = ''
for k in prmsList:
v = prms[k];
if (k in conditionsList):
if (not k in trivialConditions):
readFunc += '\t\t&& ((_' + hasFlags + '.v & Flag::f_' + k + ') ? _' + k + '.read(from, end) : ((_' + k + ' = MTP' + v + '()), true))\n';
else:
readFunc += '\t\t&& _' + k + '.read(from, end)\n';
if readFunc != '':
methodBodies += '\treturn' + readFunc[4:len(readFunc)-1] + ';\n';
else:
methodBodies += '\treturn true;\n';
methodBodies += '}\n';
funcsText += '\tvoid write(mtpBuffer &to) const;\n'; # write method
if (isTemplate != ''):
methodBodies += 'template <typename TQueryType>\n'
methodBodies += 'void MTP' + name + '<TQueryType>::write(mtpBuffer &to) const {\n';
else:
methodBodies += 'void MTP' + name + '::write(mtpBuffer &to) const {\n';
for k in prmsList:
v = prms[k];
if (k in conditionsList):
if (not k in trivialConditions):
methodBodies += '\tif (_' + hasFlags + '.v & Flag::f_' + k + ') _' + k + '.write(to);\n';
else:
methodBodies += '\t_' + k + '.write(to);\n';
methodBodies += '}\n';
if (isTemplate != ''):
funcsText += '\n\tusing ResponseType = typename TQueryType::ResponseType;\n\n';
inlineMethods += methodBodies;
else:
funcsText += '\n\tusing ResponseType = MTP' + resType + ';\n\n'; # method return type
methods += methodBodies;
if (len(prms) > len(trivialConditions)):
funcsText += 'private:\n';
for paramName in prmsList:
if (paramName in trivialConditions):
continue;
paramType = prms[paramName];
if (paramName == isTemplate):
ptypeFull = paramType;
else:
ptypeFull = 'MTP' + paramType;
funcsText += '\t' + ptypeFull + ' _' + paramName + ';\n';
funcsText += '\n';
funcsText += '};\n'; # class ending
if (isTemplate != ''):
funcsText += 'template <typename TQueryType>\n';
funcsText += 'using MTP' + Name + ' = MTPBoxed<MTP' + name + '<TQueryType>>;\n';
else:
funcsText += 'using MTP' + Name + ' = MTPBoxed<MTP' + name + '>;\n';
funcs = funcs + 1;
if (not restype in funcsDict):
funcsList.append(restype);
funcsDict[restype] = [];
# TypesDict[restype] = resType;
funcsDict[restype].append([name, typeid, prmsList, prms, hasFlags, conditionsList, conditions, trivialConditions, isTemplate]);
else:
if (isTemplate != ''):
print('Template types not allowed: "' + resType + '" in line: ' + line);
continue;
if (not restype in typesDict):
typesList.append(restype);
typesDict[restype] = [];
TypesDict[restype] = resType;
typesDict[restype].append([name, typeid, prmsList, prms, hasFlags, conditionsList, conditions, trivialConditions, isTemplate]);
consts = consts + 1;
# text serialization: types and funcs
def addTextSerialize(lst, dct, dataLetter):
result = '';
for restype in lst:
v = dct[restype];
for data in v:
name = data[0];
prmsList = data[2];
prms = data[3];
hasFlags = data[4];
conditionsList = data[5];
conditions = data[6];
trivialConditions = data[7];
isTemplate = data[8];
templateArgument = ''
if (isTemplate != ''):
templateArgument = '<MTP::SecureRequest>'
result += 'bool Serialize_' + name + '(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n';
if (len(conditions)):
result += '\tauto flag = MTP' + dataLetter + name + templateArgument + '::Flags::from_raw(iflag);\n\n';
if (len(prms)):
result += '\tif (stage) {\n';
result += '\t\tto.add(",\\n").addSpaces(lev);\n';
result += '\t} else {\n';
result += '\t\tto.add("{ ' + name + '");\n';
result += '\t\tto.add("\\n").addSpaces(lev);\n';
result += '\t}\n';
result += '\tswitch (stage) {\n';
stage = 0;
for k in prmsList:
v = prms[k];
result += '\tcase ' + str(stage) + ': to.add(" ' + k + ': "); ++stages.back(); ';
if (k == hasFlags):
result += 'if (start >= end) return false; else flags.back() = *start; ';
if (k in trivialConditions):
result += 'if (flag & MTP' + dataLetter + name + templateArgument + '::Flag::f_' + k + ') { ';
result += 'to.add("YES [ BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); ';
result += '} else { to.add("[ SKIPPED BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); } ';
else:
if (k in conditions):
result += 'if (flag & MTP' + dataLetter + name + templateArgument + '::Flag::f_' + k + ') { ';
result += 'types.push_back(';
vtypeget = re.match(r'^[Vv]ector<MTP([A-Za-z0-9\._]+)>', v);
if (vtypeget):
if (not re.match(r'^[A-Z]', v)):
result += 'mtpc_vector';
else:
result += '0';
restype = vtypeget.group(1);
try:
if boxed[restype]:
restype = 0;
except KeyError:
if re.match(r'^[A-Z]', restype):
restype = 0;
else:
restype = v;
try:
if boxed[restype]:
restype = 0;
except KeyError:
if re.match(r'^[A-Z]', restype):
restype = 0;
if (restype):
try:
conses = typesDict[restype];
if (len(conses) > 1):
print('Complex bare type found: "' + restype + '" trying to serialize "' + k + '" of type "' + v + '"');
continue;
if (vtypeget):
result += '); vtypes.push_back(';
result += 'mtpc_' + conses[0][0];
if (not vtypeget):
result += '); vtypes.push_back(0';
except KeyError:
if (vtypeget):
result += '); vtypes.push_back(';
if (re.match(r'^flags<', restype)):
result += 'mtpc_flags';
else:
result += 'mtpc_' + restype + '+0';
if (not vtypeget):
result += '); vtypes.push_back(0';
else:
if (not vtypeget):
result += '0';
result += '); vtypes.push_back(0';
result += '); stages.push_back(0); flags.push_back(0); ';
if (k in conditions):
result += '} else { to.add("[ SKIPPED BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); } ';
result += 'break;\n';
stage = stage + 1;
result += '\tdefault: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;\n';
result += '\t}\n';
else:
result += '\tto.add("{ ' + name + ' }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();\n';
result += '\treturn true;\n';
result += '}\n\n';
return result;
# text serialization: types and funcs
def addTextSerializeInit(lst, dct):
result = '';
for restype in lst:
v = dct[restype];
for data in v:
name = data[0];
result += '\tresult.insert(mtpc_' + name + ', Serialize_' + name + ');\n';
return result;
textSerializeMethods += addTextSerialize(typesList, typesDict, 'D');
textSerializeInit += addTextSerializeInit(typesList, typesDict) + '\n';
textSerializeMethods += addTextSerialize(funcsList, funcsDict, '');
textSerializeInit += addTextSerializeInit(funcsList, funcsDict) + '\n';
for restype in typesList:
v = typesDict[restype];
resType = TypesDict[restype];
withData = 0;
creatorsDeclarations = '';
creatorsBodies = '';
flagDeclarations = '';
constructsText = '';
constructsBodies = '';
forwards += 'class MTP' + restype + ';\n';
forwTypedefs += 'using MTP' + resType + ' = MTPBoxed<MTP' + restype + '>;\n';
withType = (len(v) > 1);
switchLines = '';
friendDecl = '';
getters = '';
visitor = '';
reader = '';
writer = '';
sizeList = [];
sizeFast = '';
newFast = '';
sizeCases = '';
for data in v:
name = data[0];
typeid = data[1];
prmsList = data[2];
prms = data[3];
hasFlags = data[4];
conditionsList = data[5];
conditions = data[6];
trivialConditions = data[7];
dataText = '';
if (len(prms) > len(trivialConditions)):
withData = 1;
dataText += '\nclass MTPD' + name + ' : public MTP::internal::TypeData {\n'; # data class
else:
dataText += '\nclass MTPD' + name + ' {\n'; # empty data class for visitors
dataText += 'public:\n';
dataText += '\ttemplate <typename Other>\n';
dataText += '\tstatic constexpr bool Is() { return std::is_same_v<std::decay_t<Other>, MTPD' + name + '>; };\n\n';
sizeList = [];
creatorParams = [];
creatorParamsList = [];
readText = '';
writeText = '';
if (hasFlags != ''):
dataText += '\tenum class Flag : uint32 {\n';
maxbit = 0;
parentFlagsCheck['MTPD' + name] = {};
for paramName in conditionsList:
dataText += '\t\tf_' + paramName + ' = (1U << ' + conditions[paramName] + '),\n';
parentFlagsCheck['MTPD' + name][paramName] = conditions[paramName];
maxbit = max(maxbit, int(conditions[paramName]));
if (maxbit > 0):
dataText += '\n';
dataText += '\t\tMAX_FIELD = (1U << ' + str(maxbit) + '),\n';
dataText += '\t};\n';
dataText += '\tusing Flags = base::flags<Flag>;\n';
dataText += '\tfriend inline constexpr bool is_flag_type(Flag) { return true; };\n';
dataText += '\n';
if (len(conditions)):
for paramName in conditionsList:
if (paramName in trivialConditions):
dataText += '\t[[nodiscard]] bool is_' + paramName + '() const;\n';
constructsBodies += 'bool MTPD' + name + '::is_' + paramName + '() const {\n';
constructsBodies += '\treturn _' + hasFlags + '.v & Flag::f_' + paramName + ';\n';
constructsBodies += '}\n';
dataText += '\n';
switchLines += '\tcase mtpc_' + name + ': '; # for by-type-id type constructor
getters += '\t[[nodiscard]] const MTPD' + name + ' &c_' + name + '() const;\n'; # const getter
visitor += '\tcase mtpc_' + name + ': return base::match_method(c_' + name + '(), std::forward<Method>(method), std::forward<Methods>(methods)...);\n';
forwards += 'class MTPD' + name + ';\n'; # data class forward declaration
if (len(prms) > len(trivialConditions)):
dataText += '\tMTPD' + name + '();\n'; # default constructor
switchLines += 'setData(new MTPD' + name + '()); ';
constructsBodies += 'MTPD' + name + '::MTPD' + name + '() = default;\n';
constructsBodies += 'const MTPD' + name + ' &MTP' + restype + '::c_' + name + '() const {\n';
if (withType):
constructsBodies += '\tExpects(_type == mtpc_' + name + ');\n\n';
constructsBodies += '\treturn queryData<MTPD' + name + '>();\n';
constructsBodies += '}\n';
constructsText += '\texplicit MTP' + restype + '(const MTPD' + name + ' *data);\n'; # by-data type constructor
constructsBodies += 'MTP' + restype + '::MTP' + restype + '(const MTPD' + name + ' *data) : TypeDataOwner(data)';
if (withType):
constructsBodies += ', _type(mtpc_' + name + ')';
constructsBodies += ' {\n}\n';
dataText += '\tMTPD' + name + '('; # params constructor
prmsStr = [];
prmsInit = [];
for paramName in prmsList:
if (paramName in trivialConditions):
continue;
paramType = prms[paramName];
if (paramType in ['int', 'Int', 'bool', 'Bool']):
prmsStr.append('MTP' + paramType + ' ' + paramName + '_');
creatorParams.append('MTP' + paramType + ' ' + paramName + '_');
else:
prmsStr.append('const MTP' + paramType + ' &' + paramName + '_');
creatorParams.append('const MTP' + paramType + ' &' + paramName + '_');
creatorParamsList.append(paramName + '_');
prmsInit.append('_' + paramName + '(' + paramName + '_)');
if (paramName in conditions):
readText += '\t\t&& (v' + paramName + '() ? _' + paramName + '.read(from, end) : ((_' + paramName + ' = MTP' + paramType + '()), true))\n';
writeText += '\t\tif (const auto v' + paramName + ' = v.v' + paramName + '()) v' + paramName + '->write(to);\n';
sizeList.append('(v.v' + paramName + '() ? v.v' + paramName + '()->innerLength() : 0)');
else:
readText += '\t\t&& _' + paramName + '.read(from, end)\n';
writeText += '\t\tv.v' + paramName + '().write(to);\n';
sizeList.append('v.v' + paramName + '().innerLength()');
dataText += ', '.join(prmsStr) + ');\n';
constructsBodies += 'MTPD' + name + '::MTPD' + name + '(' + ', '.join(prmsStr) + ') : ' + ', '.join(prmsInit) + ' {\n}\n';
dataText += '\n';
dataText += '\t[[nodiscard]] bool read(const mtpPrime *&from, const mtpPrime *end);\n';
dataText += '\n';
constructsBodies += 'bool MTPD' + name + '::read(const mtpPrime *&from, const mtpPrime *end) {\n';
if readText != '':
constructsBodies += '\treturn' + readText[4:len(readText)-1] + ';\n';
else:
constructsBodies += '\treturn true;\n';
constructsBodies += '}\n';
if len(prmsList) > 0:
for paramName in prmsList: # getters
if (paramName in trivialConditions):
continue;
paramType = prms[paramName];
if (paramName in conditions):
dataText += '\t[[nodiscard]] MTP::conditional<MTP' + paramType + '> v' + paramName + '() const;\n';
constructsBodies += 'MTP::conditional<MTP' + paramType + '> MTPD' + name + '::v' + paramName + '() const {\n';
constructsBodies += '\treturn (_' + hasFlags + '.v & Flag::f_' + paramName + ') ? &_' + paramName + ' : nullptr;\n';
constructsBodies += '}\n';
else:
dataText += '\t[[nodiscard]] const MTP' + paramType + ' &v' + paramName + '() const;\n';
constructsBodies += 'const MTP' + paramType + ' &MTPD' + name + '::v' + paramName + '() const {\n';
constructsBodies += '\treturn _' + paramName + ';\n';
constructsBodies += '}\n';
dataText += '\n';
dataText += 'private:\n';
for paramName in prmsList: # fields declaration
if (paramName in trivialConditions):
continue;
paramType = prms[paramName];
dataText += '\tMTP' + paramType + ' _' + paramName + ';\n';
dataText += '\n';
sizeCases += '\tcase mtpc_' + name + ': {\n';
sizeCases += '\t\tconst MTPD' + name + ' &v(c_' + name + '());\n';
sizeCases += '\t\treturn ' + ' + '.join(sizeList) + ';\n';
sizeCases += '\t}\n';
sizeFast = '\tconst MTPD' + name + ' &v(c_' + name + '());\n\treturn ' + ' + '.join(sizeList) + ';\n';
newFast = 'new MTPD' + name + '()';
else:
constructsBodies += 'const MTPD' + name + ' &MTP' + restype + '::c_' + name + '() const {\n';
if (withType):
constructsBodies += '\tExpects(_type == mtpc_' + name + ');\n\n';
constructsBodies += '\tstatic const MTPD' + name + ' result;\n';
constructsBodies += '\treturn result;\n';
constructsBodies += '}\n';
sizeFast = '\treturn 0;\n';
switchLines += 'break;\n';
dataText += '};\n'; # class ending
dataTexts += dataText; # add data class
if (not friendDecl):
friendDecl += '\tfriend class MTP::internal::TypeCreator;\n';
creatorProxyText += '\tinline static MTP' + restype + ' new_' + name + '(' + ', '.join(creatorParams) + ') {\n';
if (len(prms) > len(trivialConditions)): # creator with params
creatorProxyText += '\t\treturn MTP' + restype + '(new MTPD' + name + '(' + ', '.join(creatorParamsList) + '));\n';
else:
if (withType): # creator by type
creatorProxyText += '\t\treturn MTP' + restype + '(mtpc_' + name + ');\n';
else: # single creator
creatorProxyText += '\t\treturn MTP' + restype + '();\n';
creatorProxyText += '\t}\n';
creatorsDeclarations += 'MTP' + restype + ' MTP_' + name + '(' + ', '.join(creatorParams) + ');\n';
creatorsBodies += 'MTP' + restype + ' MTP_' + name + '(' + ', '.join(creatorParams) + ') {\n';
creatorsBodies += '\treturn MTP::internal::TypeCreator::new_' + name + '(' + ', '.join(creatorParamsList) + ');\n';
creatorsBodies += '}\n';
if (withType):
reader += '\tcase mtpc_' + name + ': _type = cons; '; # read switch line
if (len(prms) > len(trivialConditions)):
reader += '{\n';
reader += '\t\tif (const auto data = new MTPD' + name + '(); data->read(from, end)) {\n';
reader += '\t\t\tsetData(data);\n';
reader += '\t\t} else {\n';
reader += '\t\t\tdelete data;\n';
reader += '\t\t\treturn false;\n';
reader += '\t\t}\n';
reader += '\t} break;\n';
writer += '\tcase mtpc_' + name + ': {\n'; # write switch line
writer += '\t\tconst MTPD' + name + ' &v = c_' + name + '();\n';
writer += writeText;
writer += '\t} break;\n';
else:
reader += 'break;\n';
else:
if (len(prms) > len(trivialConditions)):
reader += '\tif (const auto data = new MTPD' + name + '(); data->read(from, end)) {\n';
reader += '\t\tsetData(data);\n';
reader += '\t} else {\n';
reader += '\t\tdelete data;\n';
reader += '\t\treturn false;\n';
reader += '\t}\n';
writer += '\tconst MTPD' + name + ' &v = c_' + name + '();\n';
writer += writeText;
forwards += '\n';
typesText += '\nclass MTP' + restype; # type class declaration
if (withData):
typesText += ' : private MTP::internal::TypeDataOwner'; # if has data fields
typesText += ' {\n';
typesText += 'public:\n';
typesText += '\tMTP' + restype + '();\n'; # default constructor
if (withData and not withType):
methods += '\nMTP' + restype + '::MTP' + restype + '() : TypeDataOwner(' + newFast + ') {\n}\n';
else:
methods += '\nMTP' + restype + '::MTP' + restype + '() = default;\n';
typesText += getters;
typesText += '\n';
typesText += '\ttemplate <typename Method, typename ...Methods>\n';
typesText += '\tdecltype(auto) match(Method &&method, Methods &&...methods) const;\n';
visitorMethods += 'template <typename Method, typename ...Methods>\n';
visitorMethods += 'decltype(auto) MTP' + restype + '::match(Method &&method, Methods &&...methods) const {\n';
if (withType):
visitorMethods += '\tswitch (_type) {\n';
visitorMethods += visitor;
visitorMethods += '\t}\n';
visitorMethods += '\tUnexpected("Type in MTP' + restype + '::match.");\n';
else:
visitorMethods += '\treturn base::match_method(c_' + v[0][0] + '(), std::forward<Method>(method), std::forward<Methods>(methods)...);\n';
visitorMethods += '}\n\n';
typesText += '\n\tuint32 innerLength() const;\n'; # size method
methods += '\nuint32 MTP' + restype + '::innerLength() const {\n';
if (withType and sizeCases):
methods += '\tswitch (_type) {\n';
methods += sizeCases;
methods += '\t}\n';
methods += '\treturn 0;\n';
else:
methods += sizeFast;
methods += '}\n';
typesText += '\tmtpTypeId type() const;\n'; # type id method
methods += 'mtpTypeId MTP' + restype + '::type() const {\n';
if (withType):
methods += '\tExpects(_type != 0);\n\n';
methods += '\treturn _type;\n';
else:
methods += '\treturn mtpc_' + v[0][0] + ';\n';
methods += '}\n';
typesText += '\t[[nodiscard]] bool read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons'; # read method
if (not withType):
typesText += ' = mtpc_' + name;
typesText += ');\n';
methods += 'bool MTP' + restype + '::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {\n';
if (withData):
if not (withType):
methods += '\tif (cons != mtpc_' + v[0][0] + ') return false;\n';
if (withType):
methods += '\tswitch (cons) {\n'
methods += reader;
methods += '\tdefault: return false;\n';
methods += '\t}\n';
else:
methods += reader;
methods += '\treturn true;\n';
methods += '}\n';
typesText += '\tvoid write(mtpBuffer &to) const;\n'; # write method
methods += 'void MTP' + restype + '::write(mtpBuffer &to) const {\n';
if (withType and writer != ''):
methods += '\tswitch (_type) {\n';
methods += writer;
methods += '\t}\n';
else:
methods += writer;
methods += '}\n';
typesText += '\n\tusing ResponseType = void;\n'; # no response types declared
typesText += '\nprivate:\n'; # private constructors
if (withType): # by-type-id constructor
typesText += '\texplicit MTP' + restype + '(mtpTypeId type);\n';
methods += 'MTP' + restype + '::MTP' + restype + '(mtpTypeId type) : ';
methods += '_type(type)';
methods += ' {\n';
methods += '\tswitch (type) {\n'; # type id check
methods += switchLines;
methods += '\tdefault: Unexpected("Type in MTP' + restype + '::MTP' + restype + '.");\n';
methods += '\t}\n';
methods += '}\n'; # by-type-id constructor end
if (withData):
typesText += constructsText;
methods += constructsBodies;
if (friendDecl):
typesText += '\n' + friendDecl;
if (withType):
typesText += '\n\tmtpTypeId _type = 0;\n'; # type field var
typesText += '};\n'; # type class ended
flagOperators += flagDeclarations;
factories += creatorsDeclarations;
methods += creatorsBodies;
typesText += 'using MTP' + resType + ' = MTPBoxed<MTP' + restype + '>;\n'; # boxed type definition
flagOperators += '\n'
for childName in parentFlagsList:
parentName = parentFlags[childName];
for flag in parentFlagsCheck[childName]:
#
# 'channelForbidden' has 'until_date' flag and 'channel' doesn't have it.
# But as long as flags don't collide this is not a problem.
#
# if (not flag in parentFlagsCheck[parentName]):
# print('Flag ' + flag + ' not found in ' + parentName + ' which should be a flags-parent of ' + childName);
# sys.exit(1);
#
if (flag in parentFlagsCheck[parentName]):
if (parentFlagsCheck[childName][flag] != parentFlagsCheck[parentName][flag]):
print('Flag ' + flag + ' has different value in ' + parentName + ' which should be a flags-parent of ' + childName);
sys.exit(1);
else:
parentFlagsCheck[parentName][flag] = parentFlagsCheck[childName][flag];
flagOperators += 'inline ' + parentName + '::Flags mtpCastFlags(' + childName + '::Flags flags) { return static_cast<' + parentName + '::Flag>(flags.value()); }\n';
flagOperators += 'inline ' + parentName + '::Flags mtpCastFlags(MTPflags<' + childName + '::Flags> flags) { return mtpCastFlags(flags.v); }\n';
# manual types added here
textSerializeMethods += '\
bool _serialize_rpc_result(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n\
if (stage) {\n\
to.add(",\\n").addSpaces(lev);\n\
} else {\n\
to.add("{ rpc_result");\n\
to.add("\\n").addSpaces(lev);\n\
}\n\
switch (stage) {\n\
case 0: to.add(" req_msg_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\
case 1: to.add(" result: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;\n\
}\n\
return true;\n\
}\n\
\n\
bool _serialize_msg_container(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n\
if (stage) {\n\
to.add(",\\n").addSpaces(lev);\n\
} else {\n\
to.add("{ msg_container");\n\
to.add("\\n").addSpaces(lev);\n\
}\n\
switch (stage) {\n\
case 0: to.add(" messages: "); ++stages.back(); types.push_back(mtpc_vector); vtypes.push_back(mtpc_core_message); stages.push_back(0); flags.push_back(0); break;\n\
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;\n\
}\n\
return true;\n\
}\n\
\n\
bool _serialize_core_message(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n\
if (stage) {\n\
to.add(",\\n").addSpaces(lev);\n\
} else {\n\
to.add("{ core_message");\n\
to.add("\\n").addSpaces(lev);\n\
}\n\
switch (stage) {\n\
case 0: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\
case 1: to.add(" seq_no: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\
case 2: to.add(" bytes: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\
case 3: to.add(" body: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;\n\
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;\n\
}\n\
return true;\n\
}\n\
\n';
textSerializeInit += '\
result.insert(mtpc_rpc_result, _serialize_rpc_result);\n\
result.insert(mtpc_msg_container, _serialize_msg_container);\n\
result.insert(mtpc_core_message, _serialize_core_message);\n';
# module itself
header = '\
/*\n\
WARNING! All changes made in this file will be lost!\n\
Created from \'' + os.path.basename(input_file) + '\' by \'codegen_scheme\'\n\
\n\
This file is part of Telegram Desktop,\n\
the official desktop application for the Telegram messaging service.\n\
\n\
For license and copyright information please follow this link:\n\
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL\n\
*/\n\
#pragma once\n\
\n\
#include "mtproto/core_types.h"\n\
#include "base/flags.h"\n\
\n\
// Creator current layer and proxy class declaration\n\
namespace MTP {\n\
namespace internal {\n\
\n\
' + layer + '\n\
\n\
class TypeCreator;\n\
\n\
} // namespace internal\n\
} // namespace MTP\n\
\n\
// Type id constants\n\
enum {\n\
' + ',\n'.join(enums) + '\n\
};\n\
\n\
// Type forward declarations\n\
' + forwards + '\n\
// Boxed types definitions\n\
' + forwTypedefs + '\n\
// Type classes definitions\n\
' + typesText + '\n\
// Type constructors with data\n\
' + dataTexts + '\n\
// RPC methods\n\
' + funcsText + '\n\
// Template methods definition\n\
' + inlineMethods + '\n\
// Visitor definition\n\
' + visitorMethods + '\n\
// Flag operators definition\n\
' + flagOperators + '\n\
// Factory methods declaration\n\
' + factories + '\n\
// Human-readable text serialization\n\
[[nodiscard]] bool mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpPrime cons, uint32 level, mtpPrime vcons);\n'
source = '\
/*\n\
WARNING! All changes made in this file will be lost!\n\
Created from \'' + os.path.basename(input_file) + '\' by \'codegen_scheme\'\n\
\n\
This file is part of Telegram Desktop,\n\
the official desktop application for the Telegram messaging service.\n\
\n\
For license and copyright information please follow this link:\n\
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL\n\
*/\n\
#include "scheme.h"\n\
\n\
// Creator proxy class definition\n\
namespace MTP {\n\
namespace internal {\n\
\n\
class TypeCreator {\n\
public:\n\
' + creatorProxyText + '\n\
};\n\
\n\
} // namespace internal\n\
} // namespace MTP\n\
\n\
// Methods definition\n\
' + methods + '\n\
\n\
using Types = QVector<mtpTypeId>;\n\
using StagesFlags = QVector<int32>;\n\
\n\
' + textSerializeMethods + '\n\
namespace {\n\
\n\
using TextSerializer = bool (*)(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag);\n\
using TextSerializers = QMap<mtpTypeId, TextSerializer>;\n\
\n\
QMap<mtpTypeId, TextSerializer> createTextSerializers() {\n\
auto result = QMap<mtpTypeId, TextSerializer>();\n\
\n\
' + textSerializeInit + '\n\
return result;\n\
}\n\
\n\
} // namespace\n\
\n\
bool mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpPrime cons, uint32 level, mtpPrime vcons) {\n\
static auto serializers = createTextSerializers();\n\
\n\
QVector<mtpTypeId> types, vtypes;\n\
QVector<int32> stages, flags;\n\
types.reserve(20); vtypes.reserve(20); stages.reserve(20); flags.reserve(20);\n\
types.push_back(mtpTypeId(cons)); vtypes.push_back(mtpTypeId(vcons)); stages.push_back(0); flags.push_back(0);\n\
\n\
mtpTypeId type = cons, vtype = vcons;\n\
int32 stage = 0, flag = 0;\n\
\n\
while (!types.isEmpty()) {\n\
type = types.back();\n\
vtype = vtypes.back();\n\
stage = stages.back();\n\
flag = flags.back();\n\
if (!type) {\n\
if (from >= end) {\n\
to.error("insufficient data");\n\
return false;\n\
} else if (stage) {\n\
to.error("unknown type on stage > 0");\n\
return false;\n\
}\n\
types.back() = type = *from;\n\
++from;\n\
}\n\
\n\
int32 lev = level + types.size() - 1;\n\
auto it = serializers.constFind(type);\n\
if (it != serializers.cend()) {\n\
if (!(*it.value())(to, stage, lev, types, vtypes, stages, flags, from, end, flag)) {\n\
to.error();\n\
return false;\n\
}\n\
} else if (mtpTextSerializeCore(to, from, end, type, lev, vtype)) {\n\
types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();\n\
} else {\n\
to.error();\n\
return false;\n\
}\n\
}\n\
return true;\n\
}\n';
already_header = ''
if os.path.isfile(output_header):
with open(output_header, 'r') as already:
already_header = already.read()
if already_header != header:
with open(output_header, 'w') as out:
out.write(header)
already_source = ''
if os.path.isfile(output_source):
with open(output_source, 'r') as already:
already_source = already.read()
if already_source != source:
with open(output_source, 'w') as out:
out.write(source)