feat: add Parsed and deinit
This commit is contained in:
1 parent
3480459c98
commit
d4a836ad27
1 file changed
+157
+157
@@ -0,0 +1,157 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const rowmod = @import("row.zig");
|
||||||
|
const idx = @import("index.zig");
|
||||||
|
const querymod = @import("query.zig");
|
||||||
|
const dbmod = @import("db.zig");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const Predicate = querymod.Predicate;
|
||||||
|
const Condition = querymod.Condition;
|
||||||
|
|
||||||
|
/// Errors a caller can act on when a `table select` expression is rejected.
|
||||||
|
/// Parsing never panics on malformed input - every failure mode a user can
|
||||||
|
/// trigger from the CLI maps to one of these.
|
||||||
|
pub const ParseError = error{
|
||||||
|
UnexpectedToken,
|
||||||
|
UnexpectedEnd,
|
||||||
|
InvalidIdentifier,
|
||||||
|
UnknownOperator,
|
||||||
|
InvalidValue,
|
||||||
|
} || Allocator.Error;
|
||||||
|
|
||||||
|
/// Owns every allocation a parsed Predicate tree depends on (field-name and
|
||||||
|
/// text-value copies, and_/or_ slices, not_ boxes) in one arena, so freeing
|
||||||
|
/// is a single `deinit()` regardless of how deep the tree is.
|
||||||
|
pub const Parsed = struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
predicate: Predicate,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Parsed) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const TokenKind = enum { lparen, rparen, word, eof };
|
||||||
|
|
||||||
|
const Token = struct {
|
||||||
|
kind: TokenKind,
|
||||||
|
text: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Lexer = struct {
|
||||||
|
src: []const u8,
|
||||||
|
pos: usize = 0,
|
||||||
|
|
||||||
|
fn next(self: *Lexer) Token {
|
||||||
|
while (self.pos < self.src.len and std.ascii.isWhitespace(self.src[self.pos])) : (self.pos += 1) {}
|
||||||
|
if (self.pos >= self.src.len) return .{ .kind = .eof, .text = "" };
|
||||||
|
|
||||||
|
const c = self.src[self.pos];
|
||||||
|
if (c == '(') {
|
||||||
|
self.pos += 1;
|
||||||
|
return .{ .kind = .lparen, .text = "(" };
|
||||||
|
}
|
||||||
|
if (c == ')') {
|
||||||
|
self.pos += 1;
|
||||||
|
return .{ .kind = .rparen, .text = ")" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = self.pos;
|
||||||
|
while (self.pos < self.src.len) {
|
||||||
|
const ch = self.src[self.pos];
|
||||||
|
if (std.ascii.isWhitespace(ch) or ch == '(' or ch == ')') break;
|
||||||
|
self.pos += 1;
|
||||||
|
}
|
||||||
|
return .{ .kind = .word, .text = self.src[start..self.pos] };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn isKeyword(tok: Token, kw: []const u8) bool {
|
||||||
|
return tok.kind == .word and std.mem.eql(u8, tok.text, kw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursive-descent parser for the `table select` predicate language:
|
||||||
|
///
|
||||||
|
/// expr := and ("or" and)*
|
||||||
|
/// and := unary ("and" unary)*
|
||||||
|
/// unary := "not" unary | primary
|
||||||
|
/// primary := "(" expr ")" | field op value | field ("is_null" | "is_not_null")
|
||||||
|
/// op := eq | ne | lt | lte | gt | gte | like
|
||||||
|
///
|
||||||
|
/// Precedence (loosest to tightest) is or < and < not, matching every other
|
||||||
|
/// C-family boolean expression language, so `a eq 1 or b eq 2 and not c eq 3`
|
||||||
|
/// parses as `a eq 1 or (b eq 2 and (not (c eq 3)))`.
|
||||||
|
const Parser = struct {
|
||||||
|
lexer: Lexer,
|
||||||
|
allocator: Allocator,
|
||||||
|
cur: Token,
|
||||||
|
|
||||||
|
fn advance(self: *Parser) void {
|
||||||
|
self.cur = self.lexer.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseOr(self: *Parser) ParseError!Predicate {
|
||||||
|
var list = std.ArrayList(Predicate).init(self.allocator);
|
||||||
|
try list.append(try self.parseAnd());
|
||||||
|
while (isKeyword(self.cur, "or")) {
|
||||||
|
self.advance();
|
||||||
|
try list.append(try self.parseAnd());
|
||||||
|
}
|
||||||
|
if (list.items.len == 1) return list.items[0];
|
||||||
|
return .{ .or_ = try list.toOwnedSlice() };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseAnd(self: *Parser) ParseError!Predicate {
|
||||||
|
var list = std.ArrayList(Predicate).init(self.allocator);
|
||||||
|
try list.append(try self.parseUnary());
|
||||||
|
while (isKeyword(self.cur, "and")) {
|
||||||
|
self.advance();
|
||||||
|
try list.append(try self.parseUnary());
|
||||||
|
}
|
||||||
|
if (list.items.len == 1) return list.items[0];
|
||||||
|
return .{ .and_ = try list.toOwnedSlice() };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseUnary(self: *Parser) ParseError!Predicate {
|
||||||
|
if (isKeyword(self.cur, "not")) {
|
||||||
|
self.advance();
|
||||||
|
const inner = try self.parseUnary();
|
||||||
|
const boxed = try self.allocator.create(Predicate);
|
||||||
|
boxed.* = inner;
|
||||||
|
return .{ .not_ = boxed };
|
||||||
|
}
|
||||||
|
return self.parsePrimary();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parsePrimary(self: *Parser) ParseError!Predicate {
|
||||||
|
if (self.cur.kind == .lparen) {
|
||||||
|
self.advance();
|
||||||
|
const inner = try self.parseOr();
|
||||||
|
if (self.cur.kind != .rparen) return error.UnexpectedToken;
|
||||||
|
self.advance();
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
return self.parseCondition();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseCondition(self: *Parser) ParseError!Predicate {
|
||||||
|
if (self.cur.kind != .word) return if (self.cur.kind == .eof) error.UnexpectedEnd else error.UnexpectedToken;
|
||||||
|
const field = self.cur.text;
|
||||||
|
if (!idx.validIdentifier(field)) return error.InvalidIdentifier;
|
||||||
|
const fieldCopy = try self.allocator.dupe(u8, field);
|
||||||
|
self.advance();
|
||||||
|
|
||||||
|
if (self.cur.kind != .word) return error.UnexpectedEnd;
|
||||||
|
const op = self.cur.text;
|
||||||
|
self.advance();
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, op, "in")) return self.parseIn(fieldCopy);
|
||||||
|
if (std.mem.eql(u8, op, "between")) return self.parseBetween(fieldCopy);
|
||||||
|
if (std.mem.eql(u8, op, "is_null")) return .{ .is_null = fieldCopy };
|
||||||
|
if (std.mem.eql(u8, op, "is_not_null")) return .{ .is_not_null = fieldCopy };
|
||||||
|
|
||||||
|
if (self.cur.kind != .word) return error.UnexpectedEnd;
|
||||||
|
const rawValue = rowmod.Value.parseCli(self.cur.text) catch return error.InvalidValue;
|
||||||
|
const value = try rawValue.dupe(self.allocator);
|
||||||
|
self.advance();
|
||||||
|
|
||||||
Reference in new issue
Block a user