first commit
This commit is contained in:
commit
e3f0ff1d84
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.zig-cache
|
95
README.md
Normal file
95
README.md
Normal file
@ -0,0 +1,95 @@
|
||||
# ZipponDB
|
||||
|
||||
Note: Make a stupide mascotte
|
||||
|
||||
# Written in Zig
|
||||
|
||||
Zig is fast, blablabla
|
||||
|
||||
# How it's work
|
||||
|
||||
Meme "That's the neat part..."
|
||||
|
||||
Zippon is a strutural relational potentially in memory written entirely in Zig from stractch.
|
||||
|
||||
You build a binary according to your schema, you can just run it with some arguments and it will create and manage a folder 'zipponDB_DATA'.
|
||||
Then you do what you want with it, including:
|
||||
- Run it with your app as a file and folder
|
||||
- Create a Docker and open some port
|
||||
- Create a Docker with a small API
|
||||
- Other stuffs, Im sure some will find something nice
|
||||
|
||||
Note that you can have multiple binary that run together. Each binary have a unique id that is use to segregate binary inside the folder 'zipponDB_DATA'
|
||||
|
||||
# Benchmark
|
||||
|
||||
# Create a schema
|
||||
|
||||
Zippon use struct as way of saving data. A struct is a way of storing multiple data of different type.
|
||||
Very similar to a row in a table, columns being datatype and a row a single struct.
|
||||
|
||||
The schema is directly INSIDE the binary, so each binary are per schema ! This is for effenciency, idk to be honest, I guess ? lol
|
||||
|
||||
# Migration
|
||||
|
||||
For now you can't migrate the data of one binary to another, so you will need to different binary.
|
||||
|
||||
# Zippon language
|
||||
|
||||
Ok so I went crazy on that, on have it how language. It is stupide and I love it. I wanted to do like EdgeDB but no, too simple.
|
||||
Anyway, I tried to do something different, to do something different, idk, you're the jduge of it.
|
||||
|
||||
```
|
||||
GRAB User { name = 'Adrien' }
|
||||
Get all user named Adrien
|
||||
|
||||
GRAB User [1; email] { }
|
||||
Get one email
|
||||
|
||||
GRAB User {} | ASCENDING name |
|
||||
Get all users ordered by name
|
||||
|
||||
GRAB User [name] { age > 10 AND name != 'Adrien' } | DECENDING age |
|
||||
Get just the name of all users that are 10 years old or more and not named Adrien ordered by age
|
||||
|
||||
GRAB User { bestfriend = { name = 'Adrien' } }
|
||||
GRAB User { bestfriend = User{ name = 'Adrien' } } // Same
|
||||
Get all user that have a best friend named Adrien
|
||||
|
||||
GRAB User [10] { IN User [1] { age > 10 } | ASC name |.friends }
|
||||
Get 10 users that are friend with the first user older than 10 years old in ascending name order
|
||||
|
||||
GRAB Message [100; comments [ date ] ] { .writter = { name = 'Adrien' }.bestfriend }
|
||||
Get the date of 100 comments from the best friend of the writter named Adrien
|
||||
|
||||
GRAB User { IN Message { date > '12-01-2014' }.writter }
|
||||
Get all users that sended a message after the 12 january 2014
|
||||
|
||||
GRAB User { !IN Comment { }.writter }
|
||||
Get all user that didn't wrote a comment
|
||||
|
||||
GRAB User { IN User { name = 'Adrien' }.friends }
|
||||
Get all user that are friends with an Adrien
|
||||
|
||||
UPDATE User [1] { name = 'Adrien' } => ( email = 'new@email.com' )
|
||||
|
||||
REMOVE User { id = '000-000' }
|
||||
|
||||
ADD User ( name = 'Adrien', email = 'email', age = 40 }
|
||||
```
|
||||
|
||||
- {} Are filters
|
||||
- [] Are how much; what data
|
||||
- () Are new or updated data (Not already savec)
|
||||
- || Are additional options
|
||||
- Data are in struct format and can have link
|
||||
|
||||
|
||||
# How it's really work
|
||||
|
||||
NOTE: Do this in a separe file
|
||||
|
||||
## Tokenizer
|
||||
|
||||
The tokenizer of the language is
|
||||
# ZipponDB
|
5
TODO.md
Normal file
5
TODO.md
Normal file
@ -0,0 +1,5 @@
|
||||
# UUID
|
||||
[ ] Create new random UUID
|
||||
[ ] Find one UUID in an array
|
||||
|
||||
Possible way to look for data
|
66
build.zig
Normal file
66
build.zig
Normal file
@ -0,0 +1,66 @@
|
||||
const std = @import("std");
|
||||
|
||||
// Although this function looks imperative, note that its job is to
|
||||
// declaratively construct a build graph that will be executed by an external
|
||||
// runner.
|
||||
pub fn build(b: *std.Build) void {
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard optimization options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "zippon2",
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// This declares intent for the executable to be installed into the
|
||||
// standard location when the user invokes the "install" step (the default
|
||||
// step when running `zig build`).
|
||||
b.installArtifact(exe);
|
||||
|
||||
// This *creates* a Run step in the build graph, to be executed when another
|
||||
// step is evaluated that depends on it. The next line below will establish
|
||||
// such a dependency.
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
||||
// By making the run step depend on the install step, it will be run from the
|
||||
// installation directory rather than directly from within the cache directory.
|
||||
// This is not necessary, however, if the application depends on other installed
|
||||
// files, this ensures they will be present and in the expected location.
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
// This allows the user to pass arguments to the application in the build
|
||||
// command itself, like this: `zig build run -- arg1 arg2 etc`
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||
// and can be selected like this: `zig build run`
|
||||
// This will evaluate the `run` step rather than the default, which is "install".
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
const exe_unit_tests = b.addTest(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||
|
||||
// Similar to creating the run step earlier, this exposes a `test` step to
|
||||
// the `zig build --help` menu, providing a way for the user to request
|
||||
// running the unit tests.
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_exe_unit_tests.step);
|
||||
}
|
72
build.zig.zon
Normal file
72
build.zig.zon
Normal file
@ -0,0 +1,72 @@
|
||||
.{
|
||||
// This is the default name used by packages depending on this one. For
|
||||
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||
// as the key in the `dependencies` table. Although the user can choose a
|
||||
// different name, most users will stick with this provided value.
|
||||
//
|
||||
// It is redundant to include "zig" in this name because it is already
|
||||
// within the Zig package namespace.
|
||||
.name = "zippon2",
|
||||
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.0.0",
|
||||
|
||||
// This field is optional.
|
||||
// This is currently advisory only; Zig does not yet do anything
|
||||
// with this value.
|
||||
//.minimum_zig_version = "0.11.0",
|
||||
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
//.example = .{
|
||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// // the new URL.
|
||||
// .url = "https://example.com/foo.tar.gz",
|
||||
//
|
||||
// // This is computed from the file contents of the directory of files that is
|
||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||
// // `paths`.
|
||||
// //
|
||||
// // This field is the source of truth; packages do not come from a `url`; they
|
||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// // obtain a package matching this `hash`.
|
||||
// //
|
||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
// .hash = "...",
|
||||
//
|
||||
// // When this is provided, the package is found in a directory relative to the
|
||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||
// // computed. This field and `url` are mutually exclusive.
|
||||
// .path = "foo",
|
||||
|
||||
// // When this is set to `true`, a package is declared to be lazily
|
||||
// // fetched. This makes the dependency only get fetched if it is
|
||||
// // actually used.
|
||||
// .lazy = false,
|
||||
//},
|
||||
},
|
||||
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
// For example...
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
42
src/dtypes.zig
Normal file
42
src/dtypes.zig
Normal 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
src/main.zig
Normal file
90
src/main.zig
Normal 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
src/tokenizer.zig
Normal file
383
src/tokenizer.zig
Normal 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
src/uuid.zig
Normal file
180
src/uuid.zig
Normal 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));
|
||||
}
|
BIN
zig-out/bin/zippon2
Executable file
BIN
zig-out/bin/zippon2
Executable file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user