Skip to content

Commit c6df31d

Browse files
committed
feat: walk, createVisitor
1 parent ee7add2 commit c6df31d

File tree

4 files changed

+166
-20
lines changed

4 files changed

+166
-20
lines changed

README.md

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# java-ast
22

3-
Java Parser for JavaScript/TypeScript, based on [antlr4ts](https://www.npmjs.com/package/antlr4ts)
3+
Java Parser for JavaScript/TypeScript, based on [antlr4ts](https://www.npmjs.com/package/antlr4ts), grammar taken from [antlr4's java grammar](https://github.com/antlr/grammars-v4/tree/master/java/java) too (so please report bugs and open pull requests related to grammars upstream)
44

55
[![npm version](https://img.shields.io/npm/v/java-ast.svg)](https://www.npmjs.com/package/java-ast)
66
[![Build Status](https://travis-ci.org/urish/java-ast.png?branch=master)](https://travis-ci.org/urish/java-ast)
@@ -9,8 +9,28 @@ Java Parser for JavaScript/TypeScript, based on [antlr4ts](https://www.npmjs.com
99
## Usage Example
1010

1111
```typescript
12-
import { parse } from './index';
12+
import { parse, createVisitor } from 'java-ast';
1313

14-
const ast = parse(`package test;\n\nclass TestClass {}\n`);
15-
// do something with ast, e.g. console.log(ast.toStringTree());
14+
const countMethods = (source: string) => {
15+
let ast = parse(source);
16+
17+
return createVisitor({
18+
visitMethodDeclaration: () => 1,
19+
defaultResult: () => 0,
20+
aggregateResult: (a, b) => a + b,
21+
}).visit(ast);
22+
};
23+
24+
console.log(
25+
countMethods(`
26+
class A {
27+
int a;
28+
void b() {}
29+
void c() {}
30+
}
31+
class B {
32+
void z() {}
33+
}
34+
`),
35+
); // logs 3
1636
```

src/index.spec.ts

+27-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import { ParseTreeWalker } from 'antlr4ts/tree/ParseTreeWalker';
2-
import { parse } from './index';
3-
import { ExpressionContext } from './parser/JavaParser';
4-
import { JavaParserListener } from './parser/JavaParserListener';
1+
import { createVisitor, parse, walk } from './index';
52

6-
describe('Java AST parser', () => {
3+
describe('parser', () => {
74
it('should parse the given Java code and return the AST', () => {
85
const tree = parse(`
96
class TestClass {
@@ -22,15 +19,32 @@ describe('Java AST parser', () => {
2219
`);
2320

2421
const expressions = [];
25-
ParseTreeWalker.DEFAULT.walk(
26-
{
27-
enterExpression(context: ExpressionContext) {
28-
expressions.push(context.text);
29-
},
30-
} as JavaParserListener,
31-
tree,
32-
);
22+
walk({ enterExpression: (c) => expressions.push(c.text) }, tree);
3323

3424
expect(expressions).toContain('super(1)');
3525
});
3626
});
27+
28+
describe('usage example', () => {
29+
it('works', () => {
30+
const countMethods = (source: string) =>
31+
createVisitor({
32+
visitMethodDeclaration: () => 1,
33+
defaultResult: () => 0,
34+
aggregateResult: (a, b) => a + b,
35+
}).visit(parse(source));
36+
37+
expect(
38+
countMethods(`
39+
class A {
40+
int a;
41+
void b() {}
42+
void c() {}
43+
}
44+
class B {
45+
void z() {}
46+
}
47+
`),
48+
).toEqual(3);
49+
});
50+
});

src/index.ts

+113-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,127 @@
1+
// parse
12
import { ANTLRInputStream, CommonTokenStream } from 'antlr4ts';
23
import { JavaLexer } from './parser/JavaLexer';
34
import { CompilationUnitContext, JavaParser } from './parser/JavaParser';
4-
export { CompilationUnitContext };
5+
6+
// walk
7+
import { ParseTreeWalker } from 'antlr4ts/tree/ParseTreeWalker';
8+
import { JavaParserListener } from './parser/JavaParserListener';
9+
import { JavaParserVisitor } from './parser/JavaParserVisitor';
10+
11+
// createVisitor
12+
import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor';
13+
import { ParseTreeVisitor } from 'antlr4ts/tree/ParseTreeVisitor';
14+
import { RuleNode } from 'antlr4ts/tree/RuleNode';
515

616
/**
717
* Parses the given source code and returns the AST
818
* @param source Java source code to parse
919
*/
10-
export function parse(source: string): CompilationUnitContext {
20+
export function parse(source: string): ParseTree {
1121
const chars = new ANTLRInputStream(source);
1222
const lexer = new JavaLexer(chars);
1323
const tokens = new CommonTokenStream(lexer);
1424
const parser = new JavaParser(tokens);
1525
return parser.compilationUnit();
1626
}
27+
28+
// Just to create a more user-friendly name as all arguments that are name 'tree' take this
29+
// (type alias doesn't create a new name)
30+
// tslint:disable-next-line:no-empty-interface
31+
export interface ParseTree extends CompilationUnitContext {}
32+
33+
/**
34+
* Walks a parse tree
35+
* @see https://github.com/antlr/antlr4/blob/master/doc/listeners.md
36+
*/
37+
export function walk(listener: JavaParserListener, tree: ParseTree) {
38+
ParseTreeWalker.DEFAULT.walk(listener, tree);
39+
}
40+
export { JavaParserListener } from './parser/JavaParserListener';
41+
42+
/**
43+
* Create a parse tree visitor
44+
*/
45+
export function createVisitor<T>(visitor: Visitor<T>): ConcreteVisitor<T> {
46+
// we don't want users to write classes because it's not JavaScript-y
47+
// so we'll set implementation of abstract methods and other visit* methods in constructor
48+
// @ts-ignore
49+
return new class extends AbstractParseTreeVisitor<T> {
50+
constructor() {
51+
super();
52+
Object.assign(this, visitor);
53+
}
54+
}();
55+
}
56+
57+
export interface Visitor<T>
58+
extends AbstractVisitor<T>,
59+
Omit<JavaParserVisitor<T>, NonOverridableMethods> {}
60+
61+
type NonOverridableMethods = keyof ParseTreeVisitor<any>;
62+
63+
// Just to create a better name
64+
export interface ConcreteVisitor<T> extends AbstractParseTreeVisitor<T> {}
65+
66+
// from AbstractParseTreeVisitor
67+
interface AbstractVisitor<T> {
68+
/**
69+
* Gets the default value returned by visitor methods. This value is
70+
* returned by the default implementations of
71+
* {@link #visitTerminal visitTerminal}, {@link #visitErrorNode visitErrorNode}.
72+
* The default implementation of {@link #visitChildren visitChildren}
73+
* initializes its aggregate result to this value.
74+
*
75+
* @return The default value returned by visitor methods.
76+
*/
77+
defaultResult: () => T;
78+
79+
/**
80+
* Aggregates the results of visiting multiple children of a node. After
81+
* either all children are visited or {@link #shouldVisitNextChild} returns
82+
* {@code false}, the aggregate value is returned as the result of
83+
* {@link #visitChildren}.
84+
*
85+
* <p>The default implementation returns {@code nextResult}, meaning
86+
* {@link #visitChildren} will return the result of the last child visited
87+
* (or return the initial value if the node has no children).</p>
88+
*
89+
* @param aggregate The previous aggregate value. In the default
90+
* implementation, the aggregate value is initialized to
91+
* {@link #defaultResult}, which is passed as the {@code aggregate} argument
92+
* to this method after the first child node is visited.
93+
* @param nextResult The result of the immediately preceeding call to visit
94+
* a child node.
95+
*
96+
* @return The updated aggregate result.
97+
*/
98+
aggregateResult: (aggregate: T, nextResult: T) => T;
99+
100+
/**
101+
* This method is called after visiting each child in
102+
* {@link #visitChildren}. This method is first called before the first
103+
* child is visited; at that point {@code currentResult} will be the initial
104+
* value (in the default implementation, the initial value is returned by a
105+
* call to {@link #defaultResult}. This method is not called after the last
106+
* child is visited.
107+
*
108+
* <p>The default implementation always returns {@code true}, indicating that
109+
* {@code visitChildren} should only return after all children are visited.
110+
* One reason to override this method is to provide a "short circuit"
111+
* evaluation option for situations where the result of visiting a single
112+
* child has the potential to determine the result of the visit operation as
113+
* a whole.</p>
114+
*
115+
* @param node The {@link RuleNode} whose children are currently being
116+
* visited.
117+
* @param currentResult The current aggregate result of the children visited
118+
* to the current point.
119+
*
120+
* @return {@code true} to continue visiting children. Otherwise return
121+
* {@code false} to stop visiting children and immediately return the
122+
* current aggregate result from {@link #visitChildren}.
123+
*/
124+
shouldVisitNextChild?: (node: RuleNode, currentResult: T) => boolean;
125+
}
126+
127+
export * from './parser/JavaContexts';

tslint.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"rules": {
66
"quotemark": [true, "single", "avoid-escape", "avoid-template"],
77
"member-access": [true, "no-public"],
8-
"interface-name": [true, "never-prefix"]
8+
"interface-name": [true, "never-prefix"],
9+
"object-literal-sort-keys": false
910
},
1011
"linterOptions": {
1112
"exclude": ["src/parser/*.ts"]

0 commit comments

Comments
 (0)