123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- import { SyntaxNode, TreeCursor } from '@lezer/common';
- import { QueryBuilderOperation } from './types';
- // This is used for error type for some reason
- export const ErrorName = '⚠';
- export function getLeftMostChild(cur: SyntaxNode): SyntaxNode {
- return cur.firstChild ? getLeftMostChild(cur.firstChild) : cur;
- }
- export function makeError(expr: string, node: SyntaxNode) {
- return {
- text: getString(expr, node),
- // TODO: this are positions in the string with the replaced variables. Means it cannot be used to show exact
- // placement of the error for the user. We need some translation table to positions before the variable
- // replace.
- from: node.from,
- to: node.to,
- parentType: node.parent?.name,
- };
- }
- // Taken from template_srv, but copied so to not mess with the regex.index which is manipulated in the service
- /*
- * This regex matches 3 types of variable reference with an optional format specifier
- * \$(\w+) $var1
- * \[\[([\s\S]+?)(?::(\w+))?\]\] [[var2]] or [[var2:fmt2]]
- * \${(\w+)(?::(\w+))?} ${var3} or ${var3:fmt3}
- */
- const variableRegex = /\$(\w+)|\[\[([\s\S]+?)(?::(\w+))?\]\]|\${(\w+)(?:\.([^:^\}]+))?(?::([^\}]+))?}/g;
- /**
- * As variables with $ are creating parsing errors, we first replace them with magic string that is parsable and at
- * the same time we can get the variable and it's format back from it.
- * @param expr
- */
- export function replaceVariables(expr: string) {
- return expr.replace(variableRegex, (match, var1, var2, fmt2, var3, fieldPath, fmt3) => {
- const fmt = fmt2 || fmt3;
- let variable = var1;
- let varType = '0';
- if (var2) {
- variable = var2;
- varType = '1';
- }
- if (var3) {
- variable = var3;
- varType = '2';
- }
- return `__V_${varType}__` + variable + '__V__' + (fmt ? '__F__' + fmt + '__F__' : '');
- });
- }
- const varTypeFunc = [
- (v: string, f?: string) => `\$${v}`,
- (v: string, f?: string) => `[[${v}${f ? `:${f}` : ''}]]`,
- (v: string, f?: string) => `\$\{${v}${f ? `:${f}` : ''}\}`,
- ];
- /**
- * Get back the text with variables in their original format.
- * @param expr
- */
- function returnVariables(expr: string) {
- return expr.replace(/__V_(\d)__(.+?)__V__(?:__F__(\w+)__F__)?/g, (match, type, v, f) => {
- return varTypeFunc[parseInt(type, 10)](v, f);
- });
- }
- /**
- * Get the actual string of the expression. That is not stored in the tree so we have to get the indexes from the node
- * and then based on that get it from the expression.
- * @param expr
- * @param node
- */
- export function getString(expr: string, node: SyntaxNode | TreeCursor | null | undefined) {
- if (!node) {
- return '';
- }
- return returnVariables(expr.substring(node.from, node.to));
- }
- /**
- * Create simple scalar binary op object.
- * @param opDef - definition of the op to be created
- * @param expr
- * @param numberNode - the node for the scalar
- * @param hasBool - whether operation has a bool modifier. Is used only for ops for which it makes sense.
- */
- export function makeBinOp(
- opDef: { id: string; comparison?: boolean },
- expr: string,
- numberNode: SyntaxNode,
- hasBool: boolean
- ): QueryBuilderOperation {
- const params: any[] = [parseFloat(getString(expr, numberNode))];
- if (opDef.comparison) {
- params.push(hasBool);
- }
- return {
- id: opDef.id,
- params,
- };
- }
- /**
- * Get all nodes with type in the tree. This traverses the tree so it is safe only when you know there shouldn't be
- * too much nesting but you just want to skip some of the wrappers. For example getting function args this way would
- * not be safe is it would also find arguments of nested functions.
- * @param expr
- * @param cur
- * @param type
- */
- export function getAllByType(expr: string, cur: SyntaxNode, type: string): string[] {
- if (cur.name === type) {
- return [getString(expr, cur)];
- }
- const values: string[] = [];
- let pos = 0;
- let child = cur.childAfter(pos);
- while (child) {
- values.push(...getAllByType(expr, child, type));
- pos = child.to;
- child = cur.childAfter(pos);
- }
- return values;
- }
- // Debugging function for convenience. Gives you nice output similar to linux tree util.
- // @ts-ignore
- export function log(expr: string, cur?: SyntaxNode) {
- if (!cur) {
- console.log('<empty>');
- return;
- }
- const json = toJson(expr, cur);
- const text = jsonToText(json);
- if (!text) {
- console.log('<empty>');
- return;
- }
- console.log(text);
- }
- function toJson(expr: string, cur: SyntaxNode) {
- const treeJson: any = {};
- const name = nodeToString(expr, cur);
- const children = [];
- let pos = 0;
- let child = cur.childAfter(pos);
- while (child) {
- children.push(toJson(expr, child));
- pos = child.to;
- child = cur.childAfter(pos);
- }
- treeJson.name = name;
- treeJson.children = children;
- return treeJson;
- }
- type JsonNode = {
- name: string;
- children: JsonNode[];
- };
- function jsonToText(
- node: JsonNode,
- context: { lastChild: boolean; indent: string } = {
- lastChild: true,
- indent: '',
- }
- ) {
- const name = node.name;
- const { lastChild, indent } = context;
- const newIndent = indent !== '' ? indent + (lastChild ? '└─' : '├─') : '';
- let text = newIndent + name;
- const children = node.children;
- children.forEach((child: any, index: number) => {
- const isLastChild = index === children.length - 1;
- text +=
- '\n' +
- jsonToText(child, {
- lastChild: isLastChild,
- indent: indent + (lastChild ? ' ' : '│ '),
- });
- });
- return text;
- }
- function nodeToString(expr: string, node: SyntaxNode) {
- return node.name + ': ' + getString(expr, node);
- }
|