@tonkite/tlb-parser
Version:
Parse TL-B syntax into TypeScript objects
272 lines (222 loc) • 7.18 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 = {
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;
}