mirror of
https://github.com/danog/tl-schema.git
synced 2024-12-12 09:29:50 +01:00
223 lines
6.4 KiB
Python
Executable File
223 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# From https://gist.github.com/stek29/067ce1d4d764147e21cc0667ba5c063d
|
|
# for json
|
|
import json
|
|
from collections import OrderedDict
|
|
|
|
# for tl
|
|
import re
|
|
import struct
|
|
import binascii
|
|
|
|
__all__ = [
|
|
'TLToken',
|
|
'TLTokenJSON',
|
|
'TLTokenTL',
|
|
'TLTokenFileTL',
|
|
'TLTokenFileJSON'
|
|
]
|
|
|
|
class TLToken:
|
|
def __init__(self, name, crc, params, result, is_method=None):
|
|
# name
|
|
self.name = str(name)
|
|
# aka id, should be already "converted" to int8 (struct.unpack'd for example)
|
|
self.crc = int(crc)
|
|
# [{'name': 'foo', 'type': 'long'}, {'name': 'bar', 'type': 'flags.1?int'}, ...]
|
|
self.params = list(params)
|
|
# type
|
|
self.result = str(result)
|
|
if is_method is None:
|
|
is_method = '.' in name
|
|
self.is_method = is_method
|
|
|
|
def __str__(self):
|
|
return 'TLToken: ' + self.name
|
|
|
|
class TLTokenJSON(TLToken):
|
|
def __init__(self, json_obj):
|
|
if not isinstance(json_obj, dict):
|
|
json_obj = json.loads(json_obj)
|
|
|
|
is_method = 'method' in json_obj
|
|
super().__init__(
|
|
name=json_obj['method' if is_method else 'predicate'],
|
|
crc=json_obj['id'],
|
|
params=json_obj['params'],
|
|
result=json_obj['type'],
|
|
is_method=is_method
|
|
)
|
|
|
|
@staticmethod
|
|
def dump(inst, as_string=False):
|
|
if not isinstance(inst.crc, int):
|
|
raise Exception('lol kek cheburek')
|
|
dic = OrderedDict((
|
|
('id', str(inst.crc)),
|
|
('method' if inst.is_method else 'predicate', inst.name),
|
|
('params', inst.params),
|
|
('type', inst.result)
|
|
))
|
|
return dic if not as_string else json.dumps(dic)
|
|
|
|
def __repr__(self):
|
|
return self.dump(self, True)
|
|
|
|
|
|
VECTOR = 'vector#1cb5c415'
|
|
TL_line_regex = re.compile(r"([a-zA-Z\.0-9_]+)#([0-9a-f]+)([^=]*)=\s*([a-zA-Z\.<>0-9_]+);")
|
|
TL_param_regex = re.compile(r"\s([^:]+):(\S+)")
|
|
|
|
class TLTokenTL(TLToken):
|
|
def __init__(self, tl_line, is_method=None):
|
|
line = TL_line_regex.match(tl_line)
|
|
|
|
crc = struct.unpack('>i',
|
|
binascii.a2b_hex(
|
|
'{:>08}'.format(line.group(2))
|
|
)
|
|
)[0]
|
|
|
|
params = [
|
|
{'name': name, 'type': type}
|
|
for name, type in
|
|
TL_param_regex.findall(line.group(3))
|
|
if type != 'Type}'
|
|
]
|
|
super().__init__(
|
|
name=line.group(1),
|
|
crc=crc,
|
|
params=params,
|
|
result=line.group(4),
|
|
is_method=is_method
|
|
)
|
|
|
|
@staticmethod
|
|
def dump(inst):
|
|
return '{name}#{crc} {params} = {result};'.format(
|
|
name=inst.name,
|
|
crc=TLTokenTL.crc2hex(inst.crc),
|
|
params=' '.join('{name}:{type}'.format(**param) for param in inst.params),
|
|
result=inst.result
|
|
).replace(' ', ' ')
|
|
|
|
@staticmethod
|
|
def crc2hex(crc: int):
|
|
return str(binascii.b2a_hex(struct.pack('>i', crc)), encoding='ascii').lstrip('0')
|
|
|
|
def __repr__(self):
|
|
return self.dump(self)
|
|
|
|
|
|
|
|
class TLTokenFileTL:
|
|
@staticmethod
|
|
def read(file, no_verify=True):
|
|
def repack(line):
|
|
return re.sub(r'#0+', '#', ' '.join(re.split(r' *', line)))
|
|
def verify(line, token, is_method):
|
|
tl_line = repr(token)
|
|
return repack(line) == repack(tl_line)
|
|
|
|
tokens = list()
|
|
|
|
methods_now = False
|
|
for l in file:
|
|
l = l.strip()
|
|
|
|
if not l or l.startswith('//'):
|
|
# comment
|
|
continue
|
|
elif l == '---functions---':
|
|
methods_now = True
|
|
continue
|
|
elif l == '---types---':
|
|
methods_now = False
|
|
continue
|
|
elif l.startswith(VECTOR):
|
|
# vector's line isn't supported, and has to be skipped
|
|
continue
|
|
else:
|
|
try:
|
|
token = TLTokenTL(l, methods_now)
|
|
if no_verify or verify(l, token, methods_now):
|
|
tokens.append(token)
|
|
else:
|
|
print('MISMATCH:')
|
|
print(l)
|
|
print(repack(repr(token)), end='\n\n')
|
|
except Exception as e:
|
|
print('ERROR:')
|
|
print(e)
|
|
print(l, end='\n\n')
|
|
|
|
return tokens
|
|
|
|
@staticmethod
|
|
def write(file, lst):
|
|
file.write('---types---\n')
|
|
for token in lst:
|
|
if not token.is_method:
|
|
file.write(TLTokenTL.dump(token))
|
|
file.write('\n')
|
|
|
|
file.write('---functions---\n')
|
|
for token in lst:
|
|
if token.is_method:
|
|
file.write(TLTokenTL.dump(token))
|
|
file.write('\n')
|
|
|
|
|
|
class TLTokenFileJSON:
|
|
@staticmethod
|
|
def read(file, no_verify=True):
|
|
def process(inp, out, method):
|
|
for token in inp:
|
|
try:
|
|
out.append(TLTokenJSON(token))
|
|
assert no_verify or (out[-1].is_method == method)
|
|
except Exception as e:
|
|
print('ERROR:')
|
|
print(token)
|
|
print(e, end='\n\n')
|
|
|
|
data = json.load(file)
|
|
|
|
constructors = list()
|
|
process(data['constructors'], constructors, False)
|
|
|
|
methods = list()
|
|
process(data['methods'], methods, True)
|
|
|
|
return constructors + methods
|
|
|
|
def write(file, lst):
|
|
json.dump(
|
|
OrderedDict((
|
|
('constructors', [TLTokenJSON.dump(token, as_string=False) for token in lst if not token.is_method]),
|
|
('methods', [TLTokenJSON.dump(token, as_string=False) for token in lst if token.is_method])
|
|
)),
|
|
file,
|
|
indent=2)
|
|
|
|
"""
|
|
Usage example -- converting schema from TL to JSON:
|
|
|
|
from tl_master import *
|
|
with open('schema.tl') as tl, open('schema.json', 'w') as json:
|
|
TLTokenFileJSON.write(json, TLTokenFileTL.read(tl))
|
|
"""
|
|
from sys import argv
|
|
if argv[1].endswith('.tl'):
|
|
fromf = TLTokenFileTL
|
|
tof = TLTokenFileJSON
|
|
outn = argv[1].replace('tl', 'json')
|
|
elif argv[1].endswith('.json'):
|
|
fromf = TLTokenFileJSON
|
|
tof = TLTokenFileTL
|
|
outn = argv[1].replace('json', 'tl')
|
|
|
|
with open(argv[1]) as inf, open(outn, 'w') as outf:
|
|
tof.write(outf, fromf.read(inf))
|
|
|