Overview
The LP component converts an input query into an AST.
It first tokenizes the query with LpLexer, then parses the resulting tokens with LpParser.
query
│
▼
LpLexer
│ list[LpToken]
▼
LpParser
│ Ast
▼
AST
LpManager provides the entry point that connects these two stages.
Tokens
LpToken represents a token produced by the lexer.
Each token consists of two fields:
tag: the kind of tokenvalue: the matched text
For example:
SELECT name FROM student;
is tokenized as:
[
LpToken('WORD', "SELECT"),
LpToken('WORD', "name"),
LpToken('WORD', "FROM"),
LpToken('WORD', "student"),
LpToken('SC', ";"),
]
Lexer
LpLexer converts an input query into a list of LpToken objects.
It combines all lexical rules into a single regular expression with named
groups and scans the query from left to right.
Token Rules
Tag |
Pattern |
Description |
|---|---|---|
|
|
Float literal |
|
|
Integer literal |
|
|
Bool literal |
|
|
String literal |
|
|
Identifier or keyword |
|
|
Comparison operator |
|
|
Comma |
|
|
Left parenthesis |
|
|
Right parenthesis |
|
|
Dot |
|
|
Semicolon |
|
|
Whitespace |
Although WS is recognized by the lexer, whitespace tokens are discarded and are not passed to the parser.
Invalid Tokens
The lexer tracks the end position of each regular expression match.
The start position of the next match must equal the current position.
Any unrecognized part of the query therefore creates a gap between matches and causes LpInvalidTokenError to be raised.
The same check is performed after the final match to ensure that the entire query has been consumed.
Parser
LpParser converts a sequence of LpToken objects into an AST.
It is organized as a recursive-descent parser: parser methods correspond closely to nonterminals in the grammar and parse their subexpressions by calling other parser methods.
The parser maintains:
tokens: the input token sequencepositions: the current position in the sequence
Basic Operations
Method |
Operation |
|---|---|
Return the current token without advancing. |
|
Consume the current token and advance. |
|
Match a token tag and advance only on success. |
|
Match the value of a |
|
Require and consume a specific keyword. |
These operations separate token-stream handling from the implementation of individual grammar rules.
Grammar Structure
Most parse_* methods correspond directly to grammar nonterminals.
For example:
attr
: WORD
| WORD DOT WORD
attrs
: attr
| attrs COMMA attr
parse_attr parses a single attribute reference.
A WORD by itself denotes an attribute without a table name, while WORD DOT WORD denotes a qualified attribute with a table name.
parse_attrs parses one or more attributes separated by COMMA tokens by repeatedly calling parse_attr.
Statements
parse examines the initial WORD token and dispatches to the corresponding statement parser.
An unsupported initial statement causes LpUnknownStatementError.
Parsing Example
Consider:
SELECT student.name FROM student WHERE student.id = 10;
The lexer produces:
[
LpToken('WORD', "SELECT"),
LpToken('WORD', "student"),
LpToken('DOT', "."),
LpToken('WORD', "name"),
LpToken('WORD', "FROM"),
LpToken('WORD', "student"),
LpToken('WORD', "WHERE"),
LpToken('WORD', "student"),
LpToken('DOT', "."),
LpToken('WORD', "id"),
LpToken('COMP', "="),
LpToken('INT', "10"),
LpToken('SC', ";"),
]
The parser decomposes this sequence as:
select_statement
├── "SELECT"
├── attrs
│ └── attr
├── "FROM"
├── tables
│ └── table
├── where_clause
│ └── conds
│ └── cond
│ ├── attr
│ ├── COMP
│ └── hand
│ └── value
└── SC
The result is an AstSelect containing the parsed attributes, tables,
and conditions.
Manager
LpManager provides the interface to the LP component.
Its parse method performs the complete pipeline:
tokens = lexer.tokenize(query)
parser = LpParser(tokens)
ast = parser.parse()
Users of the LP component therefore do not need to manage the lexer and parser separately.