UNPKG

@eliseev_s/tolk-tlb-transpiler

Version:

Transpile Tolk structs to TLB definitions and generate TypeScript wrappers for TON blockchain smart contracts

368 lines 14.8 kB
"use strict"; // ============================================================================ // Tolk AST Parser // ============================================================================ Object.defineProperty(exports, "__esModule", { value: true }); exports.TolkASTProcessor = void 0; const web_tree_sitter_1 = require("web-tree-sitter"); const tree_sitter_tolk_1 = require("@eliseev_s/tree-sitter-tolk"); const generator_1 = require("./generator"); const get_methods_parser_1 = require("./get-methods-parser"); class TolkASTProcessor { parser; structs = new Map(); typeAliases = new Map(); getMethods = []; constructor(parser) { this.parser = parser; } static async initialize() { await web_tree_sitter_1.Parser.init(); const language = await (0, tree_sitter_tolk_1.loadTolk)(); const parser = new web_tree_sitter_1.Parser(); parser.setLanguage(language); return new TolkASTProcessor(parser); } /** * Convert Tolk source code directly to TLB definitions */ transpile(sourceCode) { const tree = this.parser.parse(sourceCode); if (!tree) throw new Error('Tre is empty'); return this.processAST(tree); } parse(sourceCode) { const tree = this.parser.parse(sourceCode); if (!tree) { throw new Error('Failed to parse Tolk source code'); } return tree; } /** * Get parsed get methods */ getGetMethods() { return this.getMethods; } getStructs() { return Array.from(this.structs.values()); } getTypeAliases() { return Array.from(this.typeAliases.values()); } processAST(tree) { this.structs.clear(); this.typeAliases.clear(); this.getMethods = []; // First pass: collect all struct and type alias definitions this.collectDefinitions(tree.rootNode); // Parse get methods this.getMethods = get_methods_parser_1.GetMethodsParser.parseGetMethods(tree.rootNode, (node) => this.resolveType(node)); // Second pass: generate TLB using the generator const generator = new generator_1.TLBGenerator(this.structs, this.typeAliases); return generator.generateTLB(); } collectDefinitions(node) { if (node.type === 'struct_declaration') { const structInfo = this.processStructDeclaration(node); this.structs.set(structInfo.name, structInfo); } else if (node.type === 'type_alias_declaration') { const typeAlias = this.processTypeAlias(node); this.typeAliases.set(typeAlias.name, typeAlias); } // Recursively process children for (const child of node.namedChildren) { child && this.collectDefinitions(child); } } processStructDeclaration(node) { const nameNode = node.childForFieldName('name'); const name = nameNode ? nameNode.text : 'UnknownStruct'; const packPrefixNode = node.childForFieldName('pack_prefix'); const prefix = packPrefixNode ? this.extractPackPrefix(packPrefixNode) : undefined; const annotationsNode = node.childForFieldName('annotations'); const annotations = annotationsNode ? this.extractAnnotations(annotationsNode) : []; const genericTsNode = node.childForFieldName('genericTs'); const genericParams = genericTsNode ? this.extractGenerics(genericTsNode) : []; const fields = this.extractStructFields(node); return { name, prefix, fields, annotations, genericParams, }; } processTypeAlias(node) { const nameNode = node.childForFieldName('name'); const name = nameNode ? nameNode.text : 'UnknownType'; const underlyingTypeNode = node.childForFieldName('underlying_type'); const underlyingType = underlyingTypeNode ? this.resolveType(underlyingTypeNode) : { kind: 'unknown' }; const genericTsNode = node.childForFieldName('genericTs'); const genericParams = genericTsNode ? this.extractGenerics(genericTsNode) : []; return { name, underlyingType, genericParams, }; } extractPackPrefix(node) { const text = node.text.trim(); if (text.startsWith('0x')) { // Hexadecimal format const hexValue = text.slice(2); const value = parseInt(hexValue, 16); const length = hexValue.length * 4; // Each hex digit = 4 bits return { value, length, format: 'hex' }; } else if (text.startsWith('0b')) { // Binary format const binaryValue = text.slice(2); const value = parseInt(binaryValue, 2); const length = binaryValue.length; // Each binary digit = 1 bit return { value, length, format: 'binary' }; } else { // Decimal number - assume 8 bits for simplicity const value = parseInt(text, 10); return { value, length: 8, format: 'hex' }; } } extractAnnotations(node) { const annotations = []; for (const child of node.namedChildren) { if (child?.type === 'annotation') { const nameNode = child.childForFieldName('name'); const name = nameNode ? nameNode.text : ''; annotations.push({ name, parameters: [] }); } } return annotations; } extractGenerics(node) { // Simplified generic parameter extraction return []; } extractStructFields(node) { const fields = []; // Find the struct_body node let structBodyNode = null; for (let i = 0; i < node.namedChildren.length; i++) { const child = node.namedChildren[i]; if (child?.type === 'struct_body') { structBodyNode = child; break; } } if (!structBodyNode) { return fields; } // Now look for struct fields inside the struct_body for (let i = 0; i < structBodyNode.namedChildren.length; i++) { const child = structBodyNode.namedChildren[i]; if (child?.type === 'struct_field_declaration') { const nameNode = child.childForFieldName('name'); const typeNode = child.childForFieldName('type'); const defaultNode = child.childForFieldName('default'); if (nameNode && typeNode) { fields.push({ name: nameNode.text, type: this.resolveType(typeNode), default: defaultNode ? defaultNode.text : undefined, }); } } } return fields; } resolveType(typeNode) { switch (typeNode.type) { case 'primitive_type': return { kind: 'primitive', name: typeNode.text }; case 'type_identifier': { const text = typeNode.text; // Handle dict as special case if (text === 'dict') { return { kind: 'dict' }; } // Handle intN pattern (int1, int2, int3, ..., int257) const intMatch = text.match(/^int(\d+)$/); if (intMatch && intMatch[1]) { return { kind: 'int', width: parseInt(intMatch[1], 10) }; } // Handle uintN pattern (uint1, uint2, uint3, ..., uint256) const uintMatch = text.match(/^uint(\d+)$/); if (uintMatch && uintMatch[1]) { return { kind: 'uint', width: parseInt(uintMatch[1], 10) }; } // Handle bitsN pattern const bitsMatch = text.match(/^bits(\d+)$/); if (bitsMatch && bitsMatch[1]) { return { kind: 'bits', width: parseInt(bitsMatch[1], 10) }; } // Handle bytesN pattern const bytesMatch = text.match(/^bytes(\d+)$/); if (bytesMatch && bytesMatch[1]) { return { kind: 'bytes', size: parseInt(bytesMatch[1], 10) }; } return { kind: 'type_identifier', name: text }; } case 'nullable_type': { const innerNode = typeNode.namedChild(0); return { kind: 'nullable', inner: innerNode ? this.resolveType(innerNode) : { kind: 'unknown' }, }; } case 'union_type': return this.processUnionType(typeNode); case 'tensor_type': return this.processTensorType(typeNode); case 'tuple_type': return this.processTupleType(typeNode); case 'type_instantiatedTs': return this.processGenericType(typeNode); default: // Handle special type patterns const text = typeNode.text; // Handle intN pattern (int1, int2, int3, ..., int257) const intMatch = text.match(/^int(\d+)$/); if (intMatch && intMatch[1]) { return { kind: 'int', width: parseInt(intMatch[1], 10) }; } // Handle uintN pattern (uint1, uint2, uint3, ..., uint256) const uintMatch = text.match(/^uint(\d+)$/); if (uintMatch && uintMatch[1]) { return { kind: 'uint', width: parseInt(uintMatch[1], 10) }; } // Handle bitsN pattern const bitsMatch = text.match(/^bits(\d+)$/); if (bitsMatch && bitsMatch[1]) { return { kind: 'bits', width: parseInt(bitsMatch[1], 10) }; } // Handle bytesN pattern const bytesMatch = text.match(/^bytes(\d+)$/); if (bytesMatch && bytesMatch[1]) { return { kind: 'bytes', size: parseInt(bytesMatch[1], 10) }; } // Handle Cell<T> pattern if (text.startsWith('Cell<') && text.endsWith('>')) { const innerType = text.slice(5, -1); return { kind: 'cell_ref', inner: { kind: 'type_identifier', name: innerType }, }; } // Handle special types if (['RemainingBitsAndRefs', 'slice', 'builder'].includes(text)) { return { kind: 'special', name: text }; } // If it's a known type identifier, return it as such return { kind: 'type_identifier', name: text }; } } processUnionType(node) { const alternatives = this.flattenUnion(node); return { kind: 'union', alternatives }; } flattenUnion(node) { if (node.type !== 'union_type') { return [this.resolveType(node)]; } const lhsNode = node.childForFieldName('lhs'); const rhsNode = node.childForFieldName('rhs'); const lhsTypes = lhsNode ? this.flattenUnion(lhsNode) : []; const rhsTypes = rhsNode ? this.flattenUnion(rhsNode) : []; return [...lhsTypes, ...rhsTypes]; } processTensorType(node) { const items = []; for (const child of node.namedChildren) { child && items.push(this.resolveType(child)); } return { kind: 'tensor', items }; } processTupleType(node) { const items = []; for (const child of node.namedChildren) { if (child) items.push(this.resolveType(child)); } return { kind: 'tuple', items }; } processGenericType(node) { const text = node.text; const match = text.match(/^(\w+)<(.+)>$/); if (match && match[1] && match[2]) { const [, name, paramsText] = match; // Handle Cell<T> specially if (name === 'Cell') { return { kind: 'cell_ref', inner: { kind: 'type_identifier', name: paramsText.trim() }, }; } // Parse multiple type parameters separated by commas const params = []; const paramTokens = this.parseGenericParams(paramsText); for (const paramToken of paramTokens) { // For simple type identifiers, create a type_identifier const trimmed = paramToken.trim(); // Handle primitive types if ([ 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'int8', 'int16', 'int32', 'int64', 'int128', 'int256', 'bool', 'address', 'slice', 'cell', 'coins', ].includes(trimmed)) { params.push({ kind: 'primitive', name: trimmed }); } // Handle custom integer types else { const intMatch = trimmed.match(/^int(\d+)$/); if (intMatch && intMatch[1]) { params.push({ kind: 'int', width: parseInt(intMatch[1], 10) }); continue; } const uintMatch = trimmed.match(/^uint(\d+)$/); if (uintMatch && uintMatch[1]) { params.push({ kind: 'uint', width: parseInt(uintMatch[1], 10) }); continue; } // Default to type identifier params.push({ kind: 'type_identifier', name: trimmed }); } } return { kind: 'generic', name, params, }; } return { kind: 'unknown' }; } parseGenericParams(paramsText) { // Simple comma-separated parsing // This doesn't handle nested generics like Map<K, List<V>> but should be sufficient for now return paramsText.split(',').map((param) => param.trim()); } } exports.TolkASTProcessor = TolkASTProcessor; //# sourceMappingURL=parser.js.map