@ton-community/tlb-parser
Version:
Parse TLB syntax into TypeScript objects
293 lines (243 loc) • 10.2 kB
text/typescript
import type { Node, TerminalNode, IterationNode } from 'ohm-js';
import * as ast from './ast/nodes';
import { withLocations } from './ast/locations';
export const rootNodes = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Program(node: IterationNode): any {
return withLocations(new ast.Program(node.children.map((child: Node) => child['root']())), node);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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 = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ConstructorTag(node: Node): any {
// This is a string-only node:
return node.sourceString;
},
};
export const fieldNodes = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Fields(node: IterationNode): any {
return node.children.map((child: Node) => child['Field']());
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
FieldDefinition(node: Node): any {
return node['Field'](); // just a wrapper node, unwrapping
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
FieldBuiltinDef(lpar: TerminalNode, name: Node, _sep: TerminalNode, type: Node, _rpar: TerminalNode): any {
// TODO: validate `type.sourceString` to be in allowed values.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return withLocations(new ast.FieldBuiltinDef(name.sourceString, type.sourceString as any), lpar);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
FieldCurlyExprDef(lpar: TerminalNode, expr: Node, _rpar: TerminalNode): any {
return withLocations(new ast.FieldCurlyExprDef(expr['expr']()), lpar);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
FieldAnonymousDef(node: Node): any {
const { name, isRef, fields } = node['Field']();
return withLocations(new ast.FieldAnonymousDef(name, isRef, fields), node);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
FieldNamedDef(name: Node, _sep: TerminalNode, expr: Node): any {
return withLocations(new ast.FieldNamedDef(name.sourceString, expr['expr']()), name);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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 = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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
// eslint-disable-next-line @typescript-eslint/no-explicit-any
MathExpr(left: Node, ops: IterationNode, rights: IterationNode): any {
return parseMath(left, ops, rights);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
MulExpr(left: Node, ops: IterationNode, rights: IterationNode): any {
return parseMath(left, ops, rights);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
CompareExpr(node: Node): any {
return node['expr']();
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
CompareOperatorExpr(left: Node, op: TerminalNode, right: Node): any {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return withLocations(new ast.CompareExpr(left['expr'](), op.sourceString as any, right['expr']()), op);
},
// Conditional types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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
// eslint-disable-next-line @typescript-eslint/no-explicit-any
CondDotAndQuestionExpr(dotNode: Node, _sep: TerminalNode, condNode: Node): any {
return {
...dotNode['expr'](),
condExpr: condNode['expr'](),
};
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
CondQuestionExpr(left: Node, _sep: TerminalNode, condNode: Node): any {
return {
leftExpr: left['expr'](),
dotExpr: null,
condExpr: condNode['expr'](),
};
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
CondTypeExpr(node: Node): any {
return {
leftExpr: node['expr'](),
};
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
CondDotted(left: Node, _sep: TerminalNode, number: Node): any {
return {
leftExpr: left['expr'](),
dotExpr: new Number(number.sourceString),
};
},
// TypeExpr
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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,
);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
CellRefExpr(ref: TerminalNode, node: Node): any {
return withLocations(new ast.CellRefExpr(node['expr']()), ref);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BuiltinExpr(node: Node): any {
return withLocations(node['expr'](), node);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
NegateExpr(op: TerminalNode, node: Node): any {
return withLocations(new ast.NegateExpr(node['expr']()), op);
},
// Builtins
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BuiltinOneArg(lpar: TerminalNode, expr: Node, arg: Node, _rpar: TerminalNode): any {
// TODO: validate `expr` to be in allowed set of operators
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return withLocations(new ast.BuiltinOneArgExpr(expr.sourceString as any, arg['expr']()), lpar);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BuiltinZeroArgs(expr: Node): any {
// TODO: validate `expr` to be in allowed set of operators
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return withLocations(new ast.BuiltinZeroArgs(expr.sourceString as any), expr);
},
// Base rules
// eslint-disable-next-line @typescript-eslint/no-explicit-any
identifier(start: Node, rest: IterationNode): any {
return withLocations(new ast.NameExpr(start.sourceString + rest.sourceString), start);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
number(node: TerminalNode): any {
return withLocations(new ast.NumberExpr(parseInt(node.sourceString)), node);
},
// Helpers
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
opsSigns[index] as any, // validated earlier
rightExprs[index],
),
left,
);
}
return expr;
}