mirror of
https://github.com/chenasraf/search-ast-parser-js.git
synced 2026-05-17 17:48:09 +00:00
feat(parser): updates
This commit is contained in:
@@ -31,3 +31,56 @@ test('should parse OR operator', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('should parse AND operator', () => {
|
||||
const reader = new StringReader('word AND "phrase"')
|
||||
const lexer = new Lexer(reader)
|
||||
const parser = new Parser(lexer)
|
||||
const tokens = parser.parse()
|
||||
expect(tokens[0]).toEqual({
|
||||
type: 'operator',
|
||||
value: 'and',
|
||||
left: {
|
||||
type: 'word',
|
||||
value: 'word',
|
||||
},
|
||||
right: {
|
||||
type: 'phrase',
|
||||
value: 'phrase',
|
||||
quote: '"',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('should parse multiple groups and logical operators', () => {
|
||||
const reader = new StringReader('(apple OR orange) AND (drink OR juice)')
|
||||
const lexer = new Lexer(reader)
|
||||
const parser = new Parser(lexer)
|
||||
const tokens = parser.parse()
|
||||
expect(tokens[0]).toEqual({
|
||||
type: 'operator',
|
||||
value: 'and',
|
||||
left: {
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
type: 'operator',
|
||||
value: 'or',
|
||||
left: { type: 'word', value: 'apple' },
|
||||
right: { type: 'word', value: 'orange' },
|
||||
},
|
||||
],
|
||||
},
|
||||
right: {
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
type: 'operator',
|
||||
value: 'or',
|
||||
left: { type: 'word', value: 'drink' },
|
||||
right: { type: 'word', value: 'juice' },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
10
package.json
10
package.json
@@ -5,6 +5,16 @@
|
||||
"main": "index.js",
|
||||
"repository": "https://github.com/chenasraf/search-ast-parser-js",
|
||||
"author": "Chen Asraf <contact@casraf.dev>",
|
||||
"keywords": [
|
||||
"search",
|
||||
"query",
|
||||
"ast",
|
||||
"parser",
|
||||
"javascript",
|
||||
"typescript",
|
||||
"tree",
|
||||
"syntax"
|
||||
],
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
|
||||
@@ -130,15 +130,24 @@ export class Parser extends IParser {
|
||||
this.lexer.consume()
|
||||
return this.readNextToken()
|
||||
}
|
||||
while (nextToken && nextToken.token === 'whitespace') {
|
||||
nextToken = this.lexer.peek(1)
|
||||
this.lexer.consume()
|
||||
}
|
||||
nextToken = this.peekSkipWhitespace(nextToken)
|
||||
// lookahead
|
||||
switch (nextToken?.token) {
|
||||
case LexerToken.operator:
|
||||
// this.index++
|
||||
this.lexer.consume()
|
||||
const parsed = this.parseNormalLexToken(token!)!
|
||||
const nextParsed = this.readNextToken()!
|
||||
this.index++
|
||||
return this.consumeOperator(this.parseNormalLexToken(token!)!, nextToken)
|
||||
this.lexer.consume()
|
||||
return this.consumeOperator(parsed, nextToken, nextParsed)
|
||||
case LexerToken.group:
|
||||
if (nextToken.value === ')') {
|
||||
return this.parseNormalLexToken(token)
|
||||
}
|
||||
this.index++
|
||||
this.lexer.consume()
|
||||
return this.consumeGroup(nextToken!)
|
||||
}
|
||||
|
||||
// no special token coming up, proceed with this token
|
||||
@@ -149,6 +158,14 @@ export class Parser extends IParser {
|
||||
}
|
||||
}
|
||||
|
||||
private peekSkipWhitespace(nextToken: LexerTokenValue | null) {
|
||||
while (nextToken && nextToken.token === 'whitespace') {
|
||||
this.lexer.consume()
|
||||
nextToken = this.lexer.peek()
|
||||
}
|
||||
return nextToken
|
||||
}
|
||||
|
||||
private parseNormalLexToken(token: LexerTokenValue | null): ParserToken | null {
|
||||
switch (token?.token) {
|
||||
case LexerToken.word:
|
||||
@@ -158,6 +175,9 @@ export class Parser extends IParser {
|
||||
const quoteContent = this.lexer.consume()!
|
||||
this.lexer.consume()
|
||||
return this.consumePhrase(token, quoteContent)
|
||||
case LexerToken.group:
|
||||
// this.lexer.consume()
|
||||
return this.consumeGroup(token!)
|
||||
// case LexerToken.operator:
|
||||
// return this.consumeOperator(token, nextToken!)
|
||||
default:
|
||||
@@ -175,11 +195,26 @@ export class Parser extends IParser {
|
||||
return { type: 'phrase', value: quoteContent.value, quote: token.value as '"' }
|
||||
}
|
||||
|
||||
private consumeOperator(left: ParserToken, opToken: LexerTokenValue): ParserToken | null {
|
||||
this.index++
|
||||
this.lexer.consume()
|
||||
const right = this.readNextToken()
|
||||
this.lexer.consume()
|
||||
private consumeOperator(
|
||||
left: ParserToken,
|
||||
opToken: LexerTokenValue,
|
||||
right: ParserToken,
|
||||
): ParserToken | null {
|
||||
// this.lexer.consume()
|
||||
return { type: 'operator', value: opToken.value, left, right }
|
||||
}
|
||||
|
||||
private consumeGroup(token: LexerTokenValue): ParserToken | null {
|
||||
const children: ParserToken[] = []
|
||||
let nextToken = this.peekSkipWhitespace(this.lexer.peek())
|
||||
while (nextToken && nextToken?.value !== ')') {
|
||||
const child = this.readNextToken()
|
||||
if (child) {
|
||||
children.push(child)
|
||||
}
|
||||
nextToken = this.lexer.peek()
|
||||
}
|
||||
this.lexer.consume()
|
||||
return { type: 'group', children }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user