Passe basic GRAB query !

This commit is contained in:
Adrien Bouvais 2024-10-09 20:12:07 +02:00
parent 84495e831f
commit cda2ee16a8
3 changed files with 155 additions and 99 deletions

View File

@ -7,6 +7,17 @@ const ziqlTokenizer = @import("tokenizers/ziql.zig").Tokenizer;
const ziqlToken = @import("tokenizers/ziql.zig").Token;
const ziqlParser = @import("ziqlParser.zig").Parser;
const stdout = std.io.getStdOut().writer();
fn send(comptime format: []const u8, args: anytype) void {
stdout.print(format, args) catch |err| {
std.log.err("Can't send: {any}", .{err});
stdout.print("\x03\n", .{}) catch {};
};
stdout.print("\x03\n", .{}) catch {};
}
pub fn main() !void {
// TODO: Use an environment variable for the path of the DB
checkAndCreateDirectories();
@ -16,11 +27,8 @@ pub fn main() !void {
defer {
switch (gpa.deinit()) {
.ok => std.debug.print("No memory leak baby !\n", .{}),
.leak => {
std.debug.print("We fucked it up bro...\n", .{});
@panic("=(");
},
.ok => std.log.debug("No memory leak baby !\n", .{}),
.leak => std.log.debug("We fucked it up bro...\n", .{}),
}
}
@ -33,6 +41,8 @@ pub fn main() !void {
const line = try std.io.getStdIn().reader().readUntilDelimiterOrEof(line_buf, '\n');
if (line) |line_str| {
const time_initial = std.time.microTimestamp();
const null_term_line_str = try allocator.dupeZ(u8, line_str[0..line_str.len]);
defer allocator.free(null_term_line_str);
@ -47,20 +57,19 @@ pub fn main() !void {
defer allocator.free(null_term_query_str);
try runCommand(null_term_query_str);
},
.keyword_help => std.debug.print("The run command will take a ZiQL query between \" and run it. eg: run \"GRAB User\"\n", .{}),
else => std.debug.print("After command run, need a string of a query, eg: \"GRAB User\"\n", .{}),
.keyword_help => send("The run command will take a ZiQL query between \" and run it. eg: run \"GRAB User\"\n", .{}),
else => send("After command run, need a string of a query, eg: \"GRAB User\"\n", .{}),
}
},
.keyword_schema => {
const second_token = cliToker.next();
switch (second_token.tag) {
.keyword_describe => std.debug.print("{s}\n", .{ // TODO: Change that to use the SchemaEngine
.keyword_describe => send("{s}\n", .{ // TODO: Change that to use the SchemaEngine
\\User (
\\ name: str,
\\ email: str,
\\)
\\
\\Message (
\\ content: str,
\\)
@ -70,7 +79,7 @@ pub fn main() !void {
try data_engine.initDataFolder();
},
.keyword_help => {
std.debug.print("{s}", .{
send("{s}", .{
\\Here are all available options to use with the schema command:
\\
\\describe Print the schema use by the current engine.
@ -82,7 +91,7 @@ pub fn main() !void {
}
},
.keyword_help => {
std.debug.print("{s}", .{
send("{s}", .{
\\Welcome to ZipponDB!
\\
\\run To run a query. Args => query: str, the query to execute.
@ -95,8 +104,12 @@ pub fn main() !void {
},
.keyword_quit => break,
.eof => {},
else => std.debug.print("Command need to start with a keyword, including: run, schema, help and quit\n", .{}),
else => send("Command need to start with a keyword, including: run, schema, help and quit\n", .{}),
}
const time_final = std.time.microTimestamp();
const duration = time_final - time_initial;
std.debug.print("Time: {d:.2}ms\n", .{@as(f64, @floatFromInt(duration)) / 1000.0});
}
}
}

View File

@ -12,7 +12,7 @@ const DataType = @import("types/dataType.zig").DataType;
pub const FileEngine = struct {
allocator: Allocator,
path_to_DATA_dir: []const u8, // The path to the DATA folder
max_file_size: usize = 1e+7, // 10mb
max_file_size: usize = 5e+4, // 50kb TODO: Change
const DataEngineError = error{
ErrorCreateDataFolder,
@ -57,31 +57,26 @@ pub const FileEngine = struct {
}
/// Take a condition and an array of UUID and fill the array with all UUID that match the condition
pub fn getUUIDListUsingCondition(self: FileEngine, condition: Condition, uuid_array: *std.ArrayList(UUID)) !void {
pub fn getUUIDListUsingCondition(self: *FileEngine, condition: Condition, uuid_array: *std.ArrayList(UUID)) !void {
var file_names = std.ArrayList([]const u8).init(self.allocator);
self.getFilesNames(condition.struct_name, condition.member_name, &file_names) catch @panic("Can't get list of files");
defer {
for (file_names.items) |elem| {
self.allocator.free(elem);
}
file_names.deinit();
}
defer file_names.deinit();
const sub_path = std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}/{s}", .{ self.path_to_DATA_dir, condition.struct_name, condition.member_name, file_names.items[0] }) catch @panic("Can't create sub_path for init a DataIterator");
var current_file = file_names.pop();
var sub_path = std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}/{s}", .{ self.path_to_DATA_dir, condition.struct_name, condition.member_name, current_file }) catch @panic("Can't create sub_path for init a DataIterator");
defer self.allocator.free(sub_path);
var file = std.fs.cwd().openFile(sub_path, .{}) catch @panic("Can't open first file to init a data iterator");
defer file.close();
var output: [1024 * 50]u8 = undefined; // Maybe need to increase that as it limit the size of a line in files
var output: [1024 * 50]u8 = undefined; // Maybe need to increase that as it limit the size of a line in a file
var output_fbs = std.io.fixedBufferStream(&output);
const writer = output_fbs.writer();
var buffered = std.io.bufferedReader(file.reader());
var reader = buffered.reader();
var file_index: usize = 0;
var compare_value: ComparisonValue = undefined;
switch (condition.data_type) {
.int => compare_value = ComparisonValue{ .int = dataParsing.parseInt(condition.value) },
@ -108,14 +103,23 @@ pub const FileEngine = struct {
reader.streamUntilDelimiter(writer, '\n', null) catch |err| switch (err) {
error.EndOfStream => {
output_fbs.reset(); // clear buffer before exit
file_index += 1;
self.allocator.free(current_file);
if (file_index == file_names.items.len) break;
if (file_names.items.len == 0) break;
// FIXME: Update the file and reader to be the next file of the list
std.debug.print("End of stream\n", .{});
current_file = file_names.pop();
break;
// Do I leak memory here ? Do I deinit every time ?
self.allocator.free(sub_path);
sub_path = std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}/{s}", .{ self.path_to_DATA_dir, condition.struct_name, condition.member_name, current_file }) catch @panic("Can't create sub_path for init a DataIterator");
// Same here, do I close everytime ?
file.close();
file = std.fs.cwd().openFile(sub_path, .{}) catch @panic("Can't open first file to init a data iterator");
buffered = std.io.bufferedReader(file.reader());
reader = buffered.reader();
continue;
}, // file read till the end
else => {
std.debug.print("Error while reading file: {any}\n", .{err});
@ -185,13 +189,9 @@ 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
pub fn writeEntity(self: FileEngine, struct_name: []const u8, data_map: std.StringHashMap([]const u8)) !void {
// 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
pub fn writeEntity(self: FileEngine, struct_name: []const u8, data_map: std.StringHashMap([]const u8)) !UUID {
const uuid_str = UUID.init().format_uuid();
defer std.debug.print("Added new {s} successfully using UUID: {s}\n", .{
struct_name,
uuid_str,
});
const member_names = schemaEngine.structName2structMembers(struct_name);
for (member_names) |member_name| {
@ -207,7 +207,7 @@ pub const FileEngine = struct {
.mode = .read_write,
}) catch {
std.debug.print("Error opening data file.", .{});
return;
continue; // TODO: Error handeling
};
defer file.close();
@ -219,14 +219,14 @@ pub const FileEngine = struct {
const new_file_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}/{d}.zippondata", .{ self.path_to_DATA_dir, struct_name, member_name, max_index + 1 });
defer self.allocator.free(new_file_path);
std.debug.print("new file path: {s}\n", .{new_file_path});
const new_file = std.fs.cwd().createFile(new_file_path, .{}) catch @panic("Error creating new data file");
defer new_file.close();
try new_file.writer().print("{s} {s}\n", .{ &uuid_str, data_map.get(member_name).? });
}
}
return UUID.parse(&uuid_str);
}
/// Use a filename in the format 1.zippondata and return the 1
@ -245,6 +245,7 @@ pub const FileEngine = struct {
defer member_dir.close();
var iter = member_dir.iterate();
defer iter.reset();
while (try iter.next()) |entry| {
if ((entry.kind != std.fs.Dir.Entry.Kind.file) or (std.mem.eql(u8, "main.zippondata", entry.name))) continue;
try file_names.*.append(try self.allocator.dupe(u8, entry.name));
@ -281,10 +282,10 @@ pub const FileEngine = struct {
var iter = member_dir.iterate();
while (try iter.next()) |entry| {
if ((entry.kind != std.fs.Dir.Entry.Kind.file) or (std.mem.eql(u8, "main.zippondata", entry.name))) continue;
if (entry.kind != std.fs.Dir.Entry.Kind.file) continue;
count += 1;
}
return count;
return count - 1;
}
// TODO: Give the option to keep , dump or erase the data

View File

@ -7,6 +7,17 @@ const Token = @import("tokenizers/ziql.zig").Token;
const UUID = @import("types/uuid.zig").UUID;
const Allocator = std.mem.Allocator;
const stdout = std.io.getStdOut().writer();
fn send(comptime format: []const u8, args: anytype) void {
stdout.print(format, args) catch |err| {
std.log.err("Can't send: {any}", .{err});
stdout.print("\x03\n", .{}) catch {};
};
stdout.print("\x03\n", .{}) catch {};
}
pub const Parser = struct {
allocator: Allocator,
state: State,
@ -31,6 +42,7 @@ pub const Parser = struct {
pub fn deinit(self: *Parser) void {
self.additional_data.deinit();
self.allocator.free(self.struct_name);
}
const Options = struct {
@ -50,7 +62,7 @@ pub const Parser = struct {
// For the main parse function
expect_struct_name,
expect_filter,
expect_additional_data,
parse_additional_data,
expect_filter_or_additional_data,
expect_new_data,
expect_right_arrow,
@ -121,23 +133,23 @@ pub const Parser = struct {
switch (token.tag) {
.keyword_grab => {
self.action = .GRAB;
self.state = State.expect_struct_name;
self.state = .expect_struct_name;
},
.keyword_add => {
self.action = .ADD;
self.state = State.expect_struct_name;
self.state = .expect_struct_name;
},
.keyword_update => {
self.action = .UPDATE;
self.state = State.expect_struct_name;
self.state = .expect_struct_name;
},
.keyword_delete => {
self.action = .DELETE;
self.state = State.expect_struct_name;
self.state = .expect_struct_name;
},
else => {
self.printError("Error: Expected action keyword. Available: GRAB ADD DELETE UPDATE", &token);
self.state = State.end;
self.state = .end;
},
}
},
@ -146,34 +158,34 @@ pub const Parser = struct {
self.struct_name = try self.allocator.dupe(u8, self.toker.getTokenSlice(token));
if (!schemaEngine.isStructNameExists(self.struct_name)) self.printError("Error: struct name not found in schema.", &token);
switch (self.action) {
.ADD => self.state = State.expect_new_data,
else => self.state = State.expect_filter_or_additional_data,
.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 = State.expect_additional_data,
.l_brace => self.state = State.filter_and_send,
.l_bracket => self.state = .parse_additional_data,
.l_brace => self.state = .filter_and_send,
else => self.printError("Error: Expect [ for additional data or { for a filter", &token),
}
},
.expect_additional_data => {
.parse_additional_data => {
try self.parseAdditionalData(&self.additional_data);
self.state = State.filter_and_send;
self.state = .filter_and_send;
},
.filter_and_send => {
var array = std.ArrayList(UUID).init(self.allocator);
defer array.deinit();
try self.parseFilter(&array, self.struct_name, true);
self.sendEntity(array.items);
self.state = State.end;
self.state = .end;
},
.expect_new_data => {
switch (token.tag) {
.l_paren => {
keep_next = true;
self.state = State.parse_new_data_and_add_data;
self.state = .parse_new_data_and_add_data;
},
else => self.printError("Error: Expecting new data starting with (", &token),
}
@ -187,8 +199,12 @@ pub const Parser = struct {
// TODO: Print the list of missing
if (!schemaEngine.checkIfAllMemberInMap(self.struct_name, &data_map)) self.printError("Error: Missing member", &token);
try self.data_engine.writeEntity(self.struct_name, data_map);
self.state = State.end;
const uuid = self.data_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;
},
.UPDATE => {}, // TODO:
else => unreachable,
@ -204,8 +220,9 @@ pub const Parser = struct {
// Maybe to a struct Communicator to handle all communication between use and cli
fn sendEntity(self: *Parser, uuid_array: []UUID) void {
_ = self;
_ = uuid_array;
std.debug.print("Number of uuid to send: {d}\n", .{uuid_array.len});
//send("Number of uuid to send: {d}\n", .{uuid_array.len});
}
// TODO: The parser that check what is between ||
@ -414,10 +431,10 @@ pub const Parser = struct {
fn parseAdditionalData(self: *Parser, additional_data: *AdditionalData) !void {
var token = self.toker.next();
var keep_next = false;
self.state = State.expect_count_of_entity_to_find;
self.state = .expect_count_of_entity_to_find;
while (self.state != State.end) : ({
token = if (!keep_next) self.toker.next() else token;
while (self.state != .end) : ({
token = if ((!keep_next) and (self.state != .end)) self.toker.next() else token;
keep_next = false;
}) {
switch (self.state) {
@ -441,7 +458,7 @@ pub const Parser = struct {
.expect_semicolon_OR_right_bracket => {
switch (token.tag) {
.semicolon => self.state = .expect_member,
.r_bracket => self.state = State.end,
.r_bracket => self.state = .end,
else => self.printError("Error: Expect ';' or ']'.", &token),
}
},
@ -464,10 +481,7 @@ pub const Parser = struct {
.expect_comma_OR_r_bracket_OR_l_bracket => {
switch (token.tag) {
.comma => self.state = .expect_member,
.r_bracket => {
self.state = State.end;
keep_next = true;
},
.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,
@ -480,10 +494,7 @@ pub const Parser = struct {
.expect_comma_OR_r_bracket => {
switch (token.tag) {
.comma => self.state = .expect_member,
.r_bracket => {
self.state = State.end;
keep_next = true;
},
.r_bracket => self.state = .end,
else => self.printError("Error: Expected , or ]", &token),
}
},
@ -499,9 +510,9 @@ pub const Parser = struct {
var token = self.toker.next();
var keep_next = false;
var member_name: []const u8 = undefined; // Maybe use allocator.alloc
self.state = State.expect_member;
self.state = .expect_member;
while (self.state != State.end) : ({
while (self.state != .end) : ({
token = if (!keep_next) self.toker.next() else token;
keep_next = false;
}) {
@ -511,7 +522,7 @@ pub const Parser = struct {
.identifier => {
member_name = self.toker.getTokenSlice(token);
if (!schemaEngine.isMemberNameInStruct(self.struct_name, member_name)) self.printError("Member not found in struct.", &token);
self.state = State.expect_equal;
self.state = .expect_equal;
},
else => self.printError("Error: Expected member name.", &token),
}
@ -519,7 +530,7 @@ pub const Parser = struct {
.expect_equal => {
switch (token.tag) {
// TODO: Add more comparison like IN or other stuff
.equal => self.state = State.expect_new_value,
.equal => self.state = .expect_new_value,
else => self.printError("Error: Expected =", &token),
}
},
@ -530,7 +541,7 @@ pub const Parser = struct {
switch (token.tag) {
.int_literal, .keyword_null => {
keep_next = true;
self.state = State.add_member_to_map;
self.state = .add_member_to_map;
},
else => self.printError("Error: Expected int", &token),
}
@ -539,7 +550,7 @@ pub const Parser = struct {
switch (token.tag) {
.float_literal, .keyword_null => {
keep_next = true;
self.state = State.add_member_to_map;
self.state = .add_member_to_map;
},
else => self.printError("Error: Expected float", &token),
}
@ -548,7 +559,7 @@ pub const Parser = struct {
switch (token.tag) {
.bool_literal_true, .bool_literal_false, .keyword_null => {
keep_next = true;
self.state = State.add_member_to_map;
self.state = .add_member_to_map;
},
else => self.printError("Error: Expected bool: true false", &token),
}
@ -557,7 +568,7 @@ pub const Parser = struct {
switch (token.tag) {
.string_literal, .keyword_null => {
keep_next = true;
self.state = State.add_member_to_map;
self.state = .add_member_to_map;
},
else => self.printError("Error: Expected string between ''", &token),
}
@ -568,7 +579,7 @@ pub const Parser = struct {
.l_bracket => {
const start_index = token.loc.start;
token = self.toker.next();
while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) {
while (token.tag != .r_bracket) : (token = self.toker.next()) {
switch (token.tag) {
.int_literal => continue,
else => self.printError("Error: Expected int or ].", &token),
@ -576,7 +587,7 @@ pub const Parser = struct {
}
// 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 = State.expect_comma_OR_end;
self.state = .expect_comma_OR_end;
},
else => self.printError("Error: Expected [ to start an array", &token),
}
@ -586,7 +597,7 @@ pub const Parser = struct {
.l_bracket => {
const start_index = token.loc.start;
token = self.toker.next();
while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) {
while (token.tag != .r_bracket) : (token = self.toker.next()) {
switch (token.tag) {
.float_literal => continue,
else => self.printError("Error: Expected float or ].", &token),
@ -594,7 +605,7 @@ pub const Parser = struct {
}
// 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 = State.expect_comma_OR_end;
self.state = .expect_comma_OR_end;
},
else => self.printError("Error: Expected [ to start an array", &token),
}
@ -604,7 +615,7 @@ pub const Parser = struct {
.l_bracket => {
const start_index = token.loc.start;
token = self.toker.next();
while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) {
while (token.tag != .r_bracket) : (token = self.toker.next()) {
switch (token.tag) {
.bool_literal_false, .bool_literal_true => continue,
else => self.printError("Error: Expected bool or ].", &token),
@ -612,7 +623,7 @@ pub const Parser = struct {
}
// 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 = State.expect_comma_OR_end;
self.state = .expect_comma_OR_end;
},
else => self.printError("Error: Expected [ to start an array", &token),
}
@ -622,7 +633,7 @@ pub const Parser = struct {
.l_bracket => {
const start_index = token.loc.start;
token = self.toker.next();
while (token.tag != Token.Tag.r_bracket) : (token = self.toker.next()) {
while (token.tag != .r_bracket) : (token = self.toker.next()) {
switch (token.tag) {
.string_literal => continue,
else => self.printError("Error: Expected str or ].", &token),
@ -630,7 +641,7 @@ pub const Parser = struct {
}
// 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 = State.expect_comma_OR_end;
self.state = .expect_comma_OR_end;
},
else => self.printError("Error: Expected [ to start an array", &token),
}
@ -639,13 +650,13 @@ pub const Parser = struct {
},
.add_member_to_map => {
member_map.put(member_name, self.toker.getTokenSlice(token)) catch @panic("Could not add member name and value to map in getMapOfMember");
self.state = State.expect_comma_OR_end;
self.state = .expect_comma_OR_end;
},
.add_array_to_map => {},
.expect_comma_OR_end => {
switch (token.tag) {
.r_paren => self.state = State.end,
.comma => self.state = State.expect_member,
.r_paren => self.state = .end,
.comma => self.state = .expect_member,
else => self.printError("Error: Expect , or )", &token),
}
},
@ -654,29 +665,28 @@ pub const Parser = struct {
}
}
// TODO: Stop panicking !
fn printError(self: *Parser, message: []const u8, token: *Token) void {
std.debug.print("\n", .{});
std.debug.print("{s}\n", .{self.toker.buffer});
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) {
std.debug.print(" ", .{});
stdout.print(" ", .{}) catch {};
}
// Print the '^' characters for the error span.
var i: usize = token.loc.start;
while (i < token.loc.end) : (i += 1) {
std.debug.print("^", .{});
stdout.print("^", .{}) catch {};
}
std.debug.print(" \n", .{}); // Align with the message
stdout.print(" \n", .{}) catch {}; // Align with the message
std.debug.print("{s}\n", .{message});
stdout.print("{s}\n", .{message}) catch {};
std.debug.print("{any}\n{any}\n", .{ token.tag, token.loc });
stdout.print("{any}\n{any}\n", .{ token.tag, token.loc }) catch {};
@panic("");
send("", .{});
}
};
@ -770,6 +780,38 @@ fn compareUUIDArray(arr1: std.ArrayList(UUID), arr2: std.ArrayList(UUID)) bool {
return true;
}
test "GRAB with additional data" {
try testParsing("GRAB User [1] {age < 18}");
try testParsing("GRAB User [name] {age < 18}");
try testParsing("GRAB User [100; name] {age < 18}");
}
test "GRAB filter with string" {
// TODO: Use a fixe dataset for testing, to choose in the build.zig
// It should check if the right number of entity is found too
try testParsing("GRAB User {name = 'Brittany Rogers'}");
try testParsing("GRAB User {name != 'Brittany Rogers'}");
}
test "GRAB filter with int" {
// TODO: Use a fixe dataset for testing, to choose in the build.zig
// It should check if the right number of entity is found too
try testParsing("GRAB User {age = 18}");
try testParsing("GRAB User {age > 18}");
try testParsing("GRAB User {age < 18}");
try testParsing("GRAB User {age <= 18}");
try testParsing("GRAB User {age >= 18}");
try testParsing("GRAB User {age != 18}");
}
fn testParsing(source: [:0]const u8) !void {
const allocator = std.testing.allocator;
var tokenizer = Tokenizer.init(source);
var parser = Parser.init(allocator, &tokenizer);
defer parser.deinit();
try parser.parse();
}
test "Parse condition" {
const condition1 = Condition{ .data_type = .int, .member_name = "age", .operation = .superior_or_equal, .struct_name = "User", .value = "26" };
try testConditionParsing("age >= 26", condition1);
@ -821,7 +863,6 @@ fn testNewDataParsing(source: [:0]const u8, expected_member_map: std.StringHashM
var parser = Parser.init(allocator, &tokenizer);
parser.struct_name = allocator.dupe(u8, "User") catch @panic("Cant alloc struct name");
defer parser.deinit();
defer allocator.free(parser.struct_name);
var data_map = std.StringHashMap([]const u8).init(allocator);
defer data_map.deinit();
@ -857,6 +898,8 @@ test "Parse filter" {
var tokenizer = Tokenizer.init("{name = 'Adrien'}");
var parser = Parser.init(allocator, &tokenizer);
parser.struct_name = allocator.dupe(u8, "User") catch @panic("Cant alloc struct name"); // Otherwise get an error trying to free this when deinit
defer parser.deinit();
_ = tokenizer.next(); // Start at name
@ -913,7 +956,6 @@ fn testAdditionalData(source: [:0]const u8, expected_AdditionalData: Parser.Addi
var parser = Parser.init(allocator, &tokenizer);
parser.struct_name = allocator.dupe(u8, "User") catch @panic("Cant alloc struct name");
defer parser.deinit();
defer allocator.free(parser.struct_name);
_ = tokenizer.next();
parser.parseAdditionalData(&parser.additional_data) catch |err| {