feat: metona-sqlark v0.1.12 — 前端TypeScript关系型数据库
- 4种存储引擎:Memory / IndexedDB / OPFS / Hybrid - 完整SQL支持:SELECT/INSERT/UPDATE/DELETE/JOIN/GROUP BY/HAVING/DISTINCT - Query Builder链式API + TypeScript泛型支持 - 聚合函数:COUNT/SUM/AVG/MIN/MAX - 事务、插件系统(14 hooks)、发布订阅、数据迁移、导入导出 - React/Vue框架集成 - 264个测试用例,93.46%覆盖率 - 零运行时依赖
This commit is contained in:
@@ -0,0 +1,717 @@
|
||||
/**
|
||||
* metona-sqlark SQL Parser — 递归下降语法分析器
|
||||
* @module sql/parser
|
||||
*
|
||||
* Token 流 → AST Statement。
|
||||
* 支持的语法是标准 SQL 的子集。
|
||||
*/
|
||||
|
||||
import { Lexer } from './lexer';
|
||||
import { TokenType, type Token } from './tokens';
|
||||
import type {
|
||||
Statement,
|
||||
SelectStatement,
|
||||
InsertStatement,
|
||||
UpdateStatement,
|
||||
DeleteStatement,
|
||||
CreateTableStatement,
|
||||
DropTableStatement,
|
||||
ASTColumnDef,
|
||||
} from '../query/ast';
|
||||
import type { WhereCondition, OrderBy, SortDirection } from '../constants';
|
||||
import { DatabaseError } from '../constants';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Parser
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export class Parser {
|
||||
private lexer: Lexer;
|
||||
private curToken!: Token;
|
||||
private peekToken!: Token;
|
||||
|
||||
constructor(sql: string) {
|
||||
this.lexer = new Lexer(sql);
|
||||
// 预读两个 token
|
||||
this.nextToken();
|
||||
this.nextToken();
|
||||
}
|
||||
|
||||
/** 解析完整 SQL 语句 */
|
||||
parseStatement(): Statement {
|
||||
switch (this.curToken.type) {
|
||||
case TokenType.SELECT:
|
||||
return this.parseSelect();
|
||||
case TokenType.INSERT:
|
||||
return this.parseInsert();
|
||||
case TokenType.UPDATE:
|
||||
return this.parseUpdate();
|
||||
case TokenType.DELETE:
|
||||
return this.parseDelete();
|
||||
case TokenType.CREATE:
|
||||
return this.parseCreateTable();
|
||||
case TokenType.DROP:
|
||||
return this.parseDropTable();
|
||||
default:
|
||||
throw this.error(`Unexpected token "${this.curToken.value}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// SELECT
|
||||
// ===================================================================
|
||||
|
||||
private parseSelect(): SelectStatement {
|
||||
this.expect(TokenType.SELECT);
|
||||
|
||||
// DISTINCT(可选)
|
||||
let distinct = false;
|
||||
if (this.curTokenIs(TokenType.DISTINCT)) {
|
||||
distinct = true;
|
||||
this.nextToken();
|
||||
}
|
||||
|
||||
// 列
|
||||
const columns: string[] = [];
|
||||
if (this.curTokenIs(TokenType.STAR)) {
|
||||
columns.push('*');
|
||||
this.nextToken();
|
||||
} else {
|
||||
columns.push(...this.parseColumnList());
|
||||
}
|
||||
|
||||
// FROM
|
||||
this.expect(TokenType.FROM);
|
||||
const tableName = this.expectIdentifier('table name');
|
||||
|
||||
// 表别名(可选)
|
||||
let alias: string | undefined;
|
||||
if (this.curTokenIs(TokenType.AS)) {
|
||||
this.nextToken();
|
||||
alias = this.expectIdentifier('alias');
|
||||
} else if (this.curToken.type === TokenType.IDENTIFIER && !this._isReservedAfterFrom()) {
|
||||
alias = this.curToken.value;
|
||||
this.nextToken();
|
||||
}
|
||||
|
||||
const stmt: SelectStatement = {
|
||||
type: 'SELECT',
|
||||
columns,
|
||||
distinct: distinct || undefined,
|
||||
from: tableName,
|
||||
alias,
|
||||
where: {},
|
||||
};
|
||||
|
||||
// JOIN 子句(可选,支持多个)
|
||||
const joins = this.parseJoinClauses();
|
||||
if (joins.length > 0) {
|
||||
stmt.joins = joins;
|
||||
}
|
||||
|
||||
// WHERE(可选)
|
||||
if (this.curTokenIs(TokenType.WHERE)) {
|
||||
this.nextToken();
|
||||
stmt.where = this.parseCondition();
|
||||
}
|
||||
|
||||
// GROUP BY(可选)
|
||||
if (this.curTokenIs(TokenType.GROUP)) {
|
||||
this.nextToken();
|
||||
this.expect(TokenType.BY);
|
||||
stmt.groupBy = this.parseIdentifierList();
|
||||
}
|
||||
|
||||
// HAVING(可选)
|
||||
if (this.curTokenIs(TokenType.HAVING)) {
|
||||
this.nextToken();
|
||||
stmt.having = this.parseCondition();
|
||||
}
|
||||
|
||||
// ORDER BY(可选)
|
||||
if (this.curTokenIs(TokenType.ORDER)) {
|
||||
this.nextToken();
|
||||
this.expect(TokenType.BY);
|
||||
stmt.orderBy = this.parseOrderByList();
|
||||
}
|
||||
|
||||
// LIMIT(可选)
|
||||
if (this.curTokenIs(TokenType.LIMIT)) {
|
||||
this.nextToken();
|
||||
stmt.limit = this.expectNumber('LIMIT value');
|
||||
}
|
||||
|
||||
// OFFSET(可选)
|
||||
if (this.curTokenIs(TokenType.OFFSET)) {
|
||||
this.nextToken();
|
||||
stmt.offset = this.expectNumber('OFFSET value');
|
||||
}
|
||||
|
||||
return stmt;
|
||||
}
|
||||
|
||||
/** 解析 JOIN 子句列表 */
|
||||
private parseJoinClauses(): import('../query/ast').JoinClause[] {
|
||||
const joins: import('../query/ast').JoinClause[] = [];
|
||||
while (this._isJoinKeyword()) {
|
||||
joins.push(this.parseJoinClause());
|
||||
}
|
||||
return joins;
|
||||
}
|
||||
|
||||
private _isJoinKeyword(): boolean {
|
||||
return (
|
||||
this.curTokenIs(TokenType.INNER) ||
|
||||
this.curTokenIs(TokenType.LEFT) ||
|
||||
this.curTokenIs(TokenType.RIGHT) ||
|
||||
this.curTokenIs(TokenType.CROSS) ||
|
||||
this.curTokenIs(TokenType.JOIN)
|
||||
);
|
||||
}
|
||||
|
||||
/** 解析单个 JOIN 子句 */
|
||||
private parseJoinClause(): import('../query/ast').JoinClause {
|
||||
let type: import('../query/ast').JoinType = 'INNER';
|
||||
|
||||
if (this.curTokenIs(TokenType.INNER)) {
|
||||
type = 'INNER';
|
||||
this.nextToken();
|
||||
} else if (this.curTokenIs(TokenType.LEFT)) {
|
||||
type = 'LEFT';
|
||||
this.nextToken();
|
||||
if (this.curTokenIs(TokenType.OUTER)) this.nextToken(); // 可选 OUTER
|
||||
} else if (this.curTokenIs(TokenType.RIGHT)) {
|
||||
type = 'RIGHT';
|
||||
this.nextToken();
|
||||
if (this.curTokenIs(TokenType.OUTER)) this.nextToken();
|
||||
} else if (this.curTokenIs(TokenType.CROSS)) {
|
||||
type = 'CROSS';
|
||||
this.nextToken();
|
||||
}
|
||||
|
||||
this.expect(TokenType.JOIN);
|
||||
const tableName = this.expectIdentifier('table name');
|
||||
|
||||
// JOIN 表别名(可选)
|
||||
let alias: string | undefined;
|
||||
if (this.curTokenIs(TokenType.AS)) {
|
||||
this.nextToken();
|
||||
alias = this.expectIdentifier('alias');
|
||||
} else if (this.curToken.type === TokenType.IDENTIFIER && !this._isJoinReserved()) {
|
||||
alias = this.curToken.value;
|
||||
this.nextToken();
|
||||
}
|
||||
|
||||
// ON 条件(CROSS JOIN 不需要 ON)
|
||||
let on = {};
|
||||
if (type !== 'CROSS' && this.curTokenIs(TokenType.ON)) {
|
||||
this.nextToken();
|
||||
on = this.parseCondition();
|
||||
}
|
||||
|
||||
return { type, table: tableName, alias, on };
|
||||
}
|
||||
|
||||
/** 判断当前 token 是否为 FROM 之后的保留字 */
|
||||
private _isReservedAfterFrom(): boolean {
|
||||
return (
|
||||
this.curTokenIs(TokenType.WHERE) ||
|
||||
this.curTokenIs(TokenType.ORDER) ||
|
||||
this.curTokenIs(TokenType.LIMIT) ||
|
||||
this.curTokenIs(TokenType.OFFSET) ||
|
||||
this.curTokenIs(TokenType.GROUP) ||
|
||||
this._isJoinKeyword()
|
||||
);
|
||||
}
|
||||
|
||||
private _isJoinReserved(): boolean {
|
||||
return (
|
||||
this.curTokenIs(TokenType.ON) ||
|
||||
this.curTokenIs(TokenType.WHERE) ||
|
||||
this.curTokenIs(TokenType.ORDER) ||
|
||||
this.curTokenIs(TokenType.LIMIT) ||
|
||||
this._isJoinKeyword()
|
||||
);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// INSERT
|
||||
// ===================================================================
|
||||
|
||||
private parseInsert(): InsertStatement {
|
||||
this.expect(TokenType.INSERT);
|
||||
this.expect(TokenType.INTO);
|
||||
|
||||
const tableName = this.expectIdentifier('table name');
|
||||
|
||||
// 列名(可选)
|
||||
let columns: string[] | undefined;
|
||||
if (this.curTokenIs(TokenType.LPAREN)) {
|
||||
this.nextToken();
|
||||
columns = this.parseIdentifierList();
|
||||
this.expect(TokenType.RPAREN);
|
||||
}
|
||||
|
||||
// VALUES
|
||||
this.expect(TokenType.VALUES);
|
||||
|
||||
// 值列表
|
||||
const values: unknown[][] = [];
|
||||
do {
|
||||
if (this.curTokenIs(TokenType.COMMA)) {
|
||||
this.nextToken();
|
||||
}
|
||||
this.expect(TokenType.LPAREN);
|
||||
const rowValues = this.parseValueList();
|
||||
this.expect(TokenType.RPAREN);
|
||||
values.push(rowValues);
|
||||
} while (this.curTokenIs(TokenType.COMMA));
|
||||
|
||||
return {
|
||||
type: 'INSERT',
|
||||
into: tableName,
|
||||
columns,
|
||||
values,
|
||||
};
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// UPDATE
|
||||
// ===================================================================
|
||||
|
||||
private parseUpdate(): UpdateStatement {
|
||||
this.expect(TokenType.UPDATE);
|
||||
const tableName = this.expectIdentifier('table name');
|
||||
this.expect(TokenType.SET);
|
||||
|
||||
// SET col=val, ...
|
||||
const sets: Record<string, unknown> = {};
|
||||
do {
|
||||
if (this.curTokenIs(TokenType.COMMA)) this.nextToken();
|
||||
const col = this.expectIdentifier('column name');
|
||||
this.expect(TokenType.EQ);
|
||||
sets[col] = this.parseValue();
|
||||
} while (this.curTokenIs(TokenType.COMMA));
|
||||
|
||||
let where: WhereCondition = {};
|
||||
if (this.curTokenIs(TokenType.WHERE)) {
|
||||
this.nextToken();
|
||||
where = this.parseCondition();
|
||||
}
|
||||
|
||||
return { type: 'UPDATE', table: tableName, sets, where };
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// DELETE
|
||||
// ===================================================================
|
||||
|
||||
private parseDelete(): DeleteStatement {
|
||||
this.expect(TokenType.DELETE);
|
||||
this.expect(TokenType.FROM);
|
||||
const tableName = this.expectIdentifier('table name');
|
||||
|
||||
let where: WhereCondition = {};
|
||||
if (this.curTokenIs(TokenType.WHERE)) {
|
||||
this.nextToken();
|
||||
where = this.parseCondition();
|
||||
}
|
||||
|
||||
return { type: 'DELETE', from: tableName, where };
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// CREATE TABLE
|
||||
// ===================================================================
|
||||
|
||||
private parseCreateTable(): CreateTableStatement {
|
||||
this.expect(TokenType.CREATE);
|
||||
this.expect(TokenType.TABLE);
|
||||
const tableName = this.expectIdentifier('table name');
|
||||
this.expect(TokenType.LPAREN);
|
||||
|
||||
const columns: ASTColumnDef[] = [];
|
||||
do {
|
||||
if (this.curTokenIs(TokenType.COMMA)) this.nextToken();
|
||||
columns.push(this.parseColumnDef());
|
||||
} while (this.curTokenIs(TokenType.COMMA));
|
||||
|
||||
this.expect(TokenType.RPAREN);
|
||||
|
||||
return { type: 'CREATE_TABLE', name: tableName, columns };
|
||||
}
|
||||
|
||||
private parseColumnDef(): ASTColumnDef {
|
||||
const name = this.expectIdentifier('column name');
|
||||
const type = this.expectIdentifier('column type').toLowerCase();
|
||||
|
||||
const col: ASTColumnDef = { name, type };
|
||||
|
||||
// 修饰符
|
||||
while (
|
||||
this.curTokenIs(TokenType.PRIMARY) ||
|
||||
this.curTokenIs(TokenType.UNIQUE) ||
|
||||
this.curTokenIs(TokenType.NOT) ||
|
||||
this.curTokenIs(TokenType.DEFAULT)
|
||||
) {
|
||||
if (this.curTokenIs(TokenType.PRIMARY)) {
|
||||
this.nextToken();
|
||||
this.expect(TokenType.KEY);
|
||||
col.primaryKey = true;
|
||||
} else if (this.curTokenIs(TokenType.UNIQUE)) {
|
||||
this.nextToken();
|
||||
col.unique = true;
|
||||
} else if (this.curTokenIs(TokenType.NOT)) {
|
||||
this.nextToken();
|
||||
// 支持 NOT NULL → required
|
||||
this.expect(TokenType.NULL);
|
||||
col.required = true;
|
||||
} else if (this.curTokenIs(TokenType.DEFAULT)) {
|
||||
this.nextToken();
|
||||
col.default = this.parseValue();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// DROP TABLE
|
||||
// ===================================================================
|
||||
|
||||
private parseDropTable(): DropTableStatement {
|
||||
this.expect(TokenType.DROP);
|
||||
this.expect(TokenType.TABLE);
|
||||
const tableName = this.expectIdentifier('table name');
|
||||
return { type: 'DROP_TABLE', name: tableName };
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 条件表达式
|
||||
// ===================================================================
|
||||
|
||||
/** condition → simple_cond ((AND|OR) simple_cond)* */
|
||||
private parseCondition(): WhereCondition {
|
||||
let left = this.parseSimpleCondition();
|
||||
|
||||
while (this.curTokenIs(TokenType.AND) || this.curTokenIs(TokenType.OR)) {
|
||||
const isAnd = this.curTokenIs(TokenType.AND);
|
||||
this.nextToken();
|
||||
const right = this.parseSimpleCondition();
|
||||
|
||||
if (isAnd) {
|
||||
// 合并到 $and
|
||||
left = { $and: [left, right] } as unknown as WhereCondition;
|
||||
} else {
|
||||
left = { $or: [left, right] } as unknown as WhereCondition;
|
||||
}
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
/** simple_cond → column op value | column IS [NOT] NULL | column [NOT] LIKE pattern
|
||||
* | column [NOT] IN (values) | NOT condition | (condition) */
|
||||
private parseSimpleCondition(): WhereCondition {
|
||||
// NOT expr
|
||||
if (this.curTokenIs(TokenType.NOT)) {
|
||||
this.nextToken();
|
||||
const inner = this.parseSimpleCondition();
|
||||
return { $not: inner } as unknown as WhereCondition;
|
||||
}
|
||||
|
||||
// (condition)
|
||||
if (this.curTokenIs(TokenType.LPAREN)) {
|
||||
this.nextToken();
|
||||
const inner = this.parseCondition();
|
||||
this.expect(TokenType.RPAREN);
|
||||
return inner;
|
||||
}
|
||||
|
||||
// column
|
||||
const column = this.parseColumnRef();
|
||||
|
||||
// IS NULL / IS NOT NULL
|
||||
if (this.curTokenIs(TokenType.IDENTIFIER) && this.curToken.value.toUpperCase() === 'IS') {
|
||||
this.nextToken();
|
||||
const isNot = this.curTokenIs(TokenType.NOT);
|
||||
if (isNot) this.nextToken();
|
||||
this.expect(TokenType.NULL);
|
||||
const result: WhereCondition = {};
|
||||
result[column] = isNot ? { $ne: null } : { $eq: null };
|
||||
return result;
|
||||
}
|
||||
|
||||
// LIKE
|
||||
if (this.curTokenIs(TokenType.LIKE)) {
|
||||
this.nextToken();
|
||||
const pattern = this.parseValue();
|
||||
const result: WhereCondition = {};
|
||||
result[column] = { $like: pattern };
|
||||
return result;
|
||||
}
|
||||
|
||||
// IN
|
||||
if (this.curTokenIs(TokenType.IN)) {
|
||||
this.nextToken();
|
||||
this.expect(TokenType.LPAREN);
|
||||
const values = this.parseValueList();
|
||||
this.expect(TokenType.RPAREN);
|
||||
const result: WhereCondition = {};
|
||||
result[column] = { $in: values };
|
||||
return result;
|
||||
}
|
||||
|
||||
// 比较运算符
|
||||
const op = this.parseComparisonOp();
|
||||
|
||||
// 尝试解析列引用(identifier DOT identifier 格式)
|
||||
let value: unknown;
|
||||
if (
|
||||
(this.curToken.type === TokenType.IDENTIFIER || this._isKeywordAsIdent()) &&
|
||||
this.peekTokenIs(TokenType.DOT)
|
||||
) {
|
||||
const colRef = this.parseColumnRef();
|
||||
value = { $col: colRef };
|
||||
} else {
|
||||
value = this.parseValue();
|
||||
}
|
||||
|
||||
const result: WhereCondition = {};
|
||||
result[column] = { [op]: value };
|
||||
return result;
|
||||
}
|
||||
|
||||
private peekTokenIs(type: TokenType): boolean {
|
||||
return this.peekToken.type === type;
|
||||
}
|
||||
|
||||
private parseComparisonOp(): string {
|
||||
switch (this.curToken.type) {
|
||||
case TokenType.EQ: this.nextToken(); return '$eq';
|
||||
case TokenType.NEQ: this.nextToken(); return '$ne';
|
||||
case TokenType.GT: this.nextToken(); return '$gt';
|
||||
case TokenType.GTE: this.nextToken(); return '$gte';
|
||||
case TokenType.LT: this.nextToken(); return '$lt';
|
||||
case TokenType.LTE: this.nextToken(); return '$lte';
|
||||
default:
|
||||
throw this.error(`Expected comparison operator, got "${this.curToken.value}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 辅助解析
|
||||
// ===================================================================
|
||||
|
||||
private parseColumnList(): string[] {
|
||||
const cols: string[] = [];
|
||||
cols.push(this.parseColumnRef());
|
||||
while (this.curTokenIs(TokenType.COMMA)) {
|
||||
this.nextToken();
|
||||
cols.push(this.parseColumnRef());
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
|
||||
/** 解析列引用:支持 'col'、'table.col' 和 'COUNT(*)'/'SUM(col)' 等 */
|
||||
private parseColumnRef(): string {
|
||||
// 聚合函数?
|
||||
if (
|
||||
this.curTokenIs(TokenType.COUNT) ||
|
||||
this.curTokenIs(TokenType.SUM) ||
|
||||
this.curTokenIs(TokenType.AVG) ||
|
||||
this.curTokenIs(TokenType.MIN) ||
|
||||
this.curTokenIs(TokenType.MAX)
|
||||
) {
|
||||
return this.parseAggregateCall();
|
||||
}
|
||||
|
||||
const first = this.expectIdentifier('column name');
|
||||
if (this.curTokenIs(TokenType.DOT)) {
|
||||
this.nextToken();
|
||||
const second = this.expectIdentifier('column name');
|
||||
return `${first}.${second}`;
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
/** 解析聚合函数调用: COUNT(*), SUM(col), AVG(col), MIN(col), MAX(col) */
|
||||
private parseAggregateCall(): string {
|
||||
const func = this.curToken.value.toUpperCase();
|
||||
this.nextToken();
|
||||
this.expect(TokenType.LPAREN);
|
||||
|
||||
let arg: string;
|
||||
if (this.curTokenIs(TokenType.STAR)) {
|
||||
arg = '*';
|
||||
this.nextToken();
|
||||
} else {
|
||||
arg = this.parseColumnRef();
|
||||
}
|
||||
|
||||
this.expect(TokenType.RPAREN);
|
||||
|
||||
// 可选别名: AS alias
|
||||
let alias = '';
|
||||
if (this.curTokenIs(TokenType.AS)) {
|
||||
this.nextToken();
|
||||
alias = this.expectIdentifier('alias');
|
||||
} else if (this.curToken.type === TokenType.IDENTIFIER && this._isAggregateAlias()) {
|
||||
alias = this.curToken.value;
|
||||
this.nextToken();
|
||||
}
|
||||
|
||||
if (alias) {
|
||||
return `${func}(${arg}) AS ${alias}`;
|
||||
}
|
||||
return `${func}(${arg})`;
|
||||
}
|
||||
|
||||
private _isAggregateAlias(): boolean {
|
||||
return !this._isReservedAfterFrom() && !this._isJoinKeyword();
|
||||
}
|
||||
|
||||
private parseIdentifierList(): string[] {
|
||||
const ids: string[] = [];
|
||||
ids.push(this.expectIdentifier('identifier'));
|
||||
while (this.curTokenIs(TokenType.COMMA)) {
|
||||
this.nextToken();
|
||||
ids.push(this.expectIdentifier('identifier'));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private parseValueList(): unknown[] {
|
||||
const vals: unknown[] = [];
|
||||
vals.push(this.parseValue());
|
||||
while (this.curTokenIs(TokenType.COMMA)) {
|
||||
this.nextToken();
|
||||
vals.push(this.parseValue());
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
private parseOrderByList(): OrderBy[] {
|
||||
const list: OrderBy[] = [];
|
||||
list.push(this.parseOrderBy());
|
||||
while (this.curTokenIs(TokenType.COMMA)) {
|
||||
this.nextToken();
|
||||
list.push(this.parseOrderBy());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private parseOrderBy(): OrderBy {
|
||||
const column = this.expectIdentifier('column name');
|
||||
let direction: SortDirection = 'asc';
|
||||
if (this.curTokenIs(TokenType.ASC)) {
|
||||
this.nextToken();
|
||||
} else if (this.curTokenIs(TokenType.DESC)) {
|
||||
direction = 'desc';
|
||||
this.nextToken();
|
||||
}
|
||||
return { column, direction };
|
||||
}
|
||||
|
||||
/** 解析字面量值 */
|
||||
private parseValue(): unknown {
|
||||
switch (this.curToken.type) {
|
||||
case TokenType.STRING: {
|
||||
const val = this.curToken.value;
|
||||
this.nextToken();
|
||||
return val;
|
||||
}
|
||||
case TokenType.NUMBER: {
|
||||
const val = Number(this.curToken.value);
|
||||
this.nextToken();
|
||||
return val;
|
||||
}
|
||||
case TokenType.TRUE: this.nextToken(); return true;
|
||||
case TokenType.FALSE: this.nextToken(); return false;
|
||||
case TokenType.NULL: this.nextToken(); return null;
|
||||
default:
|
||||
throw this.error(`Expected value, got "${this.curToken.value}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Token 操作
|
||||
// ===================================================================
|
||||
|
||||
private nextToken(): void {
|
||||
this.curToken = this.peekToken;
|
||||
this.peekToken = this.lexer.nextToken();
|
||||
}
|
||||
|
||||
private curTokenIs(type: TokenType): boolean {
|
||||
return this.curToken.type === type;
|
||||
}
|
||||
|
||||
private expect(type: TokenType): void {
|
||||
if (this.curTokenIs(type)) {
|
||||
this.nextToken();
|
||||
return;
|
||||
}
|
||||
throw this.error(`Expected ${type}, got "${this.curToken.value}"`);
|
||||
}
|
||||
|
||||
private expectIdentifier(context: string): string {
|
||||
if (this.curToken.type === TokenType.IDENTIFIER || this._isKeywordAsIdent()) {
|
||||
const val = this.curToken.value;
|
||||
this.nextToken();
|
||||
return val;
|
||||
}
|
||||
throw this.error(`Expected ${context}, got "${this.curToken.value}"`);
|
||||
}
|
||||
|
||||
/** 关键字可以作为标识符(如列名等于关键字) */
|
||||
private _isKeywordAsIdent(): boolean {
|
||||
return (
|
||||
this.curToken.type !== TokenType.EOF &&
|
||||
this.curToken.type !== TokenType.ILLEGAL &&
|
||||
this.curToken.type !== TokenType.STRING &&
|
||||
this.curToken.type !== TokenType.NUMBER &&
|
||||
this.curToken.type !== TokenType.COMMA &&
|
||||
this.curToken.type !== TokenType.LPAREN &&
|
||||
this.curToken.type !== TokenType.RPAREN &&
|
||||
this.curToken.type !== TokenType.SEMICOLON &&
|
||||
this.curToken.type !== TokenType.EQ &&
|
||||
this.curToken.type !== TokenType.NEQ &&
|
||||
this.curToken.type !== TokenType.GT &&
|
||||
this.curToken.type !== TokenType.GTE &&
|
||||
this.curToken.type !== TokenType.LT &&
|
||||
this.curToken.type !== TokenType.LTE &&
|
||||
this.curToken.type !== TokenType.DOT &&
|
||||
this.curToken.type !== TokenType.STAR
|
||||
);
|
||||
}
|
||||
|
||||
private expectNumber(context: string): number {
|
||||
if (this.curToken.type === TokenType.NUMBER) {
|
||||
const val = Number(this.curToken.value);
|
||||
this.nextToken();
|
||||
return val;
|
||||
}
|
||||
throw this.error(`Expected ${context}, got "${this.curToken.value}"`);
|
||||
}
|
||||
|
||||
private error(msg: string): DatabaseError {
|
||||
return new DatabaseError(
|
||||
`Parse error at position ${this.curToken.position}: ${msg}`,
|
||||
'PARSE_ERROR',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 便捷方法
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 解析 SQL 字符串为 AST Statement */
|
||||
export function parse(sql: string): Statement {
|
||||
const parser = new Parser(sql);
|
||||
const stmt = parser.parseStatement();
|
||||
return stmt;
|
||||
}
|
||||
Reference in New Issue
Block a user