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

1096 lines
42 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_file = ''
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_file = arg
if input_file == '':
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');
# 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 = '';
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 = 'constexpr auto 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 auto is_flag_type(Flag) { return true; };\n';
funcsText += '\n';
if (len(conditions)):
for paramName in conditionsList:
if (paramName in trivialConditions):
funcsText += '\tbool is_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n';
else:
funcsText += '\tbool has_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n';
funcsText += '\n';
if (len(prms) > len(trivialConditions)):
for paramName in prmsList:
if (paramName in trivialConditions):
continue;
paramType = prms[paramName];
prmsInit.append('v' + paramName + '(_' + paramName + ')');
prmsNames.append('_' + paramName);
if (paramName == isTemplate):
ptypeFull = paramType;
else:
ptypeFull = 'MTP' + paramType;
funcsText += '\t' + ptypeFull + ' v' + paramName + ';\n';
if (paramType in ['int', 'Int', 'bool', 'Bool', 'flags<Flags>']):
prmsStr.append(ptypeFull + ' _' + paramName);
else:
prmsStr.append('const ' + ptypeFull + ' &_' + paramName);
funcsText += '\n';
funcsText += '\tMTP' + name + '() = default;\n'; # constructor
if (len(prms) > len(trivialConditions)):
funcsText += '\tMTP' + name + '(' + ', '.join(prmsStr) + ') : ' + ', '.join(prmsInit) + ' {\n\t}\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('(has_' + k + '() ? v' + k + '.innerLength() : 0)');
else:
size.append('v' + 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 += '\tvoid read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_' + name + ');\n'; # read method
if (isTemplate != ''):
methodBodies += 'template <typename TQueryType>\n'
methodBodies += 'void MTP' + name + '<TQueryType>::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {\n';
else:
methodBodies += 'void MTP' + name + '::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {\n';
for k in prmsList:
v = prms[k];
if (k in conditionsList):
if (not k in trivialConditions):
methodBodies += '\tif (has_' + k + '()) { v' + k + '.read(from, end); } else { v' + k + ' = MTP' + v + '(); }\n';
else:
methodBodies += '\tv' + k + '.read(from, end);\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 (has_' + k + '()) v' + k + '.write(to);\n';
else:
methodBodies += '\tv' + k + '.write(to);\n';
methodBodies += '}\n';
if (isTemplate != ''):
funcsText += '\n\tusing ResponseType = typename TQueryType::ResponseType;\n';
inlineMethods += methodBodies;
else:
funcsText += '\n\tusing ResponseType = MTP' + resType + ';\n'; # method return type
methods += methodBodies;
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 += 'void 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) throw Exception("start >= end in flags"); 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 += '}\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 auto is_flag_type(Flag) { return true; };\n';
dataText += '\n';
if (len(conditions)):
for paramName in conditionsList:
if (paramName in trivialConditions):
dataText += '\tbool is_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n';
else:
dataText += '\tbool has_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n';
dataText += '\n';
switchLines += '\tcase mtpc_' + name + ': '; # for by-type-id type constructor
getters += '\tconst 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 + '() = default;\n'; # default constructor
switchLines += 'setData(new MTPD' + name + '()); ';
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('v' + paramName + '(_' + paramName + ')');
if (withType):
readText += '\t';
writeText += '\t';
if (paramName in conditions):
readText += '\tif (v->has_' + paramName + '()) { v->v' + paramName + '.read(from, end); } else { v->v' + paramName + ' = MTP' + paramType + '(); }\n';
writeText += '\tif (v.has_' + paramName + '()) v.v' + paramName + '.write(to);\n';
sizeList.append('(v.has_' + paramName + '() ? v.v' + paramName + '.innerLength() : 0)');
else:
readText += '\tv->v' + paramName + '.read(from, end);\n';
writeText += '\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';
for paramName in prmsList: # fields declaration
if (paramName in trivialConditions):
continue;
paramType = prms[paramName];
dataText += '\tMTP' + paramType + ' v' + paramName + ';\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\tauto v = new MTPD' + name + '();\n';
reader += '\t\tsetData(v);\n';
reader += readText;
reader += '\t} break;\n';
writer += '\tcase mtpc_' + name + ': {\n'; # write switch line
writer += '\t\tauto &v = c_' + name + '();\n';
writer += writeText;
writer += '\t} break;\n';
else:
reader += 'break;\n';
else:
if (len(prms) > len(trivialConditions)):
reader += '\n\tauto v = new MTPD' + name + '();\n';
reader += '\tsetData(v);\n';
reader += readText;
writer += '\tconst auto &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 + '()'; # default constructor
if (withData and not withType):
typesText += ';\n';
methods += '\nMTP' + restype + '::MTP' + restype + '() : TypeDataOwner(' + newFast + ') {\n}\n';
else:
typesText += ' = 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 += '\tvoid read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons'; # read method
if (not withType):
typesText += ' = mtpc_' + name;
typesText += ');\n';
methods += 'void MTP' + restype + '::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {\n';
if (withData):
if not (withType):
methods += '\tif (cons != mtpc_' + v[0][0] + ') throw mtpErrorUnexpected(cons, "MTP' + restype + '");\n';
if (withType):
methods += '\tswitch (cons) {\n'
methods += reader;
methods += '\tdefault: throw mtpErrorUnexpected(cons, "MTP' + restype + '");\n';
methods += '\t}\n';
else:
methods += reader;
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: throw mtpErrorBadTypeId(type, "MTP' + restype + '");\n\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 += '\
void _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\
}\n\
\n\
void _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\
}\n\
\n\
void _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\
}\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\
void 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 = void (*)(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\
\n\
return result;\n\
}\n\
\n\
} // namespace\n\
\n\
void 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\
const mtpPrime *start = from;\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\
throw Exception("from >= end");\n\
} else if (stage) {\n\
throw Exception("unknown type on stage > 0");\n\
}\n\
types.back() = type = *from;\n\
start = ++from;\n\
}\n\
\n\
int32 lev = level + types.size() - 1;\n\
auto it = serializers.constFind(type);\n\
if (it != serializers.cend()) {\n\
(*it.value())(to, stage, lev, types, vtypes, stages, flags, start, end, flag);\n\
} else {\n\
mtpTextSerializeCore(to, from, end, type, lev, vtype);\n\
types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();\n\
}\n\
}\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)