434 lines
15 KiB
Zig
434 lines
15 KiB
Zig
const std = @import("std");
|
|
const metadata = @import("metadata.zig");
|
|
const Allocator = std.mem.Allocator;
|
|
const Tokenizer = @import("ziqlTokenizer.zig").Tokenizer;
|
|
const Token = @import("ziqlTokenizer.zig").Token;
|
|
const DataEngine = @import("dataEngine.zig").DataEngine;
|
|
const UUID = @import("uuid.zig").UUID;
|
|
|
|
// To work now
|
|
// GRAB User {}
|
|
// GRAB User {name = 'Adrien'}
|
|
// GRAB User {name='Adrien' AND age < 30}
|
|
// GRAB User [1] {}
|
|
// GRAB User [10; name] {age < 30}
|
|
//
|
|
// For later
|
|
|
|
const stdout = std.io.getStdOut().writer();
|
|
|
|
pub const Parser = struct {
|
|
arena: std.heap.ArenaAllocator,
|
|
allocator: Allocator,
|
|
toker: *Tokenizer,
|
|
state: State,
|
|
|
|
additional_data: AdditionalData,
|
|
|
|
pub fn init(allocator: Allocator, toker: *Tokenizer) Parser {
|
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
|
return Parser{
|
|
.arena = arena,
|
|
.allocator = arena.allocator(),
|
|
.toker = toker,
|
|
.state = State.start,
|
|
.additional_data = AdditionalData.init(allocator),
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Parser) void {
|
|
self.additional_data.deinit();
|
|
self.arena.deinit();
|
|
}
|
|
|
|
// This is the [] part
|
|
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| {
|
|
std.debug.print("{d}\n", .{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 };
|
|
}
|
|
};
|
|
|
|
const State = enum {
|
|
start,
|
|
invalid,
|
|
end,
|
|
|
|
// For the main parse function
|
|
expect_filter,
|
|
|
|
// For the additional data parser
|
|
expect_count_of_entity_to_find,
|
|
expect_semicolon_OR_right_bracket,
|
|
expect_member,
|
|
next_member_OR_end_OR_new_additional_data,
|
|
next_member_OR_end,
|
|
|
|
// For the filter parser
|
|
expect_condition,
|
|
};
|
|
|
|
pub fn parse(self: *Parser) !void {
|
|
var data_engine = DataEngine.init(self.allocator, null);
|
|
defer data_engine.deinit();
|
|
|
|
var struct_name_token = self.toker.next();
|
|
if (!self.isStructInSchema(self.toker.getTokenSlice(struct_name_token))) {
|
|
try self.printError("Error: Struct name not in current shema.", &struct_name_token);
|
|
return;
|
|
}
|
|
|
|
var token = self.toker.next();
|
|
var keep_next = false;
|
|
|
|
while (self.state != State.end) : ({
|
|
token = if (!keep_next) self.toker.next() else token;
|
|
keep_next = false;
|
|
}) {
|
|
switch (self.state) {
|
|
.start => {
|
|
switch (token.tag) {
|
|
.l_bracket => {
|
|
try self.parseAdditionalData(&self.additional_data);
|
|
self.state = State.expect_filter;
|
|
},
|
|
.l_brace => {
|
|
self.state = State.expect_filter;
|
|
keep_next = true;
|
|
},
|
|
else => {
|
|
try self.printError("Error: Expected filter starting with {} or what to return starting with []", &token);
|
|
return;
|
|
},
|
|
}
|
|
},
|
|
.expect_filter => {
|
|
var array = std.ArrayList(UUID).init(self.allocator);
|
|
try self.parseFilter(&array, struct_name_token);
|
|
self.state = State.end;
|
|
},
|
|
else => return,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parseFilter(self: *Parser, left_array: *std.ArrayList(UUID), struct_name_token: Token) !void {
|
|
const right_array = std.ArrayList(UUID).init(self.allocator);
|
|
var token = self.toker.next();
|
|
var keep_next = false;
|
|
self.state = State.expect_member;
|
|
|
|
_ = right_array;
|
|
_ = left_array;
|
|
|
|
while (self.state != State.end) : ({
|
|
token = if (!keep_next) self.toker.next() else token;
|
|
keep_next = false;
|
|
}) {
|
|
switch (self.state) {
|
|
.expect_member => {
|
|
if (!self.isMemberPartOfStruct(self.toker.getTokenSlice(struct_name_token), self.toker.getTokenSlice(token))) {
|
|
try self.printError("Error: Member not part of struct.", &token);
|
|
}
|
|
self.state = State.expect_condition;
|
|
},
|
|
else => return,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// When this function is call, the tokenizer last token retrieved should be [.
|
|
/// Check if an int is here -> check if ; is here -> check if member is here -> check if [ is here -> loop
|
|
pub 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;
|
|
|
|
while (self.state != State.end) : ({
|
|
token = if (!keep_next) self.toker.next() else token;
|
|
keep_next = false;
|
|
}) {
|
|
switch (self.state) {
|
|
.expect_count_of_entity_to_find => {
|
|
switch (token.tag) {
|
|
.number_literal => {
|
|
const count = std.fmt.parseInt(usize, self.toker.getTokenSlice(token), 10) catch {
|
|
try self.printError("Error while transforming this into a integer.", &token);
|
|
self.state = .invalid;
|
|
continue;
|
|
};
|
|
additional_data.entity_count_to_find = count;
|
|
self.state = .expect_semicolon_OR_right_bracket;
|
|
},
|
|
else => {
|
|
self.state = .expect_member;
|
|
keep_next = true;
|
|
},
|
|
}
|
|
},
|
|
.expect_semicolon_OR_right_bracket => {
|
|
switch (token.tag) {
|
|
.semicolon => {
|
|
self.state = .expect_member;
|
|
},
|
|
.r_bracket => {
|
|
return;
|
|
},
|
|
else => {
|
|
try self.printError(
|
|
"Error: Expect ';' or ']'.",
|
|
&token,
|
|
);
|
|
self.state = .invalid;
|
|
},
|
|
}
|
|
},
|
|
.expect_member => {
|
|
switch (token.tag) {
|
|
.identifier => {
|
|
// TODO: Check if the member name exist
|
|
try additional_data.member_to_find.append(
|
|
AdditionalDataMember.init(
|
|
self.allocator,
|
|
self.toker.getTokenSlice(token),
|
|
),
|
|
);
|
|
|
|
self.state = .next_member_OR_end_OR_new_additional_data;
|
|
},
|
|
else => {
|
|
try self.printError(
|
|
"Error: A member name should be here.",
|
|
&token,
|
|
);
|
|
},
|
|
}
|
|
},
|
|
.next_member_OR_end_OR_new_additional_data => {
|
|
switch (token.tag) {
|
|
.comma => {
|
|
self.state = .expect_member;
|
|
},
|
|
.r_bracket => {
|
|
return;
|
|
},
|
|
.l_bracket => {
|
|
try self.parseAdditionalData(
|
|
&additional_data.member_to_find.items[additional_data.member_to_find.items.len - 1].additional_data,
|
|
);
|
|
self.state = .next_member_OR_end;
|
|
},
|
|
else => {
|
|
try self.printError(
|
|
"Error: Expected a comma ',' or the end or a new list of member to return.",
|
|
&token,
|
|
);
|
|
},
|
|
}
|
|
},
|
|
.next_member_OR_end => {
|
|
switch (token.tag) {
|
|
.comma => {
|
|
self.state = .expect_member;
|
|
},
|
|
.r_bracket => {
|
|
return;
|
|
},
|
|
else => {
|
|
try self.printError(
|
|
"Error: Expected a comma or the end of the list of member name to return.",
|
|
&token,
|
|
);
|
|
},
|
|
}
|
|
},
|
|
.invalid => {
|
|
@panic("=)");
|
|
},
|
|
else => {
|
|
try self.printError(
|
|
"Error: Unknow state.",
|
|
&token,
|
|
);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn printError(self: *Parser, message: []const u8, token: *Token) !void {
|
|
try stdout.print("\n", .{});
|
|
try stdout.print("{s}\n", .{self.toker.buffer});
|
|
|
|
// Calculate the number of spaces needed to reach the start position.
|
|
var spaces: usize = 0;
|
|
while (spaces < token.loc.start) : (spaces += 1) {
|
|
try stdout.print(" ", .{});
|
|
}
|
|
|
|
// Print the '^' characters for the error span.
|
|
var i: usize = token.loc.start;
|
|
while (i < token.loc.end) : (i += 1) {
|
|
try stdout.print("^", .{});
|
|
}
|
|
try stdout.print(" \n", .{}); // Align with the message
|
|
|
|
try stdout.print("{s}\n", .{message});
|
|
|
|
@panic("");
|
|
}
|
|
|
|
/// Take a struct name and a member name and return true if the member name is part of the struct
|
|
fn isMemberPartOfStruct(_: *Parser, struct_name: []const u8, member_name: []const u8) bool {
|
|
const all_struct_member = metadata.structName2structMembers(struct_name);
|
|
|
|
for (all_struct_member) |key| {
|
|
if (std.mem.eql(u8, key, member_name)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Check if a string is a name of a struct in the currently use engine
|
|
fn isStructInSchema(_: *Parser, struct_name_to_check: []const u8) bool {
|
|
for (metadata.struct_name_list) |struct_name| {
|
|
if (std.mem.eql(u8, struct_name_to_check, struct_name)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// TODO: Optimize. Maybe just do a new list and return it instead
|
|
fn OR(arr1: *std.ArrayList(UUID), arr2: *std.ArrayList(UUID)) std.ArrayList(UUID) {
|
|
defer arr1.deinit();
|
|
defer arr2.deinit();
|
|
|
|
var arr = try arr1.clone();
|
|
|
|
for (0..arr2.items.len) |i| {
|
|
if (!arr.contains(arr2[i])) {
|
|
arr.append(arr2[i]);
|
|
}
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
fn AND(arr1: *std.ArrayList(UUID), arr2: *std.ArrayList(UUID)) std.ArrayList(UUID) {
|
|
defer arr1.deinit();
|
|
defer arr2.deinit();
|
|
|
|
var arr = try arr1.clone();
|
|
|
|
for (0..arr1.items.len) |i| {
|
|
if (arr2.contains(arr1[i])) {
|
|
arr.append(arr1[i]);
|
|
}
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
test "Test AdditionalData" {
|
|
const allocator = std.testing.allocator;
|
|
|
|
var additional_data1 = Parser.AdditionalData.init(allocator);
|
|
additional_data1.entity_count_to_find = 1;
|
|
testAdditionalData("[1]", additional_data1);
|
|
|
|
var additional_data2 = Parser.AdditionalData.init(allocator);
|
|
defer additional_data2.deinit();
|
|
try additional_data2.member_to_find.append(
|
|
Parser.AdditionalDataMember.init(
|
|
allocator,
|
|
"name",
|
|
),
|
|
);
|
|
testAdditionalData("[name]", additional_data2);
|
|
|
|
var additional_data3 = Parser.AdditionalData.init(allocator);
|
|
additional_data3.entity_count_to_find = 1;
|
|
defer additional_data3.deinit();
|
|
try additional_data3.member_to_find.append(
|
|
Parser.AdditionalDataMember.init(
|
|
allocator,
|
|
"name",
|
|
),
|
|
);
|
|
testAdditionalData("[1; name]", additional_data3);
|
|
|
|
var additional_data4 = Parser.AdditionalData.init(allocator);
|
|
additional_data4.entity_count_to_find = 100;
|
|
defer additional_data4.deinit();
|
|
try additional_data4.member_to_find.append(
|
|
Parser.AdditionalDataMember.init(
|
|
allocator,
|
|
"friend",
|
|
),
|
|
);
|
|
testAdditionalData("[100; friend [name]]", additional_data4);
|
|
}
|
|
|
|
fn testAdditionalData(source: [:0]const u8, expected_AdditionalData: Parser.AdditionalData) void {
|
|
const allocator = std.testing.allocator;
|
|
var tokenizer = Tokenizer.init(source);
|
|
var data_engine = DataEngine.init(allocator);
|
|
defer data_engine.deinit();
|
|
|
|
var parser = Parser.init(allocator, &tokenizer, &data_engine);
|
|
|
|
defer parser.deinit();
|
|
_ = tokenizer.next();
|
|
parser.parse_additional_data(&parser.additional_data) catch |err| {
|
|
std.debug.print("Error parsing additional data: {any}\n", .{err});
|
|
};
|
|
|
|
compareAdditionalData(expected_AdditionalData, parser.additional_data);
|
|
}
|
|
|
|
// TODO: Check AdditionalData inside AdditionalData
|
|
fn compareAdditionalData(ad1: Parser.AdditionalData, ad2: Parser.AdditionalData) void {
|
|
std.testing.expectEqual(ad1.entity_count_to_find, ad2.entity_count_to_find) catch {
|
|
std.debug.print("Additional data entity_count_to_find are not equal.\n", .{});
|
|
};
|
|
|
|
var founded = false;
|
|
|
|
for (ad1.member_to_find.items) |elem1| {
|
|
founded = false;
|
|
for (ad2.member_to_find.items) |elem2| {
|
|
if (std.mem.eql(u8, elem1.name, elem2.name)) {
|
|
compareAdditionalData(elem1.additional_data, elem2.additional_data);
|
|
founded = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!founded) @panic("Member not found");
|
|
}
|
|
}
|