UNPKG

@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
"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