feat(codegen): support bare types and vectors
closes MTQ-48
This commit is contained in:
parent
754a288c87
commit
99e83b40aa
23 changed files with 726 additions and 257 deletions
|
@ -2,17 +2,44 @@ import { computeConstructorIdFromEntry } from '../ctor-id'
|
|||
import { TL_PRIMITIVES, TlEntry } from '../types'
|
||||
import { snakeToCamel } from './utils'
|
||||
|
||||
export interface ReaderCodegenOptions {
|
||||
/**
|
||||
* Whether to include `flags` field in the result object
|
||||
* @default false
|
||||
*/
|
||||
includeFlags?: boolean
|
||||
|
||||
/**
|
||||
* Name of the variable to use for the readers map
|
||||
* @default 'm'
|
||||
*/
|
||||
variableName?: string
|
||||
|
||||
/**
|
||||
* Whether to include methods in the readers map
|
||||
*/
|
||||
includeMethods?: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS: ReaderCodegenOptions = {
|
||||
includeFlags: false,
|
||||
variableName: 'm',
|
||||
includeMethods: false,
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate binary reader code for a given entry.
|
||||
*
|
||||
* @param entry Entry to generate reader for
|
||||
* @param includeFlags Whether to include `flags` field in the result object
|
||||
* @param params Options
|
||||
* @returns Code as a writers map entry
|
||||
*/
|
||||
export function generateReaderCodeForTlEntry(
|
||||
entry: TlEntry,
|
||||
includeFlags = false,
|
||||
params = DEFAULT_OPTIONS,
|
||||
): string {
|
||||
const { variableName, includeFlags } = { ...DEFAULT_OPTIONS, ...params }
|
||||
|
||||
if (entry.id === 0) entry.id = computeConstructorIdFromEntry(entry)
|
||||
|
||||
const pre = `${entry.id}:function(r){`
|
||||
|
@ -49,20 +76,19 @@ export function generateReaderCodeForTlEntry(
|
|||
|
||||
const argName = snakeToCamel(arg.name)
|
||||
|
||||
if (arg.predicate) {
|
||||
const s = arg.predicate.split('.')
|
||||
if (arg.typeModifiers?.predicate) {
|
||||
const predicate = arg.typeModifiers.predicate
|
||||
const s = predicate.split('.')
|
||||
const fieldName = s[0]
|
||||
const bitIndex = parseInt(s[1])
|
||||
|
||||
if (!(fieldName in flagsFields)) {
|
||||
throw new Error(
|
||||
`Invalid predicate: ${arg.predicate} - unknown field (in ${entry.name})`,
|
||||
`Invalid predicate: ${predicate} - unknown field (in ${entry.name})`,
|
||||
)
|
||||
}
|
||||
if (isNaN(bitIndex) || bitIndex < 0 || bitIndex > 32) {
|
||||
throw new Error(
|
||||
`Invalid predicate: ${arg.predicate} - invalid bit`,
|
||||
)
|
||||
throw new Error(`Invalid predicate: ${predicate} - invalid bit`)
|
||||
}
|
||||
|
||||
const condition = `${fieldName}&${1 << bitIndex}`
|
||||
|
@ -86,30 +112,39 @@ export function generateReaderCodeForTlEntry(
|
|||
returnCode += `${argName}:`
|
||||
}
|
||||
|
||||
let vector = false
|
||||
let type = arg.type
|
||||
const m = type.match(/^[Vv]ector[< ](.+?)[> ]$/)
|
||||
|
||||
if (m) {
|
||||
vector = true
|
||||
type = m[1]
|
||||
}
|
||||
|
||||
if (type in TL_PRIMITIVES) {
|
||||
if (type === 'Bool') type = 'boolean'
|
||||
if (type === 'Bool' || type === 'bool') type = 'boolean'
|
||||
} else {
|
||||
type = 'object'
|
||||
}
|
||||
|
||||
let code
|
||||
let reader = `r.${type}`
|
||||
const isBare =
|
||||
arg.typeModifiers?.isBareType || arg.typeModifiers?.isBareUnion
|
||||
|
||||
if (vector) {
|
||||
code = `r.vector(r.${type})`
|
||||
} else {
|
||||
code = `r.${type}()`
|
||||
if (isBare) {
|
||||
if (!arg.typeModifiers?.constructorId) {
|
||||
throw new Error(
|
||||
`Cannot generate reader for ${entry.name}#${arg.name} - no constructor id referenced`,
|
||||
)
|
||||
}
|
||||
|
||||
reader = `${variableName}[${arg.typeModifiers.constructorId}]`
|
||||
}
|
||||
|
||||
if (arg.predicate) {
|
||||
let code
|
||||
|
||||
if (arg.typeModifiers?.isVector) {
|
||||
code = `r.vector(${reader})`
|
||||
} else if (arg.typeModifiers?.isBareVector) {
|
||||
code = `r.vector(${reader},1)`
|
||||
} else {
|
||||
code = `${reader}(${isBare ? 'r' : ''})`
|
||||
}
|
||||
|
||||
if (arg.typeModifiers?.predicate) {
|
||||
code += ':void 0'
|
||||
}
|
||||
|
||||
|
@ -127,20 +162,19 @@ export function generateReaderCodeForTlEntry(
|
|||
* Generate binary reader code for a given schema.
|
||||
*
|
||||
* @param entries Entries to generate reader for
|
||||
* @param varName Name of the variable containing the result
|
||||
* @param methods Whether to include method readers
|
||||
* @param params Codegen options
|
||||
*/
|
||||
export function generateReaderCodeForTlEntries(
|
||||
entries: TlEntry[],
|
||||
varName: string,
|
||||
methods = true,
|
||||
params = DEFAULT_OPTIONS,
|
||||
): string {
|
||||
let ret = `var ${varName}={\n`
|
||||
const { variableName, includeMethods } = { ...DEFAULT_OPTIONS, ...params }
|
||||
let ret = `var ${variableName}={\n`
|
||||
|
||||
entries.forEach((entry) => {
|
||||
if (entry.kind === 'method' && !methods) return
|
||||
if (entry.kind === 'method' && !includeMethods) return
|
||||
|
||||
ret += generateReaderCodeForTlEntry(entry) + '\n'
|
||||
ret += generateReaderCodeForTlEntry(entry, params) + '\n'
|
||||
})
|
||||
|
||||
return ret + '}'
|
||||
|
|
|
@ -31,11 +31,6 @@ function fullTypeName(
|
|||
link = false,
|
||||
): string {
|
||||
if (type in PRIMITIVE_TO_TS) return PRIMITIVE_TO_TS[type]
|
||||
let m
|
||||
|
||||
if ((m = type.match(/^[Vv]ector[< ](.+?)[> ]$/))) {
|
||||
return fullTypeName(m[1], baseNamespace, namespace, method, link) + '[]'
|
||||
}
|
||||
|
||||
const [ns, name] = splitNameToNamespace(type)
|
||||
let res = baseNamespace
|
||||
|
@ -145,7 +140,7 @@ export function generateTypescriptDefinitionsForTlEntry(
|
|||
|
||||
ret += ` ${snakeToCamel(arg.name)}`
|
||||
|
||||
if (arg.predicate) ret += '?'
|
||||
if (arg.typeModifiers?.predicate) ret += '?'
|
||||
|
||||
let type = arg.type
|
||||
let typeFinal = false
|
||||
|
@ -158,6 +153,10 @@ export function generateTypescriptDefinitionsForTlEntry(
|
|||
|
||||
if (!typeFinal) type = fullTypeName(arg.type, baseNamespace)
|
||||
|
||||
if (arg.typeModifiers?.isVector || arg.typeModifiers?.isBareVector) {
|
||||
type += '[]'
|
||||
}
|
||||
|
||||
ret += `: ${type};\n`
|
||||
})
|
||||
|
||||
|
@ -219,7 +218,10 @@ export function generateTypescriptDefinitionsForTlSchema(
|
|||
namespace = 'tl',
|
||||
errors?: TlErrors,
|
||||
): [string, string] {
|
||||
let ts = PRELUDE.replace('$NS$', namespace).replace('$LAYER$', String(layer))
|
||||
let ts = PRELUDE.replace('$NS$', namespace).replace(
|
||||
'$LAYER$',
|
||||
String(layer),
|
||||
)
|
||||
let js = PRELUDE_JS.replace('$NS$', namespace).replace(
|
||||
'$LAYER$',
|
||||
String(layer),
|
||||
|
|
|
@ -2,8 +2,39 @@ import { computeConstructorIdFromEntry } from '../ctor-id'
|
|||
import { TL_PRIMITIVES, TlEntry } from '../types'
|
||||
import { snakeToCamel } from './utils'
|
||||
|
||||
export interface WriterCodegenOptions {
|
||||
/**
|
||||
* Whether to use `flags` field from the input
|
||||
* @default false
|
||||
*/
|
||||
includeFlags?: boolean
|
||||
|
||||
/**
|
||||
* Name of the variable to use for the writers map
|
||||
* @default 'm'
|
||||
*/
|
||||
variableName?: string
|
||||
|
||||
/**
|
||||
* Whether to include prelude code (function `h`)
|
||||
*/
|
||||
includePrelude?: boolean
|
||||
|
||||
/**
|
||||
* Whether to generate bare writer (without constructor id write)
|
||||
*/
|
||||
bare?: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS: WriterCodegenOptions = {
|
||||
includeFlags: false,
|
||||
variableName: 'm',
|
||||
includePrelude: true,
|
||||
bare: false,
|
||||
}
|
||||
|
||||
const TL_WRITER_PRELUDE =
|
||||
'function h(o, p){' +
|
||||
'function h(o,p){' +
|
||||
'var q=o[p];' +
|
||||
'if(q===void 0)' +
|
||||
"throw Error('Object '+o._+' is missing required property '+p);" +
|
||||
|
@ -11,37 +42,42 @@ const TL_WRITER_PRELUDE =
|
|||
|
||||
/**
|
||||
* Generate writer code for a single entry.
|
||||
* `h` (has) function should be available
|
||||
* `h` (has) function from the prelude should be available
|
||||
*
|
||||
* @param entry Entry to generate writer for
|
||||
* @param withFlags Whether to include `flags` field in the result object
|
||||
* @param params Options
|
||||
* @returns Code as a readers map entry
|
||||
*/
|
||||
export function generateWriterCodeForTlEntry(
|
||||
entry: TlEntry,
|
||||
withFlags = false,
|
||||
params = DEFAULT_OPTIONS,
|
||||
): string {
|
||||
const { bare, includeFlags, variableName } = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...params,
|
||||
}
|
||||
|
||||
if (entry.id === 0) entry.id = computeConstructorIdFromEntry(entry)
|
||||
|
||||
let ret = `'${entry.name}':function(w${
|
||||
entry.arguments.length ? ',v' : ''
|
||||
}){`
|
||||
const name = bare ? entry.id : `'${entry.name}'`
|
||||
let ret = `${name}:function(w${entry.arguments.length ? ',v' : ''}){`
|
||||
|
||||
ret += `w.uint(${entry.id});`
|
||||
if (!bare) ret += `w.uint(${entry.id});`
|
||||
|
||||
const flagsFields: Record<string, 1> = {}
|
||||
|
||||
entry.arguments.forEach((arg) => {
|
||||
if (arg.type === '#') {
|
||||
ret += `var ${arg.name}=${withFlags ? `v.${arg.name}` : '0'};`
|
||||
ret += `var ${arg.name}=${includeFlags ? `v.${arg.name}` : '0'};`
|
||||
|
||||
entry.arguments.forEach((arg1) => {
|
||||
const predicate = arg1.typeModifiers?.predicate
|
||||
|
||||
let s
|
||||
|
||||
if (
|
||||
!arg1.predicate ||
|
||||
(s = arg1.predicate.split('.'))[0] !== arg.name
|
||||
) { return }
|
||||
if (!predicate || (s = predicate.split('.'))[0] !== arg.name) {
|
||||
return
|
||||
}
|
||||
|
||||
const arg1Name = snakeToCamel(arg1.name)
|
||||
|
||||
|
@ -49,7 +85,7 @@ export function generateWriterCodeForTlEntry(
|
|||
|
||||
if (isNaN(bitIndex) || bitIndex < 0 || bitIndex > 32) {
|
||||
throw new Error(
|
||||
`Invalid predicate: ${arg1.predicate} - invalid bit`,
|
||||
`Invalid predicate: ${predicate} - invalid bit`,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -57,7 +93,10 @@ export function generateWriterCodeForTlEntry(
|
|||
|
||||
if (arg1.type === 'true') {
|
||||
ret += `if(v.${arg1Name}===true)${action}`
|
||||
} else if (arg1.type.match(/^[Vv]ector/)) {
|
||||
} else if (
|
||||
arg1.typeModifiers?.isVector ||
|
||||
arg1.typeModifiers?.isBareVector
|
||||
) {
|
||||
ret += `var _${arg1Name}=v.${arg1Name}&&v.${arg1Name}.length;if(_${arg1Name})${action}`
|
||||
} else {
|
||||
ret += `var _${arg1Name}=v.${arg1Name}!==undefined;if(_${arg1Name})${action}`
|
||||
|
@ -72,21 +111,16 @@ export function generateWriterCodeForTlEntry(
|
|||
|
||||
const argName = snakeToCamel(arg.name)
|
||||
|
||||
let vector = false
|
||||
let type = arg.type
|
||||
const m = type.match(/^[Vv]ector[< ](.+?)[> ]$/)
|
||||
|
||||
if (m) {
|
||||
vector = true
|
||||
type = m[1]
|
||||
}
|
||||
let accessor = `v.${argName}`
|
||||
|
||||
if (arg.predicate) {
|
||||
if (arg.typeModifiers?.predicate) {
|
||||
if (type === 'true') return // included in flags
|
||||
|
||||
ret += `if(_${argName})`
|
||||
} else {
|
||||
ret += `h(v,'${argName}');`
|
||||
accessor = `h(v,'${argName}')`
|
||||
}
|
||||
|
||||
if (type in TL_PRIMITIVES) {
|
||||
|
@ -95,10 +129,26 @@ export function generateWriterCodeForTlEntry(
|
|||
type = 'object'
|
||||
}
|
||||
|
||||
if (vector) {
|
||||
ret += `w.vector(w.${type}, v.${argName});`
|
||||
let writer = `w.${type}`
|
||||
const isBare =
|
||||
arg.typeModifiers?.isBareType || arg.typeModifiers?.isBareUnion
|
||||
|
||||
if (isBare) {
|
||||
if (!arg.typeModifiers?.constructorId) {
|
||||
throw new Error(
|
||||
`Cannot generate writer for ${entry.name}#${arg.name} - no constructor id referenced`,
|
||||
)
|
||||
}
|
||||
|
||||
writer = `${variableName}._bare[${arg.typeModifiers.constructorId}]`
|
||||
}
|
||||
|
||||
if (arg.typeModifiers?.isVector) {
|
||||
ret += `w.vector(${writer},${accessor});`
|
||||
} else if (arg.typeModifiers?.isBareVector) {
|
||||
ret += `w.vector(${writer},${accessor},1);`
|
||||
} else {
|
||||
ret += `w.${type}(v.${argName});`
|
||||
ret += `${writer}(${isBare ? 'w,' : ''}${accessor});`
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -109,23 +159,47 @@ export function generateWriterCodeForTlEntry(
|
|||
* Generate writer code for a given TL schema.
|
||||
*
|
||||
* @param entries Entries to generate writers for
|
||||
* @param varName Name of the variable to use for the writers map
|
||||
* @param prelude Whether to include the prelude (containing `h` function)
|
||||
* @param withFlags Whether to include `flags` field in the result object
|
||||
* @param params Codegen options
|
||||
*/
|
||||
export function generateWriterCodeForTlEntries(
|
||||
entries: TlEntry[],
|
||||
varName: string,
|
||||
prelude = true,
|
||||
withFlags = false,
|
||||
params = DEFAULT_OPTIONS,
|
||||
): string {
|
||||
let ret = ''
|
||||
if (prelude) ret += TL_WRITER_PRELUDE
|
||||
ret += `var ${varName}={\n`
|
||||
const { includePrelude, variableName } = { ...DEFAULT_OPTIONS, ...params }
|
||||
|
||||
let ret = ''
|
||||
if (includePrelude) ret += TL_WRITER_PRELUDE
|
||||
ret += `var ${variableName}={\n`
|
||||
|
||||
const usedAsBareIds: Record<number, 1> = {}
|
||||
entries.forEach((entry) => {
|
||||
ret += generateWriterCodeForTlEntry(entry, withFlags) + '\n'
|
||||
ret += generateWriterCodeForTlEntry(entry, params) + '\n'
|
||||
|
||||
entry.arguments.forEach((arg) => {
|
||||
if (arg.typeModifiers?.constructorId) {
|
||||
usedAsBareIds[arg.typeModifiers.constructorId] = 1
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (Object.keys(usedAsBareIds).length) {
|
||||
ret += '_bare:{\n'
|
||||
|
||||
Object.keys(usedAsBareIds).forEach((id) => {
|
||||
const entry = entries.find((e) => e.id === parseInt(id))
|
||||
|
||||
if (!entry) {
|
||||
return
|
||||
}
|
||||
|
||||
ret +=
|
||||
generateWriterCodeForTlEntry(entry, {
|
||||
...params,
|
||||
bare: true,
|
||||
}) + '\n'
|
||||
})
|
||||
ret += '}'
|
||||
}
|
||||
|
||||
return ret + '}'
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import CRC32 from 'crc-32'
|
||||
|
||||
import { parseTlToEntries } from './parse'
|
||||
import { writeTlEntryToString } from './stringify'
|
||||
import { TlEntry } from './types'
|
||||
|
||||
|
@ -9,19 +10,8 @@ import { TlEntry } from './types'
|
|||
* @param line Line containing TL entry definition
|
||||
*/
|
||||
export function computeConstructorIdFromString(line: string): number {
|
||||
return (
|
||||
CRC32.str(
|
||||
// normalize
|
||||
line
|
||||
.replace(
|
||||
/[{};]|[a-zA-Z0-9_]+:flags\.[0-9]+\?true|#[0-9a-f]{1,8}/g,
|
||||
'',
|
||||
)
|
||||
.replace(/[<>]/g, ' ')
|
||||
.replace(/ +/g, ' ')
|
||||
.replace(':bytes', ':string')
|
||||
.trim(),
|
||||
) >>> 0
|
||||
return computeConstructorIdFromEntry(
|
||||
parseTlToEntries(line, { forIdComputation: true })[0],
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
TlFullSchema,
|
||||
TlSchemaDiff,
|
||||
} from './types'
|
||||
import { stringifyArgumentType } from './utils'
|
||||
|
||||
/**
|
||||
* Compute difference between two TL entries.
|
||||
|
@ -89,17 +90,16 @@ export function generateTlEntriesDifference(
|
|||
name: arg.name,
|
||||
}
|
||||
|
||||
if (arg.type !== oldArg.type) {
|
||||
diff.type = {
|
||||
old: oldArg.type,
|
||||
new: arg.type,
|
||||
}
|
||||
}
|
||||
const argStr = stringifyArgumentType(arg.type, arg.typeModifiers)
|
||||
const oldArgStr = stringifyArgumentType(
|
||||
oldArg.type,
|
||||
oldArg.typeModifiers,
|
||||
)
|
||||
|
||||
if (arg.predicate !== oldArg.predicate) {
|
||||
diff.predicate = {
|
||||
old: oldArg.predicate,
|
||||
new: arg.predicate,
|
||||
if (argStr !== oldArgStr) {
|
||||
diff.type = {
|
||||
old: oldArgStr,
|
||||
new: argStr,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ export function generateTlEntriesDifference(
|
|||
}
|
||||
}
|
||||
|
||||
if (diff.type || diff.predicate || diff.comment) {
|
||||
if (diff.type || diff.comment) {
|
||||
argsDiff.modified.push(diff)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -33,10 +33,10 @@ export function mergeTlEntries(entries: TlEntry[]): TlEntry | string {
|
|||
if (arg.type === '#') {
|
||||
flagsLastIndex[arg.name] = idx
|
||||
}
|
||||
if (arg.predicate) {
|
||||
const flagsField = arg.predicate.split('.')[0]
|
||||
flagsLastIndex[flagsField] = idx
|
||||
}
|
||||
// if (arg.predicate) {
|
||||
// const flagsField = arg.predicate.split('.')[0]
|
||||
// flagsLastIndex[flagsField] = idx
|
||||
// }
|
||||
})
|
||||
|
||||
for (let i = 1; i < entries.length; i++) {
|
||||
|
@ -55,7 +55,9 @@ export function mergeTlEntries(entries: TlEntry[]): TlEntry | string {
|
|||
result.name !== entry.name ||
|
||||
result.type !== entry.type ||
|
||||
result.id !== ctorId
|
||||
) { return 'basic info mismatch' }
|
||||
) {
|
||||
return 'basic info mismatch'
|
||||
}
|
||||
|
||||
// since we re-calculated id manually, we can skip checking
|
||||
// generics and arguments, and get straight to merging
|
||||
|
@ -72,13 +74,14 @@ export function mergeTlEntries(entries: TlEntry[]): TlEntry | string {
|
|||
// yay a new arg
|
||||
// we can only add optional true args, since any others will change id
|
||||
// ids match, so this must be the case
|
||||
if (!entryArgument.predicate) {
|
||||
if (!entryArgument.typeModifiers?.predicate) {
|
||||
throw new Error('new argument is not optional')
|
||||
}
|
||||
|
||||
// we also need to make sure we put it *after* the respective flags field
|
||||
|
||||
const flagsField = entryArgument.predicate.split('.')[0]
|
||||
const flagsField =
|
||||
entryArgument.typeModifiers.predicate.split('.')[0]
|
||||
const targetIdx = flagsLastIndex[flagsField]
|
||||
|
||||
// targetIdx *must* exist, otherwise ids wouldn't match
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
import { computeConstructorIdFromString } from './ctor-id'
|
||||
import { TL_PRIMITIVES, TlEntry } from './types'
|
||||
import { parseTdlibStyleComment } from './utils'
|
||||
import { TL_PRIMITIVES, TlArgument, TlEntry } from './types'
|
||||
import { parseArgumentType, parseTdlibStyleComment } from './utils'
|
||||
|
||||
const SINGLE_REGEX =
|
||||
/^(.+?)(?:#([0-9a-f]{1,8}))?(?: \?)?(?: {(.+?:.+?)})? ((?:.+? )*)= (.+);$/
|
||||
|
||||
function applyPrefix(prefix: string, type: string): string {
|
||||
if (type in TL_PRIMITIVES) return type
|
||||
|
||||
const m = type.match(/^[Vv]ector[< ](.+?)[> ]$/)
|
||||
if (m) return `Vector<${applyPrefix(prefix, m[1])}>`
|
||||
|
||||
return prefix + type
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse TL schema into a list of entries.
|
||||
*
|
||||
|
@ -50,13 +41,17 @@ export function parseTlToEntries(
|
|||
prefix?: string
|
||||
|
||||
/**
|
||||
* Whether to apply the prefix to arguments as well
|
||||
* Whether this invocation is for computing constructor ids.
|
||||
* If true, the `id` field will be set to 0 for all entries.
|
||||
*/
|
||||
applyPrefixToArguments?: boolean
|
||||
forIdComputation?: boolean
|
||||
},
|
||||
): TlEntry[] {
|
||||
const ret: TlEntry[] = []
|
||||
|
||||
const entries: Record<string, TlEntry> = {}
|
||||
const unions: Record<string, TlEntry[]> = {}
|
||||
|
||||
const lines = tl.split('\n')
|
||||
|
||||
let currentKind: TlEntry['kind'] = 'class'
|
||||
|
@ -124,9 +119,11 @@ export function parseTlToEntries(
|
|||
return
|
||||
}
|
||||
|
||||
const typeIdNum = typeId ?
|
||||
parseInt(typeId, 16) :
|
||||
computeConstructorIdFromString(line)
|
||||
let typeIdNum = typeId ? parseInt(typeId, 16) : 0
|
||||
|
||||
if (typeIdNum === 0 && !params?.forIdComputation) {
|
||||
typeIdNum = computeConstructorIdFromString(line)
|
||||
}
|
||||
|
||||
const argsParsed =
|
||||
args && !args.match(/\[ [a-z]+ ]/i) ?
|
||||
|
@ -138,7 +135,7 @@ export function parseTlToEntries(
|
|||
|
||||
const entry: TlEntry = {
|
||||
kind: currentKind,
|
||||
name: prefix + typeName,
|
||||
name: typeName,
|
||||
id: typeIdNum,
|
||||
type,
|
||||
arguments: [],
|
||||
|
@ -153,31 +150,18 @@ export function parseTlToEntries(
|
|||
}
|
||||
|
||||
if (argsParsed.length) {
|
||||
argsParsed.forEach(([name, typ]) => {
|
||||
let [predicate, type] = typ.split('?')
|
||||
|
||||
if (!type) {
|
||||
// no predicate, `predicate` is the type
|
||||
|
||||
if (params?.applyPrefixToArguments) {
|
||||
predicate = applyPrefix(prefix, predicate)
|
||||
}
|
||||
entry.arguments.push({
|
||||
name,
|
||||
type: predicate,
|
||||
})
|
||||
} else {
|
||||
// there is a predicate
|
||||
|
||||
if (params?.applyPrefixToArguments) {
|
||||
type = applyPrefix(prefix, type)
|
||||
}
|
||||
entry.arguments.push({
|
||||
name,
|
||||
type,
|
||||
predicate,
|
||||
})
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -201,11 +185,59 @@ export function parseTlToEntries(
|
|||
}
|
||||
|
||||
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:
|
||||
// - find arguments where type is not a union and put corresponding modifiers
|
||||
// - apply prefix
|
||||
ret.forEach((entry, entryIdx) => {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -29,11 +29,21 @@ export function patchRuntimeTlSchema(
|
|||
} {
|
||||
const entries = parseTlToEntries(schema)
|
||||
|
||||
const readersCode = generateReaderCodeForTlEntries(entries, '_', false)
|
||||
const writersCode = generateWriterCodeForTlEntries(entries, '_', true)
|
||||
const readersCode = generateReaderCodeForTlEntries(entries, {
|
||||
variableName: '_',
|
||||
includeMethods: false,
|
||||
})
|
||||
const writersCode = generateWriterCodeForTlEntries(entries, {
|
||||
variableName: '_',
|
||||
includePrelude: true,
|
||||
})
|
||||
|
||||
const newReaders = evalForResult<TlReaderMap>(readersCode.replace('var _=', 'return'))
|
||||
const newWriters = evalForResult<TlWriterMap>(writersCode.replace('var _=', 'return'))
|
||||
const newReaders = evalForResult<TlReaderMap>(
|
||||
readersCode.replace('var _=', 'return'),
|
||||
)
|
||||
const newWriters = evalForResult<TlWriterMap>(
|
||||
writersCode.replace('var _=', 'return'),
|
||||
)
|
||||
|
||||
return {
|
||||
readerMap: {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { TlEntry } from './types'
|
||||
import { stringifyArgumentType } from './utils'
|
||||
|
||||
function normalizeType(s: string): string {
|
||||
return s
|
||||
|
@ -39,18 +40,22 @@ export function writeTlEntryToString(
|
|||
}
|
||||
|
||||
for (const arg of entry.arguments) {
|
||||
if (forIdComputation && arg.predicate && arg.type === 'true') continue
|
||||
if (
|
||||
forIdComputation &&
|
||||
arg.typeModifiers?.predicate &&
|
||||
arg.type === 'true'
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
str += arg.name + ':'
|
||||
|
||||
if (arg.predicate) {
|
||||
str += arg.predicate + '?'
|
||||
}
|
||||
const type = stringifyArgumentType(arg.type, arg.typeModifiers)
|
||||
|
||||
if (forIdComputation) {
|
||||
str += normalizeType(arg.type) + ' '
|
||||
str += normalizeType(type) + ' '
|
||||
} else {
|
||||
str += arg.type + ' '
|
||||
str += type + ' '
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,62 @@
|
|||
/**
|
||||
* Modifiers for {@link TlArgument.type}
|
||||
*/
|
||||
export interface TlArgumentModifiers {
|
||||
/**
|
||||
* Predicate of the argument
|
||||
* @example `flags.3`
|
||||
*/
|
||||
predicate?: string
|
||||
|
||||
/**
|
||||
* Whether `type` is in fact a `Vector`
|
||||
* @example `type=long, isVector=true => Vector<long>
|
||||
*/
|
||||
isVector?: boolean
|
||||
|
||||
/**
|
||||
* Whether `type` is in fact a `vector` (a bare vector, not to be confused with `Vector`).
|
||||
*
|
||||
* The difference between `Vector<T>` and `vector<T>` is that in the latter case
|
||||
* constructor ID of the vector itself (1cb5c415) is omitted
|
||||
*
|
||||
* @example `type=long, isVector=false, isBareVector=true => vector<long>
|
||||
*/
|
||||
isBareVector?: boolean
|
||||
|
||||
/**
|
||||
* Whether `type` is in fact a "bare" type (a %-prefixed type) from within a union.
|
||||
*
|
||||
* The difference between `T` and `%T` is that in the latter case
|
||||
* constructor ID of `T` is omitted.
|
||||
*
|
||||
* Note: If there are more than 1 types within that union, this syntax is not valid.
|
||||
*
|
||||
* @example `type=Message, isBare=true => %Message
|
||||
*/
|
||||
isBareUnion?: boolean
|
||||
|
||||
/**
|
||||
* Whether `type` is in fact a "bare" type (a %-prefixed type)
|
||||
*
|
||||
* The difference between `T` and `%T` is that in the latter case
|
||||
* constructor ID of `T` is omitted.
|
||||
*
|
||||
* The difference with {@link isBareUnion} is in the kind of `type`.
|
||||
* For {@link isBareUnion}, `type` is a name of a union (e.g. `Message`),
|
||||
* for {@link isBareType} it is a name of a type (e.g. `message`).
|
||||
*/
|
||||
isBareType?: boolean
|
||||
|
||||
/**
|
||||
* For simplicity, when {@link isBareUnion} or {@link isBareType} is true,
|
||||
* this field contains the constructor ID of the type being referenced.
|
||||
*
|
||||
* May still be undefined if the constructor ID is not known.
|
||||
*/
|
||||
constructorId?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument of a TL entry
|
||||
*/
|
||||
|
@ -8,15 +67,14 @@ export interface TlArgument {
|
|||
name: string
|
||||
|
||||
/**
|
||||
* Type of the argument
|
||||
* Type of the argument. Usually a name of a Union, but not always
|
||||
*/
|
||||
type: string
|
||||
|
||||
/**
|
||||
* Predicate of the argument
|
||||
* @example `flags.3`
|
||||
* Modifiers for {@link type}
|
||||
*/
|
||||
predicate?: string
|
||||
typeModifiers?: TlArgumentModifiers
|
||||
|
||||
/**
|
||||
* Comment of the argument
|
||||
|
@ -268,11 +326,6 @@ export interface TlArgumentDiff {
|
|||
*/
|
||||
type?: PropertyDiff<string>
|
||||
|
||||
/**
|
||||
* Predicate of the argument diff
|
||||
*/
|
||||
predicate?: PropertyDiff<string | undefined>
|
||||
|
||||
/**
|
||||
* Comment of the argument diff
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TlEntry } from './types'
|
||||
import { TlArgumentModifiers, TlEntry } from './types'
|
||||
|
||||
/**
|
||||
* Split qualified TL entry name into namespace and name
|
||||
|
@ -60,3 +60,43 @@ export function groupTlEntriesByNamespace(
|
|||
|
||||
return ret
|
||||
}
|
||||
|
||||
export function stringifyArgumentType(
|
||||
type: string,
|
||||
modifiers?: TlArgumentModifiers,
|
||||
) {
|
||||
if (!modifiers) return type
|
||||
let ret = type
|
||||
|
||||
if (modifiers?.isBareUnion) ret = `%${ret}`
|
||||
if (modifiers?.isVector) ret = `Vector<${ret}>`
|
||||
else if (modifiers?.isBareVector) ret = `vector<${ret}>`
|
||||
if (modifiers.predicate) ret = `${modifiers.predicate}?${ret}`
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
export function parseArgumentType(type: string): [string, TlArgumentModifiers] {
|
||||
const modifiers: TlArgumentModifiers = {}
|
||||
const [predicate, type_] = type.split('?')
|
||||
|
||||
if (type_) {
|
||||
modifiers.predicate = predicate
|
||||
type = type_
|
||||
}
|
||||
|
||||
if (type.startsWith('Vector<')) {
|
||||
modifiers.isVector = true
|
||||
type = type.substring(7, type.length - 1)
|
||||
} else if (type.startsWith('vector<') || type.startsWith('%vector<')) {
|
||||
modifiers.isBareVector = true
|
||||
type = type.substring(7, type.length - 1)
|
||||
}
|
||||
|
||||
if (type.startsWith('%')) {
|
||||
modifiers.isBareUnion = true
|
||||
type = type.substring(1)
|
||||
}
|
||||
|
||||
return [type, modifiers]
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
import { generateReaderCodeForTlEntry } from '../../src/codegen/reader'
|
||||
import { parseTlToEntries } from '../../src/parse'
|
||||
import { generateReaderCodeForTlEntry, parseTlToEntries } from '../../src'
|
||||
|
||||
describe('generateReaderCodeForTlEntry', () => {
|
||||
const test = (tl: string, ...js: string[]) => {
|
||||
const entry = parseTlToEntries(tl)[0]
|
||||
const entry = parseTlToEntries(tl).slice(-1)[0]
|
||||
expect(generateReaderCodeForTlEntry(entry)).eq(
|
||||
`${entry.id}:function(r){${js.join('')}},`,
|
||||
)
|
||||
|
@ -139,9 +138,38 @@ describe('generateReaderCodeForTlEntry', () => {
|
|||
)
|
||||
})
|
||||
|
||||
it('generates code for bare types', () => {
|
||||
test(
|
||||
'message#0949d9dc = Message;\n' +
|
||||
'msg_container#73f1f8dc messages:vector<%Message> = MessageContainer;',
|
||||
'return{',
|
||||
"_:'msg_container',",
|
||||
'messages:r.vector(m[155834844],1),',
|
||||
'}',
|
||||
)
|
||||
test(
|
||||
'future_salt#0949d9dc = FutureSalt;\n' +
|
||||
'future_salts#ae500895 salts:Vector<future_salt> current:FutureSalt = FutureSalts;',
|
||||
'return{',
|
||||
"_:'future_salts',",
|
||||
'salts:r.vector(m[155834844]),',
|
||||
'current:r.object(),',
|
||||
'}',
|
||||
)
|
||||
test(
|
||||
'future_salt#0949d9dc = FutureSalt;\n' +
|
||||
'future_salts#ae500895 salts:vector<future_salt> current:future_salt = FutureSalts;',
|
||||
'return{',
|
||||
"_:'future_salts',",
|
||||
'salts:r.vector(m[155834844],1),',
|
||||
'current:m[155834844](r),',
|
||||
'}',
|
||||
)
|
||||
})
|
||||
|
||||
it('generates code with raw flags for constructors with flags', () => {
|
||||
const entry = parseTlToEntries('test flags:# flags2:# = Test;')[0]
|
||||
expect(generateReaderCodeForTlEntry(entry, true)).eq(
|
||||
expect(generateReaderCodeForTlEntry(entry, { includeFlags: true })).eq(
|
||||
`${entry.id}:function(r){${[
|
||||
'var flags=r.uint(),',
|
||||
'flags2=r.uint();',
|
||||
|
|
|
@ -4,9 +4,9 @@ import { describe, it } from 'mocha'
|
|||
import {
|
||||
generateTypescriptDefinitionsForTlEntry,
|
||||
generateTypescriptDefinitionsForTlSchema,
|
||||
} from '../../src/codegen/types'
|
||||
import { parseTlToEntries } from '../../src/parse'
|
||||
import { parseFullTlSchema } from '../../src/schema'
|
||||
parseFullTlSchema,
|
||||
parseTlToEntries,
|
||||
} from '../../src'
|
||||
|
||||
describe('generateTypescriptDefinitionsForTlEntry', () => {
|
||||
const test = (tl: string, ...ts: string[]) => {
|
||||
|
@ -112,7 +112,7 @@ describe('generateTypescriptDefinitionsForTlEntry', () => {
|
|||
it('wraps long comments', () => {
|
||||
test(
|
||||
'// This is a test constructor with a very very very very very very very very long comment\n' +
|
||||
'test = Test;',
|
||||
'test = Test;',
|
||||
'/**',
|
||||
' * This is a test constructor with a very very very very very',
|
||||
' * very very very long comment',
|
||||
|
@ -124,8 +124,8 @@ describe('generateTypescriptDefinitionsForTlEntry', () => {
|
|||
|
||||
test(
|
||||
'---functions---\n' +
|
||||
'// This is a test method with a very very very very very very very very long comment\n' +
|
||||
'test = Test;',
|
||||
'// This is a test method with a very very very very very very very very long comment\n' +
|
||||
'test = Test;',
|
||||
'/**',
|
||||
' * This is a test method with a very very very very very very',
|
||||
' * very very long comment',
|
||||
|
@ -141,7 +141,7 @@ describe('generateTypescriptDefinitionsForTlEntry', () => {
|
|||
it('should not break @link tags', () => {
|
||||
test(
|
||||
'// This is a test constructor with a very long comment {@link whatever} more text\n' +
|
||||
'test = Test;',
|
||||
'test = Test;',
|
||||
'/**',
|
||||
' * This is a test constructor with a very long comment',
|
||||
' * {@link whatever} more text',
|
||||
|
@ -156,7 +156,7 @@ describe('generateTypescriptDefinitionsForTlEntry', () => {
|
|||
it('writes generic types', () => {
|
||||
test(
|
||||
'---functions---\ninvokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;',
|
||||
'interface RawInvokeWithoutUpdatesRequest<X extends tl.TlObject> {',
|
||||
'interface RawInvokeWithoutUpdatesRequest<X extends tl.TlObject = tl.TlObject> {',
|
||||
" _: 'invokeWithoutUpdates';",
|
||||
' query: X;',
|
||||
'}',
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
import { generateWriterCodeForTlEntry } from '../../src/codegen/writer'
|
||||
import { parseTlToEntries } from '../../src/parse'
|
||||
import {
|
||||
generateWriterCodeForTlEntries,
|
||||
generateWriterCodeForTlEntry,
|
||||
parseTlToEntries,
|
||||
} from '../../src'
|
||||
|
||||
describe('generateWriterCodeForTlEntry', () => {
|
||||
const test = (tl: string, ...js: string[]) => {
|
||||
const entry = parseTlToEntries(tl)[0]
|
||||
const entry = parseTlToEntries(tl).slice(-1)[0]
|
||||
expect(generateWriterCodeForTlEntry(entry)).eq(
|
||||
`'${entry.name}':function(w${
|
||||
entry.arguments.length ? ',v' : ''
|
||||
|
@ -21,30 +24,21 @@ describe('generateWriterCodeForTlEntry', () => {
|
|||
it('generates code for constructors with simple arguments', () => {
|
||||
test(
|
||||
'inputBotInlineMessageID#890c3d89 dc_id:int id:long access_hash:long = InputBotInlineMessageID;',
|
||||
"h(v,'dcId');",
|
||||
'w.int(v.dcId);',
|
||||
"h(v,'id');",
|
||||
'w.long(v.id);',
|
||||
"h(v,'accessHash');",
|
||||
'w.long(v.accessHash);',
|
||||
"w.int(h(v,'dcId'));",
|
||||
"w.long(h(v,'id'));",
|
||||
"w.long(h(v,'accessHash'));",
|
||||
)
|
||||
test(
|
||||
'contact#145ade0b user_id:long mutual:Bool = Contact;',
|
||||
"h(v,'userId');",
|
||||
'w.long(v.userId);',
|
||||
"h(v,'mutual');",
|
||||
'w.boolean(v.mutual);',
|
||||
"w.long(h(v,'userId'));",
|
||||
"w.boolean(h(v,'mutual'));",
|
||||
)
|
||||
test(
|
||||
'maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords;',
|
||||
"h(v,'n');",
|
||||
'w.int(v.n);',
|
||||
"h(v,'x');",
|
||||
'w.double(v.x);',
|
||||
"h(v,'y');",
|
||||
'w.double(v.y);',
|
||||
"h(v,'zoom');",
|
||||
'w.double(v.zoom);',
|
||||
"w.int(h(v,'n'));",
|
||||
"w.double(h(v,'x'));",
|
||||
"w.double(h(v,'y'));",
|
||||
"w.double(h(v,'zoom'));",
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -65,8 +59,7 @@ describe('generateWriterCodeForTlEntry', () => {
|
|||
'var _timeout=v.timeout!==undefined;',
|
||||
'if(_timeout)flags|=2;',
|
||||
'w.uint(flags);',
|
||||
"h(v,'pts');",
|
||||
'w.int(v.pts);',
|
||||
"w.int(h(v,'pts'));",
|
||||
'if(_timeout)w.int(v.timeout);',
|
||||
)
|
||||
})
|
||||
|
@ -79,8 +72,7 @@ describe('generateWriterCodeForTlEntry', () => {
|
|||
'var _timeout=v.timeout!==undefined;',
|
||||
'if(_timeout)flags|=2;',
|
||||
'w.uint(flags);',
|
||||
"h(v,'pts');",
|
||||
'w.int(v.pts);',
|
||||
"w.int(h(v,'pts'));",
|
||||
'if(_timeout)w.int(v.timeout);',
|
||||
'var flags2=0;',
|
||||
'if(v.canDeleteChannel===true)flags2|=1;',
|
||||
|
@ -91,12 +83,9 @@ describe('generateWriterCodeForTlEntry', () => {
|
|||
it('generates code for constructors with vector arguments', () => {
|
||||
test(
|
||||
'contacts.resolvedPeer#7f077ad9 peer:Peer chats:Vector<Chat> users:Vector<User> = contacts.ResolvedPeer;',
|
||||
"h(v,'peer');",
|
||||
'w.object(v.peer);',
|
||||
"h(v,'chats');",
|
||||
'w.vector(w.object, v.chats);',
|
||||
"h(v,'users');",
|
||||
'w.vector(w.object, v.users);',
|
||||
"w.object(h(v,'peer'));",
|
||||
"w.vector(w.object,h(v,'chats'));",
|
||||
"w.vector(w.object,h(v,'users'));",
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -107,25 +96,55 @@ describe('generateWriterCodeForTlEntry', () => {
|
|||
'var _entities=v.entities&&v.entities.length;',
|
||||
'if(_entities)flags|=8;',
|
||||
'w.uint(flags);',
|
||||
"h(v,'message');",
|
||||
'w.string(v.message);',
|
||||
'if(_entities)w.vector(w.object, v.entities);',
|
||||
"w.string(h(v,'message'));",
|
||||
'if(_entities)w.vector(w.object,v.entities);',
|
||||
)
|
||||
})
|
||||
|
||||
it('generates code for constructors with generics', () => {
|
||||
test(
|
||||
'invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;',
|
||||
"h(v,'layer');",
|
||||
'w.int(v.layer);',
|
||||
"h(v,'query');",
|
||||
'w.object(v.query);',
|
||||
"w.int(h(v,'layer'));",
|
||||
"w.object(h(v,'query'));",
|
||||
)
|
||||
})
|
||||
|
||||
it('generates code for bare vectors', () => {
|
||||
test(
|
||||
'message#0949d9dc = Message;\n' +
|
||||
'msg_container#73f1f8dc messages:vector<%Message> = MessageContainer;',
|
||||
"w.vector(m._bare[155834844],h(v,'messages'),1);",
|
||||
)
|
||||
test(
|
||||
'future_salt#0949d9dc = FutureSalt;\n' +
|
||||
'future_salts#ae500895 salts:Vector<future_salt> current:FutureSalt = FutureSalts;',
|
||||
"w.vector(m._bare[155834844],h(v,'salts'));",
|
||||
"w.object(h(v,'current'));",
|
||||
)
|
||||
})
|
||||
|
||||
it('generates code for bare types', () => {
|
||||
const entries = parseTlToEntries(
|
||||
'future_salt#0949d9dc salt:bytes = FutureSalt;\n' +
|
||||
'future_salts#ae500895 salts:vector<future_salt> current:future_salt = FutureSalts;',
|
||||
)
|
||||
|
||||
expect(
|
||||
generateWriterCodeForTlEntries(entries, { includePrelude: false }),
|
||||
).eq(
|
||||
`
|
||||
var m={
|
||||
'future_salt':function(w,v){w.uint(155834844);w.bytes(h(v,'salt'));},
|
||||
'future_salts':function(w,v){w.uint(2924480661);w.vector(m._bare[155834844],h(v,'salts'),1);m._bare[155834844](w,h(v,'current'));},
|
||||
_bare:{
|
||||
155834844:function(w,v){w.bytes(h(v,'salt'));},
|
||||
}}`.replace(/^\s+/gm, ''),
|
||||
)
|
||||
})
|
||||
|
||||
it('generates code with raw flags for constructors with flags', () => {
|
||||
const entry = parseTlToEntries('test flags:# flags2:# = Test;')[0]
|
||||
expect(generateWriterCodeForTlEntry(entry, true)).eq(
|
||||
expect(generateWriterCodeForTlEntry(entry, { includeFlags: true })).eq(
|
||||
`'${entry.name}':function(w,v){${[
|
||||
`w.uint(${entry.id});`,
|
||||
'var flags=v.flags;',
|
||||
|
|
|
@ -4,8 +4,9 @@ import { describe, it } from 'mocha'
|
|||
import {
|
||||
computeConstructorIdFromEntry,
|
||||
computeConstructorIdFromString,
|
||||
} from '../src/ctor-id'
|
||||
import { TlEntry } from '../src/types'
|
||||
TlArgument,
|
||||
TlEntry,
|
||||
} from '../src'
|
||||
|
||||
describe('computeConstructorIdFromString', () => {
|
||||
const test = (tl: string, expected: number) => {
|
||||
|
@ -75,8 +76,10 @@ describe('computeConstructorIdFromEntry', () => {
|
|||
return {
|
||||
name: a[0],
|
||||
type: t[1],
|
||||
predicate: t[0],
|
||||
}
|
||||
typeModifiers: {
|
||||
predicate: t[0],
|
||||
},
|
||||
} satisfies TlArgument
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -99,9 +99,9 @@ describe('generateTlEntriesDifference', () => {
|
|||
},
|
||||
{
|
||||
name: 'egg',
|
||||
predicate: {
|
||||
old: 'flags.0',
|
||||
new: 'flags.1',
|
||||
type: {
|
||||
old: 'flags.0?Egg',
|
||||
new: 'flags.1?Egg',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -44,18 +44,19 @@ describe('mergeTlEntries', () => {
|
|||
'test flags:# baz:flags.1?true = Test;',
|
||||
'test#e86481ba flags:# foo:flags.0?true bar:flags.0?true baz:flags.1?true = Test;',
|
||||
)
|
||||
// ordering of optional flags should not matter
|
||||
test(
|
||||
'test flags:# foo:flags.0?true = Test;\n' +
|
||||
'test flags:# bar:flags.0?true = Test;\n' +
|
||||
'test flags:# baz:flags.1?true = Test;',
|
||||
'test#e86481ba flags:# foo:flags.0?true bar:flags.0?true baz:flags.1?true = Test;',
|
||||
'test#e86481ba flags:# bar:flags.0?true baz:flags.1?true foo:flags.0?true = Test;',
|
||||
)
|
||||
test(
|
||||
'test flags:# foo:flags.0?true = Test;\n' +
|
||||
'test flags:# foo:flags.0?true bar:flags.0?true = Test;\n' +
|
||||
'test flags:# baz:flags.1?true = Test;\n' +
|
||||
'test flags:# bar:flags.0?true baz:flags.1?true = Test;',
|
||||
'test#e86481ba flags:# foo:flags.0?true bar:flags.0?true baz:flags.1?true = Test;',
|
||||
'test#e86481ba flags:# bar:flags.0?true baz:flags.1?true foo:flags.0?true = Test;',
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
import { parseTlToEntries } from '../src/parse'
|
||||
import { TlEntry } from '../src/types'
|
||||
import { parseTlToEntries, TlEntry } from '../src'
|
||||
|
||||
describe('tl parser', () => {
|
||||
const test = (tl: string, expected: TlEntry[]) => {
|
||||
expect(parseTlToEntries(tl)).eql(expected)
|
||||
const test = (
|
||||
tl: string,
|
||||
expected: TlEntry[],
|
||||
params?: Parameters<typeof parseTlToEntries>[1],
|
||||
) => {
|
||||
expect(parseTlToEntries(tl, params)).eql(expected)
|
||||
}
|
||||
|
||||
it('skips empty lines and comments', () => {
|
||||
|
@ -69,6 +72,132 @@ boolTrue#997275b5 = Bool;
|
|||
])
|
||||
})
|
||||
|
||||
it('parses vectors', () => {
|
||||
test('msg_resend_req#7d861a08 msg_ids:Vector<long> = MsgResendReq;', [
|
||||
{
|
||||
kind: 'class',
|
||||
name: 'msg_resend_req',
|
||||
id: 0x7d861a08,
|
||||
type: 'MsgResendReq',
|
||||
arguments: [
|
||||
{
|
||||
name: 'msg_ids',
|
||||
type: 'long',
|
||||
typeModifiers: {
|
||||
isVector: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('parses bare vectors', () => {
|
||||
// note: not from schema, schema uses bare `future_salt` instead
|
||||
test(
|
||||
'future_salts#ae500895 req_msg_id:long now:int salts:vector<FutureSalt> = FutureSalts;',
|
||||
[
|
||||
{
|
||||
kind: 'class',
|
||||
name: 'future_salts',
|
||||
id: 0xae500895,
|
||||
type: 'FutureSalts',
|
||||
arguments: [
|
||||
{
|
||||
name: 'req_msg_id',
|
||||
type: 'long',
|
||||
},
|
||||
{
|
||||
name: 'now',
|
||||
type: 'int',
|
||||
},
|
||||
{
|
||||
name: 'salts',
|
||||
type: 'FutureSalt',
|
||||
typeModifiers: {
|
||||
isBareVector: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
it('parses bare unions', () => {
|
||||
test(
|
||||
'message#0949d9dc = Message;\n' + // stub so we can reference it
|
||||
'msg_container#73f1f8dc messages:vector<%Message> = MessageContainer;',
|
||||
[
|
||||
{
|
||||
kind: 'class',
|
||||
name: 'message',
|
||||
id: 0x0949d9dc,
|
||||
type: 'Message',
|
||||
arguments: [],
|
||||
},
|
||||
{
|
||||
kind: 'class',
|
||||
name: 'msg_container',
|
||||
id: 0x73f1f8dc,
|
||||
type: 'MessageContainer',
|
||||
arguments: [
|
||||
{
|
||||
name: 'messages',
|
||||
type: 'Message',
|
||||
typeModifiers: {
|
||||
isBareVector: true,
|
||||
isBareUnion: true,
|
||||
constructorId: 0x0949d9dc,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
it('parses bare types', () => {
|
||||
test(
|
||||
'future_salt#0949d9dc = FutureSalt;\n' + // stub so we can reference it
|
||||
'future_salts#ae500895 req_msg_id:long now:int salts:vector<future_salt> = FutureSalts;',
|
||||
[
|
||||
{
|
||||
kind: 'class',
|
||||
name: 'future_salt',
|
||||
id: 0x0949d9dc,
|
||||
type: 'FutureSalt',
|
||||
arguments: [],
|
||||
},
|
||||
{
|
||||
kind: 'class',
|
||||
name: 'future_salts',
|
||||
id: 0xae500895,
|
||||
type: 'FutureSalts',
|
||||
arguments: [
|
||||
{
|
||||
name: 'req_msg_id',
|
||||
type: 'long',
|
||||
},
|
||||
{
|
||||
name: 'now',
|
||||
type: 'int',
|
||||
},
|
||||
{
|
||||
name: 'salts',
|
||||
type: 'future_salt',
|
||||
typeModifiers: {
|
||||
isBareVector: true,
|
||||
isBareType: true,
|
||||
constructorId: 0x0949d9dc,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
it('parses methods with arguments', () => {
|
||||
test(
|
||||
'---functions---\nauth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization;',
|
||||
|
@ -148,7 +277,7 @@ boolTrue#997275b5 = Bool;
|
|||
|
||||
it('parses predicates', () => {
|
||||
test(
|
||||
'help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector<Chat> users:Vector<User> psa_type:flags.1?string psa_message:flags.2?string = help.PromoData;',
|
||||
'help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer psa_type:flags.1?string psa_message:flags.2?string = help.PromoData;',
|
||||
[
|
||||
{
|
||||
kind: 'class',
|
||||
|
@ -163,7 +292,9 @@ boolTrue#997275b5 = Bool;
|
|||
{
|
||||
name: 'proxy',
|
||||
type: 'true',
|
||||
predicate: 'flags.0',
|
||||
typeModifiers: {
|
||||
predicate: 'flags.0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'expires',
|
||||
|
@ -173,23 +304,19 @@ boolTrue#997275b5 = Bool;
|
|||
name: 'peer',
|
||||
type: 'Peer',
|
||||
},
|
||||
{
|
||||
name: 'chats',
|
||||
type: 'Vector<Chat>',
|
||||
},
|
||||
{
|
||||
name: 'users',
|
||||
type: 'Vector<User>',
|
||||
},
|
||||
{
|
||||
name: 'psa_type',
|
||||
type: 'string',
|
||||
predicate: 'flags.1',
|
||||
typeModifiers: {
|
||||
predicate: 'flags.1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'psa_message',
|
||||
type: 'string',
|
||||
predicate: 'flags.2',
|
||||
typeModifiers: {
|
||||
predicate: 'flags.2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -299,4 +426,42 @@ users.getUsers id:Vector<InputUser> = Vector<User>;
|
|||
'yet another at the end',
|
||||
])
|
||||
})
|
||||
|
||||
it('applies prefix to constructors', () => {
|
||||
test(
|
||||
'future_salt#0949d9dc = FutureSalt;\n' + // stub to reference
|
||||
'future_salts#ae500895 salts:vector<future_salt> current:FutureSalt = FutureSalts;',
|
||||
[
|
||||
{
|
||||
kind: 'class',
|
||||
name: 'mt_future_salt',
|
||||
id: 0x0949d9dc,
|
||||
type: 'FutureSalt',
|
||||
arguments: [],
|
||||
},
|
||||
{
|
||||
kind: 'class',
|
||||
name: 'mt_future_salts',
|
||||
id: 0xae500895,
|
||||
type: 'FutureSalts',
|
||||
arguments: [
|
||||
{
|
||||
name: 'salts',
|
||||
type: 'mt_future_salt',
|
||||
typeModifiers: {
|
||||
isBareVector: true,
|
||||
isBareType: true,
|
||||
constructorId: 0x0949d9dc,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'current',
|
||||
type: 'FutureSalt', // prefix is not applied to non-constructors
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
{ prefix: 'mt_' },
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ import { expect } from 'chai'
|
|||
import { describe, it } from 'mocha'
|
||||
|
||||
import { writeTlEntryToString } from '../src/stringify'
|
||||
import { TlEntry } from '../src/types'
|
||||
import { TlArgument, TlEntry } from '../src/types'
|
||||
|
||||
describe('writeTlEntryToString', () => {
|
||||
const make = (name: string, type: string, ...args: string[]): TlEntry => ({
|
||||
|
@ -18,14 +18,16 @@ describe('writeTlEntryToString', () => {
|
|||
return {
|
||||
name: a[0],
|
||||
type: t[1],
|
||||
predicate: t[0],
|
||||
}
|
||||
typeModifiers: {
|
||||
predicate: t[0],
|
||||
},
|
||||
} satisfies TlArgument
|
||||
}
|
||||
|
||||
return {
|
||||
name: a[0],
|
||||
type: t[0],
|
||||
}
|
||||
} satisfies TlArgument
|
||||
}),
|
||||
})
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"What is this?": [
|
||||
"It is guaranteed that user/chat/channel ids fit in int53,",
|
||||
"so we can safely replace `long` with `int53` there.",
|
||||
"as well as file size (up to 4 gigs),",
|
||||
"so we can safely replace `long` with `int53` for simpler usage from within JS.",
|
||||
"Note: this is a non-exhaustive list",
|
||||
"When contributing, please maintain alphabetical key ordering"
|
||||
],
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -24,10 +24,7 @@ async function main() {
|
|||
const schema = await fetchMtprotoSchema()
|
||||
|
||||
console.log('Parsing...')
|
||||
let entries = parseTlToEntries(schema, {
|
||||
prefix: 'mt_',
|
||||
applyPrefixToArguments: true,
|
||||
})
|
||||
let entries = parseTlToEntries(schema, { prefix: 'mt_' })
|
||||
|
||||
// remove manually parsed types
|
||||
entries = entries.filter(
|
||||
|
|
|
@ -56,11 +56,16 @@ async function generateReaders(
|
|||
) {
|
||||
console.log('Generating readers...')
|
||||
|
||||
let code = generateReaderCodeForTlEntries(apiSchema.entries, 'r', false)
|
||||
let code = generateReaderCodeForTlEntries(apiSchema.entries, {
|
||||
variableName: 'm',
|
||||
includeMethods: false,
|
||||
})
|
||||
|
||||
const mtpCode = generateReaderCodeForTlEntries(mtpSchema.entries, '')
|
||||
code = code.substring(0, code.length - 1) + mtpCode.substring(7)
|
||||
code += '\nexports.default = r;'
|
||||
const mtpCode = generateReaderCodeForTlEntries(mtpSchema.entries, {
|
||||
variableName: 'm',
|
||||
})
|
||||
code = code.substring(0, code.length - 1) + mtpCode.substring(8)
|
||||
code += '\nexports.default = m;'
|
||||
|
||||
await writeFile(OUT_READERS_FILE, ESM_PRELUDE + code)
|
||||
}
|
||||
|
@ -71,11 +76,16 @@ async function generateWriters(
|
|||
) {
|
||||
console.log('Generating writers...')
|
||||
|
||||
let code = generateWriterCodeForTlEntries(apiSchema.entries, 'r')
|
||||
let code = generateWriterCodeForTlEntries(apiSchema.entries, {
|
||||
variableName: 'm',
|
||||
})
|
||||
|
||||
const mtpCode = generateWriterCodeForTlEntries(mtpSchema.entries, '', false)
|
||||
const mtpCode = generateWriterCodeForTlEntries(mtpSchema.entries, {
|
||||
variableName: 'm',
|
||||
includePrelude: false,
|
||||
})
|
||||
code = code.substring(0, code.length - 1) + mtpCode.substring(7)
|
||||
code += '\nexports.default = r;'
|
||||
code += '\nexports.default = m;'
|
||||
|
||||
await writeFile(OUT_WRITERS_FILE, ESM_PRELUDE + code)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue