-
Notifications
You must be signed in to change notification settings - Fork 0
/
cst-visitor.js
170 lines (139 loc) · 4.64 KB
/
cst-visitor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
'use strict'
// Written Docs for this tutorial step can be found here:
// https://github.com/SAP/chevrotain/blob/master/docs/tutorial/step3a_adding_actions_separated.md
// Tutorial Step 3a:
// Adding Actions(semantics) to our grammar using a CST Visitor.
const selectLexer = require('./lexer')
// re-using the parser implemented in step two.
const parser = require('./parser')
const SelectParser = parser.SelectParser
// A new parser instance with CST output (enabled by default).
const parserInstance = new SelectParser([])
// The base visitor class can be accessed via the a parser instance.
const BaseSQLVisitor = parserInstance.getBaseCstVisitorConstructor()
class SQLToAstVisitor extends BaseSQLVisitor {
constructor() {
super()
this.validateVisitor()
}
selectStatement(ctx) {
// "this.visit" can be used to visit none-terminals and will invoke the correct visit method for the CstNode passed.
const select = this.visit(ctx.selectClause)
// "this.visit" can work on either a CstNode or an Array of CstNodes.
// If an array is passed (ctx.fromClause is an array) it is equivalent
// to passing the first element of that array
const from = this.visit(ctx.fromClause)
// "whereClause" is optional, "this.visit" will ignore empty arrays (optional)
const where = this.visit(ctx.whereClause)
// "orderByClause" is optional, "this.visit" will ignore empty arrays (optional)
const orderBy = this.visit(ctx.orderByClause)
// "limitClause" is optional, "this.visit" will ignore empty arrays (optional)
const limit = this.visit(ctx.limitClause)
return {
type: 'SELECT_STMT',
selectClause: select,
fromClause: from,
whereClause: where,
orderByClause: orderBy,
limitClause: limit,
}
}
selectClause(ctx) {
// Each Terminal or Non-Terminal in a grammar rule are collected into
// an array with the same name(key) in the ctx object.
const columns = ctx.Identifier.map(identToken => identToken.image)
return {
type: 'SELECT_CLAUSE',
columns: columns,
}
}
fromClause(ctx) {
const tableName = ctx.Identifier[0].image
return {
type: 'FROM_CLAUSE',
table: tableName,
}
}
whereClause(ctx) {
const condition = this.visit(ctx.expression)
return {
type: 'WHERE_CLAUSE',
condition: condition,
}
}
orderByClause(ctx) {
const expression = ctx.Identifier[0].image
const condition = this.visit(ctx.orderByExpression) || 'ASC'
return {
type: 'ORDERBY_CLAUSE',
expression: expression,
condition: condition,
}
}
limitClause(ctx) {
const limit = parseInt(ctx.Integer[0].image, 10)
return {
type: 'LIMIT_CLAUSE',
limit: limit,
}
}
orderByExpression(ctx) {
if (ctx.Desc) {
return ctx.Desc[0].image
}
return ctx.Asc[0].image
}
expression(ctx) {
// Note the usage of the "rhs" and "lhs" labels defined in step 2 in the expression rule.
const lhs = this.visit(ctx.lhs[0])
const operator = this.visit(ctx.relationalOperator)
const rhs = this.visit(ctx.rhs[0])
return {
type: 'EXPRESSION',
lhs: lhs,
operator: operator,
rhs: rhs,
}
}
// these two visitor methods will return a string.
atomicExpression(ctx) {
if (ctx.Integer) {
return parseInt(ctx.Integer[0].image, 10)
} else {
return ctx.Identifier[0].image
}
}
relationalOperator(ctx) {
if (ctx.GreaterThanEqual) {
return ctx.GreaterThanEqual[0].image
} else if (ctx.GreaterThan) {
return ctx.GreaterThan[0].image
} else if (ctx.LessThanEqual) {
return ctx.LessThanEqual[0].image
} else if (ctx.LessThan) {
return ctx.LessThan[0].image
} else if (ctx.Equal) {
return ctx.Equal[0].image
} else if (ctx.NotEqual) {
return ctx.NotEqual[0].image
}
}
}
// Our visitor has no state, so a single instance is sufficient.
const toAstVisitorInstance = new SQLToAstVisitor()
module.exports = {
toAst: function(inputText) {
const lexResult = selectLexer.lex(inputText)
// ".input" is a setter which will reset the parser's internal's state.
parserInstance.input = lexResult.tokens
// Automatic CST created when parsing
const cst = parserInstance.selectStatement()
if (parserInstance.errors.length > 0) {
throw Error(
'Parsing errors detected!\n' + parserInstance.errors[0].message
)
}
const ast = toAstVisitorInstance.visit(cst)
return ast
},
}