diff --git a/src/cli.zig b/src/cli.zig index 53a71ce..61a6817 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -85,187 +85,185 @@ pub fn main() !void { var token = cliToker.next(); state = .expect_main_command; - while ((state != .end) and (state != .quit)) : (token = cliToker.next()) { - switch (state) { - .expect_main_command => switch (token.tag) { - .keyword_run => { - if (!file_engine.usable) { - send("Error: No database selected. Please use db new or db use.", .{}); + while ((state != .end) and (state != .quit)) : (token = cliToker.next()) switch (state) { + .expect_main_command => switch (token.tag) { + .keyword_run => { + if (!file_engine.usable) { + send("Error: No database selected. Please use db new or db use.", .{}); + state = .end; + continue; + } + state = .expect_query; + }, + .keyword_db => state = .expect_db_command, + .keyword_schema => { + if (!file_engine.usable) { + send("Error: No database selected. Please use db new or db use.", .{}); + state = .end; + continue; + } + state = .expect_schema_command; + }, + .keyword_help => { + send("{s}", .{ + \\Welcome to ZipponDB v0.1! + \\ + \\Available commands: + \\run To run a query. + \\db Create or chose a database. + \\schema Initialize the database schema. + \\quit Stop the CLI with memory safety. + \\ + \\ For more informations: https://github.com/MrBounty/ZipponDB + \\ + }); + state = .end; + }, + .keyword_quit => state = .quit, + .eof => state = .end, + else => { + send("Command need to start with a keyword, including: run, db, schema, help and quit", .{}); + state = .end; + }, + }, + + .expect_db_command => switch (token.tag) { + .keyword_new => state = .expect_path_to_new_db, + .keyword_use => state = .expect_path_to_db, + .keyword_metrics => { + if (!file_engine.usable) { + send("Error: No database selected. Please use db new or db use.", .{}); + state = .end; + continue; + } + + var buffer = std.ArrayList(u8).init(allocator); + defer buffer.deinit(); + + try file_engine.writeDbMetrics(&buffer); + send("{s}", .{buffer.items}); + state = .end; + }, + .keyword_help => { + send("{s}", .{ + \\Available commands: + \\new Create a new database using a path to a sub folder. + \\use Select another ZipponDB folder to use as database. + \\metrics Print some metrics of the current database. + \\ + \\ For more informations: https://github.com/MrBounty/ZipponDB + \\ + }); + state = .end; + }, + else => { + send("Error: db commands available: new, metrics, swap & help", .{}); + state = .end; + }, + }, + + .expect_path_to_db => switch (token.tag) { + .identifier => { + file_engine.deinit(); + file_engine = FileEngine.init(allocator, try allocator.dupe(u8, cliToker.getTokenSlice(token))); + send("Successfully started using the database!", .{}); + state = .end; + }, + else => { + send("Error Expect a path to a ZipponDB folder.", .{}); + state = .end; + }, + }, + + .expect_path_to_new_db => switch (token.tag) { + .identifier => { + checkAndCreateDirectories(cliToker.getTokenSlice(token), allocator) catch |err| { + send("Error: Coulnt create database directories: {any}", .{err}); + state = .end; + continue; + }; + file_engine.deinit(); + file_engine = FileEngine.init(allocator, try allocator.dupe(u8, cliToker.getTokenSlice(token))); + send("Successfully initialized the database!", .{}); + state = .end; + }, + else => { + send("Error Expect a path to a folder.", .{}); + state = .end; + }, + }, + + .expect_query => switch (token.tag) { + .string_literal => { + const null_term_query_str = try allocator.dupeZ(u8, line_str[token.loc.start + 1 .. token.loc.end - 1]); + defer allocator.free(null_term_query_str); + runQuery(null_term_query_str, &file_engine); + state = .end; + }, + .keyword_help => { + send("The run command take a ZiQL query between \" and run it. eg: run \"GRAB User\"", .{}); + state = .end; + }, + else => { + send("Error: After command run, need a query, eg: \"GRAB User\"", .{}); + state = .end; + }, + }, + + .expect_schema_command => switch (token.tag) { + .keyword_describe => { + if (std.mem.eql(u8, file_engine.path_to_ZipponDB_dir, "")) send("Error: No database selected. Please use db bew or db use.", .{}); + + if (file_engine.null_terminated_schema_buff.len == 0) { + send("Need to init the schema first. Please use the schema init path/to/schema command to start.", .{}); + } else { + send("Schema:\n {s}", .{file_engine.null_terminated_schema_buff}); + } + state = .end; + }, + .keyword_init => state = .expect_path_to_schema, + .keyword_help => { + send("{s}", .{ + \\Available commands: + \\describe Print the schema use by the currently selected database. + \\init Take the path to a schema file and initialize the database. + \\ + \\ For more informations: https://github.com/MrBounty/ZipponDB + \\ + }); + state = .end; + }, + else => { + send("Error: schema commands available: describe, init & help", .{}); + state = .end; + }, + }, + + .expect_path_to_schema => switch (token.tag) { + .identifier => { + file_engine.initDataFolder(cliToker.getTokenSlice(token)) catch |err| switch (err) { + error.SchemaFileNotFound => { + send("Coulnt find the schema file at {s}", .{cliToker.getTokenSlice(token)}); state = .end; - continue; - } - state = .expect_query; - }, - .keyword_db => state = .expect_db_command, - .keyword_schema => { - if (!file_engine.usable) { - send("Error: No database selected. Please use db new or db use.", .{}); + }, + else => { + send("Error initializing the schema", .{}); state = .end; - continue; - } - state = .expect_schema_command; - }, - .keyword_help => { - send("{s}", .{ - \\Welcome to ZipponDB v0.1! - \\ - \\Available commands: - \\run To run a query. - \\db Create or chose a database. - \\schema Initialize the database schema. - \\quit Stop the CLI with memory safety. - \\ - \\ For more informations: https://github.com/MrBounty/ZipponDB - \\ - }); - state = .end; - }, - .keyword_quit => state = .quit, - .eof => state = .end, - else => { - send("Command need to start with a keyword, including: run, db, schema, help and quit", .{}); - state = .end; - }, + }, + }; + send("Successfully initialized the database schema!", .{}); + state = .end; }, - - .expect_db_command => switch (token.tag) { - .keyword_new => state = .expect_path_to_new_db, - .keyword_use => state = .expect_path_to_db, - .keyword_metrics => { - if (!file_engine.usable) { - send("Error: No database selected. Please use db new or db use.", .{}); - state = .end; - continue; - } - - var buffer = std.ArrayList(u8).init(allocator); - defer buffer.deinit(); - - try file_engine.writeDbMetrics(&buffer); - send("{s}", .{buffer.items}); - state = .end; - }, - .keyword_help => { - send("{s}", .{ - \\Available commands: - \\new Create a new database using a path to a sub folder. - \\use Select another ZipponDB folder to use as database. - \\metrics Print some metrics of the current database. - \\ - \\ For more informations: https://github.com/MrBounty/ZipponDB - \\ - }); - state = .end; - }, - else => { - send("Error: db commands available: new, metrics, swap & help", .{}); - state = .end; - }, + else => { + send("Error: Expect path to schema file.", .{}); + state = .end; }, + }, - .expect_path_to_db => switch (token.tag) { - .identifier => { - file_engine.deinit(); - file_engine = FileEngine.init(allocator, try allocator.dupe(u8, cliToker.getTokenSlice(token))); - send("Successfully started using the database!", .{}); - state = .end; - }, - else => { - send("Error Expect a path to a ZipponDB folder.", .{}); - state = .end; - }, - }, - - .expect_path_to_new_db => switch (token.tag) { - .identifier => { - checkAndCreateDirectories(cliToker.getTokenSlice(token), allocator) catch |err| { - send("Error: Coulnt create database directories: {any}", .{err}); - state = .end; - continue; - }; - file_engine.deinit(); - file_engine = FileEngine.init(allocator, try allocator.dupe(u8, cliToker.getTokenSlice(token))); - send("Successfully initialized the database!", .{}); - state = .end; - }, - else => { - send("Error Expect a path to a folder.", .{}); - state = .end; - }, - }, - - .expect_query => switch (token.tag) { - .string_literal => { - const null_term_query_str = try allocator.dupeZ(u8, line_str[token.loc.start + 1 .. token.loc.end - 1]); - defer allocator.free(null_term_query_str); - runQuery(null_term_query_str, &file_engine); - state = .end; - }, - .keyword_help => { - send("The run command take a ZiQL query between \" and run it. eg: run \"GRAB User\"", .{}); - state = .end; - }, - else => { - send("Error: After command run, need a query, eg: \"GRAB User\"", .{}); - state = .end; - }, - }, - - .expect_schema_command => switch (token.tag) { - .keyword_describe => { - if (std.mem.eql(u8, file_engine.path_to_ZipponDB_dir, "")) send("Error: No database selected. Please use db bew or db use.", .{}); - - if (file_engine.null_terminated_schema_buff.len == 0) { - send("Need to init the schema first. Please use the schema init path/to/schema command to start.", .{}); - } else { - send("Schema:\n {s}", .{file_engine.null_terminated_schema_buff}); - } - state = .end; - }, - .keyword_init => state = .expect_path_to_schema, - .keyword_help => { - send("{s}", .{ - \\Available commands: - \\describe Print the schema use by the currently selected database. - \\init Take the path to a schema file and initialize the database. - \\ - \\ For more informations: https://github.com/MrBounty/ZipponDB - \\ - }); - state = .end; - }, - else => { - send("Error: schema commands available: describe, init & help", .{}); - state = .end; - }, - }, - - .expect_path_to_schema => switch (token.tag) { - .identifier => { - file_engine.initDataFolder(cliToker.getTokenSlice(token)) catch |err| switch (err) { - error.SchemaFileNotFound => { - send("Coulnt find the schema file at {s}", .{cliToker.getTokenSlice(token)}); - state = .end; - }, - else => { - send("Error initializing the schema", .{}); - state = .end; - }, - }; - send("Successfully initialized the database schema!", .{}); - state = .end; - }, - else => { - send("Error: Expect path to schema file.", .{}); - state = .end; - }, - }, - - .quit, .end => break, - } - } - if (state == .quit) break; + .quit, .end => break, + }; } + if (state == .quit) break; } } diff --git a/src/fileEngine.zig b/src/fileEngine.zig index 54ede00..65e6698 100644 --- a/src/fileEngine.zig +++ b/src/fileEngine.zig @@ -8,17 +8,25 @@ const SchemaStruct = @import("schemaParser.zig").Parser.SchemaStruct; const SchemaParser = @import("schemaParser.zig").Parser; const SchemaTokenizer = @import("tokenizers/schema.zig").Tokenizer; const SchemaToken = @import("tokenizers/schema.zig").Token; -const AdditionalData = @import("ziqlParser.zig").Parser.AdditionalData; +const AdditionalData = @import("parsing-tools/additionalData.zig").AdditionalData; -//TODO: Create a union class and chose between file and memory +// TODO: Use those errors everywhere in this file +const FileEngineError = error{ + SchemaFileNotFound, + SchemaNotConform, + DATAFolderNotFound, + StructFolderNotFound, + CantMakeDir, + CantMakeFile, +}; /// Manage everything that is relate to read or write in files /// Or even get stats, whatever. If it touch files, it's here pub const FileEngine = struct { allocator: Allocator, usable: bool, - path_to_ZipponDB_dir: []const u8, // Make that into a list - max_file_size: usize = 5e+4, // 50kb TODO: Change + path_to_ZipponDB_dir: []const u8, // TODO: Put in config file + max_file_size: usize = 5e+4, // 50kb TODO: Put in config file null_terminated_schema_buff: [:0]u8, struct_array: std.ArrayList(SchemaStruct), @@ -81,10 +89,6 @@ pub const FileEngine = struct { } }; - pub fn setPath(self: *FileEngine, path: []const u8) void { - self.path_to_ZipponDB_dir = path; - } - /// Take a list of UUID and, a buffer array and the additional data to write into the buffer the JSON to send /// TODO: Optimize /// FIXME: Array of string are not working @@ -161,62 +165,55 @@ pub const FileEngine = struct { } } - if (founded) { - try out_writer.writeAll("{"); - try out_writer.writeAll("id:\""); - try out_writer.print("{s}", .{output_fbs.getWritten()[0..36]}); - try out_writer.writeAll("\", "); - for (self.structName2structMembers(struct_name), self.structName2DataType(struct_name)) |member_name, member_type| { - token = data_toker.next(); - // FIXME: When relationship will be implemented, need to check if the len of NON link is 0 - if ((additional_data.member_to_find.items.len == 0) or (self.isMemberNameInAdditionalData(self.locToSlice(member_name), additional_data))) { - // write the member name and = sign - try out_writer.print("{s}: ", .{self.locToSlice(member_name)}); + if (!founded) continue; - switch (member_type) { - .str => { - const str_slice = data_toker.getTokenSlice(token); - try out_writer.print("\"{s}\"", .{str_slice[1 .. str_slice.len - 1]}); - }, - .str_array => { - try out_writer.writeAll(data_toker.getTokenSlice(token)); - token = data_toker.next(); - while (token.tag != .r_bracket) : (token = data_toker.next()) { - try out_writer.writeAll("\""); - try out_writer.writeAll(data_toker.getTokenSlice(token)[1..(token.loc.end - token.loc.start)]); - try out_writer.writeAll("\""); - try out_writer.writeAll(" "); - } - try out_writer.writeAll(data_toker.getTokenSlice(token)); - }, - .int_array, .float_array, .bool_array, .id_array => { - while (token.tag != .r_bracket) : (token = data_toker.next()) { - try out_writer.writeAll(data_toker.getTokenSlice(token)); - try out_writer.writeAll(" "); - } - try out_writer.writeAll(data_toker.getTokenSlice(token)); - }, - else => try out_writer.writeAll(data_toker.getTokenSlice(token)), //write the value as if + try out_writer.writeAll("{"); + try out_writer.writeAll("id:\""); + try out_writer.print("{s}", .{output_fbs.getWritten()[0..36]}); + try out_writer.writeAll("\", "); + for (self.structName2structMembers(struct_name), self.structName2DataType(struct_name)) |member_name, member_type| { + token = data_toker.next(); + // FIXME: When relationship will be implemented, need to check if the len of NON link is 0 + if (!(additional_data.member_to_find.items.len == 0) or !(additional_data.contains(self.locToSlice(member_name)))) continue; + + // write the member name and = sign + try out_writer.print("{s}: ", .{self.locToSlice(member_name)}); + + switch (member_type) { + .str => { + const str_slice = data_toker.getTokenSlice(token); + try out_writer.print("\"{s}\"", .{str_slice[1 .. str_slice.len - 1]}); + }, + .str_array => { + try out_writer.writeAll(data_toker.getTokenSlice(token)); + token = data_toker.next(); + while (token.tag != .r_bracket) : (token = data_toker.next()) { + try out_writer.writeAll("\""); + try out_writer.writeAll(data_toker.getTokenSlice(token)[1..(token.loc.end - token.loc.start)]); + try out_writer.writeAll("\""); + try out_writer.writeAll(" "); } - try out_writer.writeAll(", "); - } + try out_writer.writeAll(data_toker.getTokenSlice(token)); + }, + .int_array, .float_array, .bool_array, .id_array => { + while (token.tag != .r_bracket) : (token = data_toker.next()) { + try out_writer.writeAll(data_toker.getTokenSlice(token)); + try out_writer.writeAll(" "); + } + try out_writer.writeAll(data_toker.getTokenSlice(token)); + }, + else => try out_writer.writeAll(data_toker.getTokenSlice(token)), //write the value as if } - try out_writer.writeAll("}"); try out_writer.writeAll(", "); } + try out_writer.writeAll("}"); + try out_writer.writeAll(", "); } - // Write the end } try out_writer.writeAll("]"); } - fn isMemberNameInAdditionalData(_: *FileEngine, member_name: []const u8, additional_data: AdditionalData) bool { - for (additional_data.member_to_find.items) |elem| { - if (std.mem.eql(u8, member_name, elem.name)) return true; - } - return false; - } - /// Use a struct name to populate a list with all UUID of this struct + /// TODO: Optimize this, I'm sure I can do better than that pub fn getAllUUIDList(self: *FileEngine, struct_name: []const u8, uuid_array: *std.ArrayList(UUID)) !void { const max_file_index = try self.maxFileIndex(struct_name); var current_index: usize = 0; @@ -431,9 +428,7 @@ pub const FileEngine = struct { } } - // TODO: Clean a bit the code - // Do I need multiple files too ? I mean it duplicate UUID a lot, if it's just to save a name like 'Bob', storing a long UUID is overkill - // I could just use a tabular data format with separator using space - Or maybe I encode the uuid to take a minimum space as I always know it size + // Do I need a map here ? Cant I use something else ? pub fn writeEntity(self: *FileEngine, struct_name: []const u8, data_map: std.StringHashMap([]const u8)) !UUID { const uuid = UUID.init(); @@ -566,7 +561,14 @@ pub const FileEngine = struct { } } - if (founded) { + if (!founded) { + // stream until the delimiter + output_fbs.reset(); + try new_file.writeAll(" "); + try reader.streamUntilDelimiter(writer, '\n', null); + try new_file.writeAll(output_fbs.getWritten()); + try new_file.writeAll("\n"); + } else { for (self.structName2structMembers(struct_name), self.structName2DataType(struct_name)) |member_name, member_type| { // For all collum in the right order, check if the key is in the map, if so use it to write the new value, otherwise use the old file output_fbs.reset(); @@ -610,13 +612,6 @@ pub const FileEngine = struct { try reader.streamUntilDelimiter(writer, '\n', null); try new_file.writeAll("\n"); - } else { - // stream until the delimiter - output_fbs.reset(); - try new_file.writeAll(" "); - try reader.streamUntilDelimiter(writer, '\n', null); - try new_file.writeAll(output_fbs.getWritten()); - try new_file.writeAll("\n"); } } } @@ -807,40 +802,13 @@ pub const FileEngine = struct { } } - // Maybe make it so it use itself to search if it find a directory - fn getDirTotalSize(self: FileEngine, dir: std.fs.Dir) !u64 { - var total: u64 = 0; - var stat: std.fs.File.Stat = undefined; - var iter = dir.iterate(); - while (try iter.next()) |entry| { - if (entry.kind == .directory) { - const sub_dir = try dir.openDir(entry.name, .{ .iterate = true }); - total += try self.getDirTotalSize(sub_dir); - } - - if (entry.kind != .file) continue; - stat = try dir.statFile(entry.name); - total += stat.size; - } - return total; - } - - const FileError = error{ - SchemaFileNotFound, - SchemaNotConform, - DATAFolderNotFound, - StructFolderNotFound, - CantMakeDir, - CantMakeFile, - }; - /// Request a path to a schema file and then create the struct folder /// TODO: Check if some data already exist and if so ask if the user want to delete it and make a backup - pub fn initDataFolder(self: *FileEngine, path_to_schema_file: []const u8) FileError!void { + pub fn initDataFolder(self: *FileEngine, path_to_schema_file: []const u8) FileEngineError!void { var schema_buf = self.allocator.alloc(u8, 1024 * 50) catch @panic("Cant allocate the schema buffer"); defer self.allocator.free(schema_buf); - const file = std.fs.cwd().openFile(path_to_schema_file, .{}) catch return FileError.SchemaFileNotFound; + const file = std.fs.cwd().openFile(path_to_schema_file, .{}) catch return FileEngineError.SchemaFileNotFound; defer file.close(); const len = file.readAll(schema_buf) catch @panic("Can't read schema file"); @@ -860,19 +828,19 @@ pub const FileEngine = struct { const path = std.fmt.allocPrint(self.allocator, "{s}/DATA", .{self.path_to_ZipponDB_dir}) catch @panic("Cant allocate path"); defer self.allocator.free(path); - var data_dir = std.fs.cwd().openDir(path, .{}) catch return FileError.DATAFolderNotFound; + var data_dir = std.fs.cwd().openDir(path, .{}) catch return FileEngineError.DATAFolderNotFound; defer data_dir.close(); for (self.struct_array.items) |struct_item| { data_dir.makeDir(self.locToSlice(struct_item.name)) catch |err| switch (err) { error.PathAlreadyExists => {}, - else => return FileError.CantMakeDir, + else => return FileEngineError.CantMakeDir, }; - const struct_dir = data_dir.openDir(self.locToSlice(struct_item.name), .{}) catch return FileError.StructFolderNotFound; + const struct_dir = data_dir.openDir(self.locToSlice(struct_item.name), .{}) catch return FileEngineError.StructFolderNotFound; _ = struct_dir.createFile("0.zippondata", .{}) catch |err| switch (err) { error.PathAlreadyExists => {}, - else => return FileError.CantMakeFile, + else => return FileEngineError.CantMakeFile, }; } @@ -880,6 +848,7 @@ pub const FileEngine = struct { } // Stuff for schema + // TODO: Check all those functions and remove if not use pub fn readSchemaFile(allocator: Allocator, sub_path: []const u8, buffer: []u8) !usize { const path = try std.fmt.allocPrint(allocator, "{s}/schema.zipponschema", .{sub_path}); @@ -1011,6 +980,7 @@ test "Get list of UUID using condition" { // Series of functions to use just before creating an entity. // Will transform the string of data into data of the right type./ +// TODO: Put those functions somewhere else pub fn parseInt(value_str: []const u8) i64 { return std.fmt.parseInt(i64, value_str, 10) catch return 0; } diff --git a/src/parsing-tools/additionalData.zig b/src/parsing-tools/additionalData.zig new file mode 100644 index 0000000..28ced75 --- /dev/null +++ b/src/parsing-tools/additionalData.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +/// This is the [] part +/// IDK if saving it into the Parser struct is a good idea +pub const AdditionalData = struct { + entity_count_to_find: usize = 0, + member_to_find: std.ArrayList(AdditionalDataMember), + + pub fn init(allocator: Allocator) AdditionalData { + return AdditionalData{ .member_to_find = std.ArrayList(AdditionalDataMember).init(allocator) }; + } + + pub fn deinit(self: *AdditionalData) void { + for (0..self.member_to_find.items.len) |i| { + self.member_to_find.items[i].additional_data.deinit(); + } + + self.member_to_find.deinit(); + } + + pub fn contains(additional_data: AdditionalData, member_name: []const u8) bool { + for (additional_data.member_to_find.items) |elem| { + if (std.mem.eql(u8, member_name, elem.name)) return true; + } + return false; + } +}; + +// This is name in: [name] +// There is an additional data because it can be [friend [1; name]] +pub const AdditionalDataMember = struct { + name: []const u8, + additional_data: AdditionalData, + + pub fn init(allocator: Allocator, name: []const u8) AdditionalDataMember { + const additional_data = AdditionalData.init(allocator); + return AdditionalDataMember{ .name = name, .additional_data = additional_data }; + } +}; diff --git a/src/parsing-tools/utils.zig b/src/parsing-tools/utils.zig new file mode 100644 index 0000000..bd75e8c --- /dev/null +++ b/src/parsing-tools/utils.zig @@ -0,0 +1,31 @@ +const std = @import("std"); + +pub fn getEnvVariables(allocator: std.mem.Allocator, variable: []const u8) ?[]const u8 { + var env_map = std.process.getEnvMap(allocator) catch return null; + defer env_map.deinit(); + + var iter = env_map.iterator(); + + while (iter.next()) |entry| { + if (std.mem.eql(u8, entry.key_ptr.*, variable)) return allocator.dupe(u8, entry.value_ptr.*) catch return null; + } + + return null; +} + +pub fn getDirTotalSize(dir: std.fs.Dir) !u64 { + var total: u64 = 0; + var stat: std.fs.File.Stat = undefined; + var iter = dir.iterate(); + while (try iter.next()) |entry| { + if (entry.kind == .directory) { + const sub_dir = try dir.openDir(entry.name, .{ .iterate = true }); + total += try getDirTotalSize(sub_dir); + } + + if (entry.kind != .file) continue; + stat = try dir.statFile(entry.name); + total += stat.size; + } + return total; +} diff --git a/src/schemaParser.zig b/src/schemaParser.zig index d5214b7..4c194d1 100644 --- a/src/schemaParser.zig +++ b/src/schemaParser.zig @@ -6,6 +6,7 @@ const Token = @import("tokenizers/schema.zig").Token; const stdout = std.io.getStdOut().writer(); +// Fuse this with the same function in the ZiQL parser fn send(comptime format: []const u8, args: anytype) void { stdout.print(format, args) catch |err| { std.log.err("Can't send: {any}", .{err}); @@ -68,124 +69,122 @@ pub const Parser = struct { while ((state != .end) and (state != .invalid)) : ({ token = if (!keep_next) self.toker.next() else token; keep_next = false; - }) { - switch (state) { - .expect_struct_name_OR_end => switch (token.tag) { - .identifier => { - state = .expect_l_paren; - struct_array.append(SchemaStruct.init(self.allocator, token.loc)) catch @panic("Error appending a struct name."); - }, - .eof => state = .end, - else => { - self.printError("Error parsing schema: Expected a struct name", &token); - state = .invalid; - }, + }) switch (state) { + .expect_struct_name_OR_end => switch (token.tag) { + .identifier => { + state = .expect_l_paren; + struct_array.append(SchemaStruct.init(self.allocator, token.loc)) catch @panic("Error appending a struct name."); }, - - .expect_l_paren => switch (token.tag) { - .l_paren => state = .expect_member_name, - else => { - self.printError("Error parsing schema: Expected (", &token); - state = .invalid; - }, + .eof => state = .end, + else => { + self.printError("Error parsing schema: Expected a struct name", &token); + state = .invalid; }, + }, - .expect_member_name_OR_r_paren => switch (token.tag) { - .identifier => { - state = .expect_member_name; - keep_next = true; - }, - .r_paren => { - state = .expect_struct_name_OR_end; - index += 1; - }, - else => { - self.printError("Error parsing schema: Expected member name or )", &token); - state = .invalid; - }, + .expect_l_paren => switch (token.tag) { + .l_paren => state = .expect_member_name, + else => { + self.printError("Error parsing schema: Expected (", &token); + state = .invalid; }, + }, - .expect_member_name => { - state = .expect_two_dot; - struct_array.items[index].members.append(token.loc) catch @panic("Error appending a member name."); + .expect_member_name_OR_r_paren => switch (token.tag) { + .identifier => { + state = .expect_member_name; + keep_next = true; }, - - .expect_two_dot => switch (token.tag) { - .two_dot => state = .expect_value_type, - else => { - self.printError("Error parsing schema: Expected :", &token); - state = .invalid; - }, + .r_paren => { + state = .expect_struct_name_OR_end; + index += 1; }, - - .expect_value_type => switch (token.tag) { - .type_int => { - state = .expect_comma; - struct_array.items[index].types.append(DataType.int) catch @panic("Error appending a type."); - }, - .type_str => { - state = .expect_comma; - struct_array.items[index].types.append(DataType.str) catch @panic("Error appending a type."); - }, - .type_float => { - state = .expect_comma; - struct_array.items[index].types.append(DataType.float) catch @panic("Error appending a type."); - }, - .type_bool => { - state = .expect_comma; - struct_array.items[index].types.append(DataType.bool) catch @panic("Error appending a type."); - }, - .type_date => @panic("Date not yet implemented"), - .identifier => @panic("Link not yet implemented"), - .lr_bracket => state = .expext_array_type, - else => { - self.printError("Error parsing schema: Expected data type", &token); - state = .invalid; - }, + else => { + self.printError("Error parsing schema: Expected member name or )", &token); + state = .invalid; }, + }, - .expext_array_type => switch (token.tag) { - .type_int => { - state = .expect_comma; - struct_array.items[index].types.append(DataType.int_array) catch @panic("Error appending a type."); - }, - .type_str => { - state = .expect_comma; - struct_array.items[index].types.append(DataType.str_array) catch @panic("Error appending a type."); - }, - .type_float => { - state = .expect_comma; - struct_array.items[index].types.append(DataType.float_array) catch @panic("Error appending a type."); - }, - .type_bool => { - state = .expect_comma; - struct_array.items[index].types.append(DataType.bool_array) catch @panic("Error appending a type."); - }, - .type_date => { - self.printError("Error parsing schema: Data not yet implemented", &token); - state = .invalid; - }, - .identifier => { - self.printError("Error parsing schema: Relationship not yet implemented", &token); - state = .invalid; - }, - else => { - self.printError("Error parsing schema: Expected data type", &token); - state = .invalid; - }, + .expect_member_name => { + state = .expect_two_dot; + struct_array.items[index].members.append(token.loc) catch @panic("Error appending a member name."); + }, + + .expect_two_dot => switch (token.tag) { + .two_dot => state = .expect_value_type, + else => { + self.printError("Error parsing schema: Expected :", &token); + state = .invalid; }, + }, - .expect_comma => switch (token.tag) { - .comma => state = .expect_member_name_OR_r_paren, - else => { - self.printError("Error parsing schema: Expected ,", &token); - state = .invalid; - }, + .expect_value_type => switch (token.tag) { + .type_int => { + state = .expect_comma; + struct_array.items[index].types.append(DataType.int) catch @panic("Error appending a type."); }, + .type_str => { + state = .expect_comma; + struct_array.items[index].types.append(DataType.str) catch @panic("Error appending a type."); + }, + .type_float => { + state = .expect_comma; + struct_array.items[index].types.append(DataType.float) catch @panic("Error appending a type."); + }, + .type_bool => { + state = .expect_comma; + struct_array.items[index].types.append(DataType.bool) catch @panic("Error appending a type."); + }, + .type_date => @panic("Date not yet implemented"), + .identifier => @panic("Link not yet implemented"), + .lr_bracket => state = .expext_array_type, + else => { + self.printError("Error parsing schema: Expected data type", &token); + state = .invalid; + }, + }, - else => unreachable, - } - } + .expext_array_type => switch (token.tag) { + .type_int => { + state = .expect_comma; + struct_array.items[index].types.append(DataType.int_array) catch @panic("Error appending a type."); + }, + .type_str => { + state = .expect_comma; + struct_array.items[index].types.append(DataType.str_array) catch @panic("Error appending a type."); + }, + .type_float => { + state = .expect_comma; + struct_array.items[index].types.append(DataType.float_array) catch @panic("Error appending a type."); + }, + .type_bool => { + state = .expect_comma; + struct_array.items[index].types.append(DataType.bool_array) catch @panic("Error appending a type."); + }, + .type_date => { + self.printError("Error parsing schema: Data not yet implemented", &token); + state = .invalid; + }, + .identifier => { + self.printError("Error parsing schema: Relationship not yet implemented", &token); + state = .invalid; + }, + else => { + self.printError("Error parsing schema: Expected data type", &token); + state = .invalid; + }, + }, + + .expect_comma => switch (token.tag) { + .comma => state = .expect_member_name_OR_r_paren, + else => { + self.printError("Error parsing schema: Expected ,", &token); + state = .invalid; + }, + }, + + else => unreachable, + }; // if invalid, empty the list if (state == .invalid) { diff --git a/src/types/uuid.zig b/src/types/uuid.zig index b1905c5..a18575c 100644 --- a/src/types/uuid.zig +++ b/src/types/uuid.zig @@ -123,9 +123,94 @@ pub const UUID = struct { // Zero UUID pub const zero: UUID = .{ .bytes = .{0} ** 16 }; -// Convenience function to return a new v4 UUID. -pub fn newV4() UUID { - return UUID.init(); +// TODO: Optimize both +pub fn OR(arr1: *std.ArrayList(UUID), arr2: *std.ArrayList(UUID)) !void { + for (0..arr2.items.len) |i| { + if (!containUUID(arr1.*, arr2.items[i])) { + try arr1.append(arr2.items[i]); + } + } +} + +pub fn AND(arr1: *std.ArrayList(UUID), arr2: *std.ArrayList(UUID)) !void { + var i: usize = 0; + for (0..arr1.items.len) |_| { + if (!containUUID(arr2.*, arr1.items[i])) { + _ = arr1.orderedRemove(i); + } else { + i += 1; + } + } +} + +test "OR & AND" { + const allocator = std.testing.allocator; + + var right_arr = std.ArrayList(UUID).init(allocator); + defer right_arr.deinit(); + try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); + try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); + try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000005")); + try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000006")); + try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000007")); + + var left_arr1 = std.ArrayList(UUID).init(allocator); + defer left_arr1.deinit(); + try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); + try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); + try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000002")); + try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000003")); + try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000004")); + + var expected_arr1 = std.ArrayList(UUID).init(allocator); + defer expected_arr1.deinit(); + try expected_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); + try expected_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); + + try AND(&left_arr1, &right_arr); + try std.testing.expect(compareUUIDArray(left_arr1, expected_arr1)); + + var left_arr2 = std.ArrayList(UUID).init(allocator); + defer left_arr2.deinit(); + try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); + try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); + try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000002")); + try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000003")); + try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000004")); + + var expected_arr2 = std.ArrayList(UUID).init(allocator); + defer expected_arr2.deinit(); + try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); + try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); + try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000002")); + try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000003")); + try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000004")); + try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000005")); + try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000006")); + try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000007")); + + try OR(&left_arr2, &right_arr); + + try std.testing.expect(compareUUIDArray(left_arr2, expected_arr2)); +} + +fn containUUID(arr: std.ArrayList(UUID), value: UUID) bool { + return for (arr.items) |elem| { + if (value.compare(elem)) break true; + } else false; +} + +fn compareUUIDArray(arr1: std.ArrayList(UUID), arr2: std.ArrayList(UUID)) bool { + if (arr1.items.len != arr2.items.len) { + std.debug.print("Not same array len when comparing UUID. arr1: {d} arr2: {d}\n", .{ arr1.items.len, arr2.items.len }); + return false; + } + + for (0..arr1.items.len) |i| { + if (!containUUID(arr2, arr1.items[i])) return false; + } + + return true; } test "parse and format" { diff --git a/src/utils.zig b/src/utils.zig deleted file mode 100644 index 66c3009..0000000 --- a/src/utils.zig +++ /dev/null @@ -1,14 +0,0 @@ -const std = @import("std"); - -pub fn getEnvVariables(allocator: std.mem.Allocator, variable: []const u8) ?[]const u8 { - var env_map = std.process.getEnvMap(allocator) catch return null; - defer env_map.deinit(); - - var iter = env_map.iterator(); - - while (iter.next()) |entry| { - if (std.mem.eql(u8, entry.key_ptr.*, variable)) return allocator.dupe(u8, entry.value_ptr.*) catch return null; - } - - return null; -} diff --git a/src/ziqlParser.zig b/src/ziqlParser.zig index ccc156c..bdb8e82 100644 --- a/src/ziqlParser.zig +++ b/src/ziqlParser.zig @@ -4,10 +4,65 @@ const Condition = @import("fileEngine.zig").FileEngine.Condition; const Tokenizer = @import("tokenizers/ziql.zig").Tokenizer; const Token = @import("tokenizers/ziql.zig").Token; const UUID = @import("types/uuid.zig").UUID; +const AND = @import("types/uuid.zig").AND; +const OR = @import("types/uuid.zig").OR; +const AdditionalData = @import("parsing-tools/additionalData.zig").AdditionalData; +const AdditionalDataMember = @import("parsing-tools/additionalData.zig").AdditionalDataMember; const Allocator = std.mem.Allocator; const stdout = std.io.getStdOut().writer(); +const ZiQlParserError = error{ + SynthaxError, + MemberNotFound, + MemberMissing, + StructNotFound, + FeatureMissing, + ParsingValueError, + ConditionError, +}; + +const State = enum { + start, + invalid, + end, + + // Endpoint + parse_new_data_and_add_data, + filter_and_send, + filter_and_update, + filter_and_delete, + + // For the main parse function + expect_struct_name, + expect_filter, + parse_additional_data, + expect_filter_or_additional_data, + expect_new_data, + expect_right_arrow, + + // For the additional data parser + expect_count_of_entity_to_find, + expect_semicolon_OR_right_bracket, + expect_member, + expect_comma_OR_r_bracket_OR_l_bracket, + expect_comma_OR_r_bracket, + + // For the filter parser + expect_left_condition, // Condition is a struct in FileEngine, it's all info necessary to get a list of UUID usinf FileEngine.getUUIDListUsingCondition + expect_operation, // Operations are = != < <= > >= + expect_value, + expect_ANDOR_OR_end, + expect_right_uuid_array, + + // For the new data + expect_equal, + expect_new_value, + expect_comma_OR_end, + add_member_to_map, + add_array_to_map, +}; + fn send(comptime format: []const u8, args: anytype) void { stdout.print(format, args) catch |err| { std.log.err("Can't send: {any}", .{err}); @@ -43,269 +98,8 @@ pub const Parser = struct { self.allocator.free(self.struct_name); } - const Options = struct { - members_for_ordering: std.ArrayList([]const u8), // The list in the right order of member name to use to order the result - sense_for_ordering: enum { ASC, DESC }, - }; - - const State = enum { - start, - invalid, - end, - - // Endpoint - parse_new_data_and_add_data, - filter_and_send, - filter_and_update, - filter_and_delete, - - // For the main parse function - expect_struct_name, - expect_filter, - parse_additional_data, - expect_filter_or_additional_data, - expect_new_data, - expect_right_arrow, - - // For the additional data parser - expect_count_of_entity_to_find, - expect_semicolon_OR_right_bracket, - expect_member, - expect_comma_OR_r_bracket_OR_l_bracket, - expect_comma_OR_r_bracket, - - // For the filter parser - expect_left_condition, // Condition is a struct in FileEngine, it's all info necessary to get a list of UUID usinf FileEngine.getUUIDListUsingCondition - expect_operation, // Operations are = != < <= > >= - expect_value, - expect_ANDOR_OR_end, - expect_right_uuid_array, - - // For the new data - expect_equal, - expect_new_value, - expect_comma_OR_end, - add_member_to_map, - add_array_to_map, - }; - - const ZiQlParserError = error{ - SynthaxError, - MemberNotFound, - MemberMissing, - StructNotFound, - FeatureMissing, - ParsingValueError, - ConditionError, - }; - - /// This is the [] part - /// IDK if saving it into the Parser struct is a good idea - pub const AdditionalData = struct { - entity_count_to_find: usize = 0, - member_to_find: std.ArrayList(AdditionalDataMember), - - pub fn init(allocator: Allocator) AdditionalData { - return AdditionalData{ .member_to_find = std.ArrayList(AdditionalDataMember).init(allocator) }; - } - - pub fn deinit(self: *AdditionalData) void { - for (0..self.member_to_find.items.len) |i| { - self.member_to_find.items[i].additional_data.deinit(); - } - - self.member_to_find.deinit(); - } - }; - - // This is name in: [name] - // There is an additional data because it can be [friend [1; name]] - const AdditionalDataMember = struct { - name: []const u8, - additional_data: AdditionalData, - - pub fn init(allocator: Allocator, name: []const u8) AdditionalDataMember { - const additional_data = AdditionalData.init(allocator); - return AdditionalDataMember{ .name = name, .additional_data = additional_data }; - } - }; - - pub fn parse(self: *Parser) !void { - var token = self.toker.next(); - var keep_next = false; // Use in the loop to prevent to get the next token when continue. Just need to make it true and it is reset at every loop - - while (self.state != State.end) : ({ - token = if (!keep_next) self.toker.next() else token; - keep_next = false; - }) { - switch (self.state) { - .start => switch (token.tag) { - .keyword_grab => { - self.action = .GRAB; - self.state = .expect_struct_name; - }, - .keyword_add => { - self.action = .ADD; - self.state = .expect_struct_name; - }, - .keyword_update => { - self.action = .UPDATE; - self.state = .expect_struct_name; - }, - .keyword_delete => { - self.action = .DELETE; - self.state = .expect_struct_name; - }, - else => return self.printError("Error: Expected action keyword. Available: GRAB ADD DELETE UPDATE", &token, ZiQlParserError.SynthaxError), - }, - - .expect_struct_name => { - // Check if the struct name is in the schema - self.struct_name = try self.allocator.dupe(u8, self.toker.getTokenSlice(token)); - if (token.tag != .identifier) return self.printError("Error: Missing struct name", &token, ZiQlParserError.StructNotFound); - if (!self.file_engine.isStructNameExists(self.struct_name)) return self.printError("Error: struct name not found in schema.", &token, ZiQlParserError.StructNotFound); - switch (self.action) { - .ADD => self.state = .expect_new_data, - else => self.state = .expect_filter_or_additional_data, - } - }, - - .expect_filter_or_additional_data => { - keep_next = true; - switch (token.tag) { - .l_bracket => self.state = .parse_additional_data, - .l_brace => self.state = switch (self.action) { - .GRAB => .filter_and_send, - .UPDATE => .filter_and_update, - .DELETE => .filter_and_delete, - else => unreachable, - }, - .eof => { - self.state = .filter_and_send; - keep_next = true; - }, - else => return self.printError("Error: Expect [ for additional data or { for a filter", &token, ZiQlParserError.SynthaxError), - } - }, - - .parse_additional_data => { - try self.parseAdditionalData(&self.additional_data); - self.state = switch (self.action) { - .GRAB => .filter_and_send, - .UPDATE => .filter_and_update, - .DELETE => .filter_and_delete, - else => unreachable, - }; - }, - - .filter_and_send => switch (token.tag) { - .l_brace => { - var array = std.ArrayList(UUID).init(self.allocator); - defer array.deinit(); - _ = try self.parseFilter(&array, self.struct_name, true); - - self.sendEntity(&array); - self.state = .end; - }, - .eof => { - var array = std.ArrayList(UUID).init(self.allocator); - defer array.deinit(); - try self.file_engine.getAllUUIDList(self.struct_name, &array); - - self.sendEntity(&array); - self.state = .end; - }, - else => return self.printError("Error: Expected filter.", &token, ZiQlParserError.SynthaxError), - }, - - // TODO: Optimize so it doesnt use parseFilter but just parse the file and directly check the condition. Here I end up parsing 2 times. - .filter_and_update => switch (token.tag) { - .l_brace => { - var array = std.ArrayList(UUID).init(self.allocator); - defer array.deinit(); - token = try self.parseFilter(&array, self.struct_name, true); - - if (token.tag != .keyword_to) return self.printError("Error: Expected TO", &token, ZiQlParserError.SynthaxError); - - token = self.toker.next(); - if (token.tag != .l_paren) return self.printError("Error: Expected (", &token, ZiQlParserError.SynthaxError); - - var data_map = std.StringHashMap([]const u8).init(self.allocator); - defer data_map.deinit(); - try self.parseNewData(&data_map); - - try self.file_engine.updateEntities(self.struct_name, array.items, data_map); - self.state = .end; - }, - .keyword_to => { - var array = std.ArrayList(UUID).init(self.allocator); - defer array.deinit(); - try self.file_engine.getAllUUIDList(self.struct_name, &array); - - token = self.toker.next(); - if (token.tag != .l_paren) return self.printError("Error: Expected (", &token, ZiQlParserError.SynthaxError); - - var data_map = std.StringHashMap([]const u8).init(self.allocator); - defer data_map.deinit(); - try self.parseNewData(&data_map); - - try self.file_engine.updateEntities(self.struct_name, array.items, data_map); - self.state = .end; - }, - else => return self.printError("Error: Expected filter or TO.", &token, ZiQlParserError.SynthaxError), - }, - - .filter_and_delete => switch (token.tag) { - .l_brace => { - var array = std.ArrayList(UUID).init(self.allocator); - defer array.deinit(); - _ = try self.parseFilter(&array, self.struct_name, true); - - const deleted_count = try self.file_engine.deleteEntities(self.struct_name, array.items); - std.debug.print("Successfully deleted {d} {s}\n", .{ deleted_count, self.struct_name }); - self.state = .end; - }, - .eof => { - var array = std.ArrayList(UUID).init(self.allocator); - defer array.deinit(); - try self.file_engine.getAllUUIDList(self.struct_name, &array); - - const deleted_count = try self.file_engine.deleteEntities(self.struct_name, array.items); - std.debug.print("Successfully deleted all {d} {s}\n", .{ deleted_count, self.struct_name }); - self.state = .end; - }, - else => return self.printError("Error: Expected filter.", &token, ZiQlParserError.SynthaxError), - }, - - .expect_new_data => switch (token.tag) { - .l_paren => { - keep_next = true; - self.state = .parse_new_data_and_add_data; - }, - else => return self.printError("Error: Expected new data starting with (", &token, ZiQlParserError.SynthaxError), - }, - - .parse_new_data_and_add_data => { - var data_map = std.StringHashMap([]const u8).init(self.allocator); - defer data_map.deinit(); - try self.parseNewData(&data_map); - - // TODO: Print the entire list of missing - if (!self.file_engine.checkIfAllMemberInMap(self.struct_name, &data_map)) return self.printError("Error: Missing member", &token, ZiQlParserError.MemberMissing); - const uuid = self.file_engine.writeEntity(self.struct_name, data_map) catch { - send("ZipponDB error: Couln't write new data to file", .{}); - continue; - }; - send("Successfully added new {s} with UUID: {s}", .{ self.struct_name, uuid.format_uuid() }); - self.state = .end; - }, - - else => unreachable, - } - } - } - // TODO: Update to use ASC and DESC + // Maybe create a Sender struct or something like that fn sendEntity(self: *Parser, uuid_list: *std.ArrayList(UUID)) void { var buffer = std.ArrayList(u8).init(self.allocator); defer buffer.deinit(); @@ -322,10 +116,174 @@ pub const Parser = struct { send("{s}", .{buffer.items}); } - // TODO: The parser that check what is between || - // For now only |ASC name, age| - fn parseOptions(self: *Parser) void { - _ = self; + pub fn parse(self: *Parser) !void { + var token = self.toker.next(); + var keep_next = false; // Use in the loop to prevent to get the next token when continue. Just need to make it true and it is reset at every loop + + while (self.state != State.end) : ({ + token = if (!keep_next) self.toker.next() else token; + keep_next = false; + }) switch (self.state) { + .start => switch (token.tag) { + .keyword_grab => { + self.action = .GRAB; + self.state = .expect_struct_name; + }, + .keyword_add => { + self.action = .ADD; + self.state = .expect_struct_name; + }, + .keyword_update => { + self.action = .UPDATE; + self.state = .expect_struct_name; + }, + .keyword_delete => { + self.action = .DELETE; + self.state = .expect_struct_name; + }, + else => return self.printError("Error: Expected action keyword. Available: GRAB ADD DELETE UPDATE", &token, ZiQlParserError.SynthaxError), + }, + + .expect_struct_name => { + // Check if the struct name is in the schema + self.struct_name = try self.allocator.dupe(u8, self.toker.getTokenSlice(token)); + if (token.tag != .identifier) return self.printError("Error: Missing struct name", &token, ZiQlParserError.StructNotFound); + if (!self.file_engine.isStructNameExists(self.struct_name)) return self.printError("Error: struct name not found in schema.", &token, ZiQlParserError.StructNotFound); + switch (self.action) { + .ADD => self.state = .expect_new_data, + else => self.state = .expect_filter_or_additional_data, + } + }, + + .expect_filter_or_additional_data => { + keep_next = true; + switch (token.tag) { + .l_bracket => self.state = .parse_additional_data, + .l_brace => self.state = switch (self.action) { + .GRAB => .filter_and_send, + .UPDATE => .filter_and_update, + .DELETE => .filter_and_delete, + else => unreachable, + }, + .eof => self.state = .filter_and_send, + else => return self.printError("Error: Expect [ for additional data or { for a filter", &token, ZiQlParserError.SynthaxError), + } + }, + + .parse_additional_data => { + try self.parseAdditionalData(&self.additional_data); + self.state = switch (self.action) { + .GRAB => .filter_and_send, + .UPDATE => .filter_and_update, + .DELETE => .filter_and_delete, + else => unreachable, + }; + }, + + .filter_and_send => switch (token.tag) { + .l_brace => { + var array = std.ArrayList(UUID).init(self.allocator); + defer array.deinit(); + _ = try self.parseFilter(&array, self.struct_name, true); + + self.sendEntity(&array); + self.state = .end; + }, + .eof => { + var array = std.ArrayList(UUID).init(self.allocator); + defer array.deinit(); + try self.file_engine.getAllUUIDList(self.struct_name, &array); + + self.sendEntity(&array); + self.state = .end; + }, + else => return self.printError("Error: Expected filter.", &token, ZiQlParserError.SynthaxError), + }, + + // TODO: Optimize so it doesnt use parseFilter but just parse the file and directly check the condition. Here I end up parsing 2 times. + .filter_and_update => switch (token.tag) { + .l_brace => { + var array = std.ArrayList(UUID).init(self.allocator); + defer array.deinit(); + token = try self.parseFilter(&array, self.struct_name, true); + + if (token.tag != .keyword_to) return self.printError("Error: Expected TO", &token, ZiQlParserError.SynthaxError); + + token = self.toker.next(); + if (token.tag != .l_paren) return self.printError("Error: Expected (", &token, ZiQlParserError.SynthaxError); + + var data_map = std.StringHashMap([]const u8).init(self.allocator); + defer data_map.deinit(); + try self.parseNewData(&data_map); + + try self.file_engine.updateEntities(self.struct_name, array.items, data_map); + self.state = .end; + }, + .keyword_to => { + var array = std.ArrayList(UUID).init(self.allocator); + defer array.deinit(); + try self.file_engine.getAllUUIDList(self.struct_name, &array); + + token = self.toker.next(); + if (token.tag != .l_paren) return self.printError("Error: Expected (", &token, ZiQlParserError.SynthaxError); + + var data_map = std.StringHashMap([]const u8).init(self.allocator); + defer data_map.deinit(); + try self.parseNewData(&data_map); + + try self.file_engine.updateEntities(self.struct_name, array.items, data_map); + self.state = .end; + }, + else => return self.printError("Error: Expected filter or TO.", &token, ZiQlParserError.SynthaxError), + }, + + .filter_and_delete => switch (token.tag) { + .l_brace => { + var array = std.ArrayList(UUID).init(self.allocator); + defer array.deinit(); + _ = try self.parseFilter(&array, self.struct_name, true); + + const deleted_count = try self.file_engine.deleteEntities(self.struct_name, array.items); + std.debug.print("Successfully deleted {d} {s}\n", .{ deleted_count, self.struct_name }); + self.state = .end; + }, + .eof => { + var array = std.ArrayList(UUID).init(self.allocator); + defer array.deinit(); + try self.file_engine.getAllUUIDList(self.struct_name, &array); + + const deleted_count = try self.file_engine.deleteEntities(self.struct_name, array.items); + std.debug.print("Successfully deleted all {d} {s}\n", .{ deleted_count, self.struct_name }); + self.state = .end; + }, + else => return self.printError("Error: Expected filter.", &token, ZiQlParserError.SynthaxError), + }, + + .expect_new_data => switch (token.tag) { + .l_paren => { + keep_next = true; + self.state = .parse_new_data_and_add_data; + }, + else => return self.printError("Error: Expected new data starting with (", &token, ZiQlParserError.SynthaxError), + }, + + .parse_new_data_and_add_data => { + var data_map = std.StringHashMap([]const u8).init(self.allocator); + defer data_map.deinit(); + try self.parseNewData(&data_map); + + // TODO: Print the entire list of missing + if (!self.file_engine.checkIfAllMemberInMap(self.struct_name, &data_map)) return self.printError("Error: Missing member", &token, ZiQlParserError.MemberMissing); + const uuid = self.file_engine.writeEntity(self.struct_name, data_map) catch { + send("ZipponDB error: Couln't write new data to file", .{}); + continue; + }; + send("Successfully added new {s} with UUID: {s}", .{ self.struct_name, uuid.format_uuid() }); + self.state = .end; + }, + + else => unreachable, + }; } /// Take an array of UUID and populate it with what match what is between {} @@ -342,73 +300,71 @@ pub const Parser = struct { while (self.state != State.end) : ({ token = if (!keep_next) self.toker.next() else token; keep_next = false; - }) { - switch (self.state) { - .expect_left_condition => switch (token.tag) { - .r_brace => { - try self.file_engine.getAllUUIDList(struct_name, left_array); - self.state = .end; - }, - else => { - token = try self.parseCondition(&left_condition, &token); - try self.file_engine.getUUIDListUsingCondition(left_condition, left_array); - self.state = .expect_ANDOR_OR_end; - keep_next = true; - }, + }) switch (self.state) { + .expect_left_condition => switch (token.tag) { + .r_brace => { + try self.file_engine.getAllUUIDList(struct_name, left_array); + self.state = .end; }, - - .expect_ANDOR_OR_end => switch (token.tag) { - .r_brace => if (main) { - self.state = .end; - } else { - return self.printError("Error: Expected } to end main condition or AND/OR to continue it", &token, ZiQlParserError.SynthaxError); - }, - .r_paren => if (!main) { - self.state = .end; - } else { - return self.printError("Error: Expected ) to end inside condition or AND/OR to continue it", &token, ZiQlParserError.SynthaxError); - }, - .keyword_and => { - curent_operation = .and_; - self.state = .expect_right_uuid_array; - }, - .keyword_or => { - curent_operation = .or_; - self.state = .expect_right_uuid_array; - }, - else => return self.printError("Error: Expected a condition including AND OR or the end of the filter with } or )", &token, ZiQlParserError.SynthaxError), - }, - - .expect_right_uuid_array => { - var right_array = std.ArrayList(UUID).init(self.allocator); - defer right_array.deinit(); - - switch (token.tag) { - .l_paren => _ = try self.parseFilter(&right_array, struct_name, false), // run parserFilter to get the right array - .identifier => { - var right_condition = Condition.init(struct_name); - - token = try self.parseCondition(&right_condition, &token); - keep_next = true; - try self.file_engine.getUUIDListUsingCondition(right_condition, &right_array); - }, // Create a new condition and compare it - else => return self.printError("Error: Expected ( or member name.", &token, ZiQlParserError.SynthaxError), - } - - switch (curent_operation) { - .and_ => { - try AND(left_array, &right_array); - }, - .or_ => { - try OR(left_array, &right_array); - }, - } + else => { + token = try self.parseCondition(&left_condition, &token); + try self.file_engine.getUUIDListUsingCondition(left_condition, left_array); self.state = .expect_ANDOR_OR_end; + keep_next = true; }, + }, - else => unreachable, - } - } + .expect_ANDOR_OR_end => switch (token.tag) { + .r_brace => if (main) { + self.state = .end; + } else { + return self.printError("Error: Expected } to end main condition or AND/OR to continue it", &token, ZiQlParserError.SynthaxError); + }, + .r_paren => if (!main) { + self.state = .end; + } else { + return self.printError("Error: Expected ) to end inside condition or AND/OR to continue it", &token, ZiQlParserError.SynthaxError); + }, + .keyword_and => { + curent_operation = .and_; + self.state = .expect_right_uuid_array; + }, + .keyword_or => { + curent_operation = .or_; + self.state = .expect_right_uuid_array; + }, + else => return self.printError("Error: Expected a condition including AND OR or the end of the filter with } or )", &token, ZiQlParserError.SynthaxError), + }, + + .expect_right_uuid_array => { + var right_array = std.ArrayList(UUID).init(self.allocator); + defer right_array.deinit(); + + switch (token.tag) { + .l_paren => _ = try self.parseFilter(&right_array, struct_name, false), // run parserFilter to get the right array + .identifier => { + var right_condition = Condition.init(struct_name); + + token = try self.parseCondition(&right_condition, &token); + keep_next = true; + try self.file_engine.getUUIDListUsingCondition(right_condition, &right_array); + }, // Create a new condition and compare it + else => return self.printError("Error: Expected ( or member name.", &token, ZiQlParserError.SynthaxError), + } + + switch (curent_operation) { + .and_ => { + try AND(left_array, &right_array); + }, + .or_ => { + try OR(left_array, &right_array); + }, + } + self.state = .expect_ANDOR_OR_end; + }, + + else => unreachable, + }; return token; } @@ -423,112 +379,111 @@ pub const Parser = struct { while (self.state != State.end) : ({ token = if (!keep_next) self.toker.next() else token; keep_next = false; - }) { - switch (self.state) { - .expect_member => switch (token.tag) { - .identifier => { - if (!self.file_engine.isMemberNameInStruct(condition.struct_name, self.toker.getTokenSlice(token))) { - return self.printError("Error: Member not part of struct.", &token, ZiQlParserError.MemberNotFound); + }) switch (self.state) { + .expect_member => switch (token.tag) { + .identifier => { + if (!self.file_engine.isMemberNameInStruct(condition.struct_name, self.toker.getTokenSlice(token))) { + return self.printError("Error: Member not part of struct.", &token, ZiQlParserError.MemberNotFound); + } + condition.data_type = self.file_engine.memberName2DataType(condition.struct_name, self.toker.getTokenSlice(token)) orelse @panic("Couldn't find the struct and member"); + condition.member_name = self.toker.getTokenSlice(token); + self.state = State.expect_operation; + }, + else => return self.printError("Error: Expected member name.", &token, ZiQlParserError.SynthaxError), + }, + + .expect_operation => { + switch (token.tag) { + .equal => condition.operation = .equal, // = + .angle_bracket_left => condition.operation = .inferior, // < + .angle_bracket_right => condition.operation = .superior, // > + .angle_bracket_left_equal => condition.operation = .inferior_or_equal, // <= + .angle_bracket_right_equal => condition.operation = .superior_or_equal, // >= + .bang_equal => condition.operation = .different, // != + else => return self.printError("Error: Expected condition. Including < > <= >= = !=", &token, ZiQlParserError.SynthaxError), + } + self.state = State.expect_value; + }, + + .expect_value => { + switch (condition.data_type) { + .int => { + switch (token.tag) { + .int_literal => condition.value = self.toker.getTokenSlice(token), + else => return self.printError("Error: Expected int", &token, ZiQlParserError.SynthaxError), } - condition.data_type = self.file_engine.memberName2DataType(condition.struct_name, self.toker.getTokenSlice(token)) orelse @panic("Couldn't find the struct and member"); - condition.member_name = self.toker.getTokenSlice(token); - self.state = State.expect_operation; }, - else => return self.printError("Error: Expected member name.", &token, ZiQlParserError.SynthaxError), - }, + .float => { + switch (token.tag) { + .float_literal => condition.value = self.toker.getTokenSlice(token), + else => return self.printError("Error: Expected float", &token, ZiQlParserError.SynthaxError), + } + }, + .str, .id => { + switch (token.tag) { + .string_literal => condition.value = self.toker.getTokenSlice(token), + else => return self.printError("Error: Expected string", &token, ZiQlParserError.SynthaxError), + } + }, + .bool => { + switch (token.tag) { + .bool_literal_true, .bool_literal_false => condition.value = self.toker.getTokenSlice(token), + else => return self.printError("Error: Expected bool", &token, ZiQlParserError.SynthaxError), + } + }, + .int_array => { + const start_index = token.loc.start; + token = self.toker.next(); + while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) { + switch (token.tag) { + .int_literal => continue, + else => return self.printError("Error: Expected int or ].", &token, ZiQlParserError.SynthaxError), + } + } + condition.value = self.toker.buffer[start_index..token.loc.end]; + }, + .float_array => { + const start_index = token.loc.start; + token = self.toker.next(); + while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) { + switch (token.tag) { + .float_literal => continue, + else => return self.printError("Error: Expected float or ].", &token, ZiQlParserError.SynthaxError), + } + } + condition.value = self.toker.buffer[start_index..token.loc.end]; + }, + .str_array, .id_array => { + const start_index = token.loc.start; + token = self.toker.next(); + while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) { + switch (token.tag) { + .string_literal => continue, + else => return self.printError("Error: Expected string or ].", &token, ZiQlParserError.SynthaxError), + } + } + condition.value = self.toker.buffer[start_index..token.loc.end]; + }, + .bool_array => { + const start_index = token.loc.start; + token = self.toker.next(); + while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) { + switch (token.tag) { + .bool_literal_false, .bool_literal_true => continue, + else => return self.printError("Error: Expected bool or ].", &token, ZiQlParserError.SynthaxError), + } + } + condition.value = self.toker.buffer[start_index..token.loc.end]; + }, + } + self.state = .end; + }, - .expect_operation => { - switch (token.tag) { - .equal => condition.operation = .equal, // = - .angle_bracket_left => condition.operation = .inferior, // < - .angle_bracket_right => condition.operation = .superior, // > - .angle_bracket_left_equal => condition.operation = .inferior_or_equal, // <= - .angle_bracket_right_equal => condition.operation = .superior_or_equal, // >= - .bang_equal => condition.operation = .different, // != - else => return self.printError("Error: Expected condition. Including < > <= >= = !=", &token, ZiQlParserError.SynthaxError), - } - self.state = State.expect_value; - }, - - .expect_value => { - switch (condition.data_type) { - .int => { - switch (token.tag) { - .int_literal => condition.value = self.toker.getTokenSlice(token), - else => return self.printError("Error: Expected int", &token, ZiQlParserError.SynthaxError), - } - }, - .float => { - switch (token.tag) { - .float_literal => condition.value = self.toker.getTokenSlice(token), - else => return self.printError("Error: Expected float", &token, ZiQlParserError.SynthaxError), - } - }, - .str, .id => { - switch (token.tag) { - .string_literal => condition.value = self.toker.getTokenSlice(token), - else => return self.printError("Error: Expected string", &token, ZiQlParserError.SynthaxError), - } - }, - .bool => { - switch (token.tag) { - .bool_literal_true, .bool_literal_false => condition.value = self.toker.getTokenSlice(token), - else => return self.printError("Error: Expected bool", &token, ZiQlParserError.SynthaxError), - } - }, - .int_array => { - const start_index = token.loc.start; - token = self.toker.next(); - while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) { - switch (token.tag) { - .int_literal => continue, - else => return self.printError("Error: Expected int or ].", &token, ZiQlParserError.SynthaxError), - } - } - condition.value = self.toker.buffer[start_index..token.loc.end]; - }, - .float_array => { - const start_index = token.loc.start; - token = self.toker.next(); - while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) { - switch (token.tag) { - .float_literal => continue, - else => return self.printError("Error: Expected float or ].", &token, ZiQlParserError.SynthaxError), - } - } - condition.value = self.toker.buffer[start_index..token.loc.end]; - }, - .str_array, .id_array => { - const start_index = token.loc.start; - token = self.toker.next(); - while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) { - switch (token.tag) { - .string_literal => continue, - else => return self.printError("Error: Expected string or ].", &token, ZiQlParserError.SynthaxError), - } - } - condition.value = self.toker.buffer[start_index..token.loc.end]; - }, - .bool_array => { - const start_index = token.loc.start; - token = self.toker.next(); - while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) { - switch (token.tag) { - .bool_literal_false, .bool_literal_true => continue, - else => return self.printError("Error: Expected bool or ].", &token, ZiQlParserError.SynthaxError), - } - } - condition.value = self.toker.buffer[start_index..token.loc.end]; - }, - } - self.state = .end; - }, - - else => unreachable, - } - } + else => unreachable, + }; // Check if the condition is valid + // TODO: Mqke q function outside the Parser switch (condition.operation) { .equal => switch (condition.data_type) { .int, .float, .str, .bool, .id => {}, @@ -577,64 +532,62 @@ pub const Parser = struct { while (self.state != .end) : ({ token = if ((!keep_next) and (self.state != .end)) self.toker.next() else token; keep_next = false; - }) { - switch (self.state) { - .expect_count_of_entity_to_find => switch (token.tag) { - .int_literal => { - const count = std.fmt.parseInt(usize, self.toker.getTokenSlice(token), 10) catch { - return self.printError("Error while transforming this into a integer.", &token, ZiQlParserError.ParsingValueError); - }; - additional_data.entity_count_to_find = count; - self.state = .expect_semicolon_OR_right_bracket; - }, - else => { - self.state = .expect_member; - keep_next = true; - }, + }) switch (self.state) { + .expect_count_of_entity_to_find => switch (token.tag) { + .int_literal => { + const count = std.fmt.parseInt(usize, self.toker.getTokenSlice(token), 10) catch { + return self.printError("Error while transforming this into a integer.", &token, ZiQlParserError.ParsingValueError); + }; + additional_data.entity_count_to_find = count; + self.state = .expect_semicolon_OR_right_bracket; }, - - .expect_semicolon_OR_right_bracket => switch (token.tag) { - .semicolon => self.state = .expect_member, - .r_bracket => self.state = .end, - else => return self.printError("Error: Expect ';' or ']'.", &token, ZiQlParserError.SynthaxError), + else => { + self.state = .expect_member; + keep_next = true; }, + }, - .expect_member => switch (token.tag) { - .identifier => { - if (!self.file_engine.isMemberNameInStruct(self.struct_name, self.toker.getTokenSlice(token))) return self.printError("Member not found in struct.", &token, ZiQlParserError.SynthaxError); - try additional_data.member_to_find.append( - AdditionalDataMember.init( - self.allocator, - self.toker.getTokenSlice(token), - ), - ); + .expect_semicolon_OR_right_bracket => switch (token.tag) { + .semicolon => self.state = .expect_member, + .r_bracket => self.state = .end, + else => return self.printError("Error: Expect ';' or ']'.", &token, ZiQlParserError.SynthaxError), + }, - self.state = .expect_comma_OR_r_bracket_OR_l_bracket; - }, - else => return self.printError("Error: Expected a member name.", &token, ZiQlParserError.SynthaxError), + .expect_member => switch (token.tag) { + .identifier => { + if (!self.file_engine.isMemberNameInStruct(self.struct_name, self.toker.getTokenSlice(token))) return self.printError("Member not found in struct.", &token, ZiQlParserError.SynthaxError); + try additional_data.member_to_find.append( + AdditionalDataMember.init( + self.allocator, + self.toker.getTokenSlice(token), + ), + ); + + self.state = .expect_comma_OR_r_bracket_OR_l_bracket; }, + else => return self.printError("Error: Expected a member name.", &token, ZiQlParserError.SynthaxError), + }, - .expect_comma_OR_r_bracket_OR_l_bracket => switch (token.tag) { - .comma => self.state = .expect_member, - .r_bracket => self.state = .end, - .l_bracket => { - try self.parseAdditionalData( - &additional_data.member_to_find.items[additional_data.member_to_find.items.len - 1].additional_data, - ); - self.state = .expect_comma_OR_r_bracket; - }, - else => return self.printError("Error: Expected , or ] or [", &token, ZiQlParserError.SynthaxError), + .expect_comma_OR_r_bracket_OR_l_bracket => switch (token.tag) { + .comma => self.state = .expect_member, + .r_bracket => self.state = .end, + .l_bracket => { + try self.parseAdditionalData( + &additional_data.member_to_find.items[additional_data.member_to_find.items.len - 1].additional_data, + ); + self.state = .expect_comma_OR_r_bracket; }, + else => return self.printError("Error: Expected , or ] or [", &token, ZiQlParserError.SynthaxError), + }, - .expect_comma_OR_r_bracket => switch (token.tag) { - .comma => self.state = .expect_member, - .r_bracket => self.state = .end, - else => return self.printError("Error: Expected , or ]", &token, ZiQlParserError.SynthaxError), - }, + .expect_comma_OR_r_bracket => switch (token.tag) { + .comma => self.state = .expect_member, + .r_bracket => self.state = .end, + else => return self.printError("Error: Expected , or ]", &token, ZiQlParserError.SynthaxError), + }, - else => unreachable, - } - } + else => unreachable, + }; } /// Take the tokenizer and return a map of the ADD action. @@ -649,144 +602,143 @@ pub const Parser = struct { while (self.state != .end) : ({ token = if (!keep_next) self.toker.next() else token; keep_next = false; - }) { - switch (self.state) { - .expect_member => switch (token.tag) { - .identifier => { - member_name = self.toker.getTokenSlice(token); - if (!self.file_engine.isMemberNameInStruct(self.struct_name, member_name)) return self.printError("Member not found in struct.", &token, ZiQlParserError.MemberNotFound); - self.state = .expect_equal; + }) switch (self.state) { + .expect_member => switch (token.tag) { + .identifier => { + member_name = self.toker.getTokenSlice(token); + if (!self.file_engine.isMemberNameInStruct(self.struct_name, member_name)) return self.printError("Member not found in struct.", &token, ZiQlParserError.MemberNotFound); + self.state = .expect_equal; + }, + else => return self.printError("Error: Expected member name.", &token, ZiQlParserError.SynthaxError), + }, + + .expect_equal => switch (token.tag) { + // TODO: Implement stuff to manipulate array like APPEND or REMOVE + .equal => self.state = .expect_new_value, + else => return self.printError("Error: Expected =", &token, ZiQlParserError.SynthaxError), + }, + + .expect_new_value => { + const data_type = self.file_engine.memberName2DataType(self.struct_name, member_name); + switch (data_type.?) { + .int => switch (token.tag) { + .int_literal, .keyword_null => { + member_map.put(member_name, self.toker.getTokenSlice(token)) catch @panic("Could not add member name and value to map in getMapOfMember"); + self.state = .expect_comma_OR_end; + }, + else => return self.printError("Error: Expected int", &token, ZiQlParserError.SynthaxError), }, - else => return self.printError("Error: Expected member name.", &token, ZiQlParserError.SynthaxError), - }, - - .expect_equal => switch (token.tag) { - // TODO: Implement stuff to manipulate array like APPEND or REMOVE - .equal => self.state = .expect_new_value, - else => return self.printError("Error: Expected =", &token, ZiQlParserError.SynthaxError), - }, - - .expect_new_value => { - const data_type = self.file_engine.memberName2DataType(self.struct_name, member_name); - switch (data_type.?) { - .int => switch (token.tag) { - .int_literal, .keyword_null => { - member_map.put(member_name, self.toker.getTokenSlice(token)) catch @panic("Could not add member name and value to map in getMapOfMember"); - self.state = .expect_comma_OR_end; - }, - else => return self.printError("Error: Expected int", &token, ZiQlParserError.SynthaxError), + .float => switch (token.tag) { + .float_literal, .keyword_null => { + member_map.put(member_name, self.toker.getTokenSlice(token)) catch @panic("Could not add member name and value to map in getMapOfMember"); + self.state = .expect_comma_OR_end; }, - .float => switch (token.tag) { - .float_literal, .keyword_null => { - member_map.put(member_name, self.toker.getTokenSlice(token)) catch @panic("Could not add member name and value to map in getMapOfMember"); - self.state = .expect_comma_OR_end; - }, - else => return self.printError("Error: Expected float", &token, ZiQlParserError.SynthaxError), + else => return self.printError("Error: Expected float", &token, ZiQlParserError.SynthaxError), + }, + .bool => switch (token.tag) { + .bool_literal_true => { + member_map.put(member_name, "1") catch @panic("Could not add member name and value to map in getMapOfMember"); + self.state = .expect_comma_OR_end; }, - .bool => switch (token.tag) { - .bool_literal_true => { - member_map.put(member_name, "1") catch @panic("Could not add member name and value to map in getMapOfMember"); - self.state = .expect_comma_OR_end; - }, - .bool_literal_false => { - member_map.put(member_name, "0") catch @panic("Could not add member name and value to map in getMapOfMember"); - self.state = .expect_comma_OR_end; - }, - .keyword_null => { - member_map.put(member_name, self.toker.getTokenSlice(token)) catch @panic("Could not add member name and value to map in getMapOfMember"); - self.state = .expect_comma_OR_end; - }, - else => return self.printError("Error: Expected bool: true false", &token, ZiQlParserError.SynthaxError), + .bool_literal_false => { + member_map.put(member_name, "0") catch @panic("Could not add member name and value to map in getMapOfMember"); + self.state = .expect_comma_OR_end; }, - .str, .id => switch (token.tag) { - .string_literal, .keyword_null => { - member_map.put(member_name, self.toker.getTokenSlice(token)) catch @panic("Could not add member name and value to map in getMapOfMember"); - self.state = .expect_comma_OR_end; - }, - else => return self.printError("Error: Expected string between ''", &token, ZiQlParserError.SynthaxError), + .keyword_null => { + member_map.put(member_name, self.toker.getTokenSlice(token)) catch @panic("Could not add member name and value to map in getMapOfMember"); + self.state = .expect_comma_OR_end; }, - // TODO: Maybe upgrade that to use multiple state - .int_array => switch (token.tag) { - .l_bracket => { - const start_index = token.loc.start; - token = self.toker.next(); - while (token.tag != .r_bracket) : (token = self.toker.next()) { - switch (token.tag) { - .int_literal => continue, - else => return self.printError("Error: Expected int or ].", &token, ZiQlParserError.SynthaxError), - } + else => return self.printError("Error: Expected bool: true false", &token, ZiQlParserError.SynthaxError), + }, + .str, .id => switch (token.tag) { + .string_literal, .keyword_null => { + member_map.put(member_name, self.toker.getTokenSlice(token)) catch @panic("Could not add member name and value to map in getMapOfMember"); + self.state = .expect_comma_OR_end; + }, + else => return self.printError("Error: Expected string between ''", &token, ZiQlParserError.SynthaxError), + }, + // TODO: Maybe upgrade that to use multiple state + .int_array => switch (token.tag) { + .l_bracket => { + const start_index = token.loc.start; + token = self.toker.next(); + while (token.tag != .r_bracket) : (token = self.toker.next()) { + switch (token.tag) { + .int_literal => continue, + else => return self.printError("Error: Expected int or ].", &token, ZiQlParserError.SynthaxError), } - // Maybe change that as it just recreate a string that is already in the buffer - member_map.put(member_name, self.toker.buffer[start_index..token.loc.end]) catch @panic("Couln't add string of array in data map"); - self.state = .expect_comma_OR_end; - }, - else => return self.printError("Error: Expected [ to start an array", &token, ZiQlParserError.SynthaxError), + } + // Maybe change that as it just recreate a string that is already in the buffer + member_map.put(member_name, self.toker.buffer[start_index..token.loc.end]) catch @panic("Couln't add string of array in data map"); + self.state = .expect_comma_OR_end; }, - .float_array => switch (token.tag) { - .l_bracket => { - const start_index = token.loc.start; - token = self.toker.next(); - while (token.tag != .r_bracket) : (token = self.toker.next()) { - switch (token.tag) { - .float_literal => continue, - else => return self.printError("Error: Expected float or ].", &token, ZiQlParserError.SynthaxError), - } + else => return self.printError("Error: Expected [ to start an array", &token, ZiQlParserError.SynthaxError), + }, + .float_array => switch (token.tag) { + .l_bracket => { + const start_index = token.loc.start; + token = self.toker.next(); + while (token.tag != .r_bracket) : (token = self.toker.next()) { + switch (token.tag) { + .float_literal => continue, + else => return self.printError("Error: Expected float or ].", &token, ZiQlParserError.SynthaxError), } - // Maybe change that as it just recreate a string that is already in the buffer - member_map.put(member_name, self.toker.buffer[start_index..token.loc.end]) catch @panic("Couln't add string of array in data map"); - self.state = .expect_comma_OR_end; - }, - else => return self.printError("Error: Expected [ to start an array", &token, ZiQlParserError.SynthaxError), + } + // Maybe change that as it just recreate a string that is already in the buffer + member_map.put(member_name, self.toker.buffer[start_index..token.loc.end]) catch @panic("Couln't add string of array in data map"); + self.state = .expect_comma_OR_end; }, - .bool_array => switch (token.tag) { - .l_bracket => { - const start_index = token.loc.start; - token = self.toker.next(); - while (token.tag != .r_bracket) : (token = self.toker.next()) { - switch (token.tag) { - .bool_literal_false, .bool_literal_true => continue, - else => return self.printError("Error: Expected bool or ].", &token, ZiQlParserError.SynthaxError), - } + else => return self.printError("Error: Expected [ to start an array", &token, ZiQlParserError.SynthaxError), + }, + .bool_array => switch (token.tag) { + .l_bracket => { + const start_index = token.loc.start; + token = self.toker.next(); + while (token.tag != .r_bracket) : (token = self.toker.next()) { + switch (token.tag) { + .bool_literal_false, .bool_literal_true => continue, + else => return self.printError("Error: Expected bool or ].", &token, ZiQlParserError.SynthaxError), } - // Maybe change that as it just recreate a string that is already in the buffer - member_map.put(member_name, self.toker.buffer[start_index..token.loc.end]) catch @panic("Couln't add string of array in data map"); - self.state = .expect_comma_OR_end; - }, - else => return self.printError("Error: Expected [ to start an array", &token, ZiQlParserError.SynthaxError), + } + // Maybe change that as it just recreate a string that is already in the buffer + member_map.put(member_name, self.toker.buffer[start_index..token.loc.end]) catch @panic("Couln't add string of array in data map"); + self.state = .expect_comma_OR_end; }, - .str_array, .id_array => switch (token.tag) { - .l_bracket => { - const start_index = token.loc.start; - token = self.toker.next(); - while (token.tag != .r_bracket) : (token = self.toker.next()) { - switch (token.tag) { - .string_literal => continue, - else => return self.printError("Error: Expected str or ].", &token, ZiQlParserError.SynthaxError), - } + else => return self.printError("Error: Expected [ to start an array", &token, ZiQlParserError.SynthaxError), + }, + .str_array, .id_array => switch (token.tag) { + .l_bracket => { + const start_index = token.loc.start; + token = self.toker.next(); + while (token.tag != .r_bracket) : (token = self.toker.next()) { + switch (token.tag) { + .string_literal => continue, + else => return self.printError("Error: Expected str or ].", &token, ZiQlParserError.SynthaxError), } - // Maybe change that as it just recreate a string that is already in the buffer - member_map.put(member_name, self.toker.buffer[start_index..token.loc.end]) catch @panic("Couln't add string of array in data map"); - self.state = .expect_comma_OR_end; - }, - else => return self.printError("Error: Expected [ to start an array", &token, ZiQlParserError.SynthaxError), + } + // Maybe change that as it just recreate a string that is already in the buffer + member_map.put(member_name, self.toker.buffer[start_index..token.loc.end]) catch @panic("Couln't add string of array in data map"); + self.state = .expect_comma_OR_end; }, - } - }, + else => return self.printError("Error: Expected [ to start an array", &token, ZiQlParserError.SynthaxError), + }, + } + }, - .expect_comma_OR_end => { - switch (token.tag) { - .r_paren => self.state = .end, - .comma => self.state = .expect_member, - else => return self.printError("Error: Expect , or )", &token, ZiQlParserError.SynthaxError), - } - }, + .expect_comma_OR_end => { + switch (token.tag) { + .r_paren => self.state = .end, + .comma => self.state = .expect_member, + else => return self.printError("Error: Expect , or )", &token, ZiQlParserError.SynthaxError), + } + }, - else => unreachable, - } - } + else => unreachable, + }; } /// Print an error and send it to the user pointing to the token + /// TODO: There is a duplicate of this somewhere, make it a single function fn printError(self: *Parser, message: []const u8, token: *Token, err: ZiQlParserError) ZiQlParserError { stdout.print("\n", .{}) catch {}; stdout.print("{s}\n", .{message}) catch {}; @@ -810,96 +762,6 @@ pub const Parser = struct { } }; -// TODO: Optimize both -fn OR(arr1: *std.ArrayList(UUID), arr2: *std.ArrayList(UUID)) !void { - for (0..arr2.items.len) |i| { - if (!containUUID(arr1.*, arr2.items[i])) { - try arr1.append(arr2.items[i]); - } - } -} - -fn AND(arr1: *std.ArrayList(UUID), arr2: *std.ArrayList(UUID)) !void { - var i: usize = 0; - for (0..arr1.items.len) |_| { - if (!containUUID(arr2.*, arr1.items[i])) { - _ = arr1.orderedRemove(i); - } else { - i += 1; - } - } -} - -test "OR & AND" { - const allocator = std.testing.allocator; - - var right_arr = std.ArrayList(UUID).init(allocator); - defer right_arr.deinit(); - try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); - try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); - try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000005")); - try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000006")); - try right_arr.append(try UUID.parse("00000000-0000-0000-0000-000000000007")); - - var left_arr1 = std.ArrayList(UUID).init(allocator); - defer left_arr1.deinit(); - try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); - try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); - try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000002")); - try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000003")); - try left_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000004")); - - var expected_arr1 = std.ArrayList(UUID).init(allocator); - defer expected_arr1.deinit(); - try expected_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); - try expected_arr1.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); - - try AND(&left_arr1, &right_arr); - try std.testing.expect(compareUUIDArray(left_arr1, expected_arr1)); - - var left_arr2 = std.ArrayList(UUID).init(allocator); - defer left_arr2.deinit(); - try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); - try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); - try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000002")); - try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000003")); - try left_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000004")); - - var expected_arr2 = std.ArrayList(UUID).init(allocator); - defer expected_arr2.deinit(); - try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000000")); - try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000001")); - try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000002")); - try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000003")); - try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000004")); - try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000005")); - try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000006")); - try expected_arr2.append(try UUID.parse("00000000-0000-0000-0000-000000000007")); - - try OR(&left_arr2, &right_arr); - - try std.testing.expect(compareUUIDArray(left_arr2, expected_arr2)); -} - -fn containUUID(arr: std.ArrayList(UUID), value: UUID) bool { - return for (arr.items) |elem| { - if (value.compare(elem)) break true; - } else false; -} - -fn compareUUIDArray(arr1: std.ArrayList(UUID), arr2: std.ArrayList(UUID)) bool { - if (arr1.items.len != arr2.items.len) { - std.debug.print("Not same array len when comparing UUID. arr1: {d} arr2: {d}\n", .{ arr1.items.len, arr2.items.len }); - return false; - } - - for (0..arr1.items.len) |i| { - if (!containUUID(arr2, arr1.items[i])) return false; - } - - return true; -} - test "ADD" { try testParsing("ADD User (name = 'Bob', email='bob@email.com', age=55, scores=[ 1 ], friends=[])"); try testParsing("ADD User (name = 'Bob', email='bob@email.com', age=55, scores=[ 1 ], friends=[])"); @@ -941,13 +803,13 @@ test "Specific query" { } test "Synthax error" { - try expectParsingError("GRAB {}", Parser.ZiQlParserError.StructNotFound); - try expectParsingError("GRAB User {qwe = 'qwe'}", Parser.ZiQlParserError.MemberNotFound); - try expectParsingError("ADD User (name='Bob')", Parser.ZiQlParserError.MemberMissing); - try expectParsingError("GRAB User {name='Bob'", Parser.ZiQlParserError.SynthaxError); - try expectParsingError("GRAB User {age = 50 name='Bob'}", Parser.ZiQlParserError.SynthaxError); - try expectParsingError("GRAB User {age <14 AND (age>55}", Parser.ZiQlParserError.SynthaxError); - try expectParsingError("GRAB User {name < 'Hello'}", Parser.ZiQlParserError.ConditionError); + try expectParsingError("GRAB {}", ZiQlParserError.StructNotFound); + try expectParsingError("GRAB User {qwe = 'qwe'}", ZiQlParserError.MemberNotFound); + try expectParsingError("ADD User (name='Bob')", ZiQlParserError.MemberMissing); + try expectParsingError("GRAB User {name='Bob'", ZiQlParserError.SynthaxError); + try expectParsingError("GRAB User {age = 50 name='Bob'}", ZiQlParserError.SynthaxError); + try expectParsingError("GRAB User {age <14 AND (age>55}", ZiQlParserError.SynthaxError); + try expectParsingError("GRAB User {name < 'Hello'}", ZiQlParserError.ConditionError); } fn testParsing(source: [:0]const u8) !void { @@ -964,7 +826,7 @@ fn testParsing(source: [:0]const u8) !void { try parser.parse(); } -fn expectParsingError(source: [:0]const u8, err: Parser.ZiQlParserError) !void { +fn expectParsingError(source: [:0]const u8, err: ZiQlParserError) !void { const allocator = std.testing.allocator; const path = try allocator.dupe(u8, "ZipponDB");