185 lines
9.2 KiB
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("");
|
|
}
|
|
};
|