@eliseev_s/tolk-tlb-transpiler
Version:
Transpile Tolk structs to TLB definitions and generate TypeScript wrappers for TON blockchain smart contracts
390 lines • 15.5 kB
JavaScript
"use strict";
// ============================================================================
// TLB Generation Logic
// ============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.TLBGenerator = void 0;
const types_1 = require("./types");
const constants_1 = require("./constants");
const utils_1 = require("./utils");
/**
* Utilities for common TLB generation patterns
*/
class TLBGenerationUtils {
static formatPrefix(prefix) {
if (prefix.format === 'hex') {
return `#${prefix.value
.toString(16)
.toUpperCase()
.padStart(Math.ceil(prefix.length / 4), '0')}`;
}
else {
return `$${prefix.value.toString(2).padStart(prefix.length, '0')}`;
}
}
static formatUnionPrefix(tag, explicitPrefix, totalAlternatives) {
if (explicitPrefix) {
return `#${tag
.toString(16)
.toUpperCase()
.padStart(Math.max(2, Math.ceil(explicitPrefix.length / 4)), '0')}`;
}
else if (totalAlternatives !== undefined) {
return `$${tag.toString(2).padStart((0, utils_1.calculateRequiredBits)(totalAlternatives), '0')}`;
}
return '#_';
}
static formatFieldsToTLB(fields, typeConverter) {
return fields.map((field) => `${field.name}:${typeConverter(field.type)}`).join(' ');
}
static formatAnonymousFields(items, typeConverter) {
return items.map((item) => `_:${typeConverter(item)}`).join(' ');
}
static buildConstructor(constructorName, prefix, fields, typeName) {
return `${constructorName}${prefix} ${fields} = ${typeName};`;
}
static applyTypeMappingWithFallback(typeName) {
return constants_1.TYPE_MAPPINGS[typeName] || typeName;
}
static validateUnionType(type) {
return type.kind === 'union';
}
static hasExactlyTwoAlternatives(unionType) {
if (!TLBGenerationUtils.validateUnionType(unionType)) {
return false;
}
return unionType.alternatives.length === 2;
}
static extractNonNullTypeFromUnion(unionType) {
if (!TLBGenerationUtils.validateUnionType(unionType))
return null;
return (unionType.alternatives.find((alt) => !(alt.kind === 'type_identifier' && alt.name === 'null')) || null);
}
static createUnionTypeName(unionType) {
if (!TLBGenerationUtils.validateUnionType(unionType))
return 'Cell';
const variantNames = unionType.alternatives
.map((alt) => {
if (alt.kind === 'type_identifier')
return alt.name;
if (alt.kind === 'primitive')
return alt.name;
return 'Variant';
})
.join('');
return `${variantNames}Union`;
}
static formatEitherType(leftTLB, rightTLB) {
return `(Either ${leftTLB} ${rightTLB})`;
}
static formatMaybeType(innerTLB) {
return `(Maybe ${innerTLB})`;
}
static formatCellRefType(innerTLB) {
return `^${innerTLB}`;
}
static formatCollectionType(items) {
return `[${items}]`;
}
}
/**
* Handlers for different union strategies
*/
class UnionStrategyHandlers {
context;
typeConverter;
constructor(context, typeConverter) {
this.context = context;
this.typeConverter = typeConverter;
}
handleMaybePattern(unionType) {
const nonNullType = TLBGenerationUtils.extractNonNullTypeFromUnion(unionType);
if (nonNullType) {
return TLBGenerationUtils.formatMaybeType(this.typeConverter(nonNullType));
}
return 'Cell';
}
handleEitherPattern(unionType) {
if (!TLBGenerationUtils.hasExactlyTwoAlternatives(unionType)) {
return 'Cell';
}
const [leftType, rightType] = unionType.alternatives;
if (leftType && rightType) {
const leftTLB = this.typeConverter(leftType);
const rightTLB = this.typeConverter(rightType);
return TLBGenerationUtils.formatEitherType(leftTLB, rightTLB);
}
return 'Cell';
}
handleCustomPrefix(unionType) {
const unionTypeName = TLBGenerationUtils.createUnionTypeName(unionType);
this.context.inlineUnions.set(unionTypeName, unionType);
return unionTypeName;
}
}
/**
* Specialized type converters for different TolkType kinds
*/
class TypeConverters {
context;
mainConverter;
constructor(context, mainConverter) {
this.context = context;
this.mainConverter = mainConverter;
}
convertPrimitive(type) {
return TLBGenerationUtils.applyTypeMappingWithFallback(type.name);
}
convertTypeIdentifier(type) {
const typeAlias = this.context.typeAliases.get(type.name);
if (typeAlias?.underlyingType.kind === 'union') {
const strategy = UnionStrategyDeterminer.determine(typeAlias.underlyingType, this.context);
if (strategy === types_1.UnionStrategy.MAYBE_PATTERN) {
const handlers = new UnionStrategyHandlers(this.context, this.mainConverter);
return handlers.handleMaybePattern(typeAlias.underlyingType);
}
}
return TLBGenerationUtils.applyTypeMappingWithFallback(type.name);
}
convertGeneric(type) {
// Handle Cell<T> specially
if (type.name === 'Cell' && type.params.length > 0 && type.params[0]) {
return TLBGenerationUtils.formatCellRefType(this.mainConverter(type.params[0]));
}
// Handle generic type aliases
const genericTypeAlias = this.context.typeAliases.get(type.name);
if (genericTypeAlias?.underlyingType.kind === 'union') {
return this.handleGenericUnion(type, genericTypeAlias);
}
return type.name;
}
handleGenericUnion(type, typeAlias) {
const strategy = UnionStrategyDeterminer.determine(typeAlias.underlyingType, this.context);
if (strategy === types_1.UnionStrategy.EITHER_PATTERN && type.params.length === 2) {
const [leftType, rightType] = type.params;
if (leftType && rightType) {
const leftTLB = this.mainConverter(leftType);
const rightTLB = this.mainConverter(rightType);
return TLBGenerationUtils.formatEitherType(leftTLB, rightTLB);
}
}
if (strategy === types_1.UnionStrategy.MAYBE_PATTERN &&
type.params.length === 1 &&
type.params[0]) {
return TLBGenerationUtils.formatMaybeType(this.mainConverter(type.params[0]));
}
return type.name;
}
convertCollectionType(type) {
const items = TLBGenerationUtils.formatAnonymousFields(type.items, this.mainConverter);
return TLBGenerationUtils.formatCollectionType(items);
}
convertSizedType(type) {
switch (type.kind) {
case 'bits':
return `bits${type.width}`;
case 'bytes':
return `bits${(type.size || 0) * 8}`;
case 'int':
return `int${type.width}`;
case 'uint':
return `uint${type.width}`;
default:
return 'Cell';
}
}
convertSpecialType(type) {
return TLBGenerationUtils.applyTypeMappingWithFallback(type.name) || 'Cell';
}
convertCellRef(type) {
return type.inner
? TLBGenerationUtils.formatCellRefType(this.mainConverter(type.inner))
: '^Cell';
}
convertNullable(type) {
return TLBGenerationUtils.formatMaybeType(this.mainConverter(type.inner));
}
}
/**
* Union strategy determination logic
*/
class UnionStrategyDeterminer {
static determine(unionType, context) {
if (!TLBGenerationUtils.validateUnionType(unionType)) {
return types_1.UnionStrategy.CUSTOM_PREFIX;
}
// Check for Maybe pattern (T | null)
if (TLBGenerationUtils.hasExactlyTwoAlternatives(unionType)) {
const hasNull = unionType.alternatives.some((alt) => alt.kind === 'type_identifier' && alt.name === 'null');
if (hasNull)
return types_1.UnionStrategy.MAYBE_PATTERN;
}
// Check for Either pattern (2 alternatives, no explicit prefixes)
if (TLBGenerationUtils.hasExactlyTwoAlternatives(unionType)) {
const hasExplicitPrefixes = unionType.alternatives.every((alt) => {
if (alt.kind === 'type_identifier') {
const struct = context.structs.get(alt.name);
return struct?.prefix;
}
return false;
});
if (!hasExplicitPrefixes)
return types_1.UnionStrategy.EITHER_PATTERN;
}
return types_1.UnionStrategy.CUSTOM_PREFIX;
}
}
class TLBGenerator {
context;
constructor(structs, typeAliases) {
this.context = {
structs,
typeAliases,
tagRegistry: new Map(),
nextAutoTag: 0,
inlineUnions: new Map(),
builtInTypes: new Set([
'Maybe',
'Either',
'Bool',
'Cell',
'Tuple',
'Grams',
'MsgAddress',
]),
};
}
generateTLB() {
const tlbDefinitions = [];
// Clear previous state for fresh generation
this.context.inlineUnions.clear();
// Process struct declarations
for (const [name, struct] of this.context.structs) {
const tlb = this.generateStructTLB(struct);
tlbDefinitions.push(tlb);
}
// Process type alias declarations
for (const [name, typeAlias] of this.context.typeAliases) {
if (typeAlias.underlyingType.kind === 'union') {
const tlb = this.generateUnionTLB(name, typeAlias.underlyingType);
tlbDefinitions.push(tlb);
}
}
// Process inline unions discovered during processing
for (const [unionTypeName, unionType] of this.context.inlineUnions) {
const tlb = this.generateUnionTLB(unionTypeName, unionType);
tlbDefinitions.push(tlb);
}
return tlbDefinitions.filter((def) => def.statement.length > 0);
}
generateStructTLB(struct) {
const constructorName = (0, utils_1.toSnakeCase)(struct.name);
const prefix = struct.prefix ? TLBGenerationUtils.formatPrefix(struct.prefix) : '#_';
const fields = TLBGenerationUtils.formatFieldsToTLB(struct.fields, (type) => this.tolkTypeToTLB(type));
const constructor = TLBGenerationUtils.buildConstructor(constructorName, prefix, fields, struct.name);
return {
name: struct.name,
statement: [constructor],
};
}
generateUnionTLB(typeName, unionType) {
if (!TLBGenerationUtils.validateUnionType(unionType)) {
return { name: typeName, statement: [] };
}
const strategy = UnionStrategyDeterminer.determine(unionType, this.context);
switch (strategy) {
case types_1.UnionStrategy.MAYBE_PATTERN:
case types_1.UnionStrategy.EITHER_PATTERN:
// Built-in types - don't generate definitions
return { name: typeName, statement: [] };
case types_1.UnionStrategy.CUSTOM_PREFIX:
return this.generateCustomPrefixTLB(typeName, unionType);
default:
return { name: typeName, statement: [] };
}
}
generateCustomPrefixTLB(typeName, unionType) {
if (!TLBGenerationUtils.validateUnionType(unionType)) {
return { name: typeName, statement: [] };
}
const constructors = [];
for (const alt of unionType.alternatives) {
if (alt.kind === 'type_identifier') {
const struct = this.context.structs.get(alt.name);
if (struct) {
const constructor = this.generateUnionConstructor(alt.name, struct, typeName, unionType.alternatives.length);
constructors.push(constructor);
}
}
}
return { name: typeName, statement: constructors };
}
generateUnionConstructor(altName, struct, typeName, totalAlternatives) {
const tag = this.getOrAssignTag(altName, struct.prefix);
const fields = TLBGenerationUtils.formatFieldsToTLB(struct.fields, (type) => this.tolkTypeToTLB(type));
const constructorName = (0, utils_1.toSnakeCase)(altName);
const prefix = TLBGenerationUtils.formatUnionPrefix(tag, struct.prefix, totalAlternatives);
return TLBGenerationUtils.buildConstructor(constructorName, prefix, fields, typeName);
}
getOrAssignTag(typeName, explicitPrefix) {
if (explicitPrefix !== undefined) {
this.context.tagRegistry.set(typeName, explicitPrefix.value);
return explicitPrefix.value;
}
if (this.context.tagRegistry.has(typeName)) {
return this.context.tagRegistry.get(typeName);
}
const autoTag = this.context.nextAutoTag++;
this.context.tagRegistry.set(typeName, autoTag);
return autoTag;
}
tolkTypeToTLB(type) {
const converters = new TypeConverters(this.context, (t) => this.tolkTypeToTLB(t));
const handlers = new UnionStrategyHandlers(this.context, (t) => this.tolkTypeToTLB(t));
switch (type.kind) {
case 'primitive':
return converters.convertPrimitive(type);
case 'type_identifier':
return converters.convertTypeIdentifier(type);
case 'nullable':
return converters.convertNullable(type);
case 'union':
return this.generateInlineUnionType(type, handlers);
case 'generic':
return converters.convertGeneric(type);
case 'tensor':
case 'tuple':
return converters.convertCollectionType(type);
case 'bits':
case 'bytes':
case 'int':
case 'uint':
return converters.convertSizedType(type);
case 'cell_ref':
return converters.convertCellRef(type);
case 'dict':
return '(Maybe ^Cell)';
case 'special':
return converters.convertSpecialType(type);
default:
return 'Cell';
}
}
generateInlineUnionType(unionType, handlers) {
if (!TLBGenerationUtils.validateUnionType(unionType))
return 'Cell';
const strategy = UnionStrategyDeterminer.determine(unionType, this.context);
switch (strategy) {
case types_1.UnionStrategy.MAYBE_PATTERN:
return handlers.handleMaybePattern(unionType);
case types_1.UnionStrategy.EITHER_PATTERN:
return handlers.handleEitherPattern(unionType);
case types_1.UnionStrategy.CUSTOM_PREFIX:
return handlers.handleCustomPrefix(unionType);
default:
return 'Cell';
}
}
}
exports.TLBGenerator = TLBGenerator;
//# sourceMappingURL=generator.js.map