ZipponDB/src/ADD.zig

185 lines
9.2 KiB
Zig

const std = @import("std");
const metadata = @import("metadata.zig");
const UUID = @import("uuid.zig").UUID;
const Tokenizer = @import("ziqlTokenizer.zig").Tokenizer;
const Token = @import("ziqlTokenizer.zig").Token;
const DataEngine = @import("dataEngine.zig").DataEngine;
const Allocator = std.mem.Allocator;
const stdout = std.io.getStdOut().writer();
// Query that need to work now
// ADD User (name='Adrien', email='adrien.bouvais@gmail.com') OK
// ADD User (name='Adrien', email='adrien.bouvais@gmail.com', age = 26) OK
// ADD User (name='Adrien', email='adrien.bouvais@gmail.com', books = ['book1', 'book2']) OK
// ADD User (name='Adrien', email=null) OK
//
// For later: links
// ADD User (name = 'Adrien', best_friend = {name='bob'}, friends = {name != 'bob'}) NOT OK
// ADD User (name = 'Adrien', friends = {(name = 'bob' AND age > 16) OR (id = '0000-0000')} ) NOT OK
// TODO: make real test
/// Function for the ADD query command.
/// It will parse the reste of the query and create a map of member name / value.
/// Then add those value to the appropriete file. The proper file is the first one with a size < to the limit.
/// If no file is found, a new one is created.
/// Take the main.zippondata file, the index of the file where the data is saved and the string to add at the end of the line
pub const Parser = struct {
allocator: Allocator,
toker: *Tokenizer,
pub fn init(allocator: Allocator, toker: *Tokenizer) Parser {
return Parser{
.allocator = allocator,
.toker = toker,
};
}
pub fn parse(self: *Parser) !void {
var data_engine = DataEngine.init(self.allocator, null);
defer data_engine.deinit();
var struct_name_token = self.toker.next();
const struct_name = self.toker.getTokenSlice(struct_name_token);
if (!metadata.isStructNameExists(struct_name)) self.print_error("Struct not found in current schema", &struct_name_token);
var token = self.toker.next();
switch (token.tag) {
.l_paren => {},
else => {
self.print_error("Error: Expected (", &token);
},
}
var data_map = self.parseData(struct_name);
defer data_map.deinit();
if (self.checkIfAllMemberInMap(struct_name, &data_map)) {
try data_engine.writeEntity(struct_name, data_map);
} else |_| {}
}
/// Take the tokenizer and return a map of the query for the ADD command.
/// Keys are the member name and value are the string of the value in the query. E.g. 'Adrien' or '10'
/// TODO: Make it clean using a State like other parser
pub fn parseData(self: *Parser, struct_name: []const u8) std.StringHashMap([]const u8) {
var token = self.toker.next();
var member_map = std.StringHashMap([]const u8).init(self.allocator);
while (token.tag != Token.Tag.eof) : (token = self.toker.next()) {
switch (token.tag) {
.r_paren => continue,
.identifier => {
const member_name_str = self.toker.getTokenSlice(token);
if (!metadata.isMemberNameInStruct(struct_name, member_name_str)) self.print_error("Member not found in struct.", &token);
token = self.toker.next();
switch (token.tag) {
.equal => {
token = self.toker.next();
switch (token.tag) {
.string_literal, .number_literal => {
const value_str = self.toker.getTokenSlice(token);
member_map.put(member_name_str, value_str) catch self.print_error("Could not add member name and value to map in getMapOfMember", &token);
token = self.toker.next();
switch (token.tag) {
.comma, .r_paren => continue,
else => self.print_error("Error: Expected , after string or number. E.g. ADD User (name='bob', age=10)", &token),
}
},
.keyword_null => {
const value_str = "null";
member_map.put(member_name_str, value_str) catch self.print_error("Error: 001", &token);
token = self.toker.next();
switch (token.tag) {
.comma, .r_paren => continue,
else => self.print_error("Error: Expected , after string or number. E.g. ADD User (name='bob', age=10)", &token),
}
},
// Create a tag to prevent creating an array then join them. Instead just read the buffer from [ to ] in the tekenizer itself
.l_bracket => {
var array_values = std.ArrayList([]const u8).init(self.allocator);
token = self.toker.next();
while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) {
switch (token.tag) {
.string_literal, .number_literal => {
const value_str = self.toker.getTokenSlice(token);
array_values.append(value_str) catch self.print_error("Could not add value to array in getMapOfMember", &token);
},
else => self.print_error("Error: Expected string or number in array. E.g. ADD User (scores=[10 20 30])", &token),
}
}
// Maybe change that as it just recreate a string that is already in the buffer
const array_str = std.mem.join(self.allocator, " ", array_values.items) catch {
self.print_error("Couln't join the value of array", &token);
@panic("=)");
};
member_map.put(member_name_str, array_str) catch self.print_error("Could not add member name and value to map in getMapOfMember", &token);
token = self.toker.next();
switch (token.tag) {
.comma, .r_paren => continue,
else => self.print_error("Error: Expected , after string or number. E.g. ADD User (name='bob', age=10)", &token),
}
},
else => self.print_error("Error: Expected string or number after =. E.g. ADD User (name='bob')", &token),
}
},
else => self.print_error("Error: Expected = after a member declaration. E.g. ADD User (name='bob')", &token),
}
},
else => self.print_error("Error: Unknow token. This should be the name of a member. E.g. name in ADD User (name='bob')", &token),
}
}
return member_map;
}
const AddError = error{NotAllMemberInMap};
fn checkIfAllMemberInMap(_: *Parser, struct_name: []const u8, map: *std.StringHashMap([]const u8)) !void {
const all_struct_member = metadata.structName2structMembers(struct_name);
var count: u16 = 0;
var started_printing = false;
for (all_struct_member) |key| {
if (map.contains(key)) count += 1 else {
if (!started_printing) {
try stdout.print("Error: ADD query of struct: {s}; missing member: {s}", .{ struct_name, key });
started_printing = true;
} else {
try stdout.print(" {s}", .{key});
}
}
}
if (started_printing) try stdout.print("\n", .{});
if (!((count == all_struct_member.len) and (count == map.count()))) return error.NotAllMemberInMap;
}
fn print_error(self: *Parser, message: []const u8, token: *Token) void {
stdout.print("\n", .{}) catch {};
stdout.print("{s}\n", .{self.toker.buffer}) catch {};
// Calculate the number of spaces needed to reach the start position.
var spaces: usize = 0;
while (spaces < token.loc.start) : (spaces += 1) {
stdout.print(" ", .{}) catch {};
}
// Print the '^' characters for the error span.
var i: usize = token.loc.start;
while (i < token.loc.end) : (i += 1) {
stdout.print("^", .{}) catch {};
}
stdout.print(" \n", .{}) catch {}; // Align with the message
stdout.print("{s}\n", .{message}) catch {};
@panic("");
}
};