Skip to content

Commit

Permalink
feat(visitor): add ast visitor
Browse files Browse the repository at this point in the history
  • Loading branch information
Soontao committed Jul 9, 2020
1 parent d4a34f9 commit 0ff94c3
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 27 deletions.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
"name": "@odata/parser",
"version": "0.1.34",
"description": "OData(v4) Parser",
"main": "lib/parser.js",
"typings": "lib/parser.d.ts",
"main": "lib/index",
"typings": "lib/index.d.ts",
"engines": {
"node": ">=10"
},
"repository": {
"type": "git",
"url": "https://github.com/Soontao/odata-v4-parser"
},
"contributors": [
{
"name": "Theo Sun",
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Parser } from './parser';

export * from './parser';
export * from './lexer';
export * from './visitor';

export const defaultParser = new Parser();
9 changes: 9 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,16 @@ export const parserFactory = function(fn) {
};
};

/**
* odata uri parser
*/
export class Parser {
/**
* parser ast node with full odata uri
*
* @param source
* @param options
*/
odataUri(source: string, options?: any): Lexer.Token {
return parserFactory(ODataUri.odataUri)(source, options);
}
Expand Down
30 changes: 5 additions & 25 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Token, TokenType } from './lexer';
import { forEach, isArray } from '@newdash/newdash';
import { isPlainObject } from '@newdash/newdash/isPlainObject';
import { createTraverser } from './visitor';

export type SourceArray = number[] | Uint16Array;

Expand Down Expand Up @@ -35,30 +34,10 @@ export function required(value: SourceArray, index: number, comparer: Function,
return i >= (min || 0) && i <= max ? index + i : 0;
}

export type Traverser = { [key in TokenType]?: (token: Token) => void }

export function createTraverser(traverser: Traverser) {
return function t(node: Token | Array<any> | Object): void {

if (node instanceof Token) {
if (node.type in traverser) {
traverser[node.type](node);
}
}

if (isPlainObject(node) || isArray(node) || node instanceof Token) {
// @ts-ignore
forEach(node, (item) => {
t(item);
});
}


};
}

/**
* find one node by type
* find one node in ast node by type
*
* @param node
* @param type
*/
Expand All @@ -69,7 +48,8 @@ export function findOne(node: Token, type: TokenType): Token {
}

/**
* find all nodes by type
* find all nodes in ast node by type
*
* @param node
* @param type
*/
Expand Down
44 changes: 44 additions & 0 deletions src/visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { TokenType, Token } from './lexer';
import { isPlainObject } from '@newdash/newdash/isPlainObject';
import { forEach } from '@newdash/newdash/forEach';
import { isArray } from '@newdash/newdash/isArray';

/**
* AST Traverser
*/
export type Traverser = { [key in TokenType]?: (token: Token) => void }

/**
* AST Visitor
*
* @alias Traverser
*/
export type Visitor = Traverser

/**
* Traverse AST with traverser
*
* @param traverser
* @param node
*/
export function traverseAst(traverser: Traverser, node: Token | Array<any> | Object): void {

if (node instanceof Token) {
if (node.type in traverser) {
traverser[node.type](node);
}
}

if (isPlainObject(node) || isArray(node) || node instanceof Token) {
// @ts-ignore
forEach(node, (item) => {
traverseAst(traverser, item);
});
}
}

export function createTraverser(traverser: Traverser) {
return function t(node: Token | Array<any> | Object): void {
traverseAst(traverser, node);
};
}
44 changes: 44 additions & 0 deletions test/visitor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { defaultParser, createTraverser } from '../src';

describe('Visitor Parse Suite', () => {

const createSeqTokenProcessor = (key: string, arr: Array<any>) => () => arr.push(key);

const createSeqTraverser = () => {
const visitSequence = [];

const visit = createTraverser({
Top: createSeqTokenProcessor('param:top', visitSequence),
Skip: createSeqTokenProcessor('param:skip', visitSequence),
Filter: createSeqTokenProcessor('param:filter', visitSequence),
OrderBy: createSeqTokenProcessor('param:orderby', visitSequence),
Format: createSeqTokenProcessor('param:format', visitSequence),
Search: createSeqTokenProcessor('param:search', visitSequence),
InlineCount: createSeqTokenProcessor('param:inlinecount', visitSequence),

AndExpression: createSeqTokenProcessor('and', visitSequence),
BoolParenExpression: createSeqTokenProcessor('paren', visitSequence),
EqualsExpression: createSeqTokenProcessor('eq', visitSequence),
Literal: createSeqTokenProcessor('lit', visitSequence),
FirstMemberExpression: createSeqTokenProcessor('mem', visitSequence)
});

return { visit, visitSequence };
};


it('should visit filter', () => {

const expectedSeq = ['and', 'paren', 'eq', 'mem', 'lit', 'paren', 'eq', 'mem', 'lit'];

const { visit, visitSequence } = createSeqTraverser();

const node = defaultParser.filter('(A eq 2) and (V eq 3)');

visit(node);

expect(visitSequence).toEqual(expectedSeq);

});

});

0 comments on commit 0ff94c3

Please sign in to comment.