UNPKG

@ton-community/tlb-parser

Version:

Parse TLB syntax into TypeScript objects

348 lines (297 loc) 7.58 kB
import type { Node, TerminalNode, IterationNode } from 'ohm-js' import * as ast from './ast/nodes' import { withLocations } from './ast/locations' export const rootNodes = { Program(node: IterationNode): any { return withLocations( new ast.Program( node.children.map((child: Node) => child['root']()), ), node, ) }, SourceElement(node: Node): any { return withLocations( new ast.Declaration( node.child(0)['Constructor'](), node.child(1)['Field'](), node.child(3)['Combinator'](), ), node, ) }, } export const constructorNodes = { Constructor(name: TerminalNode, tag: Node): any { const nameValue = name.sourceString let tagValue = null; if (tag.numChildren !== 0) { tagValue = tag.child(0)['Constructor']() } return withLocations( new ast.Constructor(nameValue, tagValue), name, ) }, ConstructorTag(node: Node): any { // This is a string-only node: return node.sourceString }, } export const fieldNodes = { Fields(node: IterationNode): any { return node.children.map((child: Node) => child['Field']()) }, FieldDefinition(node: Node): any { return node['Field']() // just a wrapper node, unwrapping }, FieldBuiltinDef( lpar: TerminalNode, name: Node, _sep: TerminalNode, type: Node, _rpar: TerminalNode, ): any { // TODO: validate `type.sourceString` to be in allowed values. return withLocations( new ast.FieldBuiltinDef( name.sourceString, type.sourceString as any, ), lpar, ) }, FieldCurlyExprDef( lpar: TerminalNode, expr: Node, _rpar: TerminalNode, ): any { return withLocations( new ast.FieldCurlyExprDef(expr['expr']()), lpar, ) }, FieldAnonymousDef(node: Node): any { const {name, isRef, fields} = node['Field']() return withLocations( new ast.FieldAnonymousDef(name, isRef, fields), node, ) }, FieldNamedDef(name: Node, _sep: TerminalNode, expr: Node): any { return withLocations( new ast.FieldNamedDef(name.sourceString, expr['expr']()), name, ) }, FieldExprDef(node: Node): any { return withLocations( new ast.FieldExprDef(node['expr']()), node, ) }, // Helpers to parse complex anonymous fields: // TODO: move out of this semantics FieldAnonRef( ref: TerminalNode, _lpar: TerminalNode, fields: IterationNode, _rpar: TerminalNode, ) { return { 'name': null, 'isRef': ref.numChildren !== 0, 'fields': fields.children.map((field: Node) => field['Field']()), } }, FieldNamedAnonRef(name: Node, _sep: TerminalNode, fields: Node) { return { ...fields['Field'](), 'name': name.sourceString, } }, } export const combinatorNodes = { Combinator(name: Node, exprs: IterationNode): any { return withLocations( new ast.Combinator( name.sourceString, exprs.children.map((typeExpr: Node) => typeExpr['expr']()), ), name, ) }, } export const exprNodes = { // Math MathExpr(left: Node, ops: IterationNode, rights: IterationNode): any { return parseMath(left, ops, rights) }, MulExpr(left: Node, ops: IterationNode, rights: IterationNode): any { return parseMath(left, ops, rights) }, CompareExpr(node: Node): any { return node['expr']() }, CompareOperatorExpr(left: Node, op: TerminalNode, right: Node): any { return withLocations( new ast.CompareExpr( left['expr'](), op.sourceString as any, right['expr'](), ), op, ) }, // Conditional types CondExpr(expr: Node): any { const { leftExpr, dotExpr, condExpr } = expr['expr']() if (dotExpr === undefined && condExpr === undefined) { return leftExpr } return withLocations( new ast.CondExpr(leftExpr, dotExpr, condExpr), expr, ) }, // TODO: move out of this semantics CondDotAndQuestionExpr( dotNode: Node, _sep: TerminalNode, condNode: Node, ): any { return { ...dotNode['expr'](), 'condExpr': condNode['expr'](), } }, CondQuestionExpr(left: Node, _sep: TerminalNode, condNode: Node): any { return { 'leftExpr': left['expr'](), 'dotExpr': null, 'condExpr': condNode['expr'](), } }, CondTypeExpr(node: Node): any { return { 'leftExpr': node['expr'](), } }, CondDotted(left: Node, _sep: TerminalNode, number: Node): any { return { 'leftExpr': left['expr'](), 'dotExpr': new Number(number.sourceString), } }, // TypeExpr CombinatorExpr( lpar: TerminalNode, name: Node, args: IterationNode, _rpar: Node, ): any { return withLocations( new ast.CombinatorExpr( name.sourceString, args.children.map((arg: Node) => arg['expr']()), ), lpar, ) }, CellRefExpr(ref: TerminalNode, node: Node): any { return withLocations( new ast.CellRefExpr(node['expr']()), ref, ) }, BuiltinExpr(node: Node): any { return withLocations(node['expr'](), node) }, NegateExpr(op: TerminalNode, node: Node): any { return withLocations(new ast.NegateExpr(node['expr']()), op) }, // Builtins BuiltinOneArg( lpar: TerminalNode, expr: Node, arg: Node, _rpar: TerminalNode, ): any { // TODO: validate `expr` to be in allowed set of operators return withLocations( new ast.BuiltinOneArgExpr( expr.sourceString as any, arg['expr'](), ), lpar, ) }, BuiltinZeroArgs(expr: Node): any { // TODO: validate `expr` to be in allowed set of operators return withLocations( new ast.BuiltinZeroArgs(expr.sourceString as any), expr, ) }, // Base rules identifier(start: Node, rest: IterationNode): any { return withLocations( new ast.NameExpr(start.sourceString + rest.sourceString), start, ) }, number(node: TerminalNode): any { return withLocations( new ast.NumberExpr(parseInt(node.sourceString)), node, ) }, // Helpers Parens(lpar: TerminalNode, node: Node, _rpar: TerminalNode): any { // Just drop `()` around an expression, it should be fine return withLocations(node['expr'](), lpar) }, } function parseMath( left: Node, ops: IterationNode, rights: IterationNode, ): ast.Expression { const leftExpr = left['expr']() const opsSigns = [] for (let child of ops.children) { // TODO: validate op is in ast.MathOperators opsSigns.push(child.sourceString) } const rightExprs = [] for (let child of rights.children) { const rightExpr = child['expr']() if (rightExpr !== undefined) { rightExprs.push(rightExpr) } } if (opsSigns.length !== rightExprs.length) { throw new Error('Invalid math operation') // should not happen } if (opsSigns.length === 0) { // This is not a math expr, just the left part return withLocations(leftExpr, left) } // We always use the left part for all the math expressions, // it should be fine for now. let expr = withLocations( new ast.MathExpr(leftExpr, opsSigns[0] as any, rightExprs[0]), left, ) for (let index = 1; index < opsSigns.length; index++) { expr = withLocations( new ast.MathExpr( expr, opsSigns[index] as any, // validated earlier rightExprs[index], ), left, ) } return expr }