import { computeConstructorIdFromEntry } from './ctor-id.js' import { TL_PRIMITIVES, TlArgument, TlEntry } from './types.js' import { parseArgumentType, parseTdlibStyleComment } from './utils.js' const SINGLE_REGEX = /^(.+?)(?:#([0-9a-f]{1,8}))?(?: \?)?(?: {(.+?:.+?)})? ((?:.+? )*)= (.+);$/ export function computeConstructorIdFromString(line: string): number { return computeConstructorIdFromEntry(parseTlToEntries(line, { forIdComputation: true })[0]) } /** * Parse TL schema into a list of entries. * * @param tl TL schema * @param params Additional parameters */ export function parseTlToEntries( tl: string, params: { /** * Whether to throw an error if a line failed to parse */ panicOnError?: boolean /** * Function to be called if there was an error while parsing a line * * @param err Error * @param line Line that failed to parse * @param num Line number */ onError?: (err: Error, line: string, num: number) => void /** * Function to be called a comment is found not belonging to any entry * * @param comment Comment text */ onOrphanComment?: (comment: string) => void /** * Prefix to be applied to all types */ prefix?: string /** * Whether this invocation is for computing constructor ids. * If true, the `id` field will be set to 0 for all entries. */ forIdComputation?: boolean /** * Whether to parse typeModifiers for method return types */ parseMethodTypes?: boolean } = {}, ): TlEntry[] { const ret: TlEntry[] = [] const entries: Record = {} const unions: Record = {} const lines = tl.split('\n') let currentKind: TlEntry['kind'] = 'class' let currentComment = '' const prefix = params.prefix ?? '' lines.forEach((line, idx) => { line = line.trim() if (line === '') { if (params.onOrphanComment) { params.onOrphanComment(currentComment) } currentComment = '' return } if (line.match(/^\/\//)) { if (currentComment) { if (line[2] === '-') { currentComment += '\n' + line.substring(3).trim() } else { currentComment += ' ' + line.substring(2).trim() } } else { currentComment = line.substring(2).trim() } return } if (line === '---functions---') { currentKind = 'method' return } if (line === '---types---') { currentKind = 'class' return } const match = SINGLE_REGEX.exec(line) if (!match) { const err = new Error(`Failed to parse line ${idx + 1}: ${line}`) if (params.panicOnError) { throw err } else if (params.onError) { params.onError(err, line, idx + 1) } else { console.warn(err) } return } const [, typeName, typeId, generics, args, type] = match if (typeName in TL_PRIMITIVES) { return } let typeIdNum = typeId ? parseInt(typeId, 16) : 0 if (typeIdNum === 0 && !params.forIdComputation) { typeIdNum = computeConstructorIdFromString(line) } const argsParsed = args && !args.match(/\[ [a-z]+ ]/i) ? args .trim() .split(' ') .map((j) => j.split(':')) : [] const entry: TlEntry = { kind: currentKind, name: typeName, id: typeIdNum, type, arguments: [], } if (entry.kind === 'method' && params.parseMethodTypes) { const [type, modifiers] = parseArgumentType(entry.type) entry.type = type if (Object.keys(modifiers).length) { entry.typeModifiers = modifiers } } if (generics) { entry.generics = generics.split(',').map((it) => { const [name, type] = it.split(':') return { name, type } }) } if (argsParsed.length) { argsParsed.forEach(([name, type_]) => { const [type, modifiers] = parseArgumentType(type_) const item: TlArgument = { name, type, } if (Object.keys(modifiers).length) { item.typeModifiers = modifiers } entry.arguments.push(item) }) } if (currentComment) { if (currentComment.match(/^@description /)) { // tdlib-style comment const obj = parseTdlibStyleComment(currentComment) if (obj.description) entry.comment = obj.description entry.arguments.forEach((arg) => { if (arg.name in obj) { arg.comment = obj[arg.name] } }) } else { entry.comment = currentComment } currentComment = '' } ret.push(entry) entries[entry.name] = entry if (entry.kind === 'class') { if (!unions[entry.type]) unions[entry.type] = [] unions[entry.type].push(entry) } }) if (currentComment && params.onOrphanComment) { params.onOrphanComment(currentComment) } // post-process: // - add return type ctor id for methods // - find arguments where type is not a union and put corresponding modifiers // - apply prefix ret.forEach((entry, entryIdx) => { if (params.parseMethodTypes && entry.kind === 'method') { const type = entry.type if (type in unions && unions[type].length === 1) { if (!entry.typeModifiers) entry.typeModifiers = {} entry.typeModifiers.constructorId = unions[type][0].id } else if (type in entries) { if (!entry.typeModifiers) entry.typeModifiers = {} entry.typeModifiers.isBareType = true entry.typeModifiers.constructorId = entries[type].id } } entry.arguments.forEach((arg) => { const type = arg.type if (type in TL_PRIMITIVES) { return } if (type in unions && arg.typeModifiers?.isBareUnion) { if (unions[type].length !== 1) { const err = new Error( `Union ${type} has more than one entry, cannot use it like %${type} (found in ${entry.name}#${arg.name})`, ) if (params.panicOnError) { throw err } else if (params.onError) { params.onError(err, '', entryIdx) } else { console.warn(err) } } arg.typeModifiers.constructorId = unions[type][0].id } else if (type in entries) { if (!arg.typeModifiers) arg.typeModifiers = {} arg.typeModifiers.isBareType = true arg.typeModifiers.constructorId = entries[type].id if (prefix) { arg.type = prefix + arg.type } } }) if (prefix) { entry.name = prefix + entry.name } }) return ret }