first commit

This commit is contained in:
2024-09-02 11:02:33 +02:00
commit e3f0ff1d84
10 changed files with 934 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
const std = @import("std");
const UUID = @import("uuid.zig").UUID;
pub const Types = union {
user: *User,
message: *Message,
};
pub const User = struct {
id: UUID,
name: []const u8,
email: []const u8,
messages: std.ArrayList(*Message),
pub fn new(allocator: std.mem.Allocator, name: []const u8, email: []const u8) !*User {
const user = try allocator.create(User);
user.* = .{
.id = UUID.init(),
.name = name,
.email = email,
.messages = std.ArrayList(*Message).init(allocator),
};
return user;
}
};
pub const Message = struct {
id: UUID,
content: []const u8,
user: *User,
pub fn new(allocator: std.mem.Allocator, content: []const u8, user: *User) !*Message {
const message = try allocator.create(Message);
message.* = .{
.id = UUID.init(),
.content = content,
.user = user,
};
try user.*.messages.append(message);
return message;
}
};
+90
View File
@@ -0,0 +1,90 @@
const std = @import("std");
const UUID = @import("uuid.zig").UUID;
const dtypes = @import("dtypes.zig");
const Tokenizer = @import("tokenizer.zig").Tokenizer;
const Token = @import("tokenizer.zig").Token;
const Allocator = std.mem.Allocator;
const print = std.debug.print;
pub const Error = error{UUIDNotFound};
const Commands = enum { run, describe, help, unknow, @"run describe help unknow" };
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// Init the map storage string map that track all array of struct
var storage = std.StringHashMap(*std.ArrayList(dtypes.Types)).init(allocator);
defer storage.deinit();
// Create all array and put them in the main map
var userArray = std.ArrayList(dtypes.Types).init(allocator);
try storage.put("User", &userArray);
var postArray = std.ArrayList(dtypes.Types).init(allocator);
try storage.put("Post", &postArray);
var commentArray = std.ArrayList(dtypes.Types).init(allocator);
try storage.put("Comment", &commentArray);
// Add a new user
const newUser = try dtypes.User.new(allocator, "Adrien", "adrien@gmail.com");
try storage.get("User").?.append(dtypes.Types{ .user = newUser });
std.debug.print("{s}\n", .{storage.get("User").?.items[0].user.email});
// Lets get arguments and what the user want to do
var argsIterator = try std.process.ArgIterator.initWithAllocator(allocator);
defer argsIterator.deinit();
// Skip executable
_ = argsIterator.next();
if (argsIterator.next()) |commandStr| {
const command = std.meta.stringToEnum(Commands, commandStr) orelse Commands.unknow;
switch (command) {
.run => {
const query = argsIterator.next();
var tokenizer = Tokenizer.init(query.?);
var token = tokenizer.next();
while (token.tag != Token.Tag.eof) {
std.debug.print("{any}\n", .{token});
token = tokenizer.next();
}
},
.help => {
std.debug.print("Welcome to ZipponDB!.", .{});
},
.describe => {
std.debug.print("Here the current schema:\nUser (\n\tname: str,\n\temail:str,\n\tfriend:User\n)\n", .{});
},
.unknow => {
std.debug.print("Unknow command, available are: run, describe, help.\n", .{});
},
else => {},
}
} else {
std.debug.print("No args found. Available are: run, help.\n", .{});
}
}
fn getById(array: anytype, id: UUID) !*dtypes.User {
for (array.items) |data| {
if (data.id.compare(id)) {
return data;
}
}
return error.UUIDNotFound;
}
test "getById" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var users = std.ArrayList(*dtypes.User).init(allocator);
try users.append(try dtypes.User.new(allocator, "Adrien", "adrien@gmail.com"));
const adrien = try getById(users, users.items[0].id);
try std.testing.expect(UUID.compare(users.items[0].id, adrien.id));
}
+383
View File
@@ -0,0 +1,383 @@
// From https://github.com/ziglang/zig/blob/master/lib/std/zig/tokenizer.zig
const std = @import("std");
pub const Token = struct {
tag: Tag,
loc: Loc,
pub const Loc = struct {
start: usize,
end: usize,
};
pub const keywords = std.StaticStringMap(Tag).initComptime(.{
.{ "GRAB", .keyword_grab },
.{ "UPDATE", .keyword_update },
.{ "DELETE", .keyword_delete },
.{ "ADD", .keyword_add },
.{ "IN", .keyword_in },
});
pub fn getKeyword(bytes: []const u8) ?Tag {
return keywords.get(bytes);
}
pub const Tag = enum {
eof,
invalid,
keyword_grab,
keyword_update,
keyword_delete,
keyword_add,
keyword_in,
string_literal,
number_literal,
identifier,
equal,
bang,
pipe,
l_paren,
r_paren,
l_bracket,
r_bracket,
l_brace,
r_brace,
semicolon,
comma,
angle_bracket_left,
angle_bracket_right,
angle_bracket_left_equal,
angle_bracket_right_equal,
equal_angle_bracket_right,
period,
bang_equal,
};
pub fn lexeme(tag: Tag) ?[]const u8 {
return switch (tag) {
.invalid,
.identifier,
.string_literal,
.number_literal,
=> null,
.bang => "!",
.pipe => "|",
};
}
};
pub const Tokenizer = struct {
buffer: [:0]const u8,
index: usize,
/// For debugging purposes.
pub fn dump(self: *Tokenizer, token: *const Token) void {
std.debug.print("{s} \"{s}\"\n", .{ @tagName(token.tag), self.buffer[token.loc.start..token.loc.end] });
}
pub fn init(buffer: [:0]const u8) Tokenizer {
// Skip the UTF-8 BOM if present.
return .{
.buffer = buffer,
.index = if (std.mem.startsWith(u8, buffer, "\xEF\xBB\xBF")) 3 else 0,
};
}
const State = enum {
start,
invalid,
string_literal,
identifier,
equal,
bang,
angle_bracket_left,
angle_bracket_right,
string_literal_backslash,
int_exponent,
int_period,
float,
float_exponent,
int,
};
pub fn next(self: *Tokenizer) Token {
var state: State = .start;
var result: Token = .{
.tag = undefined,
.loc = .{
.start = self.index,
.end = undefined,
},
};
while (true) : (self.index += 1) {
const c = self.buffer[self.index];
switch (state) {
.start => switch (c) {
0 => {
if (self.index == self.buffer.len) return .{
.tag = .eof,
.loc = .{
.start = self.index,
.end = self.index,
},
};
state = .invalid;
},
' ', '\n', '\t', '\r' => {
result.loc.start = self.index + 1;
},
'\'' => {
state = .string_literal;
result.tag = .string_literal;
},
'a'...'z', 'A'...'Z', '_' => {
state = .identifier;
result.tag = .identifier;
},
'=' => {
state = .equal;
},
'!' => {
state = .bang;
},
'|' => {
result.tag = .pipe;
self.index += 1;
break;
},
'(' => {
result.tag = .l_paren;
self.index += 1;
break;
},
')' => {
result.tag = .r_paren;
self.index += 1;
break;
},
'[' => {
result.tag = .l_bracket;
self.index += 1;
break;
},
']' => {
result.tag = .r_bracket;
self.index += 1;
break;
},
';' => {
result.tag = .semicolon;
self.index += 1;
break;
},
',' => {
result.tag = .comma;
self.index += 1;
break;
},
'<' => {
state = .angle_bracket_left;
},
'>' => {
state = .angle_bracket_right;
},
'{' => {
result.tag = .l_brace;
self.index += 1;
break;
},
'}' => {
result.tag = .r_brace;
self.index += 1;
break;
},
'.' => {
result.tag = .period;
self.index += 1;
break;
},
'0'...'9' => {
state = .int;
result.tag = .number_literal;
},
else => {
state = .invalid;
},
},
.invalid => {
// TODO make a better invalid handler
@panic("Unknow char!!!");
},
.identifier => switch (c) {
'a'...'z', 'A'...'Z', '_', '0'...'9' => continue,
else => {
if (Token.getKeyword(self.buffer[result.loc.start..self.index])) |tag| {
result.tag = tag;
}
break;
},
},
.string_literal => switch (c) {
0 => {
if (self.index != self.buffer.len) {
state = .invalid;
continue;
}
result.tag = .invalid;
break;
},
'\n' => {
result.tag = .invalid;
break;
},
'\\' => {
state = .string_literal_backslash;
},
'\'' => {
self.index += 1;
break;
},
0x01...0x09, 0x0b...0x1f, 0x7f => {
state = .invalid;
},
else => continue,
},
.string_literal_backslash => switch (c) {
0, '\n' => {
result.tag = .invalid;
break;
},
else => {
state = .string_literal;
},
},
.bang => switch (c) {
'=' => {
result.tag = .bang_equal;
self.index += 1;
break;
},
//TODO Add the !IN
else => {
result.tag = .bang;
break;
},
},
.equal => switch (c) {
'>' => {
result.tag = .equal_angle_bracket_right;
self.index += 1;
break;
},
else => {
result.tag = .equal;
break;
},
},
.angle_bracket_left => switch (c) {
'=' => {
result.tag = .angle_bracket_left_equal;
self.index += 1;
break;
},
else => {
result.tag = .angle_bracket_left;
break;
},
},
.angle_bracket_right => switch (c) {
'=' => {
result.tag = .angle_bracket_right_equal;
self.index += 1;
break;
},
else => {
result.tag = .angle_bracket_right;
break;
},
},
.int => switch (c) {
'.' => state = .int_period,
'_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => continue,
'e', 'E', 'p', 'P' => state = .int_exponent,
else => break,
},
.int_exponent => switch (c) {
'-', '+' => {
state = .float;
},
else => {
self.index -= 1;
state = .int;
},
},
.int_period => switch (c) {
'_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => {
state = .float;
},
'e', 'E', 'p', 'P' => state = .float_exponent,
else => {
self.index -= 1;
break;
},
},
.float => switch (c) {
'_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => continue,
'e', 'E', 'p', 'P' => state = .float_exponent,
else => break,
},
.float_exponent => switch (c) {
'-', '+' => state = .float,
else => {
self.index -= 1;
state = .float;
},
},
}
}
result.loc.end = self.index;
return result;
}
};
test "keywords" {
try testTokenize("GRAB UPDATE ADD DELETE IN", &.{ .keyword_grab, .keyword_update, .keyword_add, .keyword_delete, .keyword_in });
std.debug.print("Keywords OK\n", .{});
}
test "basic query" {
try testTokenize("GRAB User {}", &.{ .keyword_grab, .identifier, .l_brace, .r_brace });
try testTokenize("GRAB User { name = 'Adrien'}", &.{ .keyword_grab, .identifier, .l_brace, .identifier, .equal, .string_literal, .r_brace });
try testTokenize("GRAB User [1; name] {}", &.{ .keyword_grab, .identifier, .l_bracket, .number_literal, .semicolon, .identifier, .r_bracket, .l_brace, .r_brace });
try testTokenize("GRAB User{}|ASCENDING name|", &.{ .keyword_grab, .identifier, .l_brace, .r_brace, .pipe, .identifier, .identifier, .pipe });
try testTokenize("DELETE User[1]{name='Adrien'}|ASCENDING name, age|", &.{ .keyword_delete, .identifier, .l_bracket, .number_literal, .r_bracket, .l_brace, .identifier, .equal, .string_literal, .r_brace, .pipe, .identifier, .identifier, .comma, .identifier, .pipe });
std.debug.print("Basic query OK\n", .{});
}
fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !void {
var tokenizer = Tokenizer.init(source);
for (expected_token_tags) |expected_token_tag| {
const token = tokenizer.next();
try std.testing.expectEqual(expected_token_tag, token.tag);
}
// Last token should always be eof, even when the last token was invalid,
// in which case the tokenizer is in an invalid state, which can only be
// recovered by opinionated means outside the scope of this implementation.
const last_token = tokenizer.next();
try std.testing.expectEqual(Token.Tag.eof, last_token.tag);
try std.testing.expectEqual(source.len, last_token.loc.start);
try std.testing.expectEqual(source.len, last_token.loc.end);
}
+180
View File
@@ -0,0 +1,180 @@
// Fast allocation-free v4 UUIDs.
// Inspired by the Go implementation at github.com/skeeto/uuid
const std = @import("std");
const crypto = std.crypto;
const fmt = std.fmt;
const testing = std.testing;
pub const Error = error{InvalidUUID};
pub const UUID = struct {
bytes: [16]u8,
pub fn init() UUID {
var uuid = UUID{ .bytes = undefined };
crypto.random.bytes(&uuid.bytes);
// Version 4
uuid.bytes[6] = (uuid.bytes[6] & 0x0f) | 0x40;
// Variant 1
uuid.bytes[8] = (uuid.bytes[8] & 0x3f) | 0x80;
return uuid;
}
pub fn compare(self: UUID, other: UUID) bool {
return std.meta.eql(self.bytes, other.bytes);
}
fn to_string(self: UUID, slice: []u8) void {
var string: [36]u8 = format_uuid(self);
std.mem.copyForwards(u8, slice, &string);
}
fn format_uuid(self: UUID) [36]u8 {
var buf: [36]u8 = undefined;
buf[8] = '-';
buf[13] = '-';
buf[18] = '-';
buf[23] = '-';
inline for (encoded_pos, 0..) |i, j| {
buf[i + 0] = hex[self.bytes[j] >> 4];
buf[i + 1] = hex[self.bytes[j] & 0x0f];
}
return buf;
}
// Indices in the UUID string representation for each byte.
const encoded_pos = [16]u8{ 0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34 };
// Hex
const hex = "0123456789abcdef";
// Hex to nibble mapping.
const hex_to_nibble = [256]u8{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};
pub fn format(
self: UUID,
comptime layout: []const u8,
options: fmt.FormatOptions,
writer: anytype,
) !void {
_ = options; // currently unused
if (layout.len != 0 and layout[0] != 's')
@compileError("Unsupported format specifier for UUID type: '" ++ layout ++ "'.");
const buf = format_uuid(self);
try fmt.format(writer, "{s}", .{buf});
}
pub fn parse(buf: []const u8) Error!UUID {
var uuid = UUID{ .bytes = undefined };
if (buf.len != 36 or buf[8] != '-' or buf[13] != '-' or buf[18] != '-' or buf[23] != '-')
return Error.InvalidUUID;
inline for (encoded_pos, 0..) |i, j| {
const hi = hex_to_nibble[buf[i + 0]];
const lo = hex_to_nibble[buf[i + 1]];
if (hi == 0xff or lo == 0xff) {
return Error.InvalidUUID;
}
uuid.bytes[j] = hi << 4 | lo;
}
return uuid;
}
};
// Zero UUID
pub const zero: UUID = .{ .bytes = .{0} ** 16 };
// Convenience function to return a new v4 UUID.
pub fn newV4() UUID {
return UUID.init();
}
test "parse and format" {
const uuids = [_][]const u8{
"d0cd8041-0504-40cb-ac8e-d05960d205ec",
"3df6f0e4-f9b1-4e34-ad70-33206069b995",
"f982cf56-c4ab-4229-b23c-d17377d000be",
"6b9f53be-cf46-40e8-8627-6b60dc33def8",
"c282ec76-ac18-4d4a-8a29-3b94f5c74813",
"00000000-0000-0000-0000-000000000000",
};
for (uuids) |uuid| {
try testing.expectFmt(uuid, "{}", .{try UUID.parse(uuid)});
}
}
test "invalid UUID" {
const uuids = [_][]const u8{
"3df6f0e4-f9b1-4e34-ad70-33206069b99", // too short
"3df6f0e4-f9b1-4e34-ad70-33206069b9912", // too long
"3df6f0e4-f9b1-4e34-ad70_33206069b9912", // missing or invalid group separator
"zdf6f0e4-f9b1-4e34-ad70-33206069b995", // invalid character
};
for (uuids) |uuid| {
try testing.expectError(Error.InvalidUUID, UUID.parse(uuid));
}
}
test "check to_string works" {
const uuid1 = UUID.init();
var string1: [36]u8 = undefined;
var string2: [36]u8 = undefined;
uuid1.to_string(&string1);
uuid1.to_string(&string2);
std.debug.print("\nUUID {s} \n", .{uuid1});
std.debug.print("\nFirst call to_string {s} \n", .{string1});
std.debug.print("Second call to_string {s} \n", .{string2});
try testing.expectEqual(string1, string2);
}
test "compare" {
const uuid1 = UUID.init();
const uuid2 = UUID.init();
try testing.expect(uuid1.compare(uuid1));
try testing.expect(!uuid1.compare(uuid2));
}