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