from bluebase.core import (
Value,
Domain,
BoolDomain,
IntDomain,
FloatDomain,
StringDomain,
Comp,
Ast,
AstTable,
AstAttr,
AstCond,
AstAssign,
AstDefine,
AstSelect,
AstInsert,
AstDelete,
AstUpdate,
AstCreateTable,
AstCreateIndex,
AstDropTable,
AstDropIndex,
AstShowTables,
AstDescribeTable,
AstDumpTable,
AstExit,
assignment,
)
from .error import (
LpUnexpectedEndOfInputError,
LpUnexpectedTokenError,
LpUnexpectedKeywordError,
LpUnexpectedCompError,
LpUnknownStatementError,
)
from .type import LpToken
__all__ = (
'LpParser',
)
[docs]
class LpParser:
"""
A parser that converts input tokens into an `AST <bluebase.core.ast>`.
Attributes:
tokens: the input tokens
position: the current position
"""
tokens: list[LpToken]
position: int
def __repr__(self) -> str: # pragma: no cover
return "LpParser()"
def __init__(self, tokens: list[LpToken]) -> None:
self.tokens = tokens
self.position = 0
[docs]
@assignment
def peek(self) -> LpToken | None:
"""
Return the token at the current position.
Hint:
If the current position is out of bounds, return ``None``.
"""
raise NotImplementedError
[docs]
@assignment
def consume(self, tag: str | None = None) -> LpToken:
"""
Consume the token at the current position and advance.
If tag is specified, consume only a token with the matching tag.
Raises:
LpUnexpectedEndOfInputError: if there are no more tokens
LpUnexpectedTokenError: if the token tag does not match
Hint:
Use `peek`.
"""
raise NotImplementedError
[docs]
@assignment
def match_tag(self, tag: str) -> bool:
"""
Match the token tag at the current position and advance on success.
Hint:
Use `peek`.
"""
raise NotImplementedError
[docs]
@assignment
def match_keyword(self, keyword: str) -> bool:
"""
Match a ``WORD`` with the given keyword and advance on success.
Hint:
Use `peek`.
"""
raise NotImplementedError
[docs]
@assignment
def expect_keyword(self, keyword: str) -> None:
"""
Consume a ``WORD`` matching the given keyword.
Raises:
LpUnexpectedKeywordError: if the token value does not match
Hint:
Use `consume`.
"""
raise NotImplementedError
[docs]
@assignment
def parse_table(self) -> AstTable:
"""
Parse a ``table``.
.. rubric:: Grammar
::
table
: WORD
| WORD "AS" WORD
Hint:
Consume the ``WORD`` for the table name.
If the ``"AS"`` keyword matches, consume the ``WORD`` for the alias.
"""
raise NotImplementedError
[docs]
@assignment
def parse_tables(self) -> list[AstTable]:
"""
Parse a ``tables``.
.. rubric:: Grammar
::
tables
: table
| tables COMMA table
Hint:
Use `parse_table` to parse each ``table``.
Whenever a ``COMMA`` is matched, parse another ``table``.
"""
raise NotImplementedError
[docs]
@assignment
def parse_attr(self) -> AstAttr:
"""
Parse a ``attr``.
.. rubric:: Grammar
::
attr
: WORD
| WORD DOT WORD
Hint:
First, consume a ``WORD``.
If a ``DOT`` matches, consume another ``WORD`` and return an `~bluebase.core.ast.AstAttr` with a table name.
If there is no ``DOT``, return an `~bluebase.core.ast.AstAttr` without a table name.
"""
raise NotImplementedError
[docs]
@assignment
def parse_attrs(self) -> list[AstAttr]:
"""
Parse a ``attrs``.
.. rubric:: Grammar
::
attrs
: attr
| attrs COMMA attr
Hint:
Use `parse_attr` to parse each ``attr``.
Whenever a ``COMMA`` is matched, parse another ``attr``.
"""
raise NotImplementedError
[docs]
@assignment
def parse_value(self) -> Value:
"""
Parse a ``value``.
.. rubric:: Grammar
::
value
: FLOAT
| INT
| BOOL
| STRING
Raises:
LpUnexpectedTokenError: if the token tag is invalid
Hint:
Consume a token and return the value based on its tag.
Note that when parsing a ``STRING``, strip the surrounding quotes.
"""
raise NotImplementedError
[docs]
@assignment
def parse_values(self) -> tuple[Value, ...]:
"""
Parse a ``values``.
.. rubric:: Grammar
::
values
: value
| values COMMA value
Hint:
Use `parse_value` to parse each ``value``.
Whenever a ``COMMA`` is matched, parse another ``value``.
"""
raise NotImplementedError
[docs]
@assignment
def parse_hand(self) -> AstAttr | Value:
"""
Parse a ``hand``.
.. rubric:: Grammar
::
hand
: attr
| value
Raises:
LpUnexpectedEndOfInputError: if there are no more tokens
Hint:
First, peek a token.
If the token is a ``WORD``, parse an ``attr``.
Otherwise, parse a ``value``.
"""
raise NotImplementedError
[docs]
@assignment
def parse_cond(self) -> AstCond:
"""
Parse a ``cond``.
.. rubric:: Grammar
::
cond
: attr COMP hand
Hint:
First, parse the left ``attr``, then consume a ``COMP``, and then parse the right ``hand``.
"""
raise NotImplementedError
[docs]
@assignment
def parse_conds(self) -> list[AstCond]:
"""
Parse a ``conds``.
.. rubric:: Grammar
::
conds
: cond
| conds "AND" cond
Hint:
Use `parse_cond` to parse each ``cond``.
Whenever a ``"AND"`` is matched, parse another ``cond``.
"""
raise NotImplementedError
[docs]
@assignment
def parse_assign(self) -> AstAssign:
"""
Parse a ``assign``.
.. rubric:: Grammar
::
assign
: attr COMP hand
Raises:
LpUnexpectedCompError: if the comparison operator is not ``'='``
Hint:
First, parse the left ``attr``, then consume a ``COMP``, and then parse the right ``hand``.
If the comparison operator is not ``'='``, raise an error.
"""
raise NotImplementedError
[docs]
@assignment
def parse_assigns(self) -> list[AstAssign]:
"""
Parse a ``assigns``.
.. rubric:: Grammar
::
assigns
: assign
| assigns COMMA assign
Hint:
Use `parse_assign` to parse each ``assign``.
Whenever a ``COMMA`` is matched, parse another ``assign``.
"""
raise NotImplementedError
[docs]
@assignment
def parse_domain(self) -> Domain:
"""
Parse a ``domain``.
.. rubric:: Grammar
::
domain
: BOOL
| INT
| FLOAT
| STRING LP INT RP
Raises:
LpUnexpectedKeywordError: if the token value is not a valid domain
Hint:
Consume a ``WORD`` and return the domain based on its value.
If the token value is ``"STRING"``, consume a ``LP``, then consume an ``INT``, and then consume a ``RP`` and return a `~bluebase.core.domain.StringDomain` with the length.
"""
raise NotImplementedError
[docs]
@assignment
def parse_define(self) -> AstDefine:
"""
Parse a ``define``.
.. rubric:: Grammar
::
define
: attr domain
Hint:
First, parse the ``attr``, then parse the ``domain``.
"""
raise NotImplementedError
[docs]
@assignment
def parse_defines(self) -> list[AstDefine]:
"""
Parse a ``defines``.
.. rubric:: Grammar
::
defines
: define
| defines COMMA define
Hint:
Use `parse_define` to parse each ``define``.
Whenever a ``COMMA`` is matched, parse another ``define``.
"""
raise NotImplementedError
[docs]
@assignment
def parse_where_clause(self) -> list[AstCond]:
"""
Parse a ``where_clause``.
.. rubric:: Grammar
::
where_clause
:
| "WHERE" conds
Hint:
If the ``"WHERE"`` keyword matches, parse the ``conds``.
Otherwise, return an empty list.
"""
raise NotImplementedError
[docs]
@assignment
def parse_select_statement(self) -> AstSelect:
"""
Parse a ``select_statement``.
.. rubric:: Grammar
::
select_statement
: "SELECT" attrs "FROM" tables where_clause SC
Hint:
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
@assignment
def parse_insert_statement(self) -> AstInsert:
"""
Parse a ``insert_statement``.
.. rubric:: Grammar
::
insert_statement
: "INSERT" "INTO" table "VALUES" LP values RP SC
Hint:
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
@assignment
def parse_delete_statement(self) -> AstDelete:
"""
Parse a ``delete_statement``.
.. rubric:: Grammar
::
delete_statement
: "DELETE" "FROM" table where_clause SC
Hint:
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
@assignment
def parse_update_statement(self) -> AstUpdate:
"""
Parse a ``update_statement``.
.. rubric:: Grammar
::
update_statement
: "UPDATE" table "SET" assigns where_clause SC
Hint:
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
@assignment
def parse_create_statement(self) -> AstCreateTable | AstCreateIndex:
"""
Parse a ``create_statement``.
.. rubric:: Grammar
::
create_statement
: "CREATE" "TABLE" create_table_body SC
| "CREATE" "INDEX" create_index_body SC
Raises:
LpUnknownStatementError: if the statement is unknown
Hint:
After matching the ``"CREATE"`` keyword, consume a ``WORD`` and match the statement.
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
@assignment
def parse_create_table_body(self) -> AstCreateTable:
"""
Parse a ``create_table_body``.
.. rubric:: Grammar
::
create_table_body
: table LP defines RP
Hint:
Note that ``"CREATE"`` and ``"TABLE"`` keywords are already consumed.
"""
raise NotImplementedError
[docs]
@assignment
def parse_create_index_body(self) -> AstCreateIndex:
"""
Parse a ``create_index_body``.
.. rubric:: Grammar
::
create_index_body
: attr "OF" table
Hint:
Note that ``"CREATE"`` and ``"INDEX"`` keywords are already consumed.
"""
raise NotImplementedError
[docs]
@assignment
def parse_drop_statement(self) -> AstDropTable | AstDropIndex:
"""
Parse a ``drop_statement``.
.. rubric:: Grammar
::
drop_statement
: "DROP" "TABLE" drop_table_body SC
| "DROP" "INDEX" drop_index_body SC
Raises:
LpUnknownStatementError: if the statement is unknown
Hint:
After matching the ``"DROP"`` keyword, consume a ``WORD`` and match the statement.
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
@assignment
def parse_drop_table_body(self) -> AstDropTable:
"""
Parse a ``drop_table_body``.
.. rubric:: Grammar
::
drop_table_body
: table
Hint:
Note that ``"DROP"`` and ``"TABLE"`` keywords are already consumed.
"""
raise NotImplementedError
[docs]
@assignment
def parse_drop_index_body(self) -> AstDropIndex:
"""
Parse a ``drop_index_body``.
.. rubric:: Grammar
::
drop_index_body
: attr "OF" table
Hint:
Note that ``"DROP"`` and ``"INDEX"`` keywords are already consumed.
"""
raise NotImplementedError
[docs]
@assignment
def parse_show_tables_statement(self) -> AstShowTables:
"""
Parse a ``show_tables_statement``.
.. rubric:: Grammar
::
show_tables_statement
: "SHOW" "TABLES" SC
Hint:
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
@assignment
def parse_describe_table_statement(self) -> AstDescribeTable:
"""
Parse a ``describe_table_statement``.
.. rubric:: Grammar
::
describe_table_statement
: "DESCRIBE" "TABLE" table SC
Hint:
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
@assignment
def parse_dump_table_statement(self) -> AstDumpTable:
"""
Parse a ``dump_table_statement``.
.. rubric:: Grammar
::
dump_table_statement
: "DUMP" "TABLE" table SC
Hint:
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
@assignment
def parse_exit_statement(self) -> AstExit:
"""
Parse a ``exit_statement``.
.. rubric:: Grammar
::
exit_statement
: "EXIT" SC
Hint:
Don't forget to consume a ``SC`` at the end.
"""
raise NotImplementedError
[docs]
def parse(self) -> Ast:
"""
Parse the input tokens into an `AST <bluebase.core.ast>`.
Raises:
LpUnexpectedEndOfInputError: if there are no more tokens
LpUnexpectedTokenError: if the first token is not a ``WORD``
LpUnknownStatementError: if the statement is unknown
"""
token = self.peek()
if token is None:
raise LpUnexpectedEndOfInputError
if token.tag != 'WORD':
raise LpUnexpectedTokenError(
token=token.value,
got=token.tag,
expected='WORD',
)
statement = token.value.upper()
match statement:
case 'SELECT':
return self.parse_select_statement()
case 'INSERT':
return self.parse_insert_statement()
case 'DELETE':
return self.parse_delete_statement()
case 'UPDATE':
return self.parse_update_statement()
case 'CREATE':
return self.parse_create_statement()
case 'DROP':
return self.parse_drop_statement()
case 'SHOW':
return self.parse_show_tables_statement()
case 'DESCRIBE':
return self.parse_describe_table_statement()
case 'DUMP':
return self.parse_dump_table_statement()
case 'EXIT':
return self.parse_exit_statement()
case _:
raise LpUnknownStatementError(statement=statement)