Compare commits
40 Commits
6c9a786926
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dc839e098c | |||
| 380145a986 | |||
| 0cce9d9bce | |||
| 572f4be896 | |||
| 8d45a93e6e | |||
| 09266e678f | |||
| cbf0d6a9da | |||
| 712e214f61 | |||
| fe4a585b6b | |||
| 9a4932e629 | |||
| 6315589fa1 | |||
| 1f07cc38ba | |||
| e09a00a4ba | |||
| 738ba5bd37 | |||
| 66d49ea8d5 | |||
| f02ece22fa | |||
| 2e97aef842 | |||
| 9e7955495f | |||
| ef15201f21 | |||
| 85f4957661 | |||
| 2756957f9b | |||
| 4f2aad8065 | |||
| f2b7817cac | |||
| 0a3d82a562 | |||
| 6363bc3bd1 | |||
| 868550703f | |||
| eb3c3814ec | |||
| fa42b20136 | |||
| 0b23707041 | |||
| b0deb958d2 | |||
| d03910f6f0 | |||
| a6f67d3f0d | |||
| 9fb8ec9454 | |||
| 99f8ae059c | |||
| 1e8e14ed67 | |||
| 11e948d500 | |||
| 31651dc96a | |||
| 34f0b1fb89 | |||
| 7963813034 | |||
| 49cf6e4237 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,2 +1 @@
|
|||||||
*.dll filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.pdf filter=lfs diff=lfs merge=lfs -text
|
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.zig-cache
|
.zig-cache
|
||||||
zig-out
|
zig-out
|
||||||
|
zig-pkg
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"files.exclude":{
|
"files.exclude": {
|
||||||
"**/.zig-cache": true,
|
"**/.zig-cache": true,
|
||||||
},
|
},
|
||||||
|
"files.associations": {
|
||||||
|
"**/*.{c,h}": "c",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
88
README.md
Normal file
88
README.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Building my own castle
|
||||||
|
|
||||||
|
I'm building my own castle in Zig ⚡.
|
||||||
|
|
||||||
|
This is a collection of experimental, recreational or actually useful libraries,
|
||||||
|
which I may or may not use as a part of some other projects. Most of them are
|
||||||
|
work in progress and might remain so indefinitely.
|
||||||
|
|
||||||
|
## cjit
|
||||||
|
|
||||||
|
JIT compiler for the C language inspired by Fabrice Bellard's Tiny C Compiler
|
||||||
|
(TCC). Meant to compile x86_64 and aarch64 for Windows and Linux straight to
|
||||||
|
virtual memory, without the ability to output an executable file or library. C
|
||||||
|
standard library is not fully supported, only some includes and builtins (see
|
||||||
|
[src/includes](packages/cjit/src/includes)).
|
||||||
|
|
||||||
|
It's in very early stage and cannot be used as of now. Currently, parts of the
|
||||||
|
tokenizer, x86_64 emit code and relocation logic are implemented. Aarch64 would
|
||||||
|
come after x86_64 is fully implemented and actually works.
|
||||||
|
|
||||||
|
You can see an example of how this library is supposed to be used in
|
||||||
|
[test/root.zig](packages/cjit/test/root.zig).
|
||||||
|
|
||||||
|
## js
|
||||||
|
|
||||||
|
Zig bindings for Fabrice Bellard's QuickJS. Original C sources are included and
|
||||||
|
compiled with Zig's build system. The bindings are not complete, but usable.
|
||||||
|
|
||||||
|
## media
|
||||||
|
|
||||||
|
Set of utilities for decoding and encoding media files. As of now, no purely Zig
|
||||||
|
decoder/encoder exists, which would be the goal. There is fairly comprehensive
|
||||||
|
PNG chunk decoder, though.
|
||||||
|
|
||||||
|
Bindings for stb_image.h from Sean T. Barrett's single-file C libraries are
|
||||||
|
included. They can be used to decode PNG, JPEG and HDR images and support Zig's
|
||||||
|
allocator interface in a thread-safe manner.
|
||||||
|
|
||||||
|
This package is actually used by my other project, codenamed
|
||||||
|
[voxel-game](/:root/renati/voxel-game).
|
||||||
|
|
||||||
|
## myid
|
||||||
|
|
||||||
|
Remnants of a project meant to provide a simpler alternative to OpenID Connect
|
||||||
|
(OIDC). It contains an HTTP server, which has since been improved and resides
|
||||||
|
in *web* package.
|
||||||
|
|
||||||
|
The ideas behind *myid* were developed further in another project I created
|
||||||
|
using TypeScript and Bun as a runtime. I might backport them back to Zig when I
|
||||||
|
find the time and motivation, which would be implemented on top of the HTTP
|
||||||
|
server from the *web* package.
|
||||||
|
|
||||||
|
## vecmath
|
||||||
|
|
||||||
|
Vector math library with support for vectors, matrices and ×8 SIMD operations
|
||||||
|
(utilizing SOA layout).
|
||||||
|
|
||||||
|
This package is actually used by my other project, codenamed
|
||||||
|
[voxel-game](/:root/renati/voxel-game) and is probably the most complete package
|
||||||
|
in this repository.
|
||||||
|
|
||||||
|
## web
|
||||||
|
|
||||||
|
An HTTP server library and collection of other utilities commonly associated
|
||||||
|
with web technologies.
|
||||||
|
|
||||||
|
The HTTP server implements HTTP version 1.1 using worker threads and supports
|
||||||
|
SSL via OpenSSL library. The server is very low level and does not provide any
|
||||||
|
logic to handle proper HTTP semantics. It is up to the user of the HTTP server
|
||||||
|
library to respect (or not) all HTTP methods and headers. The server is designed
|
||||||
|
or Linux only and uses Linux syscalls directly in its implementation.
|
||||||
|
|
||||||
|
The other utilities include:
|
||||||
|
|
||||||
|
- HTTP/1.1 request parser, which is part of the HTTP server, but can be used
|
||||||
|
independently,
|
||||||
|
- OpenSSL bindings, which are used by the HTTP server, but can be used
|
||||||
|
independently (full mechanical C translation is provided and partial manual
|
||||||
|
Zig translation),
|
||||||
|
- UUID type and generators for v4, v5 and v7,
|
||||||
|
- Generic ID type, a wrapper around UUID (or anything that is 16 bytes long)
|
||||||
|
that can be instantiated for each distinct use of ID with a tag to help avoid
|
||||||
|
type confusion at compile time.
|
||||||
|
|
||||||
|
## x11
|
||||||
|
|
||||||
|
Zig bindings for Xlib (aka libX11), a C library implementing a client of the X
|
||||||
|
Window System protocol. The bindings are not complete.
|
||||||
44
packages/cjit/build.zig
Normal file
44
packages/cjit/build.zig
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{
|
||||||
|
.whitelist = &.{
|
||||||
|
.{
|
||||||
|
.cpu_arch = .x86_64,
|
||||||
|
.os_tag = .windows,
|
||||||
|
.abi = .gnu,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.cpu_arch = .x86_64,
|
||||||
|
.os_tag = .linux,
|
||||||
|
.abi = .gnu,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const module = b.addModule("cjit", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const module_internal_test = b.addTest(.{ .root_module = module });
|
||||||
|
const run_internal_test = b.addRunArtifact(module_internal_test);
|
||||||
|
|
||||||
|
const module_external_test = b.addTest(.{
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.target = b.resolveTargetQuery(.{}),
|
||||||
|
.optimize = .Debug,
|
||||||
|
.root_source_file = b.path("test/root.zig"),
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "cjit", .module = module },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const run_external_test = b.addRunArtifact(module_external_test);
|
||||||
|
|
||||||
|
const step_test = b.step("test", "Run tests");
|
||||||
|
step_test.dependOn(&run_internal_test.step);
|
||||||
|
step_test.dependOn(&run_external_test.step);
|
||||||
|
}
|
||||||
11
packages/cjit/build.zig.zon
Normal file
11
packages/cjit/build.zig.zon
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.{
|
||||||
|
.name = .cjit,
|
||||||
|
.version = "0.0.0",
|
||||||
|
.minimum_zig_version = "0.15.2",
|
||||||
|
.paths = .{
|
||||||
|
"src",
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
},
|
||||||
|
.fingerprint = 0xaec0accc19243440,
|
||||||
|
}
|
||||||
154
packages/cjit/src/Runtime.zig
Normal file
154
packages/cjit/src/Runtime.zig
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const tokens = @import("tokens.zig");
|
||||||
|
const types = @import("types.zig");
|
||||||
|
|
||||||
|
const Sections = @import("Sections.zig");
|
||||||
|
const StackValue = @import("StackValue.zig");
|
||||||
|
const Tokenizer = tokens.Tokenizer;
|
||||||
|
const Type = types.Type;
|
||||||
|
|
||||||
|
pub const virtual_stack_size = 256;
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
symbols_needed: std.StringHashMapUnmanaged(ExternSymbol) = .{},
|
||||||
|
symbols_provided: std.StringHashMapUnmanaged(InternSymbol) = .{},
|
||||||
|
symbols_located: std.StringHashMapUnmanaged(LocatedSymbol) = .{},
|
||||||
|
relocation_table: std.ArrayList(Relocation) = .{},
|
||||||
|
/// Bounded, preallocated with the capacity of `virtual_stack_size`.
|
||||||
|
virtual_stack: std.ArrayList(StackValue),
|
||||||
|
tokenizer: Tokenizer,
|
||||||
|
sections: Sections = .{},
|
||||||
|
|
||||||
|
pub const ExternSymbol = struct {
|
||||||
|
name: []const u8,
|
||||||
|
c_type: Type,
|
||||||
|
ptr: ?*anyopaque,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InternSymbol = struct {
|
||||||
|
name: []const u8,
|
||||||
|
public: bool,
|
||||||
|
c_type: Type,
|
||||||
|
location: Location,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Location = union(enum) {
|
||||||
|
text: usize,
|
||||||
|
data: usize,
|
||||||
|
rodata: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LocatedSymbol = struct {
|
||||||
|
name: []const u8,
|
||||||
|
public: bool,
|
||||||
|
c_type: Type,
|
||||||
|
ptr: ?*anyopaque,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Relocation = struct {
|
||||||
|
addr: usize,
|
||||||
|
location: Location,
|
||||||
|
type: RelocationType,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RelocationType = enum {
|
||||||
|
global_offset_table,
|
||||||
|
rip_disp32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !Self {
|
||||||
|
var arena: std.heap.ArenaAllocator = .init(allocator);
|
||||||
|
const arena_allocator = arena.allocator();
|
||||||
|
errdefer arena.deinit();
|
||||||
|
|
||||||
|
const virtual_stack_buffer = try arena_allocator.alloc(StackValue, virtual_stack_size);
|
||||||
|
const tokenizer = try Tokenizer.init(arena_allocator);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.arena = arena,
|
||||||
|
.virtual_stack = .initBuffer(virtual_stack_buffer),
|
||||||
|
.tokenizer = tokenizer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.symbols_needed.deinit(self.allocator);
|
||||||
|
self.symbols_provided.deinit(self.allocator);
|
||||||
|
self.symbols_located.deinit(self.allocator);
|
||||||
|
self.relocation_table.deinit(self.allocator);
|
||||||
|
self.arena.deinit();
|
||||||
|
// TODO deinit sections (stage-depentent)
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile(self: *Self, filename: []const u8, code: []const u8) !void {
|
||||||
|
self.tokenizer.setSource(filename, code);
|
||||||
|
while (try self.tokenizer.nextToken(self.arena.allocator())) |token| {
|
||||||
|
_ = token;
|
||||||
|
std.debug.panic("Not implemented", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSymbol(self: *Self, comptime T: type, name: []const u8, ptr: *const T) !void {
|
||||||
|
const symbol = self.symbols_needed.getPtr(name) orelse return error.SymbolNotFound;
|
||||||
|
if (!types.isCompatible(T, symbol.c_type)) return error.IncompatibleType;
|
||||||
|
|
||||||
|
// TODO Figure out const-correctness
|
||||||
|
symbol.ptr = @ptrCast(@constCast(ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link(self: *Self) !void {
|
||||||
|
try self.sections.relocateSections(self.allocator);
|
||||||
|
|
||||||
|
const text_base = @intFromPtr(self.sections.text.data.items.ptr);
|
||||||
|
const data_base = @intFromPtr(self.sections.data.data.items.ptr);
|
||||||
|
const rodata_base = @intFromPtr(self.sections.rodata.data.items.ptr);
|
||||||
|
|
||||||
|
for (self.relocation_table.items) |relocation| {
|
||||||
|
const target_addr = switch (relocation.location) {
|
||||||
|
.text => |offset| text_base + offset,
|
||||||
|
.data => |offset| data_base + offset,
|
||||||
|
.rodata => |offset| rodata_base + offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (relocation.type) {
|
||||||
|
.global_offset_table => {
|
||||||
|
std.mem.writeInt(
|
||||||
|
usize,
|
||||||
|
self.sections.rodata.data.items[relocation.addr..][0..@sizeOf(usize)],
|
||||||
|
target_addr,
|
||||||
|
builtin.cpu.arch.endian(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
.rip_disp32 => {
|
||||||
|
const rip = text_base + relocation.addr + 4;
|
||||||
|
const disp64: isize = @bitCast(rip -% target_addr);
|
||||||
|
const disp32 = std.math.cast(i32, disp64) orelse return error.RelocationError;
|
||||||
|
std.mem.writeInt(
|
||||||
|
i32,
|
||||||
|
self.sections.text.data.items[relocation.addr..][0..4],
|
||||||
|
disp32,
|
||||||
|
builtin.cpu.arch.endian(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.relocation_table.clearAndFree(self.allocator);
|
||||||
|
|
||||||
|
try self.sections.protectSections();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getSymbol(self: *const Self, comptime T: type, name: []const u8) ?*const T {
|
||||||
|
const symbol = self.symbols_located.get(name) orelse return null;
|
||||||
|
return @ptrCast(@alignCast(symbol.ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(self: *Self, comptime T: type) error{OutOfMemory}!*T {
|
||||||
|
return self.arena.allocator().create(T);
|
||||||
|
}
|
||||||
185
packages/cjit/src/Sections.zig
Normal file
185
packages/cjit/src/Sections.zig
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
text: Section = .{ .protection = .executable },
|
||||||
|
data: Section = .{ .protection = .read_write },
|
||||||
|
rodata: Section = .{ .protection = .read_only },
|
||||||
|
|
||||||
|
pub const Section = struct {
|
||||||
|
data: std.ArrayList(u8) = .{},
|
||||||
|
protection: Protection,
|
||||||
|
|
||||||
|
pub const Protection = enum {
|
||||||
|
executable,
|
||||||
|
read_only,
|
||||||
|
read_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn writeValue(self: *Section, value: anytype, allocator: std.mem.Allocator) !void {
|
||||||
|
const T = @TypeOf(value);
|
||||||
|
std.debug.assert(std.meta.hasUniqueRepresentation(T));
|
||||||
|
const bytes = std.mem.asBytes(&value);
|
||||||
|
try self.writeBytes(bytes, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeByte(self: *Section, byte: u8, allocator: std.mem.Allocator) !void {
|
||||||
|
try self.data.append(allocator, byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeBytes(self: *Section, data: []const u8, allocator: std.mem.Allocator) !void {
|
||||||
|
try self.data.appendSlice(allocator, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alignForward(self: *Section, alignment: u16, allocator: std.mem.Allocator) !void {
|
||||||
|
const ptr = self.data.items.len;
|
||||||
|
const ptr_aligned = std.mem.alignForward(usize, ptr, alignment);
|
||||||
|
const padding = ptr_aligned - ptr;
|
||||||
|
|
||||||
|
try self.data.appendNTimes(allocator, 0, padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pageCount(self: Section) usize {
|
||||||
|
const page_size = std.heap.pageSize();
|
||||||
|
const section_size = self.data.items.len;
|
||||||
|
return @divFloor(section_size + page_size - 1, page_size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn relocateSections(self: *Self, allocator: std.mem.Allocator) !void {
|
||||||
|
const page_size = std.heap.pageSize();
|
||||||
|
|
||||||
|
const text_pages = self.text.pageCount();
|
||||||
|
const text_bytes = text_pages * page_size;
|
||||||
|
|
||||||
|
const data_pages = self.data.pageCount();
|
||||||
|
const data_bytes = data_pages * page_size;
|
||||||
|
|
||||||
|
const rodata_pages = self.rodata.pageCount();
|
||||||
|
const rodata_bytes = rodata_pages * page_size;
|
||||||
|
|
||||||
|
const total_pages = text_pages + data_pages + rodata_pages;
|
||||||
|
const total_bytes = text_bytes + data_bytes + rodata_bytes;
|
||||||
|
std.debug.assert(total_bytes == total_pages * page_size);
|
||||||
|
|
||||||
|
const ptr: [*]u8 = sw: switch (builtin.os.tag) {
|
||||||
|
.windows => {
|
||||||
|
const windows = std.os.windows;
|
||||||
|
const ntdll = windows.ntdll;
|
||||||
|
|
||||||
|
var base_addr: ?*anyopaque = null;
|
||||||
|
var size: windows.SIZE_T = total_bytes;
|
||||||
|
const status = ntdll.NtAllocateVirtualMemory(
|
||||||
|
windows.GetCurrentProcess(),
|
||||||
|
@ptrCast(&base_addr),
|
||||||
|
0,
|
||||||
|
&size,
|
||||||
|
windows.MEM_COMMIT | windows.MEM_RESERVE,
|
||||||
|
windows.PAGE_READWRITE,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status == .SUCCESS) {
|
||||||
|
break :sw @ptrCast(base_addr);
|
||||||
|
} else {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.linux => {
|
||||||
|
const linux = std.os.linux;
|
||||||
|
|
||||||
|
const rc = linux.mmap(
|
||||||
|
null,
|
||||||
|
total_bytes,
|
||||||
|
linux.PROT.READ | linux.PROT.WRITE,
|
||||||
|
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const status: linux.E = .init(rc);
|
||||||
|
|
||||||
|
if (status == .SUCCESS) {
|
||||||
|
break :sw @ptrFromInt(rc);
|
||||||
|
} else {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => @compileError("Operating system " ++ @tagName(builtin.os.tag) ++ " not supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const text_slice = ptr[0..text_bytes];
|
||||||
|
const data_slice = ptr[text_bytes .. text_bytes + data_bytes];
|
||||||
|
const rodata_slice = ptr[text_bytes + data_bytes .. text_bytes + data_bytes + rodata_bytes];
|
||||||
|
|
||||||
|
@memcpy(text_slice[0..self.text.data.items.len], self.text.data.items);
|
||||||
|
@memcpy(data_slice[0..self.data.data.items.len], self.data.data.items);
|
||||||
|
@memcpy(rodata_slice[0..self.rodata.data.items.len], self.rodata.data.items);
|
||||||
|
|
||||||
|
self.text.data.clearAndFree(allocator);
|
||||||
|
self.data.data.clearAndFree(allocator);
|
||||||
|
self.rodata.data.clearAndFree(allocator);
|
||||||
|
|
||||||
|
self.text.data = .{ .items = text_slice, .capacity = text_bytes };
|
||||||
|
self.data.data = .{ .items = data_slice, .capacity = data_bytes };
|
||||||
|
self.rodata.data = .{ .items = rodata_slice, .capacity = rodata_bytes };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn protectSections(self: *Self) !void {
|
||||||
|
const sections = [_]*Section{ &self.text, &self.data, &self.rodata };
|
||||||
|
|
||||||
|
for (sections) |section| {
|
||||||
|
switch (builtin.os.tag) {
|
||||||
|
.windows => {
|
||||||
|
const windows = std.os.windows;
|
||||||
|
const ntdll = windows.ntdll;
|
||||||
|
|
||||||
|
const protection = switch (section.protection) {
|
||||||
|
.executable => windows.PAGE_EXECUTE,
|
||||||
|
.read_only => windows.PAGE_READONLY,
|
||||||
|
.read_write => windows.PAGE_READWRITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
var base_addr: ?*anyopaque = section.data.items.ptr;
|
||||||
|
var size: windows.SIZE_T = section.data.capacity;
|
||||||
|
var old_protection: u32 = undefined;
|
||||||
|
|
||||||
|
const status = ntdll.NtProtectVirtualMemory(
|
||||||
|
windows.GetCurrentProcess(),
|
||||||
|
&base_addr,
|
||||||
|
&size,
|
||||||
|
protection,
|
||||||
|
&old_protection,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != .SUCCESS) {
|
||||||
|
return error.ProtectionError;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.linux => {
|
||||||
|
const linux = std.os.linux;
|
||||||
|
|
||||||
|
const protection: usize = switch (section.protection) {
|
||||||
|
.executable => linux.PROT.EXEC,
|
||||||
|
.read_only => linux.PROT.READ,
|
||||||
|
.read_write => linux.PROT.READ | linux.PROT.WRITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const rc = linux.mprotect(
|
||||||
|
section.data.items.ptr,
|
||||||
|
section.data.capacity,
|
||||||
|
protection,
|
||||||
|
);
|
||||||
|
const status: linux.E = .init(rc);
|
||||||
|
|
||||||
|
if (status != .SUCCESS) {
|
||||||
|
return error.ProtectionError;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => @compileError("Operating system " ++ @tagName(builtin.os.tag) ++ " not supported"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn freeSections(self: *Self) void {
|
||||||
|
_ = self;
|
||||||
|
std.debug.panic("Not implemented", .{});
|
||||||
|
}
|
||||||
20
packages/cjit/src/StackValue.zig
Normal file
20
packages/cjit/src/StackValue.zig
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const types = @import("types.zig");
|
||||||
|
const x86_64 = @import("x86_64.zig");
|
||||||
|
|
||||||
|
const Register = x86_64.Register;
|
||||||
|
const Type = types.Type;
|
||||||
|
|
||||||
|
pub const Value = union(enum) {
|
||||||
|
register: Register,
|
||||||
|
constant: u64,
|
||||||
|
symbol: []const u8,
|
||||||
|
/// Displacement in bytes from current value of base pointer register.
|
||||||
|
stack: i32,
|
||||||
|
cpu_flags: void,
|
||||||
|
};
|
||||||
|
|
||||||
|
c_type: Type,
|
||||||
|
value: Value,
|
||||||
31
packages/cjit/src/includes/builtin.h
Normal file
31
packages/cjit/src/includes/builtin.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define abs(value) __builtin_abs(value)
|
||||||
|
#define byteswap(value) __builtin_byteswap(value)
|
||||||
|
#define ceil(value) __builtin_ceil(value)
|
||||||
|
#define clz(value) __builtin_clz(value)
|
||||||
|
#define containerof(ptr, type, member) __builtin_containerof(ptr, type, member)
|
||||||
|
#define cos(value) __builtin_cos(value)
|
||||||
|
#define ctz(value) __builtin_ctz(value)
|
||||||
|
#define embedfile(path) __builtin_embedfile(path)
|
||||||
|
#define exp(value) __builtin_exp(value)
|
||||||
|
#define exp2(value) __builtin_exp2(value)
|
||||||
|
#define floor(value) __builtin_floor(value)
|
||||||
|
#define frameaddress() __builtin_frameaddress()
|
||||||
|
#define log(value) __builtin_log(value)
|
||||||
|
#define log10(value) __builtin_log10(value)
|
||||||
|
#define log2(value) __builtin_log2(value)
|
||||||
|
#define max(...) __builtin_max(__VA_ARGS__)
|
||||||
|
#define memcpy(dest, src, count) __builtin_memcpy(dest, src, count)
|
||||||
|
#define memmove(dest, src, count) __builtin_memmove(dest, src, count)
|
||||||
|
#define memset(value) __builtin_memset(value)
|
||||||
|
#define min(...) __builtin_min(__VA_ARGS__)
|
||||||
|
#define popcount(value) __builtin_popcount(value)
|
||||||
|
#define returnaddress() __builtin_returnaddress()
|
||||||
|
#define round(value) __builtin_round(value)
|
||||||
|
#define sin(value) __builtin_sin(value)
|
||||||
|
#define sqrt(value) __builtin_sqrt(value)
|
||||||
|
#define tan(value) __builtin_tan(value)
|
||||||
|
#define trunc(value) __builtin_trunc(value)
|
||||||
|
#define typename(type) __builtin_typename(type)
|
||||||
|
#define typeof(...) __builtin_typeof(__VA_ARGS__)
|
||||||
7
packages/cjit/src/includes/stdalign.h
Normal file
7
packages/cjit/src/includes/stdalign.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define alignas _Alignas
|
||||||
|
#define alignof _Alignof
|
||||||
|
|
||||||
|
#define __alignas_is_defined 1
|
||||||
|
#define __alignof_is_defined 1
|
||||||
7
packages/cjit/src/includes/stdbool.h
Normal file
7
packages/cjit/src/includes/stdbool.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define bool _Bool
|
||||||
|
|
||||||
|
#define true 1
|
||||||
|
#define false 0
|
||||||
|
#define __bool_true_false_are_defined 1
|
||||||
9
packages/cjit/src/includes/stddef.h
Normal file
9
packages/cjit/src/includes/stddef.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef long ptrdiff_t;
|
||||||
|
typedef long max_align_t;
|
||||||
|
typedef unsigned long size_t;
|
||||||
|
|
||||||
|
#define NULL ((void *)0)
|
||||||
|
|
||||||
|
#define offsetof(type, member) __builtin_offsetof(type, member)
|
||||||
118
packages/cjit/src/includes/stdint.h
Normal file
118
packages/cjit/src/includes/stdint.h
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef signed char int8_t;
|
||||||
|
typedef short int16_t;
|
||||||
|
typedef int int32_t;
|
||||||
|
typedef long int64_t;
|
||||||
|
|
||||||
|
typedef int8_t int_fast8_t;
|
||||||
|
typedef int16_t int_fast16_t;
|
||||||
|
typedef int32_t int_fast32_t;
|
||||||
|
typedef int64_t int_fast64_t;
|
||||||
|
|
||||||
|
typedef int8_t int_least8_t;
|
||||||
|
typedef int16_t int_least16_t;
|
||||||
|
typedef int32_t int_least32_t;
|
||||||
|
typedef int64_t int_least64_t;
|
||||||
|
|
||||||
|
typedef int64_t intmax_t;
|
||||||
|
typedef int64_t intptr_t;
|
||||||
|
|
||||||
|
typedef unsigned char uint8_t;
|
||||||
|
typedef unsigned short uint16_t;
|
||||||
|
typedef unsigned int uint32_t;
|
||||||
|
typedef unsigned long uint64_t;
|
||||||
|
|
||||||
|
typedef uint8_t uint_fast8_t;
|
||||||
|
typedef uint16_t uint_fast16_t;
|
||||||
|
typedef uint32_t uint_fast32_t;
|
||||||
|
typedef uint64_t uint_fast64_t;
|
||||||
|
|
||||||
|
typedef uint8_t uint_least8_t;
|
||||||
|
typedef uint16_t uint_least16_t;
|
||||||
|
typedef uint32_t uint_least32_t;
|
||||||
|
typedef uint64_t uint_least64_t;
|
||||||
|
|
||||||
|
typedef uint64_t uintmax_t;
|
||||||
|
typedef uint64_t uintptr_t;
|
||||||
|
|
||||||
|
#define INT8_MIN (-0x80)
|
||||||
|
#define INT16_MIN (-0x8000)
|
||||||
|
#define INT32_MIN (-0x80000000)
|
||||||
|
#define INT64_MIN (-0x8000000000000000L)
|
||||||
|
|
||||||
|
#define INT_FAST8_MIN INT8_MIN
|
||||||
|
#define INT_FAST16_MIN INT16_MIN
|
||||||
|
#define INT_FAST32_MIN INT32_MIN
|
||||||
|
#define INT_FAST64_MIN INT64_MIN
|
||||||
|
|
||||||
|
#define INT_LEAST8_MIN INT8_MIN
|
||||||
|
#define INT_LEAST16_MIN INT16_MIN
|
||||||
|
#define INT_LEAST32_MIN INT32_MIN
|
||||||
|
#define INT_LEAST64_MIN INT64_MIN
|
||||||
|
|
||||||
|
#define INTPTR_MIN INT64_MIN
|
||||||
|
#define INTMAX_MIN INT64_MIN
|
||||||
|
|
||||||
|
#define INT8_MAX (0x7F)
|
||||||
|
#define INT16_MAX (0x7FFF)
|
||||||
|
#define INT32_MAX (0x7FFFFFFF)
|
||||||
|
#define INT64_MAX (0x7FFFFFFFFFFFFFFFL)
|
||||||
|
|
||||||
|
#define INT_FAST8_MAX INT8_MAX
|
||||||
|
#define INT_FAST16_MAX INT16_MAX
|
||||||
|
#define INT_FAST32_MAX INT32_MAX
|
||||||
|
#define INT_FAST64_MAX INT64_MAX
|
||||||
|
|
||||||
|
#define INT_LEAST8_MAX INT8_MAX
|
||||||
|
#define INT_LEAST16_MAX INT16_MAX
|
||||||
|
#define INT_LEAST32_MAX INT32_MAX
|
||||||
|
#define INT_LEAST64_MAX INT64_MAX
|
||||||
|
|
||||||
|
#define INTPTR_MAX INT64_MAX
|
||||||
|
#define INTMAX_MAX INT64_MAX
|
||||||
|
|
||||||
|
#define UINT8_MAX (0xFF)
|
||||||
|
#define UINT16_MAX (0xFFFF)
|
||||||
|
#define UINT32_MAX (0xFFFFFFFFU)
|
||||||
|
#define UINT64_MAX (0xFFFFFFFFFFFFFFFFUL)
|
||||||
|
|
||||||
|
#define UINT_FAST8_MAX UINT8_MAX
|
||||||
|
#define UINT_FAST16_MAX UINT16_MAX
|
||||||
|
#define UINT_FAST32_MAX UINT32_MAX
|
||||||
|
#define UINT_FAST64_MAX UINT64_MAX
|
||||||
|
|
||||||
|
#define UINT_LEAST8_MAX UINT8_MAX
|
||||||
|
#define UINT_LEAST16_MAX UINT16_MAX
|
||||||
|
#define UINT_LEAST32_MAX UINT32_MAX
|
||||||
|
#define UINT_LEAST64_MAX UINT64_MAX
|
||||||
|
|
||||||
|
#define UINTPTR_MAX UINT64_MAX
|
||||||
|
#define UINTMAX_MAX UINT64_MAX
|
||||||
|
|
||||||
|
#define INT8_C(value) value
|
||||||
|
#define INT16_C(value) value
|
||||||
|
#define INT32_C(value) value
|
||||||
|
#define INT64_C(value) value ## L
|
||||||
|
|
||||||
|
#define INTMAX_C(value) value ## L
|
||||||
|
|
||||||
|
#define UINT8_C(value) value
|
||||||
|
#define UINT16_C(value) value
|
||||||
|
#define UINT32_C(value) value ## U
|
||||||
|
#define UINT64_C(value) value ## UL
|
||||||
|
|
||||||
|
#define UINTMAX_C(value) value ## UL
|
||||||
|
|
||||||
|
#define PTRDIFF_MIN (-0x8000000000000000L)
|
||||||
|
#define PTRDIFF_MAX (0x7FFFFFFFFFFFFFFFL)
|
||||||
|
|
||||||
|
#define SIZE_MAX (0xFFFFFFFFFFFFFFFFUL)
|
||||||
|
|
||||||
|
#define WINT_MIN (-0x80000000)
|
||||||
|
#define WINT_MAX (0x7FFFFFFF)
|
||||||
|
|
||||||
|
#define WCHAR_MIN (0)
|
||||||
|
#define WCHAR_MAX (0x10FFFF)
|
||||||
|
|
||||||
|
#endif
|
||||||
3
packages/cjit/src/includes/stdnoreturn.h
Normal file
3
packages/cjit/src/includes/stdnoreturn.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define noreturn _Noreturn
|
||||||
19
packages/cjit/src/root.zig
Normal file
19
packages/cjit/src/root.zig
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
pub const Runtime = @import("Runtime.zig");
|
||||||
|
pub const Sections = @import("Sections.zig");
|
||||||
|
pub const StackValue = @import("StackValue.zig");
|
||||||
|
pub const tokens = @import("tokens.zig");
|
||||||
|
pub const types = @import("types.zig");
|
||||||
|
pub const x86_64 = @import("x86_64.zig");
|
||||||
|
|
||||||
|
pub const call: std.builtin.CallingConvention = switch (builtin.cpu.arch) {
|
||||||
|
.aarch64 => .{ .aarch64_aapcs = .{} },
|
||||||
|
.x86_64 => .{ .x86_64_sysv = .{} },
|
||||||
|
else => @compileError("Architecture " ++ @tagName(builtin.cpu.arch) ++ " not supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
test {
|
||||||
|
std.testing.refAllDeclsRecursive(@This());
|
||||||
|
}
|
||||||
7
packages/cjit/src/tokens.zig
Normal file
7
packages/cjit/src/tokens.zig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pub const Builtin = @import("tokens/Builtin.zig").Builtin;
|
||||||
|
pub const Constant = @import("tokens/Constant.zig").Constant;
|
||||||
|
pub const Keyword = @import("tokens/Keyword.zig").Keyword;
|
||||||
|
pub const Punctuator = @import("tokens/Punctuator.zig").Punctuator;
|
||||||
|
pub const Token = @import("tokens/Token.zig").Token;
|
||||||
|
pub const Tokenizer = @import("tokens/Tokenizer.zig");
|
||||||
|
pub const Utf8Iterator = @import("tokens/Utf8Iterator.zig");
|
||||||
156
packages/cjit/src/tokens/Builtin.zig
Normal file
156
packages/cjit/src/tokens/Builtin.zig
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Builtin = enum {
|
||||||
|
/// Usage: `__builtin_abs(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any integer or real type. Equivalent to
|
||||||
|
/// `value < 0 ? -value : value`. Noop for unsigned integer types.
|
||||||
|
__builtin_abs,
|
||||||
|
/// Usage: `__builtin_byteswap(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any integer type.
|
||||||
|
__builtin_byteswap,
|
||||||
|
/// Usage: `__builtin_ceil(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type.
|
||||||
|
__builtin_ceil,
|
||||||
|
/// Usage: `__builtin_clz(value)`
|
||||||
|
///
|
||||||
|
/// Count leading zeroes. `value` can be any integer type. The return type
|
||||||
|
/// is `int`.
|
||||||
|
__builtin_clz,
|
||||||
|
/// Usage `__builtin_containerof(ptr, type, member)`
|
||||||
|
///
|
||||||
|
/// `ptr` must be an pointer to a struct or a union. `type` must be a type.
|
||||||
|
/// `member` must be an identifier. Given `ptr` is a pointer to a given
|
||||||
|
/// member of `type`, returns a pointer to the entire container.
|
||||||
|
__builtin_containerof,
|
||||||
|
/// Usage: `__builtin_cos(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type.
|
||||||
|
__builtin_cos,
|
||||||
|
/// Usage: `__builtin_ctz(value)`
|
||||||
|
///
|
||||||
|
/// Count trailing zeroes. `value` can be any integer type. The return type
|
||||||
|
/// is `int`.
|
||||||
|
__builtin_ctz,
|
||||||
|
/// Usage: `__builtin_embedfile(path)`
|
||||||
|
///
|
||||||
|
/// `path` must be a string literal. The return type is `const char *`. The
|
||||||
|
/// data is null-terminated.
|
||||||
|
__builtin_embedfile,
|
||||||
|
/// Usage `__builtin_exp(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type. Calculates e^value.
|
||||||
|
__builtin_exp,
|
||||||
|
/// Usage `__builtin_exp2(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type. Calculates 2^value.
|
||||||
|
__builtin_exp2,
|
||||||
|
/// Usage: `__bultin_floor(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type.
|
||||||
|
__builtin_floor,
|
||||||
|
/// Usage: `__builtin_frameaddress()`
|
||||||
|
///
|
||||||
|
/// Returns the value of base pointer. The return type is equivalent to
|
||||||
|
/// `uintptr_t`.
|
||||||
|
__builtin_frameaddress,
|
||||||
|
/// Usage: `__builtin_log(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type. Calculates natural logarithm (base e).
|
||||||
|
__builtin_log,
|
||||||
|
/// Usage: `__builtin_log10(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type. Calculates base 10 logarithm.
|
||||||
|
__builtin_log10,
|
||||||
|
/// Usage: `__builtin_log2(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type. Calculates base 2 logarithm.
|
||||||
|
__builtin_log2,
|
||||||
|
/// Usage: `__builtin_max(...)`
|
||||||
|
///
|
||||||
|
/// The arguments can be any integer or real types. NaN values are ignored.
|
||||||
|
__builtin_max,
|
||||||
|
/// Usage: `__builtin_memcpy(dest, src, count)`
|
||||||
|
///
|
||||||
|
/// `dest` and `src` must be pointers. The pointers are reinterpreted as
|
||||||
|
/// pointers to `char`. `count` is coerced to the equivalent of `size_t`.
|
||||||
|
/// `dest` must be a non-const pointer. The regions must not overlap.
|
||||||
|
__builtin_memcpy,
|
||||||
|
/// Usage: `__builtin_memmove(dest, src, count)`
|
||||||
|
///
|
||||||
|
/// `dest` and `src` must be pointers. The pointers are reinterpreted as
|
||||||
|
/// pointers to `char`. `count` is coerced to the equivalent of `size_t`.
|
||||||
|
/// `dest` must be a non-const pointer. The regions may overlap.
|
||||||
|
__builtin_memmove,
|
||||||
|
/// Usage: `__builtin_memset(dest, ch, count)`
|
||||||
|
///
|
||||||
|
/// `dest` must be a pointer. The pointer is reinterpreted as pointer to
|
||||||
|
/// `char`. `ch` is cast to `unsigned char`. `dest` must be a non-const
|
||||||
|
// pointer.
|
||||||
|
__builtin_memset,
|
||||||
|
/// Usage: `__builtin_min(...)`
|
||||||
|
///
|
||||||
|
/// The arguments can be any integer or real types. NaN values are ignored.
|
||||||
|
__builtin_min,
|
||||||
|
/// Usage: `__builtin_offsetof(type, member)`
|
||||||
|
///
|
||||||
|
/// `type` must be a type. `member` must be an identifier.
|
||||||
|
__builtin_offsetof,
|
||||||
|
/// Usage: `__builtin_popcount(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any integer type. The return type is `int`.
|
||||||
|
__builtin_popcount,
|
||||||
|
/// Usage: `__builtin_returnaddress()`
|
||||||
|
///
|
||||||
|
/// Returns the address of the instruction to run after current function
|
||||||
|
/// returns. The return type is equivalent to `uintptr_t`.
|
||||||
|
__builtin_returnaddress,
|
||||||
|
/// Usage: `__builtin_round(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type.
|
||||||
|
__builtin_round,
|
||||||
|
/// Usage: `__builtin_sin(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type.
|
||||||
|
__builtin_sin,
|
||||||
|
/// Usage: `__builtin_sqrt(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type.
|
||||||
|
__builtin_sqrt,
|
||||||
|
/// Usage: `__builtin_tan(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type.
|
||||||
|
__builtin_tan,
|
||||||
|
/// Usage: `__builtin_trunc(value)`
|
||||||
|
///
|
||||||
|
/// `value` can be any real type.
|
||||||
|
__builtin_trunc,
|
||||||
|
/// Usage: `__builtin_typename(type)`
|
||||||
|
///
|
||||||
|
/// `type` must be a type. The return type is `const char *`.
|
||||||
|
__builtin_typename,
|
||||||
|
/// Usage: `__builtin_typeof(...)`
|
||||||
|
__builtin_typeof,
|
||||||
|
|
||||||
|
pub const map: std.StaticStringMap(Builtin) = blk: {
|
||||||
|
const fields = @typeInfo(Builtin).@"enum".fields;
|
||||||
|
|
||||||
|
var kvs_list: [fields.len]struct { []const u8, Builtin } = undefined;
|
||||||
|
for (fields, 0..) |field, i| {
|
||||||
|
kvs_list[i] = .{ field.name, @field(Builtin, field.name) };
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk .initComptime(kvs_list);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn isBuiltin(identifier: []const u8) ?Builtin {
|
||||||
|
if (std.mem.startsWith(u8, identifier, "__builtin_")) {
|
||||||
|
@branchHint(.unlikely);
|
||||||
|
return map.get(identifier);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
14
packages/cjit/src/tokens/Constant.zig
Normal file
14
packages/cjit/src/tokens/Constant.zig
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Constant = union(enum) {
|
||||||
|
int: i32,
|
||||||
|
long: i64,
|
||||||
|
long_long: i64,
|
||||||
|
unsigned_int: u32,
|
||||||
|
unsigned_long: u64,
|
||||||
|
unsigned_long_long: u64,
|
||||||
|
float: f32,
|
||||||
|
double: f64,
|
||||||
|
character: u8,
|
||||||
|
wide_character: i32,
|
||||||
|
};
|
||||||
63
packages/cjit/src/tokens/Keyword.zig
Normal file
63
packages/cjit/src/tokens/Keyword.zig
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Keyword = enum {
|
||||||
|
_Alignas,
|
||||||
|
_Alignof,
|
||||||
|
_Atomic,
|
||||||
|
_Bool,
|
||||||
|
_Complex,
|
||||||
|
_Generic,
|
||||||
|
_Imaginary,
|
||||||
|
_Noreturn,
|
||||||
|
_Static_assert,
|
||||||
|
_Thread_local,
|
||||||
|
auto,
|
||||||
|
@"break",
|
||||||
|
case,
|
||||||
|
char,
|
||||||
|
@"const",
|
||||||
|
@"continue",
|
||||||
|
default,
|
||||||
|
do,
|
||||||
|
double,
|
||||||
|
@"else",
|
||||||
|
@"enum",
|
||||||
|
@"extern",
|
||||||
|
float,
|
||||||
|
@"for",
|
||||||
|
goto,
|
||||||
|
@"if",
|
||||||
|
@"inline",
|
||||||
|
int,
|
||||||
|
long,
|
||||||
|
register,
|
||||||
|
restrict,
|
||||||
|
@"return",
|
||||||
|
short,
|
||||||
|
signed,
|
||||||
|
sizeof,
|
||||||
|
static,
|
||||||
|
@"struct",
|
||||||
|
@"switch",
|
||||||
|
typedef,
|
||||||
|
@"union",
|
||||||
|
unsigned,
|
||||||
|
void,
|
||||||
|
@"volatile",
|
||||||
|
@"while",
|
||||||
|
|
||||||
|
pub const map: std.StaticStringMap(Keyword) = blk: {
|
||||||
|
const fields = @typeInfo(Keyword).@"enum".fields;
|
||||||
|
|
||||||
|
var kvs_list: [fields.len]struct { []const u8, Keyword } = undefined;
|
||||||
|
for (fields, 0..) |field, i| {
|
||||||
|
kvs_list[i] = .{ field.name, @field(Keyword, field.name) };
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk .initComptime(kvs_list);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn isKeyword(identifier: []const u8) ?Keyword {
|
||||||
|
return map.get(identifier);
|
||||||
|
}
|
||||||
|
};
|
||||||
70
packages/cjit/src/tokens/Punctuator.zig
Normal file
70
packages/cjit/src/tokens/Punctuator.zig
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Punctuator = enum(u32) {
|
||||||
|
// three characters
|
||||||
|
@"..." = strToInt3("..."),
|
||||||
|
@"<<=" = strToInt3("<<="),
|
||||||
|
@">>=" = strToInt3(">>="),
|
||||||
|
// two characters
|
||||||
|
@"--" = strToInt2("--"),
|
||||||
|
@"-=" = strToInt2("-="),
|
||||||
|
@"->" = strToInt2("->"),
|
||||||
|
@"!=" = strToInt2("!="),
|
||||||
|
@"*=" = strToInt2("*="),
|
||||||
|
@"/=" = strToInt2("/="),
|
||||||
|
@"&&" = strToInt2("&&"),
|
||||||
|
@"&=" = strToInt2("&="),
|
||||||
|
@"##" = strToInt2("##"),
|
||||||
|
@"%=" = strToInt2("%="),
|
||||||
|
@"^=" = strToInt2("^="),
|
||||||
|
@"++" = strToInt2("++"),
|
||||||
|
@"+=" = strToInt2("+="),
|
||||||
|
@"<<" = strToInt2("<<"),
|
||||||
|
@"<=" = strToInt2("<="),
|
||||||
|
@"==" = strToInt2("=="),
|
||||||
|
@">=" = strToInt2(">="),
|
||||||
|
@">>" = strToInt2(">>"),
|
||||||
|
@"|=" = strToInt2("|="),
|
||||||
|
@"||" = strToInt2("||"),
|
||||||
|
// single character
|
||||||
|
@"-" = strToInt1("-"),
|
||||||
|
@"," = strToInt1(","),
|
||||||
|
@";" = strToInt1(";"),
|
||||||
|
@":" = strToInt1(":"),
|
||||||
|
@"!" = strToInt1("!"),
|
||||||
|
@"?" = strToInt1("?"),
|
||||||
|
@"." = strToInt1("."),
|
||||||
|
@"(" = strToInt1("("),
|
||||||
|
@")" = strToInt1(")"),
|
||||||
|
@"[" = strToInt1("["),
|
||||||
|
@"]" = strToInt1("]"),
|
||||||
|
@"{" = strToInt1("{"),
|
||||||
|
@"}" = strToInt1("}"),
|
||||||
|
@"*" = strToInt1("*"),
|
||||||
|
@"/" = strToInt1("/"),
|
||||||
|
@"&" = strToInt1("&"),
|
||||||
|
@"#" = strToInt1("#"),
|
||||||
|
@"%" = strToInt1("%"),
|
||||||
|
@"^" = strToInt1("^"),
|
||||||
|
@"+" = strToInt1("+"),
|
||||||
|
@"<" = strToInt1("<"),
|
||||||
|
@"=" = strToInt1("="),
|
||||||
|
@">" = strToInt1(">"),
|
||||||
|
@"|" = strToInt1("|"),
|
||||||
|
@"~" = strToInt1("~"),
|
||||||
|
|
||||||
|
pub const line_continuation_lf = strToInt2("\\\n");
|
||||||
|
pub const line_continuation_crlf = strToInt3("\\\r\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
fn strToInt1(str: *const [1]u8) u32 {
|
||||||
|
return @as(u8, @bitCast(str.*));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strToInt2(str: *const [2]u8) u32 {
|
||||||
|
return @as(u16, @bitCast(str.*));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strToInt3(str: *const [3]u8) u32 {
|
||||||
|
return @as(u24, @bitCast(str.*));
|
||||||
|
}
|
||||||
16
packages/cjit/src/tokens/Token.zig
Normal file
16
packages/cjit/src/tokens/Token.zig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Builtin = @import("Builtin.zig").Builtin;
|
||||||
|
const Constant = @import("Constant.zig").Constant;
|
||||||
|
const Keyword = @import("Keyword.zig").Keyword;
|
||||||
|
const Punctuator = @import("Punctuator.zig").Punctuator;
|
||||||
|
|
||||||
|
pub const Token = union(enum) {
|
||||||
|
builtin: Builtin,
|
||||||
|
constant: Constant,
|
||||||
|
keyword: Keyword,
|
||||||
|
punctuator: Punctuator,
|
||||||
|
identifier: []const u8,
|
||||||
|
string_literal: [:0]const u8,
|
||||||
|
wide_string_literal: [:0]const u32,
|
||||||
|
};
|
||||||
263
packages/cjit/src/tokens/Tokenizer.zig
Normal file
263
packages/cjit/src/tokens/Tokenizer.zig
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const Builtin = @import("Builtin.zig").Builtin;
|
||||||
|
const Keyword = @import("Keyword.zig").Keyword;
|
||||||
|
const Punctuator = @import("Punctuator.zig").Punctuator;
|
||||||
|
const Token = @import("Token.zig").Token;
|
||||||
|
const Utf8Iterator = @import("Utf8Iterator.zig");
|
||||||
|
|
||||||
|
pub const max_string_length = 4096;
|
||||||
|
pub const max_wide_string_length = 4096;
|
||||||
|
|
||||||
|
filename: []const u8 = &.{},
|
||||||
|
it: Utf8Iterator = .init(&.{}),
|
||||||
|
defines: std.StringHashMapUnmanaged([]Token) = .{},
|
||||||
|
/// Bounded, preallocated with the capacity of `max_string_length`.
|
||||||
|
string: std.ArrayList(u8),
|
||||||
|
/// Bounded, preallocated with the capacity of `max_wide_string_length`.
|
||||||
|
wide_string: std.ArrayList(u32),
|
||||||
|
|
||||||
|
pub fn init(arena_allocator: std.mem.Allocator) !Self {
|
||||||
|
const string_buffer = try arena_allocator.alloc(u8, max_string_length);
|
||||||
|
const wide_string_buffer = try arena_allocator.alloc(u32, max_wide_string_length);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.string = .initBuffer(string_buffer),
|
||||||
|
.wide_string = .initBuffer(wide_string_buffer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSource(self: *Self, filename: []const u8, code: []const u8) void {
|
||||||
|
self.filename = filename;
|
||||||
|
self.it = .init(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nextToken(self: *Self, arena_allocator: std.mem.Allocator) !?Token {
|
||||||
|
try self.skipWhitespace();
|
||||||
|
|
||||||
|
// TODO Skip C and C++ style comments
|
||||||
|
// TODO Preprocessor directives
|
||||||
|
|
||||||
|
const cp = try self.peekCodepointSkipLineContinuation() orelse return null;
|
||||||
|
|
||||||
|
switch (cp) {
|
||||||
|
// Identifier start
|
||||||
|
'A'...'Z', '_', 'a'...'z', 128...std.math.maxInt(u21) => {
|
||||||
|
// This is an identifier, with the possible exception of:
|
||||||
|
// - wide string: L"
|
||||||
|
// - wide char: L'
|
||||||
|
// - any keyword
|
||||||
|
|
||||||
|
if (cp == 'L') {
|
||||||
|
const state = self.it.save();
|
||||||
|
|
||||||
|
self.it.advanceCodepoint(cp);
|
||||||
|
const cp2 = try self.peekCodepointSkipLineContinuation() orelse 0;
|
||||||
|
|
||||||
|
switch (cp2) {
|
||||||
|
// Wide string
|
||||||
|
'\"' => {
|
||||||
|
self.it.advanceCodepoint(cp2);
|
||||||
|
self.wide_string.clearRetainingCapacity();
|
||||||
|
// TODO Parse wide string
|
||||||
|
},
|
||||||
|
// Wide char
|
||||||
|
'\'' => {
|
||||||
|
self.it.advanceCodepoint(cp2);
|
||||||
|
// TODO Parse wide char
|
||||||
|
},
|
||||||
|
// Identifier or keyword
|
||||||
|
else => {
|
||||||
|
self.it.restore(state);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const identifier_start = self.it.ptr;
|
||||||
|
self.it.advanceCodepoint(cp);
|
||||||
|
|
||||||
|
var next_cp = try self.peekCodepointSkipLineContinuation();
|
||||||
|
while (next_cp != null and isIdentifierMiddle(next_cp.?)) {
|
||||||
|
self.it.advanceCodepoint(next_cp.?);
|
||||||
|
next_cp = try self.peekCodepointSkipLineContinuation();
|
||||||
|
}
|
||||||
|
|
||||||
|
const identifier = self.it.str[identifier_start..self.it.ptr];
|
||||||
|
|
||||||
|
// TODO Preprocessor
|
||||||
|
|
||||||
|
if (Keyword.isKeyword(identifier)) |keyword| {
|
||||||
|
return .{ .keyword = keyword };
|
||||||
|
} else if (Builtin.isBuiltin(identifier)) |builtin| {
|
||||||
|
return .{ .builtin = builtin };
|
||||||
|
} else {
|
||||||
|
return .{ .identifier = try arena_allocator.dupe(u8, identifier) };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// String
|
||||||
|
'\"' => {
|
||||||
|
self.it.advanceCodepoint(cp);
|
||||||
|
self.string.clearRetainingCapacity();
|
||||||
|
// TODO Parse string
|
||||||
|
},
|
||||||
|
// Char
|
||||||
|
'\'' => {
|
||||||
|
self.it.advanceCodepoint(cp);
|
||||||
|
// TODO Parse char
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Higher code points should've been already handled. The code below may
|
||||||
|
// assume that `cp` is an ASCII character.
|
||||||
|
std.debug.assert(cp < 128);
|
||||||
|
|
||||||
|
// TODO Numeric constants
|
||||||
|
|
||||||
|
const cp3 = self.it.peekThreeBytes().?;
|
||||||
|
|
||||||
|
switch (cp3 & 0x00_FF_FF_FF) {
|
||||||
|
inline @intFromEnum(Punctuator.@"..."),
|
||||||
|
@intFromEnum(Punctuator.@"<<="),
|
||||||
|
@intFromEnum(Punctuator.@">>="),
|
||||||
|
=> |p| {
|
||||||
|
self.it.ptr += 3;
|
||||||
|
self.it.col += 3;
|
||||||
|
return .{
|
||||||
|
.punctuator = @enumFromInt(p),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cp3 & 0x00_00_FF_FF) {
|
||||||
|
inline @intFromEnum(Punctuator.@"--"),
|
||||||
|
@intFromEnum(Punctuator.@"-="),
|
||||||
|
@intFromEnum(Punctuator.@"->"),
|
||||||
|
@intFromEnum(Punctuator.@"!="),
|
||||||
|
@intFromEnum(Punctuator.@"*="),
|
||||||
|
@intFromEnum(Punctuator.@"/="),
|
||||||
|
@intFromEnum(Punctuator.@"&&"),
|
||||||
|
@intFromEnum(Punctuator.@"&="),
|
||||||
|
@intFromEnum(Punctuator.@"##"),
|
||||||
|
@intFromEnum(Punctuator.@"%="),
|
||||||
|
@intFromEnum(Punctuator.@"^="),
|
||||||
|
@intFromEnum(Punctuator.@"++"),
|
||||||
|
@intFromEnum(Punctuator.@"+="),
|
||||||
|
@intFromEnum(Punctuator.@"<<"),
|
||||||
|
@intFromEnum(Punctuator.@"<="),
|
||||||
|
@intFromEnum(Punctuator.@"=="),
|
||||||
|
@intFromEnum(Punctuator.@">="),
|
||||||
|
@intFromEnum(Punctuator.@">>"),
|
||||||
|
@intFromEnum(Punctuator.@"|="),
|
||||||
|
@intFromEnum(Punctuator.@"||"),
|
||||||
|
=> |p| {
|
||||||
|
self.it.ptr += 2;
|
||||||
|
self.it.col += 2;
|
||||||
|
return .{
|
||||||
|
.punctuator = @enumFromInt(p),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cp3 & 0x00_00_00_FF) {
|
||||||
|
inline @intFromEnum(Punctuator.@"-"),
|
||||||
|
@intFromEnum(Punctuator.@","),
|
||||||
|
@intFromEnum(Punctuator.@";"),
|
||||||
|
@intFromEnum(Punctuator.@":"),
|
||||||
|
@intFromEnum(Punctuator.@"!"),
|
||||||
|
@intFromEnum(Punctuator.@"?"),
|
||||||
|
@intFromEnum(Punctuator.@"."),
|
||||||
|
@intFromEnum(Punctuator.@"("),
|
||||||
|
@intFromEnum(Punctuator.@")"),
|
||||||
|
@intFromEnum(Punctuator.@"["),
|
||||||
|
@intFromEnum(Punctuator.@"]"),
|
||||||
|
@intFromEnum(Punctuator.@"{"),
|
||||||
|
@intFromEnum(Punctuator.@"}"),
|
||||||
|
@intFromEnum(Punctuator.@"*"),
|
||||||
|
@intFromEnum(Punctuator.@"/"),
|
||||||
|
@intFromEnum(Punctuator.@"&"),
|
||||||
|
@intFromEnum(Punctuator.@"#"),
|
||||||
|
@intFromEnum(Punctuator.@"%"),
|
||||||
|
@intFromEnum(Punctuator.@"^"),
|
||||||
|
@intFromEnum(Punctuator.@"+"),
|
||||||
|
@intFromEnum(Punctuator.@"<"),
|
||||||
|
@intFromEnum(Punctuator.@"="),
|
||||||
|
@intFromEnum(Punctuator.@">"),
|
||||||
|
@intFromEnum(Punctuator.@"|"),
|
||||||
|
@intFromEnum(Punctuator.@"~"),
|
||||||
|
=> |p| {
|
||||||
|
self.it.ptr += 1;
|
||||||
|
self.it.col += 1;
|
||||||
|
return .{
|
||||||
|
.punctuator = @enumFromInt(p),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.InvalidToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peekCodepointSkipLineContinuation(self: *Self) !?u21 {
|
||||||
|
while (self.skipLineContinuation()) {}
|
||||||
|
const cp = try self.it.peekCodepoint();
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Line continuation is defined as a backslash followed imediatelly by LF or
|
||||||
|
/// CRLF. Return whether a line continuation was encountered and therefore
|
||||||
|
/// skipped past.
|
||||||
|
fn skipLineContinuation(self: *Self) bool {
|
||||||
|
if (self.it.peekThreeBytes()) |b| {
|
||||||
|
@branchHint(.likely);
|
||||||
|
if (b & 0x00_00_FF_FF == Punctuator.line_continuation_lf) {
|
||||||
|
@branchHint(.unlikely);
|
||||||
|
self.it.ptr += 2;
|
||||||
|
self.it.line += 1;
|
||||||
|
self.it.col = 1;
|
||||||
|
return true;
|
||||||
|
} else if (b & 0x00_FF_FF_FF == Punctuator.line_continuation_crlf) {
|
||||||
|
@branchHint(.unlikely);
|
||||||
|
self.it.ptr += 3;
|
||||||
|
self.it.line += 1;
|
||||||
|
self.it.col = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skipWhitespace(self: *Self) !void {
|
||||||
|
while (try self.peekCodepointSkipLineContinuation()) |cp| {
|
||||||
|
switch (cp) {
|
||||||
|
// <Character Tabulation> (HT, TAB)
|
||||||
|
0x0009,
|
||||||
|
// <End of Line> (EOL, LF, NL)
|
||||||
|
0x000A,
|
||||||
|
// <Line Tabulation> (VT)
|
||||||
|
0x000B,
|
||||||
|
// <Form Feed> (FF)
|
||||||
|
0x000C,
|
||||||
|
// <Carriage Return> (CR)
|
||||||
|
0x000D,
|
||||||
|
// Space (SP)
|
||||||
|
0x0020,
|
||||||
|
=> self.it.advanceCodepoint(cp),
|
||||||
|
else => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isIdentifierMiddle(code_point: u21) bool {
|
||||||
|
// zig fmt: off
|
||||||
|
return code_point >= '0' and code_point <= '9'
|
||||||
|
or code_point >= 'A' and code_point <= 'Z'
|
||||||
|
or code_point == '_'
|
||||||
|
or code_point >= 'a' and code_point <= 'z'
|
||||||
|
or code_point >= 128;
|
||||||
|
// zig fmt: on
|
||||||
|
}
|
||||||
99
packages/cjit/src/tokens/Utf8Iterator.zig
Normal file
99
packages/cjit/src/tokens/Utf8Iterator.zig
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
str: []const u8,
|
||||||
|
ptr: usize,
|
||||||
|
line: usize,
|
||||||
|
col: usize,
|
||||||
|
|
||||||
|
pub const State = struct {
|
||||||
|
ptr: usize,
|
||||||
|
line: usize,
|
||||||
|
col: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(str: []const u8) Self {
|
||||||
|
return .{
|
||||||
|
.str = str,
|
||||||
|
.ptr = 0,
|
||||||
|
.line = 1,
|
||||||
|
.col = 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(self: Self) State {
|
||||||
|
return .{
|
||||||
|
.ptr = self.ptr,
|
||||||
|
.line = self.line,
|
||||||
|
.col = self.col,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore(self: *Self, state: State) void {
|
||||||
|
self.ptr = state.ptr;
|
||||||
|
self.line = state.line;
|
||||||
|
self.col = state.col;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peekByte(self: *Self) ?u8 {
|
||||||
|
if (self.ptr >= self.str.len) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.str[self.ptr];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peekCodepoint(self: Self) !?u21 {
|
||||||
|
if (self.ptr >= self.str.len) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cp_len = std.unicode.utf8ByteSequenceLength(self.str[self.ptr]) catch return error.InvalidUtf8;
|
||||||
|
if (self.ptr + cp_len > self.str.len) return error.InvalidUtf8;
|
||||||
|
|
||||||
|
const cp_slice = self.str[self.ptr .. self.ptr + cp_len];
|
||||||
|
const cp = std.unicode.utf8Decode(cp_slice) catch return error.InvalidUtf8;
|
||||||
|
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peekThreeBytes(self: Self) ?u32 {
|
||||||
|
var bytes: [3]u8 = .{ 0, 0, 0 };
|
||||||
|
|
||||||
|
const bytes_left = self.str.len - self.ptr;
|
||||||
|
sw: switch (bytes_left) {
|
||||||
|
0 => return null,
|
||||||
|
1 => {
|
||||||
|
bytes[0] = self.str[self.ptr];
|
||||||
|
return @as(u24, @bitCast(bytes));
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
bytes[1] = self.str[self.ptr + 1];
|
||||||
|
continue :sw 1;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
bytes[2] = self.str[self.ptr + 2];
|
||||||
|
continue :sw 2;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call with value returned by `peekCodepoint`.
|
||||||
|
pub fn advanceCodepoint(self: *Self, cp: u21) void {
|
||||||
|
std.debug.assert(blk: {
|
||||||
|
const actual_cp = self.peekCodepoint() catch break :blk false;
|
||||||
|
break :blk cp == actual_cp;
|
||||||
|
});
|
||||||
|
|
||||||
|
const cp_len = std.unicode.utf8CodepointSequenceLength(cp) catch unreachable;
|
||||||
|
|
||||||
|
self.ptr += cp_len;
|
||||||
|
|
||||||
|
if (cp == '\n') {
|
||||||
|
self.line += 1;
|
||||||
|
// NOTE Columns start as 1, it will be incremented below.
|
||||||
|
self.col = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.col += 1;
|
||||||
|
}
|
||||||
201
packages/cjit/src/types.zig
Normal file
201
packages/cjit/src/types.zig
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Type = union(enum) {
|
||||||
|
signed_char: void,
|
||||||
|
signed_short: void,
|
||||||
|
signed_int: void,
|
||||||
|
signed_long: void,
|
||||||
|
signed_long_long: void,
|
||||||
|
|
||||||
|
unsigned_char: void,
|
||||||
|
unsigned_short: void,
|
||||||
|
unsigned_int: void,
|
||||||
|
unsigned_long: void,
|
||||||
|
unsigned_long_long: void,
|
||||||
|
|
||||||
|
float: void,
|
||||||
|
double: void,
|
||||||
|
long_double: void,
|
||||||
|
|
||||||
|
void: void,
|
||||||
|
noreturn: void,
|
||||||
|
char: void,
|
||||||
|
bool: void,
|
||||||
|
|
||||||
|
@"enum": *const Enum,
|
||||||
|
@"struct": *const Struct,
|
||||||
|
@"union": *const Union,
|
||||||
|
array: *const Array,
|
||||||
|
function: *const Function,
|
||||||
|
pointer: *const Pointer,
|
||||||
|
|
||||||
|
pub fn sizeOf(self: Type) ?usize {
|
||||||
|
return switch (self) {
|
||||||
|
.signed_char => 1,
|
||||||
|
.signed_short => 2,
|
||||||
|
.signed_int => 4,
|
||||||
|
.signed_long => 8,
|
||||||
|
.signed_long_long => 8,
|
||||||
|
|
||||||
|
.unsigned_char => 1,
|
||||||
|
.unsigned_short => 2,
|
||||||
|
.unsigned_int => 4,
|
||||||
|
.unsigned_long => 8,
|
||||||
|
.unsigned_long_long => 8,
|
||||||
|
|
||||||
|
.float => 4,
|
||||||
|
.double => 8,
|
||||||
|
.long_double => 8,
|
||||||
|
|
||||||
|
.void => null,
|
||||||
|
.noreturn => null,
|
||||||
|
.char => 1,
|
||||||
|
.bool => 1,
|
||||||
|
|
||||||
|
.@"enum" => 4,
|
||||||
|
.@"struct" => |s| s.size,
|
||||||
|
.@"union" => |u| u.size,
|
||||||
|
.array => |a| if (a.length) |l| (if (sizeOf(a.child)) |c| l * c else null) else 8,
|
||||||
|
.function => null,
|
||||||
|
.pointer => 8,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alignOf(self: Type) ?u16 {
|
||||||
|
return switch (self) {
|
||||||
|
.signed_char => 1,
|
||||||
|
.signed_short => 2,
|
||||||
|
.signed_int => 4,
|
||||||
|
.signed_long => 8,
|
||||||
|
.signed_long_long => 8,
|
||||||
|
|
||||||
|
.unsigned_char => 1,
|
||||||
|
.unsigned_short => 2,
|
||||||
|
.unsigned_int => 4,
|
||||||
|
.unsigned_long => 8,
|
||||||
|
.unsigned_long_long => 8,
|
||||||
|
|
||||||
|
.float => 4,
|
||||||
|
.double => 8,
|
||||||
|
.long_double => 8,
|
||||||
|
|
||||||
|
.void => null,
|
||||||
|
.noreturn => null,
|
||||||
|
.char => 1,
|
||||||
|
.bool => 1,
|
||||||
|
|
||||||
|
.@"enum" => 4,
|
||||||
|
.@"struct" => |s| s.@"align",
|
||||||
|
.@"union" => |u| u.@"align",
|
||||||
|
.array => |a| if (a.length != null) alignOf(a.child) else 8,
|
||||||
|
.function => null,
|
||||||
|
.pointer => 8,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Enum = struct {
|
||||||
|
name: []const u8,
|
||||||
|
constants: []const EnumConstant,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const EnumConstant = struct {
|
||||||
|
name: []const u8,
|
||||||
|
value: i32,
|
||||||
|
inferred: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Struct = struct {
|
||||||
|
name: []const u8,
|
||||||
|
fields: []const StructField,
|
||||||
|
size: usize,
|
||||||
|
@"align": u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const StructField = struct {
|
||||||
|
name: []const u8,
|
||||||
|
type: Type,
|
||||||
|
offset: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Union = struct {
|
||||||
|
name: []const u8,
|
||||||
|
fields: []const UnionField,
|
||||||
|
size: usize,
|
||||||
|
@"align": u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const UnionField = struct {
|
||||||
|
name: []const u8,
|
||||||
|
type: Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Array = struct {
|
||||||
|
child: Type,
|
||||||
|
length: ?usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Function = struct {
|
||||||
|
arguments: []const FunctionArgument,
|
||||||
|
@"return": Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FunctionArgument = struct {
|
||||||
|
name: []const u8,
|
||||||
|
type: Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Pointer = struct {
|
||||||
|
child: Type,
|
||||||
|
@"const": bool,
|
||||||
|
@"volatile": bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn isCompatible(comptime ZigType: type, c_type: Type) bool {
|
||||||
|
return switch (@typeInfo(ZigType)) {
|
||||||
|
.type => false,
|
||||||
|
.void => c_type == .void,
|
||||||
|
.bool => c_type == .bool,
|
||||||
|
.noreturn => c_type == .noreturn,
|
||||||
|
.int => |zig_int| switch (c_type) {
|
||||||
|
.signed_char => zig_int.signedness == .signed and zig_int.bits == 8,
|
||||||
|
.signed_short => zig_int.signedness == .signed and zig_int.bits == 16,
|
||||||
|
.signed_int => zig_int.signedness == .signed and zig_int.bits == 32,
|
||||||
|
.signed_long => zig_int.signedness == .signed and zig_int.bits == 64,
|
||||||
|
.signed_long_long => zig_int.signedness == .signed and zig_int.bits == 64,
|
||||||
|
|
||||||
|
.unsigned_char => zig_int.signedness == .unsigned and zig_int.bits == 8,
|
||||||
|
.unsigned_short => zig_int.signedness == .unsigned and zig_int.bits == 16,
|
||||||
|
.unsigned_int => zig_int.signedness == .unsigned and zig_int.bits == 32,
|
||||||
|
.unsigned_long => zig_int.signedness == .unsigned and zig_int.bits == 64,
|
||||||
|
.unsigned_long_long => zig_int.signedness == .unsigned and zig_int.bits == 64,
|
||||||
|
|
||||||
|
.char => zig_int.bits = 8,
|
||||||
|
|
||||||
|
else => false,
|
||||||
|
},
|
||||||
|
.float => |zig_float| switch (c_type) {
|
||||||
|
.float => zig_float.bits = 32,
|
||||||
|
.double => zig_float.bits = 64,
|
||||||
|
.long_double => zig_float.bits = 64,
|
||||||
|
},
|
||||||
|
.pointer => @compileError("TODO"),
|
||||||
|
.array => @compileError("TODO"),
|
||||||
|
.@"struct" => @compileError("TODO"),
|
||||||
|
.comptime_float => false,
|
||||||
|
.comptime_int => false,
|
||||||
|
.undefined => true,
|
||||||
|
.null => @compileError("TODO"),
|
||||||
|
.optional => @compileError("TODO"),
|
||||||
|
.error_union => false,
|
||||||
|
.error_set => false,
|
||||||
|
.@"enum" => @compileError("TODO"),
|
||||||
|
.@"union" => @compileError("TODO"),
|
||||||
|
.@"fn" => false,
|
||||||
|
.@"opaque" => @compileError("TODO"),
|
||||||
|
.frame => false,
|
||||||
|
.@"anyframe" => false,
|
||||||
|
.vector => false,
|
||||||
|
.enum_literal => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
445
packages/cjit/src/x86_64.zig
Normal file
445
packages/cjit/src/x86_64.zig
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const tokens = @import("tokens.zig");
|
||||||
|
const types = @import("types.zig");
|
||||||
|
|
||||||
|
const Location = Runtime.Location;
|
||||||
|
const Runtime = @import("Runtime.zig");
|
||||||
|
const StackValue = @import("StackValue.zig");
|
||||||
|
const Type = types.Type;
|
||||||
|
|
||||||
|
pub const Register = union(enum) {
|
||||||
|
gpr: Gpr,
|
||||||
|
xmm: Xmm,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Gpr = enum(u4) {
|
||||||
|
rax = 0,
|
||||||
|
rcx = 1,
|
||||||
|
rdx = 2,
|
||||||
|
rbx = 3,
|
||||||
|
rsp = 4,
|
||||||
|
rbp = 5,
|
||||||
|
rsi = 6,
|
||||||
|
rdi = 7,
|
||||||
|
r8 = 8,
|
||||||
|
r9 = 9,
|
||||||
|
r10 = 10,
|
||||||
|
r11 = 11,
|
||||||
|
r12 = 12,
|
||||||
|
r13 = 13,
|
||||||
|
r14 = 14,
|
||||||
|
r15 = 15,
|
||||||
|
|
||||||
|
pub fn reg(self: Gpr) u3 {
|
||||||
|
return @truncate(@intFromEnum(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x(self: Gpr) bool {
|
||||||
|
return @intFromEnum(self) & 0b1000 != 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Xmm = enum(u4) {
|
||||||
|
xmm0 = 0,
|
||||||
|
xmm1 = 1,
|
||||||
|
xmm2 = 2,
|
||||||
|
xmm3 = 3,
|
||||||
|
xmm4 = 4,
|
||||||
|
xmm5 = 5,
|
||||||
|
xmm6 = 6,
|
||||||
|
xmm7 = 7,
|
||||||
|
xmm8 = 8,
|
||||||
|
xmm9 = 9,
|
||||||
|
xmm10 = 10,
|
||||||
|
xmm11 = 11,
|
||||||
|
xmm12 = 12,
|
||||||
|
xmm13 = 13,
|
||||||
|
xmm14 = 14,
|
||||||
|
xmm15 = 15,
|
||||||
|
|
||||||
|
pub fn reg(self: Xmm) u3 {
|
||||||
|
return @truncate(@intFromEnum(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x(self: Xmm) bool {
|
||||||
|
return @intFromEnum(self) & 0b1000 != 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- EMIT HELPERS ------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const Rex = packed struct(u8) {
|
||||||
|
b: bool = false,
|
||||||
|
x: bool = false,
|
||||||
|
r: bool = false,
|
||||||
|
w: bool = false,
|
||||||
|
/// MUST always be the default value
|
||||||
|
prefix: u4 = 0b0100,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ModRM = packed struct(u8) {
|
||||||
|
rm: u3,
|
||||||
|
reg: u3,
|
||||||
|
mod: u2,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SIB = packed struct(u8) {
|
||||||
|
base: u3,
|
||||||
|
index: u3,
|
||||||
|
scale: u2,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn op16(rt: *Runtime) !void {
|
||||||
|
try rt.sections.text.writeByte(0x66, rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rex(rt: *Runtime, value: Rex) !void {
|
||||||
|
try rt.sections.text.writeByte(@bitCast(value), rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn op(rt: *Runtime, value: u8) !void {
|
||||||
|
try rt.sections.text.writeByte(value, rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn op2(rt: *Runtime, v0: u8, v1: u8) !void {
|
||||||
|
const bytes: [2]u8 = .{ v0, v1 };
|
||||||
|
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn op3(rt: *Runtime, v0: u8, v1: u8, v2: u8) !void {
|
||||||
|
const bytes: [3]u8 = .{ v0, v1, v2 };
|
||||||
|
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modrm(rt: *Runtime, value: ModRM) !void {
|
||||||
|
try rt.sections.text.writeByte(@bitCast(value), rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sib(rt: *Runtime, value: SIB) !void {
|
||||||
|
try rt.sections.text.writeByte(@bitCast(value), rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn imm8(rt: *Runtime, value: u8) !void {
|
||||||
|
try rt.sections.text.writeByte(value, rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn imm16(rt: *Runtime, value: u16) !void {
|
||||||
|
var bytes: [2]u8 = undefined;
|
||||||
|
std.mem.writeInt(u16, &bytes, value, .little);
|
||||||
|
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn imm32(rt: *Runtime, value: u32) !void {
|
||||||
|
var bytes: [4]u8 = undefined;
|
||||||
|
std.mem.writeInt(u32, &bytes, value, .little);
|
||||||
|
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn imm64(rt: *Runtime, value: u64) !void {
|
||||||
|
var bytes: [8]u8 = undefined;
|
||||||
|
std.mem.writeInt(u64, &bytes, value, .little);
|
||||||
|
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disp8(rt: *Runtime, value: i8) !void {
|
||||||
|
try rt.sections.text.writeByte(@bitCast(value), rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disp32(rt: *Runtime, value: i32) !void {
|
||||||
|
var bytes: [4]u8 = undefined;
|
||||||
|
std.mem.writeInt(i32, &bytes, value, .little);
|
||||||
|
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const Operation = enum {
|
||||||
|
add,
|
||||||
|
bit_and,
|
||||||
|
bit_not,
|
||||||
|
bit_or,
|
||||||
|
bit_xor,
|
||||||
|
bool_and,
|
||||||
|
bool_not,
|
||||||
|
bool_or,
|
||||||
|
cmp_eq,
|
||||||
|
cmp_gt,
|
||||||
|
cmp_gte,
|
||||||
|
cmp_lt,
|
||||||
|
cmp_lte,
|
||||||
|
cmp_neq,
|
||||||
|
div,
|
||||||
|
mod,
|
||||||
|
mul,
|
||||||
|
neg,
|
||||||
|
sar,
|
||||||
|
shl,
|
||||||
|
shr,
|
||||||
|
sub,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn opFloat(rt: *Runtime, operation: Operation) !void {
|
||||||
|
_ = rt;
|
||||||
|
_ = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opInt(rt: *Runtime, operation: Operation) !void {
|
||||||
|
_ = rt;
|
||||||
|
_ = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cvtIntToFloat(rt: *Runtime) !void {
|
||||||
|
_ = rt;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cvtFloatToInt(rt: *Runtime) !void {
|
||||||
|
_ = rt;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cvtFloatToFloat(rt: *Runtime, target: Type) !void {
|
||||||
|
const top = vsTop(rt);
|
||||||
|
|
||||||
|
switch (top.c_type) {
|
||||||
|
.float => switch (target) {
|
||||||
|
.float => {
|
||||||
|
// do nothing
|
||||||
|
},
|
||||||
|
.double, .long_double => {
|
||||||
|
// CVTSS2SD xmm1, xmm2/m32
|
||||||
|
// F3 0F 5A /r
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
},
|
||||||
|
.double, .long_double => switch (target) {
|
||||||
|
.float => {
|
||||||
|
// CVTSD2SS xmm1, xmm2/m64
|
||||||
|
// F2 0F 5A /r
|
||||||
|
},
|
||||||
|
.double, .long_double => {
|
||||||
|
// do nothing
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- LOAD AND STORE ----------------------------------------------------------
|
||||||
|
|
||||||
|
/// Load value into register. The value must be 1, 2, 4 or 8 bytes long.
|
||||||
|
pub fn load(rt: *Runtime, dst_register: Register, src_value: *const StackValue) !void {
|
||||||
|
const size = src_value.c_type.sizeOf().?;
|
||||||
|
std.debug.assert(size == 1 or size == 2 or size == 4 or size == 8);
|
||||||
|
|
||||||
|
switch (src_value.value) {
|
||||||
|
.register => |src_register| {
|
||||||
|
if (std.meta.eql(dst_register, src_register)) return;
|
||||||
|
// TODO
|
||||||
|
},
|
||||||
|
.constant => |constant| switch (dst_register) {
|
||||||
|
.gpr => |dest_gpr| switch (size) {
|
||||||
|
1 => {
|
||||||
|
// MOV r8, imm8
|
||||||
|
// B0+ rb ib
|
||||||
|
if (@intFromEnum(dest_gpr) >= 4) {
|
||||||
|
// NOTE spl, bpl, sil and dil need an empty REX prefix,
|
||||||
|
// otherwise ah, ch, dh and bh would be used.
|
||||||
|
try rex(rt, .{ .r = dest_gpr.x() });
|
||||||
|
}
|
||||||
|
try op(rt, 0xB0 | @as(u8, dest_gpr.reg()));
|
||||||
|
try imm8(rt, @truncate(constant));
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
// MOV r16, imm16
|
||||||
|
// B8+ rw iw
|
||||||
|
try op16(rt);
|
||||||
|
if (@intFromEnum(dest_gpr) >= 8) {
|
||||||
|
try rex(rt, .{ .r = dest_gpr.x() });
|
||||||
|
}
|
||||||
|
try op(rt, 0xB8 | @as(u8, dest_gpr.reg()));
|
||||||
|
try imm16(rt, @truncate(constant));
|
||||||
|
},
|
||||||
|
4 => {
|
||||||
|
// MOV r32, imm32
|
||||||
|
// B8+ rd id
|
||||||
|
if (@intFromEnum(dest_gpr) >= 8) {
|
||||||
|
try rex(rt, .{ .r = dest_gpr.x() });
|
||||||
|
}
|
||||||
|
try op(rt, 0xB8 | @as(u8, dest_gpr.reg()));
|
||||||
|
try imm32(rt, @truncate(constant));
|
||||||
|
},
|
||||||
|
8 => {
|
||||||
|
// MOV r64, imm64
|
||||||
|
// REX.W + B8+ rd io
|
||||||
|
try rex(rt, .{ .r = dest_gpr.x(), .w = true });
|
||||||
|
try op(rt, 0xB8 | @as(u8, dest_gpr.reg()));
|
||||||
|
try imm64(rt, constant);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
},
|
||||||
|
.xmm => |dest_xmm| {
|
||||||
|
var bytes: [8]u8 = undefined;
|
||||||
|
std.mem.writeInt(u64, &bytes, @truncate(constant), .little);
|
||||||
|
const data = bytes[0..size];
|
||||||
|
|
||||||
|
// MOVD xmm, r/m32
|
||||||
|
// 66 0F 6E /r
|
||||||
|
|
||||||
|
// MOVQ xmm, r/m64
|
||||||
|
// 66 REX.W 0F 6E /r
|
||||||
|
|
||||||
|
try op(rt, 0x66);
|
||||||
|
if (@intFromEnum(dest_xmm) >= 8 or size == 8) {
|
||||||
|
try rex(rt, .{ .r = dest_xmm.x(), .w = size == 8 });
|
||||||
|
}
|
||||||
|
try op2(rt, 0x0F, 0x6E);
|
||||||
|
// [rip + disp32]
|
||||||
|
try modrm(rt, .{ .mod = 0b00, .reg = dest_xmm.reg(), .rm = Gpr.rbp.reg() });
|
||||||
|
try allocRodataDisp32(rt, data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.symbol => |symbol| {},
|
||||||
|
.stack => |disp| {
|
||||||
|
const disp_small = std.math.minInt(i8) <= disp and disp <= std.math.maxInt(i8);
|
||||||
|
const rex_prefix: Rex = .{
|
||||||
|
.r = switch (dst_register) {
|
||||||
|
.gpr => |dest_gpr| dest_gpr.x(),
|
||||||
|
.xmm => |dest_xmm| dest_xmm.x(),
|
||||||
|
},
|
||||||
|
.w = size == 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mod: u2 = if (disp_small) 0b01 else 0b10;
|
||||||
|
const reg: u3 = switch (dst_register) {
|
||||||
|
.gpr => |dest_gpr| dest_gpr.reg(),
|
||||||
|
.xmm => |dest_xmm| dest_xmm.reg(),
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (dst_register) {
|
||||||
|
.gpr => |dest_gpr| {
|
||||||
|
switch (size) {
|
||||||
|
1 => {
|
||||||
|
// MOV r8, r/m8
|
||||||
|
// 8A /r
|
||||||
|
if (@intFromEnum(dest_gpr) >= 4) {
|
||||||
|
// NOTE spl, bpl, sil and dil need an empty REX prefix,
|
||||||
|
// otherwise ah, ch, dh and bh would be used.
|
||||||
|
try rex(rt, rex_prefix);
|
||||||
|
}
|
||||||
|
try op(rt, 0x8A);
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
// MOV r16, r/m16
|
||||||
|
// 8B /r
|
||||||
|
try op16(rt);
|
||||||
|
if (@intFromEnum(dest_gpr) >= 8) {
|
||||||
|
try rex(rt, rex_prefix);
|
||||||
|
}
|
||||||
|
try op(rt, 0x8B);
|
||||||
|
},
|
||||||
|
4 => {
|
||||||
|
// MOV r32, r/m32
|
||||||
|
// 8B /r
|
||||||
|
if (@intFromEnum(dest_gpr) >= 8) {
|
||||||
|
try rex(rt, rex_prefix);
|
||||||
|
}
|
||||||
|
try op(rt, 0x8B);
|
||||||
|
},
|
||||||
|
8 => {
|
||||||
|
// MOV r64, r/m64
|
||||||
|
// REX.W + 8B /r
|
||||||
|
try rex(rt, rex_prefix);
|
||||||
|
try op(rt, 0x8B);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.xmm => |dest_xmm| {
|
||||||
|
// MOVD xmm, r/m32
|
||||||
|
// 66 0F 6E /r
|
||||||
|
|
||||||
|
// MOVQ xmm, r/m64
|
||||||
|
// 66 REX.W 0F 6E /r
|
||||||
|
|
||||||
|
try op(rt, 0x66);
|
||||||
|
if (@intFromEnum(dest_xmm) >= 8 or size == 8) {
|
||||||
|
try rex(rt, rex_prefix);
|
||||||
|
}
|
||||||
|
try op2(rt, 0x0F, 0x6E);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// [rbp + disp8/32]
|
||||||
|
try modrm(rt, .{ .mod = mod, .reg = reg, .rm = Gpr.rbp.reg() });
|
||||||
|
if (disp_small) {
|
||||||
|
try disp8(rt, @intCast(disp));
|
||||||
|
} else {
|
||||||
|
try disp32(rt, disp);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.cpu_flags => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store register into value.
|
||||||
|
pub fn store(rt: *Runtime, dst_value: *const StackValue, src_register: Register) !void {
|
||||||
|
const size = dst_value.c_type.sizeOf().?;
|
||||||
|
std.debug.assert(size == 1 or size == 2 or size == 4 or size == 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- STACK OPERATIONS --------------------------------------------------------
|
||||||
|
|
||||||
|
/// Caller asserts that the stack is not empty.
|
||||||
|
pub fn vsTop(rt: *Runtime) *StackValue {
|
||||||
|
const vs = rt.virtual_stack.items;
|
||||||
|
return &vs[vs.len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Caller asserts that the stack has at least two values.
|
||||||
|
pub fn vsSwap(rt: *Runtime) void {
|
||||||
|
const vs = rt.virtual_stack.items;
|
||||||
|
std.mem.swap(StackValue, &vs[vs.len - 1], &vs[vs.len - 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure the top of the stack is in an XMM register. Returns the id of the
|
||||||
|
/// register. Caller asserts that the stack is not empty.
|
||||||
|
pub fn vsEnsureXmm(rt: *Runtime) Xmm {
|
||||||
|
const top = vsTop(rt);
|
||||||
|
switch (top.value) {
|
||||||
|
.register => {},
|
||||||
|
.constant => {},
|
||||||
|
.symbol => {},
|
||||||
|
.stack => {},
|
||||||
|
.cpu_flags => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure the top of the stack is in a GPR register. Returns the id of the
|
||||||
|
/// register. Caller asserts that teh stack is not empty.
|
||||||
|
pub fn vsEnsureGpr(rt: *Runtime) Gpr {
|
||||||
|
const top = vsTop(rt);
|
||||||
|
switch (top.value) {
|
||||||
|
.register => {},
|
||||||
|
.constant => {},
|
||||||
|
.symbol => {},
|
||||||
|
.stack => {},
|
||||||
|
.cpu_flags => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DATA ALLOCATIONS --------------------------------------------------------
|
||||||
|
|
||||||
|
/// Reserve `data.len` bytes in rodata and fill it with `data`, then add a
|
||||||
|
/// placeholder dips32 part of an instruction and a relocation entry for it.
|
||||||
|
pub fn allocRodataDisp32(rt: *Runtime, data: []const u8) !void {
|
||||||
|
const addr = rt.sections.text.data.items.len;
|
||||||
|
const location: Location = .{ .rodata = rt.sections.rodata.data.items.len };
|
||||||
|
|
||||||
|
try rt.sections.rodata.writeBytes(data, rt.allocator);
|
||||||
|
try disp32(rt, 0);
|
||||||
|
try rt.relocation_table.append(rt.allocator, .{
|
||||||
|
.addr = addr,
|
||||||
|
.location = location,
|
||||||
|
.type = .rip_disp32,
|
||||||
|
});
|
||||||
|
}
|
||||||
32
packages/cjit/test/root.zig
Normal file
32
packages/cjit/test/root.zig
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const cjit = @import("cjit");
|
||||||
|
|
||||||
|
fn add(a: i32, b: i32) callconv(cjit.call) i32 {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
var rt: cjit.Runtime = try .init(std.testing.allocator);
|
||||||
|
defer rt.deinit();
|
||||||
|
|
||||||
|
try rt.compile("test.c",
|
||||||
|
\\int add(int a, int b);
|
||||||
|
\\
|
||||||
|
\\int add_one(int x)
|
||||||
|
\\{
|
||||||
|
\\ return add(x, 1);
|
||||||
|
\\}
|
||||||
|
);
|
||||||
|
|
||||||
|
try rt.setSymbol(fn (i32, i32) callconv(cjit.call) i32, "add", &add);
|
||||||
|
try rt.link();
|
||||||
|
|
||||||
|
const add_one = rt.getSymbol(fn (i32) callconv(cjit.call) i32, "add_one").?;
|
||||||
|
|
||||||
|
try std.testing.expectEqual(-9, add_one(-10));
|
||||||
|
try std.testing.expectEqual(11, add_one(10));
|
||||||
|
|
||||||
|
const add_ptr = rt.getSymbol(fn (i32, i32) callconv(cjit.call) i32, "add").?;
|
||||||
|
|
||||||
|
try std.testing.expectEqual(add_ptr, &add);
|
||||||
|
}
|
||||||
@@ -195,7 +195,7 @@ const import = struct {
|
|||||||
no_add: bool = false,
|
no_add: bool = false,
|
||||||
/// Internal use.
|
/// Internal use.
|
||||||
no_exotic: bool = false,
|
no_exotic: bool = false,
|
||||||
_pad14: u18 = 0,
|
_pad18: u14 = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const JS_EVAL = packed struct(u32) {
|
pub const JS_EVAL = packed struct(u32) {
|
||||||
@@ -599,7 +599,7 @@ const import = struct {
|
|||||||
|
|
||||||
pub const Allocator = struct {
|
pub const Allocator = struct {
|
||||||
const alignment_bytes = 16;
|
const alignment_bytes = 16;
|
||||||
const alignment: std.mem.Alignment = .fromByteUnits(alignment_bytes);
|
const alignment = std.mem.Alignment.fromByteUnits(alignment_bytes);
|
||||||
|
|
||||||
const AllocationPtr = *align(alignment_bytes) anyopaque;
|
const AllocationPtr = *align(alignment_bytes) anyopaque;
|
||||||
const AllocationSlice = []align(alignment_bytes) u8;
|
const AllocationSlice = []align(alignment_bytes) u8;
|
||||||
@@ -1168,7 +1168,7 @@ pub const Value = extern struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getClassId(self: Value) ClassId {
|
pub fn getClassId(self: Value) ClassId {
|
||||||
return .{ .class_id = import.JS_GetClassID(self) };
|
return .{ .class_id = import.JS_GetClassID(self.value) };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1182,7 +1182,7 @@ pub const ClassId = extern struct {
|
|||||||
pub const invalid: ClassId = .{ .class_id = 0 };
|
pub const invalid: ClassId = .{ .class_id = 0 };
|
||||||
|
|
||||||
pub fn new() ClassId {
|
pub fn new() ClassId {
|
||||||
var class_id: import.JSClassId = 0;
|
var class_id: import.JSClassID = 0;
|
||||||
return .{ .class_id = import.JS_NewClassID(&class_id) };
|
return .{ .class_id = import.JS_NewClassID(&class_id) };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,38 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const vm_dep = b.dependency("vecmath", .{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const vm_module = vm_dep.module("vecmath");
|
|
||||||
|
|
||||||
const root_module = b.addModule("media", .{
|
const vm_dep = b.dependency("vecmath", .{
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.target = target,
|
||||||
});
|
});
|
||||||
root_module.addImport("vecmath", vm_module);
|
const vm_mod = vm_dep.module("vecmath");
|
||||||
|
|
||||||
|
const mod = b.addModule("media", .{
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.link_libc = true,
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "vecmath", .module = vm_mod },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
mod.addCSourceFile(.{
|
||||||
|
.file = b.path("src/stbi/stb_image.c"),
|
||||||
|
.flags = &.{
|
||||||
|
"-std=c17",
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra",
|
||||||
|
},
|
||||||
|
.language = .c,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mod_tests = b.addTest(.{
|
||||||
|
.root_module = mod,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.{
|
.{
|
||||||
.name = .media,
|
.name = .media,
|
||||||
.version = "0.0.0",
|
.version = "0.0.0",
|
||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.16.0",
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"src",
|
"src",
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
|||||||
BIN
packages/media/jpeg-specification.pdf
(Stored with Git LFS)
Normal file
BIN
packages/media/jpeg-specification.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
packages/media/png-specification.pdf
(Stored with Git LFS)
Normal file
BIN
packages/media/png-specification.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
25
packages/media/src/Format.zig
Normal file
25
packages/media/src/Format.zig
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
/// The number of bytes necessary to confirm that a buffer contains this media
|
||||||
|
/// format.
|
||||||
|
magic_length: usize,
|
||||||
|
/// The number of bytes necessary to confirm that a buffer contains this media
|
||||||
|
/// format and to extract metadata stored at the beggining, if any. Must be at
|
||||||
|
/// least as big as `magic_length`.
|
||||||
|
info_length: usize,
|
||||||
|
/// The file extension usually associated with this media format; all in
|
||||||
|
/// lowercase and without the dot character.
|
||||||
|
extension: []const u8,
|
||||||
|
/// The media type (aka MIME type or Content-Type) usually associated with this
|
||||||
|
/// media format; not necessarily officially registered.
|
||||||
|
media_type: []const u8,
|
||||||
|
|
||||||
|
/// Confirm whether a buffer contains this media format. The buffer doesn't have
|
||||||
|
/// to contain the entire file, only its beginning. The caller asserts that
|
||||||
|
/// `buffer` is at least `magic_length` bytes long.
|
||||||
|
isFormat: *const fn (buffer: []const u8) bool,
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
@@ -9,14 +9,26 @@ pub const Static = struct {
|
|||||||
const sample_rate: f64 = @floatFromInt(self.sample_rate);
|
const sample_rate: f64 = @floatFromInt(self.sample_rate);
|
||||||
return @floatCast(samples / sample_rate);
|
return @floatCast(samples / sample_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Sample = extern struct {
|
pub const Sample = extern struct {
|
||||||
left: i16,
|
left: i16,
|
||||||
right: i16,
|
right: i16,
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Stream = struct {
|
pub const Stream = struct {
|
||||||
source: std.io.Reader,
|
source: *std.Io.Reader,
|
||||||
sample_rate: u32,
|
sample_rate: u32,
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,11 +19,15 @@ pub fn Static(comptime W: u32, comptime H: u32) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill(self: *@This(), color: vm.Color) void {
|
pub fn fill(self: *@This(), color: vm.Color) void {
|
||||||
@memset(self.data, color);
|
@memset(&self.data, color);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Static - refAllDecls" {
|
||||||
|
std.testing.refAllDecls(Static(16, 16));
|
||||||
|
}
|
||||||
|
|
||||||
pub const Dynamic = struct {
|
pub const Dynamic = struct {
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
@@ -65,4 +69,55 @@ pub const Dynamic = struct {
|
|||||||
pub fn fill(self: *@This(), color: vm.Color) void {
|
pub fn fill(self: *@This(), color: vm.Color) void {
|
||||||
@memset(self.data[0 .. self.width * self.height], color);
|
@memset(self.data[0 .. self.width * self.height], color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Hdr = struct {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
|
||||||
|
data: [*]vm.ColorHdr,
|
||||||
|
|
||||||
|
pub fn initBuffer(width: u32, height: u32, buffer: []vm.ColorHdr) @This() {
|
||||||
|
std.debug.assert(buffer.len == width * height);
|
||||||
|
return .{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.data = buffer.ptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initAlloc(width: u32, height: u32, allocator: std.mem.Allocator) !@This() {
|
||||||
|
const buffer = try allocator.alloc(vm.ColorHdr, width * height);
|
||||||
|
return .{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.data = buffer.ptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.data[0 .. self.width * self.height]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPixel(self: *const @This(), x: u32, y: u32) vm.ColorHdr {
|
||||||
|
std.debug.assert(x < self.width and y < self.height);
|
||||||
|
return self.data[y * self.width + x];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setPixel(self: *@This(), x: u32, y: u32, color: vm.ColorHdr) void {
|
||||||
|
std.debug.assert(x < self.width and y < self.height);
|
||||||
|
self.data[y * self.width + x] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill(self: *@This(), color: vm.ColorHdr) void {
|
||||||
|
@memset(self.data[0 .. self.width * self.height], color);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
151
packages/media/src/jpeg.zig
Normal file
151
packages/media/src/jpeg.zig
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Format = @import("Format.zig");
|
||||||
|
|
||||||
|
const format: Format = .{
|
||||||
|
.magic_length = magic.len,
|
||||||
|
// NOTE The information (like width and height) is not in a fixed position
|
||||||
|
.info_length = magic.len,
|
||||||
|
.extension = "jpeg",
|
||||||
|
.media_type = "image/jpeg",
|
||||||
|
.isFormat = isJpeg,
|
||||||
|
};
|
||||||
|
|
||||||
|
const magic = "\xFF\xD8\xFF";
|
||||||
|
|
||||||
|
const Info = union(enum) {
|
||||||
|
partial: void,
|
||||||
|
full: Header,
|
||||||
|
|
||||||
|
pub fn makeFull(full: Header) Info {
|
||||||
|
return .{ .full = full };
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = struct {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Marker = enum(u8) {
|
||||||
|
/// Start of frame, Huffman coding, Baseline DCT
|
||||||
|
SOF_0 = 0xC0,
|
||||||
|
/// Start of frame, Huffman coding, Extended sequential DCT
|
||||||
|
SOF_1 = 0xC1,
|
||||||
|
/// Start of frame, Huffman coding, Progressive DCT
|
||||||
|
SOF_2 = 0xC2,
|
||||||
|
/// Start of frame, Huffman coding, Lossless (sequential)
|
||||||
|
SOF_3 = 0xC3,
|
||||||
|
/// Start of frame, Huffman coding, Differential sequential DCT
|
||||||
|
SOF_5 = 0xC5,
|
||||||
|
/// Start of frame, Huffman coding, Differential progressive DCT
|
||||||
|
SOF_6 = 0xC6,
|
||||||
|
/// Start of frame, Huffman coding, Differential lossless (sequential)
|
||||||
|
SOF_7 = 0xC7,
|
||||||
|
/// Start of frame, arithmetic coding, Extended sequential DCT
|
||||||
|
SOF_9 = 0xC9,
|
||||||
|
/// Start of frame, arithmetic coding, Progressive DCT
|
||||||
|
SOF_10 = 0xCA,
|
||||||
|
/// Start of frame, arithmetic coding, Lossless (sequential)
|
||||||
|
SOF_11 = 0xCB,
|
||||||
|
/// Start of frame, arithmetic coding, Differential sequential DCT
|
||||||
|
SOF_13 = 0xCD,
|
||||||
|
/// Start of frame, arithmetic coding, Differential progressive DCT
|
||||||
|
SOF_14 = 0xCE,
|
||||||
|
/// Start of frame, arithmetic coding, Differential lossless (sequential)
|
||||||
|
SOF_15 = 0xCF,
|
||||||
|
/// Define Huffman table(s)
|
||||||
|
DHT = 0xC4,
|
||||||
|
/// Define arithmetic coding conditioning(s)
|
||||||
|
DAC = 0xCC,
|
||||||
|
/// Start of image
|
||||||
|
SOI = 0xD8,
|
||||||
|
/// End of image
|
||||||
|
EOI = 0xD9,
|
||||||
|
/// Start of scan
|
||||||
|
SOS = 0xDA,
|
||||||
|
/// Define quantization table(s)
|
||||||
|
DQT = 0xDB,
|
||||||
|
/// Define number of lines
|
||||||
|
DNL = 0xDC,
|
||||||
|
/// Define restart interval
|
||||||
|
DRI = 0xDD,
|
||||||
|
/// Define hierarchical progression
|
||||||
|
DHP = 0xDE,
|
||||||
|
/// Expand reference component(s)
|
||||||
|
EXP = 0xDF,
|
||||||
|
/// Comment
|
||||||
|
COM = 0xFE,
|
||||||
|
/// For temporary private use in arithmetic coding
|
||||||
|
TEM = 0x01,
|
||||||
|
|
||||||
|
_,
|
||||||
|
|
||||||
|
/// Restart with modulo 8 count `m`
|
||||||
|
pub fn RST(m: u3) Marker {
|
||||||
|
return @enumFromInt(0xD0 | m);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isRST(self: Marker) ?u3 {
|
||||||
|
return if (@intFromEnum(self) & 0b1111_1000 == 0xD0) @intCast(@intFromEnum(self) & 0b0000_0111) else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reserved for application segments
|
||||||
|
pub fn APP(n: u4) Marker {
|
||||||
|
return @enumFromInt(0xE0 | n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isAPP(self: Marker) ?u3 {
|
||||||
|
return if (@intFromEnum(self) & 0b1111_0000 == 0xE0) @intCast(@intFromEnum(self) & 0b0000_1111) else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A standalone marker has no content and is not followed by segment length
|
||||||
|
/// parameter.
|
||||||
|
pub fn isStandalone(self: Marker) bool {
|
||||||
|
return self.isRST() != null or
|
||||||
|
self == .SOI or
|
||||||
|
self == .EOI or
|
||||||
|
self == .TEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||||
|
/// long.
|
||||||
|
pub fn isJpeg(buffer: []const u8) bool {
|
||||||
|
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||||
|
/// long. The information is not in a fixed position, so you need to provide a
|
||||||
|
/// substantial amount of data, in the order of kilobytes. Depending on the
|
||||||
|
/// amount of metadata, the information might be in the first kilobyte or dozens
|
||||||
|
/// of kilobytes in.
|
||||||
|
///
|
||||||
|
/// This function returns:
|
||||||
|
///
|
||||||
|
/// - `null` when the buffer is not a JPEG or it's a malformed JPEG
|
||||||
|
/// - `.partial` when the buffer appears to be a part of a JPEG, but the
|
||||||
|
/// information could not be found within the buffer provided
|
||||||
|
/// - `.full` when the buffer appears to be a part of a JPEG and the information
|
||||||
|
/// was fully contained within the buffer provided
|
||||||
|
pub fn info(buffer: []const u8) ?Info {
|
||||||
|
std.debug.assert(buffer.len >= format.info_length);
|
||||||
|
|
||||||
|
if (!isJpeg(buffer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@panic("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
41
packages/media/src/jxl.zig
Normal file
41
packages/media/src/jxl.zig
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Format = @import("Format.zig");
|
||||||
|
|
||||||
|
const format: Format = .{
|
||||||
|
.magic_length = @max(magic_naked.len, magic_container.len),
|
||||||
|
.info_length = @max(magic_naked.len, magic_container.len),
|
||||||
|
.extension = "jxl",
|
||||||
|
.media_type = "image/jxl",
|
||||||
|
.isFormat = isJxl,
|
||||||
|
};
|
||||||
|
|
||||||
|
const magic_naked = "\xFF\x0A";
|
||||||
|
const magic_container = "\x00\x00\x00\x0CJXL \r\n\x87\n";
|
||||||
|
|
||||||
|
const Header = struct {};
|
||||||
|
|
||||||
|
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||||
|
/// long.
|
||||||
|
pub fn isJxl(buffer: []const u8) bool {
|
||||||
|
std.debug.assert(buffer.len >= format.magic_length);
|
||||||
|
|
||||||
|
return std.mem.startsWith(u8, buffer, magic_naked) or
|
||||||
|
std.mem.startsWith(u8, buffer, magic_container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||||
|
/// long.
|
||||||
|
pub fn info(buffer: []const u8) ?Header {
|
||||||
|
std.debug.assert(buffer.len >= format.info_length);
|
||||||
|
|
||||||
|
if (!isJxl(buffer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@panic("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
645
packages/media/src/png.zig
Normal file
645
packages/media/src/png.zig
Normal file
@@ -0,0 +1,645 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Format = @import("Format.zig");
|
||||||
|
|
||||||
|
pub const format: Format = .{
|
||||||
|
.magic_length = magic.len,
|
||||||
|
// 4B - chunk length
|
||||||
|
// 4B - chunk type
|
||||||
|
// 13B - IHDR data
|
||||||
|
// 4B - checksum
|
||||||
|
.info_length = magic.len + 25,
|
||||||
|
.extension = "png",
|
||||||
|
.media_type = "image/png",
|
||||||
|
.isFormat = isPng,
|
||||||
|
};
|
||||||
|
|
||||||
|
const magic = "\x89PNG\r\n\x1A\n";
|
||||||
|
|
||||||
|
// --- IHDR --------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const Header = struct {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
bit_depth: BitDepth,
|
||||||
|
color_type: ColorType,
|
||||||
|
compression_method: CompressionMethod,
|
||||||
|
filter_method: FilterMethod,
|
||||||
|
interlace_method: InterlaceMethod,
|
||||||
|
|
||||||
|
pub fn decode(chunk: Chunk) !Header {
|
||||||
|
std.debug.assert(chunk.chunk_type == .IHDR);
|
||||||
|
|
||||||
|
if (chunk.data.len != 13) return error.InvalidPng;
|
||||||
|
const width = std.mem.readInt(u32, chunk.data[0..4], .big);
|
||||||
|
const height = std.mem.readInt(u32, chunk.data[4..8], .big);
|
||||||
|
const bit_depth = chunk.data[8];
|
||||||
|
const color_type = chunk.data[9];
|
||||||
|
const compression_method = chunk.data[10];
|
||||||
|
const filter_method = chunk.data[11];
|
||||||
|
const interlace_method = chunk.data[12];
|
||||||
|
|
||||||
|
if (width == 0 or width > 0x7FFF_FFFF or
|
||||||
|
height == 0 or height > 0x7FFF_FFFF or
|
||||||
|
bit_depth == 0 or bit_depth > 16 or !std.math.isPowerOfTwo(bit_depth) or
|
||||||
|
color_type == 1 or color_type == 5 or color_type > 6 or
|
||||||
|
compression_method != 0 or
|
||||||
|
filter_method != 0 or
|
||||||
|
interlace_method > 1)
|
||||||
|
{
|
||||||
|
return error.InvalidPng;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (color_type) {
|
||||||
|
0 => {
|
||||||
|
// all bit depths allowed
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
if (bit_depth < 8) return error.InvalidPng;
|
||||||
|
},
|
||||||
|
3 => {
|
||||||
|
if (bit_depth > 8) return error.InvalidPng;
|
||||||
|
},
|
||||||
|
4 => {
|
||||||
|
if (bit_depth < 8) return error.InvalidPng;
|
||||||
|
},
|
||||||
|
6 => {
|
||||||
|
if (bit_depth < 8) return error.InvalidPng;
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.bit_depth = @enumFromInt(bit_depth),
|
||||||
|
.color_type = @enumFromInt(color_type),
|
||||||
|
.compression_method = @enumFromInt(compression_method),
|
||||||
|
.filter_method = @enumFromInt(filter_method),
|
||||||
|
.interlace_method = @enumFromInt(interlace_method),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BitDepth = enum(u8) {
|
||||||
|
@"1" = 1,
|
||||||
|
@"2" = 2,
|
||||||
|
@"4" = 4,
|
||||||
|
@"8" = 8,
|
||||||
|
@"16" = 16,
|
||||||
|
|
||||||
|
pub fn range(self: BitDepth) usize {
|
||||||
|
return @as(usize, 2) << @intCast(@intFromEnum(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ColorType = enum(u8) {
|
||||||
|
grayscale = 0,
|
||||||
|
rgb = 2,
|
||||||
|
palette = 3,
|
||||||
|
grayscale_alpha = 4,
|
||||||
|
rgba = 6,
|
||||||
|
|
||||||
|
pub fn paletteUsed(self: ColorType) bool {
|
||||||
|
return @intFromEnum(self) & 0b0000_0001 != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn colorUsed(self: ColorType) bool {
|
||||||
|
return @intFromEnum(self) & 0b0000_0010 != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alphaChannelUsed(self: ColorType) bool {
|
||||||
|
return @intFromEnum(self) & 0b0000_0100 != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CompressionMethod = enum(u8) {
|
||||||
|
flate = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FilterMethod = enum(u8) {
|
||||||
|
adaptive = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InterlaceMethod = enum(u8) {
|
||||||
|
none = 0,
|
||||||
|
adam7 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- PLTE --------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const Palette = struct {
|
||||||
|
len: u16,
|
||||||
|
entries: [256][3]u8,
|
||||||
|
|
||||||
|
pub fn asSlice(self: *const Palette) []const [3]u8 {
|
||||||
|
return self.entries[0..self.len];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(chunk: Chunk, ctx: Header) !Palette {
|
||||||
|
std.debug.assert(chunk.chunk_type == .PLTE);
|
||||||
|
|
||||||
|
const len = std.math.divExact(usize, chunk.data.len, 3) catch return error.InvalidPng;
|
||||||
|
|
||||||
|
switch (ctx.color_type) {
|
||||||
|
.grayscale => return error.InvalidPng,
|
||||||
|
.rgb => if (len < 1 or len > 256) return error.InvalidPng,
|
||||||
|
.palette => if (len < 1 or len > ctx.bit_depth.range()) return error.InvalidPng,
|
||||||
|
.grayscale_alpha => return error.InvalidPng,
|
||||||
|
.rgba => if (len < 1 or len > 256) return error.InvalidPng,
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries: [256][3]u8 = undefined;
|
||||||
|
@memcpy(entries[0..len], @as([]const [3]u8, @ptrCast(chunk.data)));
|
||||||
|
return .{
|
||||||
|
.len = @intCast(len),
|
||||||
|
.entries = entries,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- tRNS --------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const Transparency = union(enum) {
|
||||||
|
grayscale: u16,
|
||||||
|
rgb: [3]u16,
|
||||||
|
palette: TransparencyPalette,
|
||||||
|
|
||||||
|
pub fn initGrayscale(grayscale: u16) Transparency {
|
||||||
|
return .{ .grayscale = grayscale };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initRgb(rgb: [3]u16) Transparency {
|
||||||
|
return .{ .rgb = rgb };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initPalette(palette: TransparencyPalette) Transparency {
|
||||||
|
return .{ .palette = palette };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(chunk: Chunk, header: Header, maybe_palette: ?Palette) !Transparency {
|
||||||
|
std.debug.assert(chunk.chunk_type == .tRNS);
|
||||||
|
|
||||||
|
return switch (header.color_type) {
|
||||||
|
.grayscale => {
|
||||||
|
if (chunk.data.len != 2) return error.InvalidPng;
|
||||||
|
const grayscale = std.mem.readInt(u16, chunk.data[0..2], .big);
|
||||||
|
return .initGrayscale(grayscale);
|
||||||
|
},
|
||||||
|
.rgb => {
|
||||||
|
if (chunk.data.len != 6) return error.InvalidPng;
|
||||||
|
const r = std.mem.readInt(u16, chunk.data[0..2], .big);
|
||||||
|
const g = std.mem.readInt(u16, chunk.data[2..4], .big);
|
||||||
|
const b = std.mem.readInt(u16, chunk.data[4..6], .big);
|
||||||
|
return .initRgb(.{ r, g, b });
|
||||||
|
},
|
||||||
|
.palette => if (maybe_palette) |palette| {
|
||||||
|
const len = chunk.data.len;
|
||||||
|
if (len < 1 or len > palette.len) return error.InvalidPng;
|
||||||
|
var entries: [256]u8 = undefined;
|
||||||
|
@memcpy(entries[0..len], chunk.data[0..len]);
|
||||||
|
return .initPalette(.{
|
||||||
|
.len = @intCast(len),
|
||||||
|
.entries = entries,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return error.InvalidPng;
|
||||||
|
},
|
||||||
|
.grayscale_alpha => error.InvalidPng,
|
||||||
|
.rgba => error.InvalidPng,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const TransparencyPalette = struct {
|
||||||
|
len: u16,
|
||||||
|
entries: [256]u8,
|
||||||
|
|
||||||
|
pub fn asSlice(self: *const TransparencyPalette) []const u8 {
|
||||||
|
return self.entries[0..self.len];
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- gAMA --------------------------------------------------------------------
|
||||||
|
|
||||||
|
const Gamma = struct {
|
||||||
|
/// Gamma times `scale`, i.e. 100000, For example, a gamma of 1/2.2 would be
|
||||||
|
/// stored as 45455.
|
||||||
|
gamma: u32,
|
||||||
|
|
||||||
|
const scale = 100000;
|
||||||
|
|
||||||
|
pub fn initExponent(exponent: f32) Gamma {
|
||||||
|
const gamma: u32 = @intFromFloat(@round(exponent * scale));
|
||||||
|
return .{ .gamma = gamma };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn asExponent(self: Gamma) f32 {
|
||||||
|
return @as(f32, @floatFromInt(self.gamma)) / scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(chunk: Chunk) Gamma {
|
||||||
|
std.debug.assert(chunk.chunk_type == .gAMA);
|
||||||
|
|
||||||
|
if (chunk.data.len != 4) return error.InvalidPng;
|
||||||
|
const gamma = std.mem.readInt(u32, chunk.data[0..4], .big);
|
||||||
|
return .{ .gamma = gamma };
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- cHRM --------------------------------------------------------------------
|
||||||
|
|
||||||
|
const Chromaticities = struct {
|
||||||
|
white_point_x: u32,
|
||||||
|
white_point_y: u32,
|
||||||
|
red_x: u32,
|
||||||
|
red_y: u32,
|
||||||
|
green_x: u32,
|
||||||
|
green_y: u32,
|
||||||
|
blue_x: u32,
|
||||||
|
blue_y: u32,
|
||||||
|
|
||||||
|
pub fn decode(chunk: Chunk) !Chunk {
|
||||||
|
std.debug.assert(chunk.chunk_type == .cHRM);
|
||||||
|
|
||||||
|
if (chunk.data.len != 32) return error.InvalidPng;
|
||||||
|
return .{
|
||||||
|
.white_point_x = std.mem.readInt(u32, chunk.data[0..4], .big),
|
||||||
|
.white_point_y = std.mem.readInt(u32, chunk.data[4..8], .big),
|
||||||
|
.red_x = std.mem.readInt(u32, chunk.data[8..12], .big),
|
||||||
|
.red_y = std.mem.readInt(u32, chunk.data[12..16], .big),
|
||||||
|
.green_x = std.mem.readInt(u32, chunk.data[16..20], .big),
|
||||||
|
.green_y = std.mem.readInt(u32, chunk.data[20..24], .big),
|
||||||
|
.blue_x = std.mem.readInt(u32, chunk.data[24..28], .big),
|
||||||
|
.blue_y = std.mem.readInt(u32, chunk.data[28..32], .big),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- sRGB --------------------------------------------------------------------
|
||||||
|
|
||||||
|
const RenderingIntent = enum(u8) {
|
||||||
|
/// Perceptual intent is for images preferring good adaptation to the output
|
||||||
|
/// device gamut at the expense of colorimetric accuracy, like photographs.
|
||||||
|
perceptual = 0,
|
||||||
|
/// Relative colorimetric intent is for images requiring color appearance
|
||||||
|
/// matching (relative to the output device white point), like logos.
|
||||||
|
relative_colorimetric = 1,
|
||||||
|
/// Saturation intent is for images preferring preservation of saturation at
|
||||||
|
/// the expense of hue and lightness, like charts and graphs.
|
||||||
|
saturation = 2,
|
||||||
|
/// Absolute colorimetric intent is for images requiring preservation of
|
||||||
|
/// absolute colorimetry, like proofs (previews of images destined for a
|
||||||
|
/// different output device).
|
||||||
|
absolute_colorimetric = 3,
|
||||||
|
|
||||||
|
pub fn decode(chunk: Chunk) RenderingIntent {
|
||||||
|
std.debug.assert(chunk.chunk_type == .sRGB);
|
||||||
|
|
||||||
|
if (chunk.data.len != 1) return error.InvalidPng;
|
||||||
|
const rendering_intent = chunk.data[0];
|
||||||
|
if (rendering_intent > 3) return error.InvalidPng;
|
||||||
|
return @enumFromInt(rendering_intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- pHYs --------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const PhysicalPixelDimensions = struct {
|
||||||
|
pixels_per_unit_x: u32,
|
||||||
|
pixels_per_unit_y: u32,
|
||||||
|
unit: PhysicalUnit,
|
||||||
|
|
||||||
|
pub fn decode(chunk: Chunk) !PhysicalPixelDimensions {
|
||||||
|
std.debug.assert(chunk.chunk_type == .pHYs);
|
||||||
|
|
||||||
|
if (chunk.data.len != 9) return error.InvalidPng;
|
||||||
|
const pixels_per_unit_x = std.mem.readInt(u32, chunk.data[0..4], .big);
|
||||||
|
const pixels_per_unit_y = std.mem.readInt(u32, chunk.data[4..8], .big);
|
||||||
|
const unit = chunk.data[8];
|
||||||
|
|
||||||
|
if (pixels_per_unit_x == 0 or pixels_per_unit_y == 0 or unit > 1) {
|
||||||
|
return error.InvalidPng;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.pixels_per_unit_x = pixels_per_unit_x,
|
||||||
|
.pixels_per_unit_y = pixels_per_unit_y,
|
||||||
|
.unit = @enumFromInt(unit),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PhysicalUnit = enum(u8) {
|
||||||
|
unknown = 0,
|
||||||
|
meter = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- tIME --------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const LastModificationTime = struct {
|
||||||
|
year: u16,
|
||||||
|
/// 1 - 12
|
||||||
|
month: u8,
|
||||||
|
/// 1 - 31
|
||||||
|
day: u8,
|
||||||
|
/// 0 - 23
|
||||||
|
hour: u8,
|
||||||
|
/// 0 - 59
|
||||||
|
minute: u8,
|
||||||
|
/// 0 - 60 (may include leap seconds)
|
||||||
|
second: u8,
|
||||||
|
|
||||||
|
pub fn decode(chunk: Chunk) !LastModificationTime {
|
||||||
|
std.debug.assert(chunk.chunk_type == .tIME);
|
||||||
|
|
||||||
|
if (chunk.data.len == 7) return error.InvalidPng;
|
||||||
|
const year = std.mem.readInt(u16, chunk.data[0..2], .big);
|
||||||
|
const month = chunk.data[2];
|
||||||
|
const day = chunk.data[3];
|
||||||
|
const hour = chunk.data[4];
|
||||||
|
const minute = chunk.data[5];
|
||||||
|
const second = chunk.data[6];
|
||||||
|
|
||||||
|
// NOTE This implementation is intentionally trying not to be too clever
|
||||||
|
// with validation.
|
||||||
|
if (month < 1 or month > 12 or
|
||||||
|
day < 1 or day > 31 or
|
||||||
|
hour > 23 or
|
||||||
|
minute > 59 or
|
||||||
|
second > 60)
|
||||||
|
{
|
||||||
|
return error.InvalidPng;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.year = year,
|
||||||
|
.month = month,
|
||||||
|
.day = day,
|
||||||
|
.hour = hour,
|
||||||
|
.minute = minute,
|
||||||
|
.second = second,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const ChunkType = enum(u32) {
|
||||||
|
IHDR = fromName("IHDR"),
|
||||||
|
PLTE = fromName("PLTE"),
|
||||||
|
IDAT = fromName("IDAT"),
|
||||||
|
IEND = fromName("IEND"),
|
||||||
|
tRNS = fromName("tRNS"),
|
||||||
|
gAMA = fromName("gAMA"),
|
||||||
|
cHRM = fromName("cHRM"),
|
||||||
|
sRGB = fromName("sRGB"),
|
||||||
|
iCCP = fromName("iCCP"),
|
||||||
|
tEXt = fromName("tEXt"),
|
||||||
|
zTXt = fromName("zTXt"),
|
||||||
|
iTXt = fromName("iTXt"),
|
||||||
|
bKGD = fromName("bKGD"),
|
||||||
|
pHYs = fromName("pHYs"),
|
||||||
|
sBIT = fromName("sBIT"),
|
||||||
|
sPLT = fromName("sPLT"),
|
||||||
|
hIST = fromName("hIST"),
|
||||||
|
tIME = fromName("tIME"),
|
||||||
|
_,
|
||||||
|
|
||||||
|
fn fromName(name: *const [4]u8) u32 {
|
||||||
|
return @bitCast(name.*);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromBytes(bytes: *const [4]u8) ChunkType {
|
||||||
|
return @enumFromInt(fromName(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toBytes(self: ChunkType) [4]u8 {
|
||||||
|
return @bitCast(@intFromEnum(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ancillary(self: ChunkType) bool {
|
||||||
|
return self.toBytes()[0] & 0b0010_0000 != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn private(self: ChunkType) bool {
|
||||||
|
return self.toBytes()[1] & 0b0010_0000 != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn safeToCopy(self: ChunkType) bool {
|
||||||
|
return self.toBytes()[3] & 0b0010_0000 != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Chunk = struct {
|
||||||
|
chunk_type: ChunkType,
|
||||||
|
data: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const StandardKeyword = enum {
|
||||||
|
/// Short (one line) title or caption for image
|
||||||
|
Title,
|
||||||
|
/// Name of image’s creator
|
||||||
|
Author,
|
||||||
|
/// Description of image (possibly long)
|
||||||
|
Description,
|
||||||
|
/// Copyright notice
|
||||||
|
Copyright,
|
||||||
|
/// Time Time of original image creation
|
||||||
|
Creation,
|
||||||
|
/// Software used to create the image
|
||||||
|
Software,
|
||||||
|
/// Legal disclaimer
|
||||||
|
Disclaimer,
|
||||||
|
/// Warning of nature of content
|
||||||
|
Warning,
|
||||||
|
/// Device used to create the image
|
||||||
|
Source,
|
||||||
|
/// Miscellaneous comment; conversion from GIF comment
|
||||||
|
Comment,
|
||||||
|
|
||||||
|
pub const map: std.StaticStringMap(StandardKeyword) = blk: {
|
||||||
|
const fields = @typeInfo(StandardKeyword).@"enum".fields;
|
||||||
|
|
||||||
|
var kvs_list: [fields.len]struct { []const u8, StandardKeyword } = undefined;
|
||||||
|
for (fields, 0..) |field, i| {
|
||||||
|
kvs_list[i] = .{ field.name, @field(StandardKeyword, field.name) };
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk .initComptime(kvs_list);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn isStandardKeyword(keyword: []const u8) ?StandardKeyword {
|
||||||
|
return map.get(keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||||
|
/// long.
|
||||||
|
pub fn isPng(buffer: []const u8) bool {
|
||||||
|
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||||
|
/// long.
|
||||||
|
pub fn info(buffer: []const u8) ?Header {
|
||||||
|
std.debug.assert(buffer.len >= format.info_length);
|
||||||
|
|
||||||
|
if (!isPng(buffer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunk, _ = (decodeChunk(buffer[format.magic_length..]) catch return null) orelse return null;
|
||||||
|
if (chunk.chunk_type != .IHDR) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = Header.decode(chunk) catch return null;
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decodeChunk(buffer: []const u8) !?struct { Chunk, []const u8 } {
|
||||||
|
var rest: []const u8 = buffer;
|
||||||
|
|
||||||
|
if (rest.len < 8) {
|
||||||
|
// Not enough data
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const length = std.mem.readInt(u32, rest[0..4], .big);
|
||||||
|
const chunk_type: ChunkType = .fromBytes(rest[4..8]);
|
||||||
|
rest = rest[8..];
|
||||||
|
|
||||||
|
if (length > 0x7FFF_FFFF) {
|
||||||
|
return error.InvalidPng;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rest.len < length) {
|
||||||
|
// Not enough data
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = rest[0..length];
|
||||||
|
rest = rest[length..];
|
||||||
|
|
||||||
|
if (rest.len < 4) {
|
||||||
|
// Not enough data
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const crc = std.mem.readInt(u32, rest[0..4], .big);
|
||||||
|
rest = rest[4..];
|
||||||
|
|
||||||
|
if (std.hash.crc.Crc32IsoHdlc.hash(data) != crc) {
|
||||||
|
return error.InvalidPng;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.{ .chunk_type = chunk_type, .data = data },
|
||||||
|
rest,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decodeChunks(buffer: []const u8, chunks: ?[]Chunk) !usize {
|
||||||
|
if (buffer.len < format.magic_length or !isPng(buffer)) {
|
||||||
|
return error.InvalidPng;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index: usize = 0;
|
||||||
|
var rest: []const u8 = buffer[format.magic_length..];
|
||||||
|
|
||||||
|
while (rest.len > 0) : (index += 1) {
|
||||||
|
const chunk, rest = try decodeChunk(rest) orelse return error.InvalidPng;
|
||||||
|
|
||||||
|
if (chunks) |ck| {
|
||||||
|
if (index < ck.len) {
|
||||||
|
ck[index] = chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decodeChunksAlloc(buffer: []const u8, allocator: std.mem.Allocator) ![]Chunk {
|
||||||
|
const n = try decodeChunks(buffer, null);
|
||||||
|
|
||||||
|
const chunks = try allocator.alloc(Chunk, n);
|
||||||
|
errdefer allocator.free(chunks);
|
||||||
|
|
||||||
|
_ = try decodeChunks(buffer, chunks);
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encodeChunks(chunks: []const Chunk, writer: *std.Io.Writer) !void {
|
||||||
|
try writer.writeAll(magic);
|
||||||
|
|
||||||
|
for (chunks) |chunk| {
|
||||||
|
var crc: std.hash.crc.Crc32IsoHdlc = .init();
|
||||||
|
var data: []const u8 = chunk.data;
|
||||||
|
|
||||||
|
while (data.len > 0) {
|
||||||
|
const bytes_written = try writer.write(data);
|
||||||
|
crc.update(data[0..bytes_written]);
|
||||||
|
data = data[bytes_written..];
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeInt(u32, crc.final(), .big);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
@@ -1,8 +1,31 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Format = @import("Format.zig");
|
||||||
|
|
||||||
|
const format: Format = .{
|
||||||
|
.magic_length = magic.len,
|
||||||
|
// 4B - sample count
|
||||||
|
// 1B - channels
|
||||||
|
// 3B - sample rate
|
||||||
|
.info_length = magic.len + 8,
|
||||||
|
.extension = "qoa",
|
||||||
|
.media_type = "audio/qoa",
|
||||||
|
.isFormat = isQoa,
|
||||||
|
};
|
||||||
|
|
||||||
|
const magic = "qoaf";
|
||||||
|
|
||||||
const Header = union(enum) {
|
const Header = union(enum) {
|
||||||
streaming: HeaderStreaming,
|
streaming: HeaderStreaming,
|
||||||
static: HeaderStatic,
|
static: HeaderStatic,
|
||||||
|
|
||||||
|
pub fn initStatic(static: HeaderStatic) Header {
|
||||||
|
return .{ .static = static };
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const HeaderStreaming = void;
|
const HeaderStreaming = void;
|
||||||
@@ -17,32 +40,48 @@ const HeaderStatic = struct {
|
|||||||
const sample_rate: f64 = @floatFromInt(self.sample_rate);
|
const sample_rate: f64 = @floatFromInt(self.sample_rate);
|
||||||
return @floatCast(samples / sample_rate);
|
return @floatCast(samples / sample_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The caller asserts that the buffer is at least 12 bytes long, which can
|
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||||
/// contain the entirety of a QOA file header and the relevant information in
|
/// long.
|
||||||
/// the first frame header.
|
pub fn isQoa(buffer: []const u8) bool {
|
||||||
pub fn info(buffer: []const u8) ?Header {
|
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
|
||||||
std.debug.assert(buffer.len >= 12);
|
}
|
||||||
|
|
||||||
|
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||||
|
/// long.
|
||||||
|
pub fn info(buffer: []const u8) ?Header {
|
||||||
|
std.debug.assert(buffer.len >= format.info_length);
|
||||||
|
|
||||||
|
if (!isQoa(buffer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const magic = buffer[0..4];
|
|
||||||
const samples = std.mem.readInt(u32, buffer[4..8], .big);
|
const samples = std.mem.readInt(u32, buffer[4..8], .big);
|
||||||
const channels = buffer[8];
|
const channels = buffer[8];
|
||||||
const sample_rate = std.mem.readInt(u24, buffer[9..12], .big);
|
const sample_rate = std.mem.readInt(u24, buffer[9..12], .big);
|
||||||
|
|
||||||
if (!std.mem.eql(u8, magic, "qoaf") or channels == 0 or channels > 8 or sample_rate == 0) {
|
if (channels == 0 or channels > 8 or
|
||||||
|
sample_rate == 0)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (samples == 0) {
|
if (samples == 0) {
|
||||||
return .streaming;
|
return .streaming;
|
||||||
} else {
|
} else {
|
||||||
return .{
|
return .initStatic(.{
|
||||||
.static = .{
|
.samples = samples,
|
||||||
.samples = samples,
|
.channels = channels,
|
||||||
.channels = channels,
|
.sample_rate = sample_rate,
|
||||||
.sample_rate = sample_rate,
|
});
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Format = @import("Format.zig");
|
||||||
|
|
||||||
|
const format: Format = .{
|
||||||
|
.magic_length = magic.len,
|
||||||
|
// 4B - width
|
||||||
|
// 4B - height
|
||||||
|
// 1B - channels
|
||||||
|
// 1B - color space
|
||||||
|
.info_length = magic.len + 10,
|
||||||
|
.extension = "qoi",
|
||||||
|
.media_type = "image/qoi",
|
||||||
|
.isFormat = isQoi,
|
||||||
|
};
|
||||||
|
|
||||||
|
const magic = "qoif";
|
||||||
|
|
||||||
const Channels = enum(u8) {
|
const Channels = enum(u8) {
|
||||||
rgb = 3,
|
rgb = 3,
|
||||||
rgba = 4,
|
rgba = 4,
|
||||||
@@ -17,18 +33,31 @@ const Header = struct {
|
|||||||
color_space: ColorSpace,
|
color_space: ColorSpace,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The caller asserts that the buffer is at least 14 bytes long, which can
|
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||||
/// contain the entirety of a QOI header.
|
/// long.
|
||||||
pub fn info(buffer: []const u8) ?Header {
|
pub fn isQoi(buffer: []const u8) bool {
|
||||||
std.debug.assert(buffer.len >= 14);
|
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||||
|
/// long.
|
||||||
|
pub fn info(buffer: []const u8) ?Header {
|
||||||
|
std.debug.assert(buffer.len >= format.info_length);
|
||||||
|
|
||||||
|
if (!isQoi(buffer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const magic = buffer[0..4];
|
|
||||||
const width = std.mem.readInt(u32, buffer[4..8], .big);
|
const width = std.mem.readInt(u32, buffer[4..8], .big);
|
||||||
const height = std.mem.readInt(u32, buffer[8..12], .big);
|
const height = std.mem.readInt(u32, buffer[8..12], .big);
|
||||||
const channels = buffer[12];
|
const channels = buffer[12];
|
||||||
const color_space = buffer[13];
|
const color_space = buffer[13];
|
||||||
|
|
||||||
if (!std.mem.eql(u8, magic, "qoif") or width == 0 or height == 0 or channels < 3 or channels > 4 or color_space > 1) {
|
if (width == 0 or
|
||||||
|
height == 0 or
|
||||||
|
channels < 3 or channels > 4 or
|
||||||
|
color_space > 1)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,3 +68,7 @@ pub fn info(buffer: []const u8) ?Header {
|
|||||||
.color_space = @enumFromInt(color_space),
|
.color_space = @enumFromInt(color_space),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
pub const audio = @import("audio.zig");
|
pub const audio = @import("audio.zig");
|
||||||
|
pub const Format = @import("Format.zig");
|
||||||
pub const image = @import("image.zig");
|
pub const image = @import("image.zig");
|
||||||
|
pub const jpeg = @import("jpeg.zig");
|
||||||
|
pub const jxl = @import("jxl.zig");
|
||||||
|
pub const png = @import("png.zig");
|
||||||
pub const qoa = @import("qoa.zig");
|
pub const qoa = @import("qoa.zig");
|
||||||
pub const qoi = @import("qoi.zig");
|
pub const qoi = @import("qoi.zig");
|
||||||
|
pub const stbi = @import("stbi.zig");
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|||||||
310
packages/media/src/stbi.zig
Normal file
310
packages/media/src/stbi.zig
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const image = @import("image.zig");
|
||||||
|
const vm = @import("vecmath");
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
|
||||||
|
mutex: std.Io.Mutex = .init,
|
||||||
|
allocations: std.AutoHashMapUnmanaged(*anyopaque, usize) = .empty,
|
||||||
|
allocated_bytes: usize = 0,
|
||||||
|
|
||||||
|
const alignment: std.mem.Alignment = .@"16";
|
||||||
|
const VoidPtr = ?*align(alignment.toByteUnits()) anyopaque;
|
||||||
|
const log = std.log.scoped(.stbi);
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, io: std.Io) Self {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.io = io,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
||||||
|
if (self.allocated_bytes > 0) {
|
||||||
|
log.warn("{d} byte(s) still allocated while deinitializing", .{self.allocated_bytes});
|
||||||
|
}
|
||||||
|
if (self.allocations.size > 0) {
|
||||||
|
log.warn("{d} allocation(s) still tracked while deinitializing", .{self.allocations.size});
|
||||||
|
var it = self.allocations.iterator();
|
||||||
|
var index: usize = 0;
|
||||||
|
while (it.next()) |entry| : (index += 1) {
|
||||||
|
log.warn("Leaked allocation ({d}/{d}) at 0x{x} of {d} byte(s)", .{
|
||||||
|
index + 1,
|
||||||
|
self.allocations.size,
|
||||||
|
@intFromPtr(entry.key_ptr.*),
|
||||||
|
entry.value_ptr.*,
|
||||||
|
});
|
||||||
|
const memory = @as([*]align(alignment.toByteUnits()) u8, @ptrCast(@alignCast(entry.key_ptr.*)))[0..entry.value_ptr.*];
|
||||||
|
self.allocator.free(memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.allocations.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
const import = struct {
|
||||||
|
extern fn stbi_load_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u8;
|
||||||
|
extern fn stbi_load_16_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u16;
|
||||||
|
extern fn stbi_loadf_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) f32;
|
||||||
|
|
||||||
|
extern fn stbi_load_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u8;
|
||||||
|
extern fn stbi_load_16_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u16;
|
||||||
|
extern fn stbi_loadf_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) f32;
|
||||||
|
|
||||||
|
extern fn stbi_info_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32) i32;
|
||||||
|
extern fn stbi_is_16_bit_from_memory(buffer: [*]const u8, len: i32) i32;
|
||||||
|
extern fn stbi_is_hdr_from_memory(buffer: [*]const u8, len: i32) i32;
|
||||||
|
|
||||||
|
extern fn stbi_info_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32) i32;
|
||||||
|
extern fn stbi_is_16_bit_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque) i32;
|
||||||
|
extern fn stbi_is_hdr_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque) i32;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn loadStaticBuf(self: *Self, comptime W: u32, comptime H: u32, buf: []const u8) !image.Static(W, H) {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
var x: i32 = undefined;
|
||||||
|
var y: i32 = undefined;
|
||||||
|
|
||||||
|
const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, null, 4) orelse return error.StbiError;
|
||||||
|
defer castle_media_stbi_free(res);
|
||||||
|
|
||||||
|
if (x != W or y != H) return error.WrongDimensions;
|
||||||
|
|
||||||
|
return .{ .data = @as(*const [W * H]vm.Color, @ptrCast(@alignCast(res))).* };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadStaticIo(self: *Self, comptime W: u32, comptime H: u32, reader: *std.Io.Reader) !image.Static(W, H) {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
var x: i32 = undefined;
|
||||||
|
var y: i32 = undefined;
|
||||||
|
|
||||||
|
const res = import.stbi_load_from_callbacks(&.std_io_reader_interface, reader, &x, &y, null, 4) orelse return error.StbiError;
|
||||||
|
defer castle_media_stbi_free(res);
|
||||||
|
|
||||||
|
if (x != W or y != H) return error.WrongDimensions;
|
||||||
|
|
||||||
|
return .{ .data = @as(*const [W * H]vm.Color, @ptrCast(@alignCast(res))).* };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On success, must free memory by calling `freeDynamic` method.
|
||||||
|
pub fn loadDynamicBuf(self: *Self, buf: []const u8) !image.Dynamic {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
var x: i32 = undefined;
|
||||||
|
var y: i32 = undefined;
|
||||||
|
|
||||||
|
const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, null, 4) orelse return error.StbiError;
|
||||||
|
|
||||||
|
const buffer_ptr: [*]vm.Color = @ptrCast(@alignCast(res));
|
||||||
|
const ux: u32 = @intCast(x);
|
||||||
|
const uy: u32 = @intCast(y);
|
||||||
|
|
||||||
|
return .initBuffer(ux, uy, buffer_ptr[0 .. ux * uy]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On success, must free memory by calling `freeDynamic` method.
|
||||||
|
pub fn loadDynamicIo(self: *Self, reader: *std.Io.Reader) !image.Dynamic {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
var x: i32 = undefined;
|
||||||
|
var y: i32 = undefined;
|
||||||
|
|
||||||
|
const res = import.stbi_load_from_callbacks(&.std_io_reader_interface, reader, &x, &y, null, 4) orelse return error.StbiError;
|
||||||
|
|
||||||
|
const buffer_ptr: [*]vm.Color = @ptrCast(@alignCast(res));
|
||||||
|
const ux: u32 = @intCast(x);
|
||||||
|
const uy: u32 = @intCast(y);
|
||||||
|
|
||||||
|
return .initBuffer(ux, uy, buffer_ptr[0 .. ux * uy]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On success, must free memory by calling `freeHdr` method.
|
||||||
|
pub fn loadHdrBuf(self: *Self, buf: []const u8) !image.Hdr {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
var x: i32 = undefined;
|
||||||
|
var y: i32 = undefined;
|
||||||
|
|
||||||
|
const res_f32 = import.stbi_loadf_from_memory(buf.ptr, @intCast(buf.len), &x, &y, null, 4) orelse return error.StbiError;
|
||||||
|
defer castle_media_stbi_free(res_f32);
|
||||||
|
|
||||||
|
const ux: u32 = @intCast(x);
|
||||||
|
const uy: u32 = @intCast(y);
|
||||||
|
const buffer_ptr_f32: [*]vm.Vector4 = @ptrCast(res_f32);
|
||||||
|
const buffer_ptr_f16: [*]vm.ColorHdr = @ptrCast(castle_media_stbi_malloc(ux * uy * @sizeOf(vm.ColorHdr)) orelse return error.OutOfMemory);
|
||||||
|
errdefer castle_media_stbi_free(buffer_ptr_f16);
|
||||||
|
|
||||||
|
for (buffer_ptr_f16[0 .. ux * uy], buffer_ptr_f32[0 .. ux * uy]) |*sample_f16, sample_f32| {
|
||||||
|
sample_f16.* = .init(
|
||||||
|
std.math.clamp(@as(f16, @floatCast(sample_f32.x)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||||
|
std.math.clamp(@as(f16, @floatCast(sample_f32.y)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||||
|
std.math.clamp(@as(f16, @floatCast(sample_f32.z)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||||
|
std.math.clamp(@as(f16, @floatCast(sample_f32.w)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .initBuffer(ux, uy, buffer_ptr_f16[0 .. ux * uy]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On success, must free memory by calling `freeHdr` method.
|
||||||
|
pub fn loadHdrIo(self: *Self, reader: *std.Io.Reader) !image.Hdr {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
var x: i32 = undefined;
|
||||||
|
var y: i32 = undefined;
|
||||||
|
|
||||||
|
const res_f32 = import.stbi_loadf_from_callbacks(&.std_io_reader_interface, reader, &x, &y, null, 4) orelse return error.StbiError;
|
||||||
|
defer castle_media_stbi_free(res_f32);
|
||||||
|
|
||||||
|
const ux: u32 = @intCast(x);
|
||||||
|
const uy: u32 = @intCast(y);
|
||||||
|
const buffer_ptr_f32: [*]vm.Vector4 = @ptrCast(res_f32);
|
||||||
|
const buffer_ptr_f16: [*]vm.ColorHdr = @ptrCast(castle_media_stbi_malloc(ux * uy * @sizeOf(vm.ColorHdr)) orelse return error.OutOfMemory);
|
||||||
|
errdefer castle_media_stbi_free(buffer_ptr_f16);
|
||||||
|
|
||||||
|
for (buffer_ptr_f16[0 .. ux * uy], buffer_ptr_f32[0 .. ux * uy]) |*sample_f16, sample_f32| {
|
||||||
|
sample_f16.* = .init(
|
||||||
|
std.math.clamp(@as(f16, @floatCast(sample_f32.x)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||||
|
std.math.clamp(@as(f16, @floatCast(sample_f32.y)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||||
|
std.math.clamp(@as(f16, @floatCast(sample_f32.z)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||||
|
std.math.clamp(@as(f16, @floatCast(sample_f32.w)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .initBuffer(ux, uy, buffer_ptr_f16[0 .. ux * uy]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn freeDynamic(self: *Self, img: image.Dynamic) void {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
castle_media_stbi_free(@ptrCast(@alignCast(img.data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn freeHdr(self: *Self, img: image.Hdr) void {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
castle_media_stbi_free(@ptrCast(@alignCast(img.data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- IO INTERFACE ------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const IoCallbacks = extern struct {
|
||||||
|
/// Fill `data` with `size` bytes. Return number of bytes actually read.
|
||||||
|
read: ?*const fn (ctx: ?*anyopaque, data: [*]u8, size: i32) callconv(.c) i32,
|
||||||
|
/// Skip the next `n` bytes, or backtrack `-n` bytes if `n < 0`.
|
||||||
|
skip: ?*const fn (ctx: ?*anyopaque, n: i32) callconv(.c) i32,
|
||||||
|
/// Return non-zero value if at the end of file/data.
|
||||||
|
eof: ?*const fn (cxt: ?*anyopaque) callconv(.c) i32,
|
||||||
|
|
||||||
|
pub const std_io_reader_interface: IoCallbacks = .{
|
||||||
|
.read = stdIoReader_ReadFn,
|
||||||
|
.skip = stdIoReader_SkipFn,
|
||||||
|
.eof = stdIoReader_EofFn,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn stdIoReader_ReadFn(ctx: ?*anyopaque, data: [*]u8, size: i32) callconv(.c) i32 {
|
||||||
|
const reader: *std.Io.Reader = @ptrCast(@alignCast(ctx.?));
|
||||||
|
const bytes_read = reader.readSliceShort(data[0..@intCast(size)]) catch return 0;
|
||||||
|
return @intCast(bytes_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stdIoReader_SkipFn(ctx: ?*anyopaque, n: i32) callconv(.c) i32 {
|
||||||
|
const reader: *std.Io.Reader = @ptrCast(@alignCast(ctx.?));
|
||||||
|
// NOTE stb_image.h actually discards the return value from this
|
||||||
|
// callback. If an actual error occurs, we're cooked (but it will be
|
||||||
|
// very likely caught as a parsing error later).
|
||||||
|
_ = reader.discardAll(@intCast(n)) catch return 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stdIoReader_EofFn(ctx: ?*anyopaque) callconv(.c) i32 {
|
||||||
|
const reader: *std.Io.Reader = @ptrCast(@alignCast(ctx.?));
|
||||||
|
|
||||||
|
_ = reader.peekByte() catch return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- MALLOC INTERFACE --------------------------------------------------------
|
||||||
|
|
||||||
|
threadlocal var current_self: *Self = undefined;
|
||||||
|
|
||||||
|
export fn castle_media_stbi_malloc(size: usize) callconv(.c) VoidPtr {
|
||||||
|
const self = current_self;
|
||||||
|
|
||||||
|
self.mutex.lock(self.io) catch return null;
|
||||||
|
defer self.mutex.unlock(self.io);
|
||||||
|
|
||||||
|
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
|
||||||
|
const memory = self.allocator.alignedAlloc(u8, alignment, size) catch return null;
|
||||||
|
|
||||||
|
self.allocations.putAssumeCapacityNoClobber(memory.ptr, size);
|
||||||
|
self.allocated_bytes += size;
|
||||||
|
//log.debug("Allocated {d} bytes(s) at 0x{x}", .{ size, @intFromPtr(memory.ptr) });
|
||||||
|
|
||||||
|
return memory.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn castle_media_stbi_realloc(maybe_ptr: VoidPtr, size: usize) callconv(.c) VoidPtr {
|
||||||
|
const self = current_self;
|
||||||
|
|
||||||
|
self.mutex.lock(self.io) catch return null;
|
||||||
|
defer self.mutex.unlock(self.io);
|
||||||
|
|
||||||
|
// NOTE If we were pedantic, we would consider the fact that we might not
|
||||||
|
// need unused capacity if the memory doesn't get relocated.
|
||||||
|
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
|
||||||
|
|
||||||
|
const old_memory = if (maybe_ptr) |ptr| blk_then: {
|
||||||
|
const old_size = self.allocations.get(ptr).?;
|
||||||
|
break :blk_then @as([*]align(alignment.toByteUnits()) u8, @ptrCast(ptr))[0..old_size];
|
||||||
|
} else blk_else: {
|
||||||
|
break :blk_else @as([]align(alignment.toByteUnits()) u8, &.{});
|
||||||
|
};
|
||||||
|
|
||||||
|
const memory = self.allocator.realloc(old_memory, size) catch return null;
|
||||||
|
|
||||||
|
if (maybe_ptr) |ptr| {
|
||||||
|
const old_size = self.allocations.fetchRemove(ptr).?.value;
|
||||||
|
self.allocated_bytes -= old_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.allocations.putAssumeCapacityNoClobber(memory.ptr, size);
|
||||||
|
self.allocated_bytes += size;
|
||||||
|
//log.debug("Reallocated into {d} bytes(s) at 0x{x} from 0x{x}", .{ size, @intFromPtr(memory.ptr), @intFromPtr(maybe_ptr) });
|
||||||
|
|
||||||
|
return memory.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn castle_media_stbi_free(maybe_ptr: VoidPtr) callconv(.c) void {
|
||||||
|
const self = current_self;
|
||||||
|
|
||||||
|
if (maybe_ptr) |ptr| {
|
||||||
|
self.mutex.lockUncancelable(self.io);
|
||||||
|
defer self.mutex.unlock(self.io);
|
||||||
|
|
||||||
|
const size = self.allocations.fetchRemove(ptr).?.value;
|
||||||
|
self.allocated_bytes -= size;
|
||||||
|
const memory = @as([*]align(alignment.toByteUnits()) u8, @ptrCast(ptr))[0..size];
|
||||||
|
|
||||||
|
self.allocator.free(memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
17
packages/media/src/stbi/stb_image.c
Normal file
17
packages/media/src/stbi/stb_image.c
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
void *castle_media_stbi_malloc(size_t size);
|
||||||
|
void *castle_media_stbi_realloc(void *ptr, size_t size);
|
||||||
|
void castle_media_stbi_free(void *ptr);
|
||||||
|
|
||||||
|
#define STBI_MALLOC(size) castle_media_stbi_malloc(size)
|
||||||
|
#define STBI_REALLOC(ptr, size) castle_media_stbi_realloc(ptr, size)
|
||||||
|
#define STBI_FREE(ptr) castle_media_stbi_free(ptr)
|
||||||
|
|
||||||
|
#define STBI_NO_STDIO
|
||||||
|
#define STBI_ONLY_JPEG
|
||||||
|
#define STBI_ONLY_PNG
|
||||||
|
#define STBI_ONLY_HDR
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
7988
packages/media/src/stbi/stb_image.h
Normal file
7988
packages/media/src/stbi/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,11 +11,19 @@ pub fn build(b: *std.Build) void {
|
|||||||
|
|
||||||
const sqlite_mod = sqlite_dep.module("sqlite");
|
const sqlite_mod = sqlite_dep.module("sqlite");
|
||||||
|
|
||||||
|
const web_dep = b.dependency("web", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const web_mod = web_dep.module("web");
|
||||||
|
|
||||||
const myid_mod = b.addModule("myid", .{
|
const myid_mod = b.addModule("myid", .{
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.root_source_file = b.path("src/root.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "sqlite", .module = sqlite_mod },
|
.{ .name = "sqlite", .module = sqlite_mod },
|
||||||
|
.{ .name = "web", .module = web_mod },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,5 +13,8 @@
|
|||||||
.url = "git+https://github.com/vrischmann/zig-sqlite#6d90ee900d186a7fbb6066f28ee13beeaf8be345",
|
.url = "git+https://github.com/vrischmann/zig-sqlite#6d90ee900d186a7fbb6066f28ee13beeaf8be345",
|
||||||
.hash = "sqlite-3.48.0-F2R_a5yODgDFvwwsytm7ZONcSqYBo3qv1PmXOtw3tqLA",
|
.hash = "sqlite-3.48.0-F2R_a5yODgDFvwwsytm7ZONcSqYBo3qv1PmXOtw3tqLA",
|
||||||
},
|
},
|
||||||
|
.web = .{
|
||||||
|
.path = "../web",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
259
packages/myid/src/data.zig
Normal file
259
packages/myid/src/data.zig
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const sqlite = @import("sqlite");
|
||||||
|
const web = @import("web");
|
||||||
|
|
||||||
|
const id = @import("id.zig");
|
||||||
|
|
||||||
|
pub const callback_regex = "^https?://([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}/";
|
||||||
|
|
||||||
|
pub const App = struct {
|
||||||
|
aid: id.App,
|
||||||
|
name: []const u8,
|
||||||
|
callbacks: []const []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AppWithSecret = struct {
|
||||||
|
aid: id.App,
|
||||||
|
name: []const u8,
|
||||||
|
callbacks: []const []const u8,
|
||||||
|
secret: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CreateAppData = struct {
|
||||||
|
name: []const u8,
|
||||||
|
callback: ?[]const []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CreateAppResult = struct {
|
||||||
|
aid: id.App,
|
||||||
|
plainSecret: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const User = struct {
|
||||||
|
uid: id.User,
|
||||||
|
name: []const u8,
|
||||||
|
email: []const u8,
|
||||||
|
admin: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const UserWithPassword = struct {
|
||||||
|
uid: id.User,
|
||||||
|
name: []const u8,
|
||||||
|
email: []const u8,
|
||||||
|
admin: bool,
|
||||||
|
passworD: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InviteUserData = struct {
|
||||||
|
name: []const u8,
|
||||||
|
email: []const u8,
|
||||||
|
admin: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CreateUserData = struct {
|
||||||
|
name: []const u8,
|
||||||
|
email: []const u8,
|
||||||
|
plainPassword: []const u8,
|
||||||
|
admin: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const All = struct {
|
||||||
|
users: []const User,
|
||||||
|
apps: []const App,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Database = struct {
|
||||||
|
db: sqlite.Db,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn init(path: [:0]const u8, allocator: std.mem.Allocator) !Database {
|
||||||
|
var db = try sqlite.Db.init(.{
|
||||||
|
.mode = .{ .File = path },
|
||||||
|
.open_flags = .{
|
||||||
|
.create = true,
|
||||||
|
.write = true,
|
||||||
|
},
|
||||||
|
.threading_mode = .MultiThread,
|
||||||
|
});
|
||||||
|
errdefer db.deinit();
|
||||||
|
|
||||||
|
_ = try db.one(void, "PRAGMA journal_mode = WAL", .{}, .{});
|
||||||
|
_ = try db.one(void, "PRAGMA foreign_keys = ON", .{}, .{});
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.db = db,
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Database) void {
|
||||||
|
self.db.deinit();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- MIGRATION -----------------------------------------------------------
|
||||||
|
|
||||||
|
fn getUserVersion(self: *Database) !i32 {
|
||||||
|
const version = try self.db.one(i32, "PRAGMA user_version", .{}, .{});
|
||||||
|
return version.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setUserVersion(self: *Database, version: i32) !void {
|
||||||
|
var buf: [100]u8 = undefined;
|
||||||
|
const query = std.fmt.bufPrint(&buf, "PRAGMA user_version = {d}", .{version}) catch unreachable;
|
||||||
|
_ = try self.db.oneDynamic(void, query, .{}, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn migrate(self: *Database) !void {
|
||||||
|
var user_version = try self.getUserVersion();
|
||||||
|
|
||||||
|
if (user_version == 0) {
|
||||||
|
_ = try self.db.exec(
|
||||||
|
\\CREATE TABLE users (
|
||||||
|
\\ uid BLOB NOT NULL,
|
||||||
|
\\ name TEXT NOT NULL,
|
||||||
|
\\ email TEXT NOT NULL UNIQUE,
|
||||||
|
\\ password TEXT NOT NULL,
|
||||||
|
\\ PRIMARY KEY (uid)
|
||||||
|
\\)
|
||||||
|
, .{}, .{});
|
||||||
|
_ = try self.db.exec(
|
||||||
|
\\CREATE TABLE apps (
|
||||||
|
\\ aid BLOB NOT NULL,
|
||||||
|
\\ name TEXT NOT NULL,
|
||||||
|
\\ secret TEXT NOT NULL,
|
||||||
|
\\ PRIMARY KEY (aid)
|
||||||
|
\\)
|
||||||
|
, .{}, .{});
|
||||||
|
_ = try self.db.exec(
|
||||||
|
\\CREATE TABLE app_callbacks (
|
||||||
|
\\ aid BLOB NOT NULL,
|
||||||
|
\\ callback TEXT NOT NULL,
|
||||||
|
\\ PRIMARY KEY (aid, callback),
|
||||||
|
\\ FOREIGN KEY (aid) REFERENCES apps (aid)
|
||||||
|
\\ ON UPDATE CASCADE
|
||||||
|
\\ ON DELETE CASCADE
|
||||||
|
\\)
|
||||||
|
, .{}, .{});
|
||||||
|
user_version += 1;
|
||||||
|
try self.setUserVersion(user_version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- USERS ---------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn createUser(self: *Database, name: []const u8, email: []const u8, plain_password: []const u8) !id.User {
|
||||||
|
const uid = id.User.init(web.UUID.v7());
|
||||||
|
var password_buf: [1000]u8 = undefined;
|
||||||
|
const password = try std.crypto.pwhash.argon2.strHash(plain_password, .{
|
||||||
|
.allocator = self.allocator,
|
||||||
|
.mode = .argon2id,
|
||||||
|
.params = .owasp_2id,
|
||||||
|
}, &password_buf);
|
||||||
|
|
||||||
|
try self.db.exec("INSERT INTO users (uid, name, email, password) VALUES (?, ?, ?, ?)", .{}, .{
|
||||||
|
.uid = uid.bytes,
|
||||||
|
.name = name,
|
||||||
|
.email = email,
|
||||||
|
.password = password,
|
||||||
|
});
|
||||||
|
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- APPS ----------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn createApp(self: *Database, name: []const u8, callbacks: []const []const u8) !CreateAppResult {
|
||||||
|
_ = try self.db.exec("BEGIN", .{}, .{});
|
||||||
|
errdefer self.db.exec("ROLLBACK", .{}, .{}) catch unreachable;
|
||||||
|
|
||||||
|
const secret_bytes = blk: {
|
||||||
|
var bytes: [24]u8 = undefined;
|
||||||
|
std.crypto.random.bytes(&bytes);
|
||||||
|
break :blk bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
var plain_secret_buf: [32]u8 = undefined;
|
||||||
|
const plain_secret = std.base64.url_safe_no_pad.Encoder.encode(&plain_secret_buf, &secret_bytes);
|
||||||
|
std.debug.assert(plain_secret_buf.len == plain_secret.len);
|
||||||
|
|
||||||
|
const aid = id.App.init(web.UUID.v7());
|
||||||
|
var secret_buf: [1000]u8 = undefined;
|
||||||
|
const secret = try std.crypto.pwhash.argon2.strHash(plain_secret, .{
|
||||||
|
.allocator = self.allocator,
|
||||||
|
.mode = .argon2id,
|
||||||
|
.params = .owasp_2id,
|
||||||
|
}, &secret_buf);
|
||||||
|
|
||||||
|
try self.db.exec("INSERT INTO apps (aid, name, secret) VALUES (?, ?, ?)", .{}, .{
|
||||||
|
.aid = aid.bytes,
|
||||||
|
.name = name,
|
||||||
|
.secret = secret,
|
||||||
|
});
|
||||||
|
|
||||||
|
var insert_callback = try self.db.prepare("INSERT INTO app_callbacks (aid, callback) VALUES (?, ?)");
|
||||||
|
defer insert_callback.deinit();
|
||||||
|
for (callbacks) |callback| {
|
||||||
|
try insert_callback.exec(.{}, .{ .aid = aid.bytes, .callback = callback });
|
||||||
|
insert_callback.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.db.exec("COMMIT", .{}, .{});
|
||||||
|
return .{
|
||||||
|
.aid = aid,
|
||||||
|
.plain_secret = plain_secret_buf,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "user version" {
|
||||||
|
var db = try Database.init(":memory:", std.testing.allocator);
|
||||||
|
defer db.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(0, try db.getUserVersion());
|
||||||
|
try db.setUserVersion(1);
|
||||||
|
try std.testing.expectEqual(1, try db.getUserVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "migrate" {
|
||||||
|
var db = try Database.init(":memory:", std.testing.allocator);
|
||||||
|
defer db.deinit();
|
||||||
|
|
||||||
|
try db.migrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "create user" {
|
||||||
|
var db = try Database.init(":memory:", std.testing.allocator);
|
||||||
|
defer db.deinit();
|
||||||
|
|
||||||
|
try db.migrate();
|
||||||
|
|
||||||
|
const uid = try db.createUser("admin", "admin@test.invalid", "admin");
|
||||||
|
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
const maybe_user = try db.db.oneAlloc(struct {
|
||||||
|
name: []const u8,
|
||||||
|
email: []const u8,
|
||||||
|
password: []const u8,
|
||||||
|
}, arena.allocator(), "SELECT name, email, password FROM users WHERE uid = ?", .{}, .{
|
||||||
|
.uid = uid.bytes,
|
||||||
|
});
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(maybe_user != null);
|
||||||
|
if (maybe_user) |user| {
|
||||||
|
try std.testing.expectEqualSlices(u8, "admin", user.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "create app" {
|
||||||
|
var db = try Database.init(":memory:", std.testing.allocator);
|
||||||
|
defer db.deinit();
|
||||||
|
|
||||||
|
try db.migrate();
|
||||||
|
|
||||||
|
_ = try db.createApp("app", &.{
|
||||||
|
"http://localhost:3000/callback",
|
||||||
|
"https://example.com/callback",
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
pub const database_path = "db.sqlite3";
|
|
||||||
pub const socket_path = "myid.sock";
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const main = @import("main.zig");
|
|
||||||
|
|
||||||
const Parser = @import("http/Parser.zig");
|
|
||||||
|
|
||||||
threadlocal var read_buffer: [2 * 1024 * 1024]u8 = undefined;
|
|
||||||
threadlocal var write_buffer: [2 * 1024 * 1024]u8 = undefined;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.http);
|
|
||||||
|
|
||||||
const status = struct {
|
|
||||||
pub const ok = "HTTP/1.1 200 OK\r\n";
|
|
||||||
pub const created = "HTTP/1.1 201 Created\r\n";
|
|
||||||
pub const accepted = "HTTP/1.1 202 Accepted\r\n";
|
|
||||||
pub const non_authoritative_information = "HTTP/1.1 203 Non-Authoritative Information\r\n";
|
|
||||||
pub const no_content = "HTTP/1.1 204 No Content\r\n";
|
|
||||||
pub const reset_content = "HTTP/1.1 205 Reset Content\r\n";
|
|
||||||
pub const partial_content = "HTTP/1.1 206 Partial Content\r\n";
|
|
||||||
pub const multi_status = "HTTP/1.1 207 Multi-Status\r\n";
|
|
||||||
pub const already_reported = "HTTP/1.1 208 Already Reported\r\n";
|
|
||||||
|
|
||||||
pub const multiple_choices = "HTTP/1.1 300 Multiple Choices\r\n";
|
|
||||||
pub const moved_permanently = "HTTP/1.1 301 Moved Permanently\r\n";
|
|
||||||
pub const found = "HTTP/1.1 302 Found\r\n";
|
|
||||||
pub const see_other = "HTTP/1.1 303 See Other\r\n";
|
|
||||||
pub const not_modified = "HTTP/1.1 304 Not Modified\r\n";
|
|
||||||
pub const temporary_redirect = "HTTP/1.1 307 Temporary Redirect\r\n";
|
|
||||||
pub const permanent_redirect = "HTTP/1.1 308 Permanent Redirect\r\n";
|
|
||||||
|
|
||||||
pub const bad_request = "HTTP/1.1 400 Bad Request\r\n";
|
|
||||||
pub const unauthorized = "HTTP/1.1 401 Unauthorized\r\n";
|
|
||||||
pub const payment_required = "HTTP/1.1 402 Payment Required\r\n";
|
|
||||||
pub const forbidden = "HTTP/1.1 403 Forbidden\r\n";
|
|
||||||
pub const not_found = "HTTP/1.1 404 Not Found\r\n";
|
|
||||||
pub const method_not_allowed = "HTTP/1.1 405 Method Not Allowed\r\n";
|
|
||||||
pub const not_acceptable = "HTTP/1.1 406 Not Acceptable\r\n";
|
|
||||||
pub const proxy_authentication_required = "HTTP/1.1 407 Proxy Authentication Required\r\n";
|
|
||||||
pub const request_timeout = "HTTP/1.1 408 Request Timeout\r\n";
|
|
||||||
pub const conflict = "HTTP/1.1 409 Conflict\r\n";
|
|
||||||
pub const gone = "HTTP/1.1 410 Gone\r\n";
|
|
||||||
pub const length_required = "HTTP/1.1 411 Length Required\r\n";
|
|
||||||
pub const precondition_failed = "HTTP/1.1 412 Precondition Failed\r\n";
|
|
||||||
pub const content_too_large = "HTTP/1.1 413 Content Too Large\r\n";
|
|
||||||
pub const uri_too_long = "HTTP/1.1 414 URI Too Long\r\n";
|
|
||||||
pub const unsupported_media_type = "HTTP/1.1 415 Unsupported Media Type\r\n";
|
|
||||||
pub const range_not_satisfiable = "HTTP/1.1 416 Range Not Satisfiable\r\n";
|
|
||||||
pub const expectation_failed = "HTTP/1.1 417 Expectation Failed\r\n";
|
|
||||||
pub const im_a_teapot = "HTTP/1.1 418 I'm a teapot\r\n";
|
|
||||||
pub const misdirected_request = "HTTP/1.1 421 Misdirected Request\r\n";
|
|
||||||
pub const unprocessable_content = "HTTP/1.1 422 Unprocessable Content\r\n";
|
|
||||||
pub const locked = "HTTP/1.1 423 Locked\r\n";
|
|
||||||
pub const failed_dependency = "HTTP/1.1 424 Failed Dependency\r\n";
|
|
||||||
pub const upgrade_required = "HTTP/1.1 426 Upgrade Required\r\n";
|
|
||||||
pub const precondition_required = "HTTP/1.1 428 Precondition Required\r\n";
|
|
||||||
pub const too_many_requests = "HTTP/1.1 429 Too Many Requests\r\n";
|
|
||||||
pub const request_header_fields_too_large = "HTTP/1.1 431 Request Header Fields Too Large\r\n";
|
|
||||||
pub const unavailable_for_legal_reasons = "HTTP/1.1 451 Unavailable For Legal Reasons\r\n";
|
|
||||||
|
|
||||||
pub const internal_server_error = "HTTP/1.1 500 Internal Server Error\r\n";
|
|
||||||
pub const not_implemented = "HTTP/1.1 501 Not Implemented\r\n";
|
|
||||||
pub const bad_gateway = "HTTP/1.1 502 Bad Gateway\r\n";
|
|
||||||
pub const service_unavailable = "HTTP/1.1 503 Service Unavailable\r\n";
|
|
||||||
pub const gateway_timeout = "HTTP/1.1 504 Gateway Timeout\r\n";
|
|
||||||
pub const http_version_not_supported = "HTTP/1.1 505 HTTP Version Not Supported\r\n";
|
|
||||||
pub const variant_also_negotiates = "HTTP/1.1 506 Variant Also Negotiates\r\n";
|
|
||||||
pub const insufficient_storage = "HTTP/1.1 507 Insufficient Storage\r\n";
|
|
||||||
pub const loop_detected = "HTTP/1.1 508 Loop Detected\r\n";
|
|
||||||
pub const not_extended = "HTTP/1.1 510 Not Extended\r\n";
|
|
||||||
pub const network_authentication_required = "HTTP/1.1 511 Network Authentication Required\r\n";
|
|
||||||
};
|
|
||||||
|
|
||||||
const ResponseEmptyOptions = struct {
|
|
||||||
status_text: []const u8 = status.ok,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ResponseOptions = struct {
|
|
||||||
status_text: []const u8 = status.ok,
|
|
||||||
media_type: []const u8 = "text/plain; charset=utf-8",
|
|
||||||
response_body: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn makeResponseEmpty(options: ResponseEmptyOptions) ![]const u8 {
|
|
||||||
var fbs = std.io.fixedBufferStream(&write_buffer);
|
|
||||||
const writer = fbs.writer();
|
|
||||||
|
|
||||||
try writer.print("{s}", .{options.status_text});
|
|
||||||
try writer.print("\r\n", .{});
|
|
||||||
|
|
||||||
return fbs.getWritten();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn makeResponseClose(options: ResponseEmptyOptions) ![]const u8 {
|
|
||||||
var fbs = std.io.fixedBufferStream(&write_buffer);
|
|
||||||
const writer = fbs.writer();
|
|
||||||
|
|
||||||
try writer.print("{s}", .{options.status_text});
|
|
||||||
try writer.print("Connection: close\r\n", .{});
|
|
||||||
try writer.print("\r\n", .{});
|
|
||||||
|
|
||||||
return fbs.getWritten();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn makeResponse(options: ResponseOptions) ![]const u8 {
|
|
||||||
var fbs = std.io.fixedBufferStream(&write_buffer);
|
|
||||||
const writer = fbs.writer();
|
|
||||||
|
|
||||||
try writer.print("{s}", .{options.status_text});
|
|
||||||
try writer.print("Content-Type: {s}\r\n", .{options.media_type});
|
|
||||||
try writer.print("Content-Length: {d}\r\n", .{options.response_body.len});
|
|
||||||
try writer.print("\r\n", .{});
|
|
||||||
try writer.print("{s}", .{options.response_body});
|
|
||||||
|
|
||||||
return fbs.getWritten();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process(conn: std.net.Server.Connection) !void {
|
|
||||||
defer conn.stream.close();
|
|
||||||
|
|
||||||
var leftover_bytes: usize = 0;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const start = try std.time.Instant.now();
|
|
||||||
|
|
||||||
var route: Parser.Route = undefined;
|
|
||||||
|
|
||||||
var parser = Parser.init(.{
|
|
||||||
.self = &route,
|
|
||||||
.route = routeCallback,
|
|
||||||
});
|
|
||||||
var total_bytes_read: usize = 0;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
var bytes_read: usize = undefined;
|
|
||||||
var chars: []const u8 = undefined;
|
|
||||||
|
|
||||||
if (leftover_bytes > 0) {
|
|
||||||
bytes_read = leftover_bytes;
|
|
||||||
chars = read_buffer[0..leftover_bytes];
|
|
||||||
leftover_bytes = 0;
|
|
||||||
} else {
|
|
||||||
bytes_read = try conn.stream.read(read_buffer[total_bytes_read..]);
|
|
||||||
chars = read_buffer[total_bytes_read .. total_bytes_read + bytes_read];
|
|
||||||
}
|
|
||||||
|
|
||||||
total_bytes_read += bytes_read;
|
|
||||||
|
|
||||||
const res = parser.consume(chars) catch |err| switch (err) {
|
|
||||||
error.MethodNotSupported => {
|
|
||||||
const response = try makeResponseClose(.{ .status_text = status.method_not_allowed });
|
|
||||||
try conn.stream.writeAll(response);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
error.HttpVersionNotSupported => {
|
|
||||||
const response = try makeResponseClose(.{ .status_text = status.http_version_not_supported });
|
|
||||||
try conn.stream.writeAll(response);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
error.MissingLineFeed => {
|
|
||||||
const response = try makeResponseClose(.{ .status_text = status.bad_request });
|
|
||||||
try conn.stream.writeAll(response);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
error.InvalidContentLength => {
|
|
||||||
const response = try makeResponseClose(.{ .status_text = status.bad_request });
|
|
||||||
try conn.stream.writeAll(response);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (total_bytes_read >= read_buffer.len and !res.done) {
|
|
||||||
if (parser.state == .body) {
|
|
||||||
const response = try makeResponseClose(.{ .status_text = status.content_too_large });
|
|
||||||
try conn.stream.writeAll(response);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const response = try makeResponseClose(.{ .status_text = status.request_header_fields_too_large });
|
|
||||||
try conn.stream.writeAll(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.done) {
|
|
||||||
leftover_bytes = bytes_read - res.consumed;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = try makeResponse(.{ .response_body = "PONG\n" });
|
|
||||||
|
|
||||||
try conn.stream.writeAll(response);
|
|
||||||
|
|
||||||
if (leftover_bytes > 0) {
|
|
||||||
@memmove(&read_buffer, read_buffer[total_bytes_read - leftover_bytes .. total_bytes_read]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = try std.time.Instant.now();
|
|
||||||
const time_ns = end.since(start);
|
|
||||||
const time_us = @divFloor(time_ns, std.time.ns_per_us);
|
|
||||||
|
|
||||||
log.info("{s} {s} ({} μs)", .{ @tagName(route.method), route.pathname, time_us });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn routeCallback(self: ?*anyopaque, route: Parser.Route) void {
|
|
||||||
@as(*Parser.Route, @alignCast(@ptrCast(self))).* = route;
|
|
||||||
}
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Parser = @This();
|
|
||||||
|
|
||||||
const Callbacks = struct {
|
|
||||||
self: ?*anyopaque = null,
|
|
||||||
|
|
||||||
route: ?*const fn (self: ?*anyopaque, route: Route) void = null,
|
|
||||||
header: ?*const fn (self: ?*anyopaque, name: []const u8, value: []const u8) void = null,
|
|
||||||
body: ?*const fn (self: ?*anyopaque, body: []const u8) void = null,
|
|
||||||
|
|
||||||
pub const init: Callbacks = .{};
|
|
||||||
};
|
|
||||||
|
|
||||||
const Error = error{
|
|
||||||
MethodNotSupported,
|
|
||||||
HttpVersionNotSupported,
|
|
||||||
MissingLineFeed,
|
|
||||||
InvalidContentLength,
|
|
||||||
};
|
|
||||||
|
|
||||||
const State = union(enum) {
|
|
||||||
pub fn methodComplete(method: Method) State {
|
|
||||||
return .{
|
|
||||||
.method_complete = .{
|
|
||||||
.method = method,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pathname(method: Method, p: []const u8) State {
|
|
||||||
return .{
|
|
||||||
.pathname_state = .{
|
|
||||||
.method = method,
|
|
||||||
.pathname = p,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn headerValue(name: []const u8, value: []const u8) State {
|
|
||||||
return .{
|
|
||||||
.header_value = .{
|
|
||||||
.name = name,
|
|
||||||
.value = value,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
init: void,
|
|
||||||
method_d: void,
|
|
||||||
method_g: void,
|
|
||||||
method_h: void,
|
|
||||||
method_p: void,
|
|
||||||
method_de: void,
|
|
||||||
method_ge: void,
|
|
||||||
method_he: void,
|
|
||||||
method_pa: void,
|
|
||||||
method_po: void,
|
|
||||||
method_pu: void,
|
|
||||||
method_del: void,
|
|
||||||
method_hea: void,
|
|
||||||
method_pat: void,
|
|
||||||
method_pos: void,
|
|
||||||
method_dele: void,
|
|
||||||
method_patc: void,
|
|
||||||
method_delet: void,
|
|
||||||
method_complete: struct { method: Method },
|
|
||||||
pathname_state: struct { method: Method, pathname: []const u8 },
|
|
||||||
pathname_complete: void,
|
|
||||||
version_h: void,
|
|
||||||
version_ht: void,
|
|
||||||
version_htt: void,
|
|
||||||
version_http: void,
|
|
||||||
@"version_http/@": void,
|
|
||||||
@"version_http/1@": void,
|
|
||||||
@"version_http/1.@": void,
|
|
||||||
version_complete: void,
|
|
||||||
start_line_end: void,
|
|
||||||
header_name_start: void,
|
|
||||||
header_name: []const u8,
|
|
||||||
header_value: struct { name: []const u8, value: []const u8 },
|
|
||||||
header_line_end: void,
|
|
||||||
headers_end: void,
|
|
||||||
body: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConsumeResult = struct {
|
|
||||||
consumed: usize,
|
|
||||||
done: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConsumeCharResult = enum {
|
|
||||||
not_done,
|
|
||||||
done,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Method = enum {
|
|
||||||
DELETE,
|
|
||||||
GET,
|
|
||||||
HEAD,
|
|
||||||
PATCH,
|
|
||||||
POST,
|
|
||||||
PUT,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Route = struct {
|
|
||||||
method: Method,
|
|
||||||
pathname: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
callbacks: Callbacks,
|
|
||||||
state: State,
|
|
||||||
current_header_is_content_length: bool,
|
|
||||||
content_length: usize,
|
|
||||||
|
|
||||||
pub fn init(callbacks: Callbacks) Parser {
|
|
||||||
return .{
|
|
||||||
.callbacks = callbacks,
|
|
||||||
.state = .init,
|
|
||||||
.current_header_is_content_length = false,
|
|
||||||
.content_length = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < chars.len) {
|
|
||||||
switch (self.state) {
|
|
||||||
.body => |body| {
|
|
||||||
const to_consume = @min(chars.len - i, self.content_length - body.len);
|
|
||||||
const new_body = body.ptr[0 .. body.len + to_consume];
|
|
||||||
self.state = .{ .body = new_body };
|
|
||||||
i += to_consume;
|
|
||||||
|
|
||||||
const done = new_body.len >= self.content_length;
|
|
||||||
|
|
||||||
if (done) {
|
|
||||||
if (self.callbacks.body) |bodyCallback| {
|
|
||||||
bodyCallback(self.callbacks.self, new_body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.consumed = i,
|
|
||||||
.done = done,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
const res = try self.consumeChar(&chars[i]);
|
|
||||||
i += 1;
|
|
||||||
if (res == .done) return .{
|
|
||||||
.consumed = i,
|
|
||||||
.done = true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.consumed = chars.len,
|
|
||||||
.done = false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|
||||||
const c = c_ptr.*;
|
|
||||||
const c_slice = @as([*]const u8, @ptrCast(c_ptr))[0..1];
|
|
||||||
switch (self.state) {
|
|
||||||
.init => switch (c) {
|
|
||||||
'D' => self.state = .method_d,
|
|
||||||
'G' => self.state = .method_g,
|
|
||||||
'H' => self.state = .method_h,
|
|
||||||
'P' => self.state = .method_p,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_d => switch (c) {
|
|
||||||
'E' => self.state = .method_de,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_g => switch (c) {
|
|
||||||
'E' => self.state = .method_ge,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_h => switch (c) {
|
|
||||||
'E' => self.state = .method_he,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_p => switch (c) {
|
|
||||||
'A' => self.state = .method_pa,
|
|
||||||
'O' => self.state = .method_po,
|
|
||||||
'U' => self.state = .method_pu,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_de => switch (c) {
|
|
||||||
'L' => self.state = .method_del,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_ge => switch (c) {
|
|
||||||
'T' => self.state = .methodComplete(.GET),
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_he => switch (c) {
|
|
||||||
'A' => self.state = .method_hea,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_pa => switch (c) {
|
|
||||||
'T' => self.state = .method_pat,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_po => switch (c) {
|
|
||||||
'S' => self.state = .method_pos,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_pu => switch (c) {
|
|
||||||
'T' => self.state = .methodComplete(.PUT),
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_del => switch (c) {
|
|
||||||
'E' => self.state = .method_dele,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_hea => switch (c) {
|
|
||||||
'D' => self.state = .methodComplete(.HEAD),
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_pat => switch (c) {
|
|
||||||
'C' => self.state = .method_patc,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_pos => switch (c) {
|
|
||||||
'T' => self.state = .methodComplete(.POST),
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_dele => switch (c) {
|
|
||||||
'T' => self.state = .method_delet,
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_patc => switch (c) {
|
|
||||||
'H' => self.state = .methodComplete(.PATCH),
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_delet => switch (c) {
|
|
||||||
'E' => self.state = .methodComplete(.DELETE),
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.method_complete => |s| switch (c) {
|
|
||||||
' ' => self.state = .pathname(s.method, @as([*]const u8, @ptrCast(c_ptr))[1..1]),
|
|
||||||
else => return error.MethodNotSupported,
|
|
||||||
},
|
|
||||||
.pathname_state => |s| switch (c) {
|
|
||||||
' ' => {
|
|
||||||
self.state = .pathname_complete;
|
|
||||||
if (self.callbacks.route) |routeCallback| {
|
|
||||||
routeCallback(self.callbacks.self, .{
|
|
||||||
.method = s.method,
|
|
||||||
.pathname = s.pathname,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + 1]),
|
|
||||||
},
|
|
||||||
.pathname_complete => switch (c) {
|
|
||||||
'H' => self.state = .version_h,
|
|
||||||
else => return error.HttpVersionNotSupported,
|
|
||||||
},
|
|
||||||
.version_h => switch (c) {
|
|
||||||
'T' => self.state = .version_ht,
|
|
||||||
else => return error.HttpVersionNotSupported,
|
|
||||||
},
|
|
||||||
.version_ht => switch (c) {
|
|
||||||
'T' => self.state = .version_htt,
|
|
||||||
else => return error.HttpVersionNotSupported,
|
|
||||||
},
|
|
||||||
.version_htt => switch (c) {
|
|
||||||
'P' => self.state = .version_http,
|
|
||||||
else => return error.HttpVersionNotSupported,
|
|
||||||
},
|
|
||||||
.version_http => switch (c) {
|
|
||||||
'/' => self.state = .@"version_http/@",
|
|
||||||
else => return error.HttpVersionNotSupported,
|
|
||||||
},
|
|
||||||
.@"version_http/@" => switch (c) {
|
|
||||||
'1' => self.state = .@"version_http/1@",
|
|
||||||
else => return error.HttpVersionNotSupported,
|
|
||||||
},
|
|
||||||
.@"version_http/1@" => switch (c) {
|
|
||||||
'.' => self.state = .@"version_http/1.@",
|
|
||||||
else => return error.HttpVersionNotSupported,
|
|
||||||
},
|
|
||||||
.@"version_http/1.@" => switch (c) {
|
|
||||||
'1' => self.state = .version_complete,
|
|
||||||
else => return error.HttpVersionNotSupported,
|
|
||||||
},
|
|
||||||
.version_complete => switch (c) {
|
|
||||||
'\r' => self.state = .start_line_end,
|
|
||||||
else => return error.HttpVersionNotSupported,
|
|
||||||
},
|
|
||||||
.start_line_end => switch (c) {
|
|
||||||
'\n' => self.state = .header_name_start,
|
|
||||||
else => return error.MissingLineFeed,
|
|
||||||
},
|
|
||||||
.header_name_start => switch (c) {
|
|
||||||
'\r' => self.state = .headers_end,
|
|
||||||
else => self.state = .{ .header_name = c_slice },
|
|
||||||
},
|
|
||||||
.header_name => |name| switch (c) {
|
|
||||||
':' => {
|
|
||||||
self.state = .headerValue(name, @as([*]const u8, @ptrCast(c_ptr))[1..1]);
|
|
||||||
self.current_header_is_content_length = std.ascii.eqlIgnoreCase(name, "Content-Length");
|
|
||||||
},
|
|
||||||
else => self.state = .{ .header_name = name.ptr[0 .. name.len + 1] },
|
|
||||||
},
|
|
||||||
.header_value => |s| switch (c) {
|
|
||||||
'\r' => {
|
|
||||||
self.state = .header_line_end;
|
|
||||||
const value_trimmed = std.mem.trim(u8, s.value, " \t");
|
|
||||||
if (self.current_header_is_content_length) {
|
|
||||||
self.content_length = std.fmt.parseInt(usize, value_trimmed, 10) catch return error.InvalidContentLength;
|
|
||||||
self.current_header_is_content_length = false;
|
|
||||||
}
|
|
||||||
if (self.callbacks.header) |headerCallback| {
|
|
||||||
headerCallback(self.callbacks.self, s.name, value_trimmed);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + 1]),
|
|
||||||
},
|
|
||||||
.header_line_end => switch (c) {
|
|
||||||
'\n' => self.state = .header_name_start,
|
|
||||||
else => return error.MissingLineFeed,
|
|
||||||
},
|
|
||||||
.headers_end => switch (c) {
|
|
||||||
'\n' => {
|
|
||||||
if (self.content_length == 0) {
|
|
||||||
if (self.callbacks.body) |bodyCallback| {
|
|
||||||
bodyCallback(self.callbacks.self, &.{});
|
|
||||||
}
|
|
||||||
return .done;
|
|
||||||
}
|
|
||||||
self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] };
|
|
||||||
},
|
|
||||||
else => return error.MissingLineFeed,
|
|
||||||
},
|
|
||||||
.body => |body| {
|
|
||||||
const new_body = body.ptr[0 .. body.len + 1];
|
|
||||||
self.state = .{ .body = new_body };
|
|
||||||
if (new_body.len >= self.content_length) {
|
|
||||||
if (self.callbacks.body) |bodyCallback| {
|
|
||||||
bodyCallback(self.callbacks.self, new_body);
|
|
||||||
}
|
|
||||||
return .done;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return .not_done;
|
|
||||||
}
|
|
||||||
6
packages/myid/src/id.zig
Normal file
6
packages/myid/src/id.zig
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const web = @import("web");
|
||||||
|
|
||||||
|
pub const App = web.Id(.app);
|
||||||
|
pub const Session = web.Id(.session);
|
||||||
|
pub const User = web.Id(.user);
|
||||||
@@ -1,235 +1,3 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sqlite = @import("sqlite");
|
const sqlite = @import("sqlite");
|
||||||
const uuid = @import("uuid.zig");
|
const web = @import("web");
|
||||||
|
|
||||||
fn Id(comptime _tag: @Type(.enum_literal)) type {
|
|
||||||
return struct {
|
|
||||||
pub const tag = _tag;
|
|
||||||
|
|
||||||
bytes: [16]u8,
|
|
||||||
|
|
||||||
pub fn new() @This() {
|
|
||||||
return .{ .bytes = uuid.uuid_v7() };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eql(a: @This(), b: @This()) bool {
|
|
||||||
return std.mem.eql(u8, &a.bytes, &b.bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode(encoded: *const [22]u8) !@This() {
|
|
||||||
var bytes: [16]u8 = undefined;
|
|
||||||
try std.base64.url_safe_no_pad.Decoder.decode(&bytes, encoded);
|
|
||||||
return .{ .bytes = bytes };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(self: @This()) [22]u8 {
|
|
||||||
var text: [22]u8 = undefined;
|
|
||||||
std.base64.url_safe_no_pad.Encoder.encode(&text, self.bytes);
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const AppId = Id(.app_id);
|
|
||||||
pub const UserId = Id(.user_id);
|
|
||||||
|
|
||||||
pub const CreateAppResult = struct {
|
|
||||||
aid: AppId,
|
|
||||||
plain_secret: [32]u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Database = struct {
|
|
||||||
db: sqlite.Db,
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
|
|
||||||
pub fn init(path: [:0]const u8, allocator: std.mem.Allocator) !Database {
|
|
||||||
var db = try sqlite.Db.init(.{
|
|
||||||
.mode = .{ .File = path },
|
|
||||||
.open_flags = .{
|
|
||||||
.create = true,
|
|
||||||
.write = true,
|
|
||||||
},
|
|
||||||
.threading_mode = .MultiThread,
|
|
||||||
});
|
|
||||||
errdefer db.deinit();
|
|
||||||
|
|
||||||
_ = try db.one(void, "PRAGMA journal_mode = WAL", .{}, .{});
|
|
||||||
_ = try db.one(void, "PRAGMA foreign_keys = ON", .{}, .{});
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.db = db,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Database) void {
|
|
||||||
self.db.deinit();
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- MIGRATION -----------------------------------------------------------
|
|
||||||
|
|
||||||
fn getUserVersion(self: *Database) !i32 {
|
|
||||||
const version = try self.db.one(i32, "PRAGMA user_version", .{}, .{});
|
|
||||||
return version.?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setUserVersion(self: *Database, version: i32) !void {
|
|
||||||
var buf: [100]u8 = undefined;
|
|
||||||
const query = std.fmt.bufPrint(&buf, "PRAGMA user_version = {d}", .{version}) catch unreachable;
|
|
||||||
_ = try self.db.oneDynamic(void, query, .{}, .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn migrate(self: *Database) !void {
|
|
||||||
var user_version = try self.getUserVersion();
|
|
||||||
|
|
||||||
if (user_version == 0) {
|
|
||||||
_ = try self.db.exec(
|
|
||||||
\\CREATE TABLE users (
|
|
||||||
\\ uid BLOB NOT NULL,
|
|
||||||
\\ name TEXT NOT NULL,
|
|
||||||
\\ email TEXT NOT NULL UNIQUE,
|
|
||||||
\\ password TEXT NOT NULL,
|
|
||||||
\\ PRIMARY KEY (uid)
|
|
||||||
\\)
|
|
||||||
, .{}, .{});
|
|
||||||
_ = try self.db.exec(
|
|
||||||
\\CREATE TABLE apps (
|
|
||||||
\\ aid BLOB NOT NULL,
|
|
||||||
\\ name TEXT NOT NULL,
|
|
||||||
\\ secret TEXT NOT NULL,
|
|
||||||
\\ PRIMARY KEY (aid)
|
|
||||||
\\)
|
|
||||||
, .{}, .{});
|
|
||||||
_ = try self.db.exec(
|
|
||||||
\\CREATE TABLE app_callbacks (
|
|
||||||
\\ aid BLOB NOT NULL,
|
|
||||||
\\ callback TEXT NOT NULL,
|
|
||||||
\\ PRIMARY KEY (aid, callback),
|
|
||||||
\\ FOREIGN KEY (aid) REFERENCES apps (aid)
|
|
||||||
\\ ON UPDATE CASCADE
|
|
||||||
\\ ON DELETE CASCADE
|
|
||||||
\\)
|
|
||||||
, .{}, .{});
|
|
||||||
user_version += 1;
|
|
||||||
try self.setUserVersion(user_version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- USERS ---------------------------------------------------------------
|
|
||||||
|
|
||||||
pub fn createUser(self: *Database, name: []const u8, email: []const u8, plain_password: []const u8) !UserId {
|
|
||||||
const uid = UserId.new();
|
|
||||||
var password_buf: [1000]u8 = undefined;
|
|
||||||
const password = try std.crypto.pwhash.argon2.strHash(plain_password, .{
|
|
||||||
.allocator = self.allocator,
|
|
||||||
.mode = .argon2id,
|
|
||||||
.params = .owasp_2id,
|
|
||||||
}, &password_buf);
|
|
||||||
|
|
||||||
try self.db.exec("INSERT INTO users (uid, name, email, password) VALUES (?, ?, ?, ?)", .{}, .{
|
|
||||||
.uid = uid.bytes,
|
|
||||||
.name = name,
|
|
||||||
.email = email,
|
|
||||||
.password = password,
|
|
||||||
});
|
|
||||||
|
|
||||||
return uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- APPS ----------------------------------------------------------------
|
|
||||||
|
|
||||||
pub fn createApp(self: *Database, name: []const u8, callbacks: []const []const u8) !CreateAppResult {
|
|
||||||
_ = try self.db.exec("BEGIN", .{}, .{});
|
|
||||||
errdefer self.db.exec("ROLLBACK", .{}, .{}) catch unreachable;
|
|
||||||
|
|
||||||
const secret_bytes = blk: {
|
|
||||||
var bytes: [24]u8 = undefined;
|
|
||||||
std.crypto.random.bytes(&bytes);
|
|
||||||
break :blk bytes;
|
|
||||||
};
|
|
||||||
|
|
||||||
var plain_secret_buf: [32]u8 = undefined;
|
|
||||||
const plain_secret = std.base64.url_safe_no_pad.Encoder.encode(&plain_secret_buf, &secret_bytes);
|
|
||||||
std.debug.assert(plain_secret_buf.len == plain_secret.len);
|
|
||||||
|
|
||||||
const aid = AppId.new();
|
|
||||||
var secret_buf: [1000]u8 = undefined;
|
|
||||||
const secret = try std.crypto.pwhash.argon2.strHash(plain_secret, .{
|
|
||||||
.allocator = self.allocator,
|
|
||||||
.mode = .argon2id,
|
|
||||||
.params = .owasp_2id,
|
|
||||||
}, &secret_buf);
|
|
||||||
|
|
||||||
try self.db.exec("INSERT INTO apps (aid, name, secret) VALUES (?, ?, ?)", .{}, .{
|
|
||||||
.aid = aid.bytes,
|
|
||||||
.name = name,
|
|
||||||
.secret = secret,
|
|
||||||
});
|
|
||||||
|
|
||||||
var insert_callback = try self.db.prepare("INSERT INTO app_callbacks (aid, callback) VALUES (?, ?)");
|
|
||||||
defer insert_callback.deinit();
|
|
||||||
for (callbacks) |callback| {
|
|
||||||
try insert_callback.exec(.{}, .{ .aid = aid.bytes, .callback = callback });
|
|
||||||
insert_callback.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.db.exec("COMMIT", .{}, .{});
|
|
||||||
return .{
|
|
||||||
.aid = aid,
|
|
||||||
.plain_secret = plain_secret_buf,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test "user version" {
|
|
||||||
var db = try Database.init(":memory:", std.testing.allocator);
|
|
||||||
defer db.deinit();
|
|
||||||
|
|
||||||
try std.testing.expectEqual(0, try db.getUserVersion());
|
|
||||||
try db.setUserVersion(1);
|
|
||||||
try std.testing.expectEqual(1, try db.getUserVersion());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "migrate" {
|
|
||||||
var db = try Database.init(":memory:", std.testing.allocator);
|
|
||||||
defer db.deinit();
|
|
||||||
|
|
||||||
try db.migrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "create user" {
|
|
||||||
var db = try Database.init(":memory:", std.testing.allocator);
|
|
||||||
defer db.deinit();
|
|
||||||
|
|
||||||
try db.migrate();
|
|
||||||
|
|
||||||
const uid = try db.createUser("admin", "admin@test.invalid", "admin");
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
||||||
const maybe_user = try db.db.oneAlloc(struct {
|
|
||||||
name: []const u8,
|
|
||||||
email: []const u8,
|
|
||||||
password: []const u8,
|
|
||||||
}, arena.allocator(), "SELECT name, email, password FROM users WHERE uid = ?", .{}, .{
|
|
||||||
.uid = uid.bytes,
|
|
||||||
});
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
try std.testing.expect(maybe_user != null);
|
|
||||||
if (maybe_user) |user| {
|
|
||||||
try std.testing.expectEqualSlices(u8, "admin", user.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "create app" {
|
|
||||||
var db = try Database.init(":memory:", std.testing.allocator);
|
|
||||||
defer db.deinit();
|
|
||||||
|
|
||||||
try db.migrate();
|
|
||||||
|
|
||||||
_ = try db.createApp("app", &.{
|
|
||||||
"http://localhost:3000/callback",
|
|
||||||
"https://example.com/callback",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
var lock: std.Thread.Mutex = .{};
|
|
||||||
var last_timestamp: std.atomic.Value(u64) = .{ .raw = 0 };
|
|
||||||
var counter: std.atomic.Value(u32) = .{ .raw = 0 };
|
|
||||||
|
|
||||||
fn getCount(timestamp: u64) u32 {
|
|
||||||
lock.lock();
|
|
||||||
defer lock.unlock();
|
|
||||||
|
|
||||||
if (last_timestamp.swap(timestamp, .monotonic) != timestamp) {
|
|
||||||
counter.store(0, .monotonic);
|
|
||||||
}
|
|
||||||
|
|
||||||
return counter.fetchAdd(1, .monotonic) % 4096;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uuid_v7() [16]u8 {
|
|
||||||
const timestamp: u64 = @intCast(@max(0, std.time.milliTimestamp()));
|
|
||||||
const count = getCount(timestamp);
|
|
||||||
const random = blk: {
|
|
||||||
var bytes: [8]u8 = undefined;
|
|
||||||
std.crypto.random.bytes(&bytes);
|
|
||||||
break :blk bytes;
|
|
||||||
};
|
|
||||||
|
|
||||||
var res: [16]u8 = undefined;
|
|
||||||
|
|
||||||
res[0] = @truncate(timestamp >> 40);
|
|
||||||
res[1] = @truncate(timestamp >> 32);
|
|
||||||
res[2] = @truncate(timestamp >> 24);
|
|
||||||
res[3] = @truncate(timestamp >> 16);
|
|
||||||
res[4] = @truncate(timestamp >> 8);
|
|
||||||
res[5] = @truncate(timestamp);
|
|
||||||
res[6] = (@as(u8, 7) << 4) | @as(u8, @truncate((count >> 8) & 0x0F));
|
|
||||||
res[7] = @truncate(count);
|
|
||||||
res[8] = 0x80 | (random[0] & 0x3F);
|
|
||||||
@memcpy(res[9..16], random[1..8]);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
|
||||||
const target = b.resolveTargetQuery(.{
|
|
||||||
.cpu_arch = .x86_64,
|
|
||||||
.os_tag = .windows,
|
|
||||||
.abi = .msvc,
|
|
||||||
});
|
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
|
||||||
|
|
||||||
const zigwin32 = b.dependency("zigwin32", .{});
|
|
||||||
const zigwin32_mod = zigwin32.module("win32");
|
|
||||||
zigwin32_mod.resolved_target = target;
|
|
||||||
zigwin32_mod.optimize = optimize;
|
|
||||||
|
|
||||||
const exe_mod = b.createModule(.{
|
|
||||||
.root_source_file = b.path("src/main.zig"),
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
|
|
||||||
exe_mod.addImport("win32", zigwin32_mod);
|
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
|
||||||
.name = "sciter",
|
|
||||||
.root_module = exe_mod,
|
|
||||||
});
|
|
||||||
|
|
||||||
b.installArtifact(exe);
|
|
||||||
b.getInstallStep().dependOn(&b.addInstallBinFile(b.path("vendor/sciter.dll"), "sciter.dll").step);
|
|
||||||
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
|
||||||
if (b.args) |args| {
|
|
||||||
run_cmd.addArgs(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
const run_step = b.step("run", "Run the app");
|
|
||||||
run_step.dependOn(&run_cmd.step);
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
.{
|
|
||||||
.name = .sciter,
|
|
||||||
.version = "0.0.0",
|
|
||||||
.minimum_zig_version = "0.15.2",
|
|
||||||
.paths = .{
|
|
||||||
"src",
|
|
||||||
"vendor",
|
|
||||||
"build.zig",
|
|
||||||
"build.zig.zon",
|
|
||||||
},
|
|
||||||
.fingerprint = 0x51b124a630f074d7,
|
|
||||||
.dependencies = .{
|
|
||||||
.zigwin32 = .{
|
|
||||||
.url = "https://github.com/marlersoft/zigwin32/archive/5587b16fa040573846a6bf531301f6206d31a6bf.zip",
|
|
||||||
.hash = "zigwin32-25.0.28-preview-AAAAAICM5AMResOGQnQ85mfe60TTOQeMtt7GRATUOKoP",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Sciter Demo</title>
|
|
||||||
<style>
|
|
||||||
html {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-size: 16px;
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hello {
|
|
||||||
width: 120px;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
margin: 1*;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
border: 1px solid black;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p id="hello">Hello, World!</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
const sciter = @import("sciter.zig");
|
|
||||||
const std = @import("std");
|
|
||||||
const wam = @import("win32").ui.windows_and_messaging;
|
|
||||||
const win32 = @import("win32").foundation;
|
|
||||||
|
|
||||||
const L = std.unicode.utf8ToUtf16LeStringLiteral;
|
|
||||||
const WINAPI = @import("std").builtin.CallingConvention.winapi;
|
|
||||||
|
|
||||||
const html = @embedFile("index.html");
|
|
||||||
|
|
||||||
const min_track_size: win32.POINT = .{ .x = 640, .y = 480 };
|
|
||||||
|
|
||||||
var sciter_api: *sciter.API = undefined;
|
|
||||||
|
|
||||||
pub fn wWinMain(
|
|
||||||
hInstance: std.os.windows.HINSTANCE,
|
|
||||||
hPrevInstance: ?std.os.windows.HINSTANCE,
|
|
||||||
lpCmdLine: [*:0]u16,
|
|
||||||
nCmdShow: i32,
|
|
||||||
) i32 {
|
|
||||||
_ = hPrevInstance;
|
|
||||||
_ = lpCmdLine;
|
|
||||||
|
|
||||||
const sciter_module = std.os.windows.LoadLibraryW(L("sciter.dll")) catch |err| {
|
|
||||||
_ = wam.MessageBoxW(
|
|
||||||
null,
|
|
||||||
switch (err) {
|
|
||||||
error.FileNotFound => L("Couldn't find sciter.dll."),
|
|
||||||
else => L("An unknown error occured while trying to load sciter.dll."),
|
|
||||||
},
|
|
||||||
L("Critical error"),
|
|
||||||
wam.MB_ICONHAND,
|
|
||||||
);
|
|
||||||
std.posix.exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sciter_api_fn: *const fn () ?*sciter.API = @ptrCast(std.os.windows.kernel32.GetProcAddress(sciter_module, "SciterAPI") orelse {
|
|
||||||
_ = wam.MessageBoxW(null, L("Couldn't load Sciter API."), L("Critical error"), wam.MB_ICONHAND);
|
|
||||||
std.posix.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
sciter_api = sciter_api_fn() orelse {
|
|
||||||
_ = wam.MessageBoxW(null, L("Couldn't load Sciter API."), L("Critical error"), wam.MB_ICONHAND);
|
|
||||||
std.posix.exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
std.debug.print("Sciter API version is: 0x{X:0>8}\n", .{sciter_api.version});
|
|
||||||
|
|
||||||
const wc = std.mem.zeroInit(wam.WNDCLASSEXW, .{
|
|
||||||
.cbSize = @sizeOf(wam.WNDCLASSEXW),
|
|
||||||
.lpfnWndProc = &WindowProc,
|
|
||||||
.hInstance = hInstance,
|
|
||||||
.hIcon = wam.LoadIconW(null, wam.IDI_APPLICATION),
|
|
||||||
.hCursor = wam.LoadCursorW(null, wam.IDC_ARROW),
|
|
||||||
.lpszClassName = L("SCITER_WINDOW"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const atom = wam.RegisterClassExW(&wc);
|
|
||||||
std.debug.assert(atom != 0);
|
|
||||||
|
|
||||||
const hwnd = wam.CreateWindowExW(
|
|
||||||
wam.WS_EX_APPWINDOW,
|
|
||||||
wc.lpszClassName,
|
|
||||||
L("Sciter Demo"),
|
|
||||||
wam.WS_OVERLAPPEDWINDOW,
|
|
||||||
-1,
|
|
||||||
-1,
|
|
||||||
-1,
|
|
||||||
-1,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
wc.hInstance,
|
|
||||||
null,
|
|
||||||
) orelse {
|
|
||||||
_ = wam.MessageBoxW(null, L("Couldn't create window."), L("Critical error"), wam.MB_ICONHAND);
|
|
||||||
std.posix.exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
_ = sciter_api.SciterLoadHtml(hwnd, html, html.len, L("/"));
|
|
||||||
_ = wam.ShowWindow(hwnd, @bitCast(nCmdShow));
|
|
||||||
|
|
||||||
var msg = std.mem.zeroes(wam.MSG);
|
|
||||||
while (wam.GetMessageW(&msg, null, 0, 0) > 0) {
|
|
||||||
_ = wam.TranslateMessage(&msg);
|
|
||||||
_ = wam.DispatchMessageW(&msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return @bitCast(@as(u32, @truncate(msg.wParam)));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn WindowProc(
|
|
||||||
hwnd: win32.HWND,
|
|
||||||
uMsg: u32,
|
|
||||||
wParam: win32.WPARAM,
|
|
||||||
lParam: win32.LPARAM,
|
|
||||||
) callconv(WINAPI) win32.LRESULT {
|
|
||||||
switch (uMsg) {
|
|
||||||
wam.WM_DESTROY => {
|
|
||||||
wam.PostQuitMessage(0);
|
|
||||||
},
|
|
||||||
wam.WM_GETMINMAXINFO => {
|
|
||||||
const mmi: *wam.MINMAXINFO = @ptrFromInt(@as(usize, @bitCast(lParam)));
|
|
||||||
mmi.ptMinTrackSize = min_track_size;
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
var handled: sciter.Bool = .FALSE;
|
|
||||||
const result = sciter_api.SciterProcND(hwnd, uMsg, wParam, lParam, &handled);
|
|
||||||
if (handled != .FALSE) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wam.DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
||||||
}
|
|
||||||
@@ -1,641 +0,0 @@
|
|||||||
pub const CStr = [*:0]const u8;
|
|
||||||
pub const CWStr = [*:0]const u16;
|
|
||||||
|
|
||||||
pub const Element = opaque {};
|
|
||||||
pub const HElement = *Element;
|
|
||||||
|
|
||||||
pub const Node = opaque {};
|
|
||||||
pub const HNode = *Node;
|
|
||||||
|
|
||||||
pub const SArchive = opaque {};
|
|
||||||
pub const HSArchive = *SArchive;
|
|
||||||
|
|
||||||
/// - Windows: `HWND`
|
|
||||||
/// - OS X: `NSView*`
|
|
||||||
/// - Linux/GTK: `GtkWidget*`
|
|
||||||
pub const HWindow = *anyopaque;
|
|
||||||
|
|
||||||
pub const BehaviorEventParams = extern struct {
|
|
||||||
cmd: BehaviorEvents,
|
|
||||||
he_target: ?HElement,
|
|
||||||
he: ?HElement,
|
|
||||||
reason: extern union {
|
|
||||||
click: enum(usize) { BY_MOUSE_CLICK, BY_KEY_CLICK, SYNTHESIZED, BY_MOUSE_ON_ICON },
|
|
||||||
edit_changed: enum(usize) { BY_INS_CHAR, BY_INS_CHARS, BY_DEL_CHAR, BY_DEL_CHARS, BY_UNDO_REDO },
|
|
||||||
custom: usize,
|
|
||||||
},
|
|
||||||
data: Value,
|
|
||||||
name: ?CWStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
const BehaviorEvents = enum(u32) {
|
|
||||||
BUTTON_CLICK = 0,
|
|
||||||
BUTTON_PRESS = 1,
|
|
||||||
|
|
||||||
VALUE_CHANGED = 2,
|
|
||||||
VALUE_CHANGING = 3,
|
|
||||||
|
|
||||||
SELECTION_CHANGED = 5,
|
|
||||||
SELECTION_CHANGING = 0xC,
|
|
||||||
|
|
||||||
POPUP_REQUEST = 7,
|
|
||||||
POPUP_READY = 8,
|
|
||||||
POPUP_DISMISSED = 9,
|
|
||||||
|
|
||||||
MENU_ITEM_ACTIVE = 0xA,
|
|
||||||
MENU_ITEM_CLICK = 0xB,
|
|
||||||
|
|
||||||
CONTEXT_MENU_REQUEST = 0x10,
|
|
||||||
|
|
||||||
VISUAL_STATUS_CHANGED = 0x11,
|
|
||||||
DISABLED_STATUS_CHANGED = 0x12,
|
|
||||||
|
|
||||||
POPUP_DISMISSING = 0x13,
|
|
||||||
|
|
||||||
CONTENT_CHANGED = 0x15,
|
|
||||||
|
|
||||||
HYPERLINK_CLICK = 0x80,
|
|
||||||
|
|
||||||
ELEMENT_COLLAPSED = 0x90,
|
|
||||||
ELEMENT_EXPANDED = 0x91,
|
|
||||||
|
|
||||||
ACTIVATE_CHILD = 0x92,
|
|
||||||
|
|
||||||
FORM_SUBMIT = 0x96,
|
|
||||||
FORM_RESET = 0x97,
|
|
||||||
|
|
||||||
DOCUMENT_COMPLETE = 0x98,
|
|
||||||
|
|
||||||
HISTORY_PUSH = 0x99,
|
|
||||||
HISTORY_DROP = 0x9A,
|
|
||||||
HISTORY_PRIOR = 0x9B,
|
|
||||||
HISTORY_NEXT = 0x9C,
|
|
||||||
HISTORY_STATE_CHANGED = 0x9D,
|
|
||||||
|
|
||||||
CLOSE_POPUP = 0x9E,
|
|
||||||
REQUEST_TOOLTIP = 0x9F,
|
|
||||||
|
|
||||||
ANIMATION = 0xA0,
|
|
||||||
TRANSITION = 0xA1,
|
|
||||||
SWIPE = 0xB0,
|
|
||||||
|
|
||||||
DOCUMENT_CREATED = 0xC0,
|
|
||||||
DOCUMENT_CLOSE_REQUEST = 0xC1,
|
|
||||||
DOCUMENT_CLOSE = 0xC2,
|
|
||||||
DOCUMENT_READY = 0xC3,
|
|
||||||
DOCUMENT_PARSED = 0xC4,
|
|
||||||
//DOCUMENT_RELOAD = 0xC5,
|
|
||||||
DOCUMENT_CLOSING = 0xC6,
|
|
||||||
CONTAINER_CLOSE_REQUEST = 0xC7,
|
|
||||||
CONTAINER_CLOSING = 0xC8,
|
|
||||||
|
|
||||||
VIDEO_INITIALIZED = 0xD1,
|
|
||||||
VIDEO_STARTED = 0xD2,
|
|
||||||
VIDEO_STOPPED = 0xD3,
|
|
||||||
VIDEO_BIND_RQ = 0xD4,
|
|
||||||
|
|
||||||
VIDEO_FRAME_REQUEST = 0xD8,
|
|
||||||
|
|
||||||
PAGINATION_STARTS = 0xE0,
|
|
||||||
PAGINATION_PAGE = 0xE1,
|
|
||||||
PAGINATION_ENDS = 0xE2,
|
|
||||||
|
|
||||||
CUSTOM = 0xF0,
|
|
||||||
|
|
||||||
EGL_RENDER = 0x20,
|
|
||||||
|
|
||||||
/// All custom event codes shall be greater than this number. All codes
|
|
||||||
/// below this will be used solely by application - Sciter will not
|
|
||||||
/// intrepret it and will do just dispatching. To send event notifications
|
|
||||||
/// with these codes use SciterSend/PostEvent API.
|
|
||||||
FIRST_APPLICATION_EVENT_CODE = 0x100,
|
|
||||||
_,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Bool = enum(u32) {
|
|
||||||
FALSE = 0,
|
|
||||||
TRUE = 1,
|
|
||||||
_,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DomResult = enum(i32) {
|
|
||||||
OK = 0,
|
|
||||||
INVALID_HWND = 1,
|
|
||||||
INVALID_HANDLE = 2,
|
|
||||||
PASSIVE_HANDLE = 3,
|
|
||||||
INVALID_PARAMETER = 4,
|
|
||||||
OPERATION_FAILED = 5,
|
|
||||||
OK_NOT_HANDLED = -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const CtlType = enum(u32) {
|
|
||||||
NO = 0,
|
|
||||||
UNKNOWN = 1,
|
|
||||||
|
|
||||||
EDIT = 2,
|
|
||||||
NUMERIC = 3,
|
|
||||||
CLICKABLE = 4,
|
|
||||||
BUTTON = 5,
|
|
||||||
CHECKBOX = 6,
|
|
||||||
RADIO = 7,
|
|
||||||
SELECT_SINGLE = 8,
|
|
||||||
SELECT_MULTIPLE = 9,
|
|
||||||
DD_SELECT = 10,
|
|
||||||
TEXTAREA = 11,
|
|
||||||
HTMLAREA = 12,
|
|
||||||
PASSWORD = 13,
|
|
||||||
PROGRESS = 14,
|
|
||||||
SLIDER = 15,
|
|
||||||
DECIMAL = 16,
|
|
||||||
CURRENCY = 17,
|
|
||||||
SCROLLBAR = 18,
|
|
||||||
LIST = 19,
|
|
||||||
RICHTEXT = 20,
|
|
||||||
CALENDAR = 21,
|
|
||||||
DATE = 22,
|
|
||||||
TIME = 23,
|
|
||||||
FILE = 24,
|
|
||||||
PATH = 25,
|
|
||||||
|
|
||||||
HYPERLINK = 26,
|
|
||||||
FORM = 27,
|
|
||||||
|
|
||||||
MENUBAR = 28,
|
|
||||||
MENU = 29,
|
|
||||||
MENUBUTTON = 30,
|
|
||||||
|
|
||||||
FRAME = 31,
|
|
||||||
FRAMESET = 32,
|
|
||||||
|
|
||||||
TOOLTIP = 33,
|
|
||||||
|
|
||||||
HIDDEN = 34,
|
|
||||||
URL = 35,
|
|
||||||
TOOLBAR = 36,
|
|
||||||
|
|
||||||
WINDOW = 37,
|
|
||||||
|
|
||||||
LABEL = 38,
|
|
||||||
IMAGE = 39,
|
|
||||||
PLAINTEXT = 40,
|
|
||||||
|
|
||||||
SELECT_TREE = 41,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ElementAreas = packed struct {
|
|
||||||
relative: enum(u4) {
|
|
||||||
ROOT = 0x1,
|
|
||||||
SELF = 0x2,
|
|
||||||
CONTAINER = 0x3,
|
|
||||||
VIEW = 0x4,
|
|
||||||
_,
|
|
||||||
} = @enumFromInt(0x0),
|
|
||||||
|
|
||||||
area: enum(u4) {
|
|
||||||
CONTENT_BOX = 0x0,
|
|
||||||
PADDING_BOX = 0x1,
|
|
||||||
BORDER_BOX = 0x2,
|
|
||||||
MARGIN_BOX = 0x3,
|
|
||||||
|
|
||||||
BACK_IMAGE_AREA = 0x4,
|
|
||||||
FORE_IMAGE_AREA = 0x5,
|
|
||||||
|
|
||||||
SCROLLABLE_AREA = 0x6,
|
|
||||||
_,
|
|
||||||
} = .CONTENT_BOX,
|
|
||||||
|
|
||||||
_pad8: u8 = 0,
|
|
||||||
|
|
||||||
as_ppx: bool = false,
|
|
||||||
|
|
||||||
_pad17: u15 = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const MethodParams = extern struct {
|
|
||||||
method_id: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const NodeType = enum(u32) { ELEMENT, TEXT, COMMENT };
|
|
||||||
|
|
||||||
pub const NodeInsTarget = enum(u32) { BEFORE, AFTER, APPEND, PREPEND };
|
|
||||||
|
|
||||||
pub const Point = extern struct {
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Rect = extern struct {
|
|
||||||
left: i32,
|
|
||||||
top: i32,
|
|
||||||
right: i32,
|
|
||||||
bottom: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RequestParam = extern struct {
|
|
||||||
name: CWStr,
|
|
||||||
value: CWStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RequestType = enum(u32) {
|
|
||||||
GET_ASYNC,
|
|
||||||
POST_ASYNC,
|
|
||||||
GET_SYNC,
|
|
||||||
POST_SYNC,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ResourceType = enum(u32) {
|
|
||||||
HTML = 0,
|
|
||||||
IMAGE = 1,
|
|
||||||
STYLE = 2,
|
|
||||||
CURSOR = 3,
|
|
||||||
SCRIPT = 4,
|
|
||||||
RAW = 5,
|
|
||||||
FONT,
|
|
||||||
SOUND,
|
|
||||||
FORCE_DWORD = 0xFFFFFFFF,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SetElementHtml = enum(u32) {
|
|
||||||
SIH_REPLACE_CONTENT = 0,
|
|
||||||
SIH_INSERT_AT_START = 1,
|
|
||||||
SIH_APPEND_AFTER_LAST = 2,
|
|
||||||
SOH_REPLACE = 3,
|
|
||||||
SOH_INSERT_BEFORE = 4,
|
|
||||||
SOH_INSERT_AFTER = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Size = extern struct {
|
|
||||||
cx: i32,
|
|
||||||
cy: i32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Value = extern struct {
|
|
||||||
type: ValueType,
|
|
||||||
unit: extern union {
|
|
||||||
value: ValueUnit,
|
|
||||||
/// For when `Value.type == .OBJECT`
|
|
||||||
object: ValueUnitObject,
|
|
||||||
},
|
|
||||||
data: u64,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ValueType = enum(u32) {
|
|
||||||
UNDEFINED = 0,
|
|
||||||
NULL = 1,
|
|
||||||
BOOL = 2,
|
|
||||||
i32 = 3,
|
|
||||||
FLOAT = 4,
|
|
||||||
STRING = 5,
|
|
||||||
DATE = 6,
|
|
||||||
BIG_INT = 7,
|
|
||||||
LENGTH = 8,
|
|
||||||
ARRAY = 9,
|
|
||||||
MAP = 10,
|
|
||||||
FUNCTION = 11,
|
|
||||||
BYTES = 12,
|
|
||||||
OBJECT = 13,
|
|
||||||
RESOURCE = 15,
|
|
||||||
DURATION = 17,
|
|
||||||
ANGLE = 18,
|
|
||||||
COLOR = 19,
|
|
||||||
ASSET = 21,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ValueUnit = enum(u32) {
|
|
||||||
EM = 1,
|
|
||||||
EX = 2,
|
|
||||||
PR = 3,
|
|
||||||
SP = 4,
|
|
||||||
|
|
||||||
PX = 7,
|
|
||||||
IN = 8,
|
|
||||||
CM = 9,
|
|
||||||
MM = 10,
|
|
||||||
PT = 11,
|
|
||||||
PC = 12,
|
|
||||||
DIP = 13,
|
|
||||||
|
|
||||||
PR_WIDTH = 16,
|
|
||||||
PR_HEIGHT = 17,
|
|
||||||
PR_VIEW_WIDTH = 18,
|
|
||||||
PR_VIEW_HEIGHT = 19,
|
|
||||||
PR_VIEW_MIN = 20,
|
|
||||||
PR_VIEW_MAX = 21,
|
|
||||||
|
|
||||||
REM = 22,
|
|
||||||
PPX = 23,
|
|
||||||
CH = 24,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ValueUnitObject = enum(u32) {
|
|
||||||
ARRAY = 0,
|
|
||||||
OBJECT = 1,
|
|
||||||
CLASS = 2,
|
|
||||||
NATIVE = 3,
|
|
||||||
FUNCTION = 4,
|
|
||||||
ERROR = 5,
|
|
||||||
BUFFER = 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ValueResult = enum(i32) {
|
|
||||||
OK_TRUE = -1,
|
|
||||||
OK = 0,
|
|
||||||
BAD_PARAMETER = 1,
|
|
||||||
INCOMPATIBLE_TYPE = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const OutputSubsystems = enum(u32) {
|
|
||||||
DOM = 0,
|
|
||||||
CSSS,
|
|
||||||
CSS,
|
|
||||||
TIS,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const CallbackNotification = extern struct {
|
|
||||||
code: u32,
|
|
||||||
hwnd: HWindow,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const XMsgCode = enum(u32) {
|
|
||||||
CREATE = 0,
|
|
||||||
DESTROY = 1,
|
|
||||||
SIZE = 2,
|
|
||||||
PAINT = 3,
|
|
||||||
RESOLUTION = 4,
|
|
||||||
HEARTBIT = 5,
|
|
||||||
MOUSE = 6,
|
|
||||||
KEY = 7,
|
|
||||||
FOCUS = 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const XMsg = extern struct {
|
|
||||||
msg: XMsgCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const OmAsset = extern struct {
|
|
||||||
isa: *OmAssetClass,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const OmAssetClass = extern struct {
|
|
||||||
asset_add_ref: *const fn (thing: *OmAsset) callconv(.c) c_long,
|
|
||||||
asset_release: *const fn (thing: *OmAsset) callconv(.c) c_long,
|
|
||||||
asset_get_interface: *const fn (thing: *OmAsset, name: CStr, out: *?*anyopaque) callconv(.c) c_long,
|
|
||||||
asset_get_passport: *const fn (thins: *OmAsset) callconv(.c) ?*OmPassport,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const OmPassport = opaque {};
|
|
||||||
|
|
||||||
pub const ValueStringCvtType = enum(u32) {
|
|
||||||
CVT_SIMPLE,
|
|
||||||
CVT_JSON_LITERAL,
|
|
||||||
CVT_JSON_MAP,
|
|
||||||
CVT_XJSON_LITERAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ByteReceiver = fn (str: [*]const u8, num_bytes: u32, param: ?*anyopaque) callconv(.c) void;
|
|
||||||
const DebugOutputProc = fn (param: ?*anyopaque, subsystem: OutputSubsystems, severity: u32, text: [*]const u16, text_length: u32) callconv(.c) void;
|
|
||||||
const ElementCallback = fn (he: HElement, param: ?*anyopaque) callconv(.c) Bool;
|
|
||||||
const ElementComparator = fn (he1: HElement, he2: HElement, param: ?*anyopaque) callconv(.c) i32;
|
|
||||||
const ElementEventProc = fn (tag: ?*anyopaque, he: HElement, evtg: u32, prms: ?*anyopaque) callconv(.c) Bool;
|
|
||||||
const HostCallback = fn (pns: *CallbackNotification, callback_param: ?*anyopaque) callconv(.c) u32;
|
|
||||||
const KeyValueCallback = fn (param: ?*anyopaque, pkey: *const Value, pval: *const Value) callconv(.c) Bool;
|
|
||||||
const NativeFunctorInvoke = fn (tag: ?*anyopaque, argc: u32, argv: [*]const Value, retval: *Value) callconv(.c) void;
|
|
||||||
const NativeFunctorRelease = fn (tag: ?*anyopaque) callconv(.c) void;
|
|
||||||
const StrReceiver = fn (str: [*]const u8, num_bytes: u32, param: ?*anyopaque) callconv(.c) void;
|
|
||||||
const WindowDelegate = fn (hwnd: HWindow, msg: u32, wParam: usize, lParam: isize, pbHandled: *Bool) callconv(.c) isize;
|
|
||||||
const WStrReceiver = fn (str: [*]const u16, num_bytes: u32, param: ?*anyopaque) callconv(.c) void;
|
|
||||||
|
|
||||||
pub const API = extern struct {
|
|
||||||
version: u32,
|
|
||||||
|
|
||||||
SciterClassName: *const fn () callconv(.c) CWStr,
|
|
||||||
SciterVersion: *const fn (n: u32) callconv(.c) u32,
|
|
||||||
SciterDataReady: *const fn (hwnd: HWindow, uri: CWStr, data: [*]const u8, dataLength: u32) callconv(.c) Bool,
|
|
||||||
SciterDataReadyAsync: *const fn (hwnd: HWindow, uri: CWStr, data: [*]const u8, dataLength: u32, requestId: ?*anyopaque) callconv(.c) Bool,
|
|
||||||
|
|
||||||
SciterProc: *const fn (hwnd: HWindow, msg: u32, wParam: usize, lParam: isize) callconv(.c) isize,
|
|
||||||
SciterProcND: *const fn (hwnd: HWindow, msg: u32, wParam: usize, lParam: isize, pbHandled: *Bool) callconv(.c) isize,
|
|
||||||
|
|
||||||
SciterLoadFile: *const fn (hwnd: HWindow, filename: CWStr) callconv(.c) Bool,
|
|
||||||
SciterLoadHtml: *const fn (hwnd: HWindow, html: [*]const u8, htmlSize: u32, baseUrl: CWStr) callconv(.c) Bool,
|
|
||||||
|
|
||||||
SciterSetCallback: *const fn (hwnd: HWindow, cb: ?*HostCallback, cbParam: ?*anyopaque) callconv(.c) void,
|
|
||||||
SciterSetMasterCSS: *const fn (utf8: [*]const u8, numBytes: u32) callconv(.c) Bool,
|
|
||||||
SciterAppendMasterCSS: *const fn (utf8: [*]const u8, numBytes: u32) callconv(.c) Bool,
|
|
||||||
SciterSetCSS: *const fn (hwnd: HWindow, utf8: [*]const u8, numBytes: u32, baseUrl: CWStr, mediaType: CWStr) callconv(.c) Bool,
|
|
||||||
SciterSetMediaType: *const fn (hwnd: HWindow, mediaType: CWStr) callconv(.c) Bool,
|
|
||||||
SciterSetMediaVars: *const fn (hwnd: HWindow, mediaVars: *const Value) callconv(.c) Bool,
|
|
||||||
SciterGetMinWidth: *const fn (hwnd: HWindow) callconv(.c) u32,
|
|
||||||
SciterGetMinHeight: *const fn (hwnd: HWindow, width: u32) callconv(.c) u32,
|
|
||||||
SciterCall: *const fn (hWnd: HWindow, functionName: CStr, argc: u32, argv: [*]const Value, retval: *Value) callconv(.c) Bool,
|
|
||||||
SciterEval: *const fn (hwnd: HWindow, script: [*]const u16, scriptLength: u32, pretval: *Value) callconv(.c) Bool,
|
|
||||||
SciterUpdateWindow: *const fn (hwnd: HWindow) callconv(.c) void,
|
|
||||||
/// Win32 MSG
|
|
||||||
SciterTranslateMessage: *const fn (lpMsg: *anyopaque) callconv(.c) Bool,
|
|
||||||
SciterSetOption: *const fn (hWnd: HWindow, option: u32, value: usize) callconv(.c) Bool,
|
|
||||||
SciterGetPPI: *const fn (hwnd: HWindow, px: *u32, py: *u32) callconv(.c) void,
|
|
||||||
SciterGetViewExpando: *const fn (hwnd: HWindow, pval: *Value) callconv(.c) Bool,
|
|
||||||
/// ID2D1RenderTarget
|
|
||||||
SciterRenderD2D: *const fn (hwnd: HWindow, prt: *anyopaque) callconv(.c) Bool,
|
|
||||||
/// ID2D1Factory
|
|
||||||
SciterD2DFactory: *const fn (ppf: *anyopaque) callconv(.c) Bool,
|
|
||||||
/// IDWriteFactory
|
|
||||||
SciterDWFactory: *const fn (ppf: *anyopaque) callconv(.c) Bool,
|
|
||||||
SciterGraphicsCaps: *const fn (pcaps: *u32) callconv(.c) Bool,
|
|
||||||
SciterSetHomeURL: *const fn (hwnd: HWindow, baseUrl: CWStr) callconv(.c) Bool,
|
|
||||||
SciterCreateNSView: *anyopaque,
|
|
||||||
SciterCreateWidget: *anyopaque,
|
|
||||||
SciterCreateWindow: *const fn (creationFlags: u32, frame: *Rect, delegate: *const WindowDelegate, delegateParam: ?*anyopaque, parent: HWindow) callconv(.c) HWindow,
|
|
||||||
|
|
||||||
SciterSetupDebugOutput: *const fn (hwndOrNull: ?HWindow, param: ?*anyopaque, pfOutput: ?*DebugOutputProc) callconv(.c) void,
|
|
||||||
|
|
||||||
// --- DOM API -------------------------------------------------------------
|
|
||||||
|
|
||||||
Sciter_UseElement: *const fn (he: HElement) callconv(.c) DomResult,
|
|
||||||
Sciter_UnuseElement: *const fn (he: HElement) callconv(.c) DomResult,
|
|
||||||
SciterGetRootElement: *const fn (hwnd: HWindow, phe: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterGetFocusElement: *const fn (hwnd: HWindow, phe: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterFindElement: *const fn (hwnd: HWindow, pt: Point, phe: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterGetChildrenCount: *const fn (he: HElement, count: *u32) callconv(.c) DomResult,
|
|
||||||
SciterGetNthChild: *const fn (he: HElement, n: u32, phe: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterGetParentElement: *const fn (he: HElement, p_parent_he: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterGetElementHtmlCB: *const fn (he: HElement, outer: Bool, rcv: *const ByteReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterGetElementTextCB: *const fn (he: HElement, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterSetElementText: *const fn (he: HElement, utf16: [*]const u16, length: u32) callconv(.c) DomResult,
|
|
||||||
SciterGetAttributeCount: *const fn (he: HElement, p_count: *u32) callconv(.c) DomResult,
|
|
||||||
SciterGetNthAttributeNameCB: *const fn (he: HElement, n: u32, rcv: *const StrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterGetNthAttributeValueCB: *const fn (he: HElement, n: u32, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterGetAttributeByNameCB: *const fn (he: HElement, name: CStr, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterSetAttributeByName: *const fn (he: HElement, name: CStr, value: CWStr) callconv(.c) DomResult,
|
|
||||||
SciterClearAttributes: *const fn (he: HElement) callconv(.c) DomResult,
|
|
||||||
SciterGetElementIndex: *const fn (he: HElement, p_index: *u32) callconv(.c) DomResult,
|
|
||||||
SciterGetElementType: *const fn (he: HElement, p_type: *CStr) callconv(.c) DomResult,
|
|
||||||
SciterGetElementTypeCB: *const fn (he: HElement, rcv: *const StrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterGetStyleAttributeCB: *const fn (he: HElement, name: CStr, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterSetStyleAttribute: *const fn (he: HElement, name: CStr, value: CWStr) callconv(.c) DomResult,
|
|
||||||
SciterGetElementLocation: *const fn (he: HElement, p_location: *Rect, areas: ElementAreas) callconv(.c) DomResult,
|
|
||||||
SciterScrollToView: *const fn (he: HElement, SciterScrollFlags: u32) callconv(.c) DomResult,
|
|
||||||
SciterUpdateElement: *const fn (he: HElement, andForceRender: Bool) callconv(.c) DomResult,
|
|
||||||
SciterRefreshElementArea: *const fn (he: HElement, rc: Rect) callconv(.c) DomResult,
|
|
||||||
SciterSetCapture: *const fn (he: HElement) callconv(.c) DomResult,
|
|
||||||
SciterReleaseCapture: *const fn (he: HElement) callconv(.c) DomResult,
|
|
||||||
SciterGetElementHwnd: *const fn (he: HElement, p_hwnd: *HWindow, rootWindow: Bool) callconv(.c) DomResult,
|
|
||||||
SciterCombineURL: *const fn (he: HElement, szUrlBuffer: [*]u16, UrlBufferSize: u32) callconv(.c) DomResult,
|
|
||||||
SciterSelectElements: *const fn (he: HElement, CSS_selectors: CStr, callback: *const ElementCallback, param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterSelectElementsW: *const fn (he: HElement, CSS_selectors: CWStr, callback: *const ElementCallback, param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterSelectParent: *const fn (he: HElement, selector: CStr, depth: u32, heFound: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterSelectParentW: *const fn (he: HElement, selector: CWStr, depth: u32, heFound: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterSetElementHtml: *const fn (he: HElement, html: [*]const u8, htmlLength: u32, where: SetElementHtml) callconv(.c) DomResult,
|
|
||||||
SciterGetElementUID: *const fn (he: HElement, puid: *u32) callconv(.c) DomResult,
|
|
||||||
SciterGetElementByUID: *const fn (hwnd: HWindow, uid: u32, phe: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterShowPopup: *const fn (hePopup: HElement, heAnchor: HElement, placement: u32) callconv(.c) DomResult,
|
|
||||||
SciterShowPopupAt: *const fn (hePopup: HElement, pos: Point, placement: u32) callconv(.c) DomResult,
|
|
||||||
SciterHidePopup: *const fn (he: HElement) callconv(.c) DomResult,
|
|
||||||
SciterGetElementState: *const fn (he: HElement, pstateBits: *u32) callconv(.c) DomResult,
|
|
||||||
SciterSetElementState: *const fn (he: HElement, stateBitsToSet: u32, stateBitsToClear: u32, updateView: Bool) callconv(.c) DomResult,
|
|
||||||
SciterCreateElement: *const fn (tagname: CStr, textOrNull: ?CWStr, phe: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterCloneElement: *const fn (he: HElement, phe: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterInsertElement: *const fn (he: HElement, hparent: HElement, index: u32) callconv(.c) DomResult,
|
|
||||||
SciterDetachElement: *const fn (he: HElement) callconv(.c) DomResult,
|
|
||||||
SciterDeleteElement: *const fn (he: HElement) callconv(.c) DomResult,
|
|
||||||
SciterSetTimer: *const fn (he: HElement, milliseconds: u32, timer_id: usize) callconv(.c) DomResult,
|
|
||||||
SciterDetachEventHandler: *const fn (he: HElement, pep: *const ElementEventProc, tag: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterAttachEventHandler: *const fn (he: HElement, pep: *const ElementEventProc, tag: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterWindowAttachEventHandler: *const fn (hwndLayout: HWindow, pep: *const ElementEventProc, tag: ?*anyopaque, subscription: u32) callconv(.c) DomResult,
|
|
||||||
SciterWindowDetachEventHandler: *const fn (hwndLayout: HWindow, pep: *const ElementEventProc, tag: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterSendEvent: *const fn (he: HElement, appEventCode: u32, heSource: HElement, reason: usize, handled: *Bool) callconv(.c) DomResult,
|
|
||||||
SciterPostEvent: *const fn (he: HElement, appEventCode: u32, heSource: HElement, reason: usize) callconv(.c) DomResult,
|
|
||||||
SciterCallBehaviorMethod: *const fn (he: HElement, params: *MethodParams) callconv(.c) DomResult,
|
|
||||||
SciterRequestElementData: *const fn (he: HElement, url: CWStr, dataType: u32, initiator: HElement) callconv(.c) DomResult,
|
|
||||||
SciterHttpRequest: *const fn (he: HElement, url: CWStr, dataType: ResourceType, requestType: RequestType, requestParams: [*]RequestParam, nParams: u32) callconv(.c) DomResult,
|
|
||||||
SciterGetScrollInfo: *const fn (he: HElement, scrollPos: *Point, viewRect: *Rect, contentSize: *Size) callconv(.c) DomResult,
|
|
||||||
SciterSetScrollPos: *const fn (he: HElement, scrollPos: Point, smooth: Bool) callconv(.c) DomResult,
|
|
||||||
SciterGetElementIntrinsicWidths: *const fn (he: HElement, pMinWidth: *i32, pMaxWidth: *i32) callconv(.c) DomResult,
|
|
||||||
SciterGetElementIntrinsicHeight: *const fn (he: HElement, forWidth: i32, pHeight: *i32) callconv(.c) DomResult,
|
|
||||||
SciterIsElementVisible: *const fn (he: HElement, pVisible: *Bool) callconv(.c) DomResult,
|
|
||||||
SciterIsElementEnabled: *const fn (he: HElement, pEnabled: *Bool) callconv(.c) DomResult,
|
|
||||||
SciterSortElements: *const fn (he: HElement, firstIndex: u32, lastIndex: u32, cmpFunc: *const ElementComparator, cmpFuncParam: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterSwapElements: *const fn (he1: HElement, he2: HElement) callconv(.c) DomResult,
|
|
||||||
SciterTraverseUIEvent: *const fn (evt: u32, eventCtlStruct: ?*anyopaque, bOutProcessed: *Bool) callconv(.c) DomResult,
|
|
||||||
SciterCallScriptingMethod: *const fn (he: HElement, name: CStr, argv: [*]const Value, argc: u32, retval: *Value) callconv(.c) DomResult,
|
|
||||||
SciterCallScriptingFunction: *const fn (he: HElement, name: CStr, argv: [*]const Value, argc: u32, retval: *Value) callconv(.c) DomResult,
|
|
||||||
SciterEvalElementScript: *const fn (he: HElement, script: [*]const u16, scriptLength: u32, retval: *Value) callconv(.c) DomResult,
|
|
||||||
SciterAttachHwndToElement: *const fn (he: HElement, hwnd: HWindow) callconv(.c) DomResult,
|
|
||||||
SciterControlGetType: *const fn (he: HElement, pType: *CtlType) callconv(.c) DomResult,
|
|
||||||
SciterGetValue: *const fn (he: HElement, pval: *Value) callconv(.c) DomResult,
|
|
||||||
SciterSetValue: *const fn (he: HElement, pval: *const Value) callconv(.c) DomResult,
|
|
||||||
SciterGetExpando: *const fn (he: HElement, pval: *Value, forceCreation: Bool) callconv(.c) DomResult,
|
|
||||||
SciterGetObject: *const fn (he: HElement, pval: *void, forceCreation: Bool) callconv(.c) DomResult,
|
|
||||||
SciterGetElementNamespace: *const fn (he: HElement, pval: *void) callconv(.c) DomResult,
|
|
||||||
SciterGetHighlightedElement: *const fn (hwnd: HWindow, phe: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterSetHighlightedElement: *const fn (hwnd: HWindow, he: HElement) callconv(.c) DomResult,
|
|
||||||
|
|
||||||
// --- DOM NODE API --------------------------------------------------------
|
|
||||||
|
|
||||||
SciterNodeAddRef: *const fn (hn: HNode) callconv(.c) DomResult,
|
|
||||||
SciterNodeRelease: *const fn (hn: HNode) callconv(.c) DomResult,
|
|
||||||
SciterNodeCastFromElement: *const fn (he: HElement, phn: *HNode) callconv(.c) DomResult,
|
|
||||||
SciterNodeCastToElement: *const fn (hn: HNode, he: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterNodeFirstChild: *const fn (hn: HNode, phn: *HNode) callconv(.c) DomResult,
|
|
||||||
SciterNodeLastChild: *const fn (hn: HNode, phn: *HNode) callconv(.c) DomResult,
|
|
||||||
SciterNodeNextSibling: *const fn (hn: HNode, phn: *HNode) callconv(.c) DomResult,
|
|
||||||
SciterNodePrevSibling: *const fn (hn: HNode, phn: *HNode) callconv(.c) DomResult,
|
|
||||||
SciterNodeParent: *const fn (hnode: HNode, pheParent: *HElement) callconv(.c) DomResult,
|
|
||||||
SciterNodeNthChild: *const fn (hnode: HNode, n: u32, phn: *HNode) callconv(.c) DomResult,
|
|
||||||
SciterNodeChildrenCount: *const fn (hnode: HNode, pn: *u32) callconv(.c) DomResult,
|
|
||||||
SciterNodeType: *const fn (hnode: HNode, pNodeType: *NodeType) callconv(.c) DomResult,
|
|
||||||
SciterNodeGetText: *const fn (hnode: HNode, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
|
||||||
SciterNodeSetText: *const fn (hnode: HNode, text: [*]const u16, textLength: u32) callconv(.c) DomResult,
|
|
||||||
SciterNodeInsert: *const fn (hnode: HNode, where: NodeInsTarget, what: HNode) callconv(.c) DomResult,
|
|
||||||
SciterNodeRemove: *const fn (hnode: HNode, finalize: Bool) callconv(.c) DomResult,
|
|
||||||
SciterCreateTextNode: *const fn (text: [*]const u16, textLength: u32, phnode: *HNode) callconv(.c) DomResult,
|
|
||||||
SciterCreateCommentNode: *const fn (text: [*]const u16, textLength: u32, phnode: *HNode) callconv(.c) DomResult,
|
|
||||||
|
|
||||||
// --- Value API -----------------------------------------------------------
|
|
||||||
|
|
||||||
ValueInit: *const fn (pval: *Value) callconv(.c) u32,
|
|
||||||
ValueClear: *const fn (pval: *Value) callconv(.c) u32,
|
|
||||||
ValueCompare: *const fn (pval1: *const Value, pval2: *const Value) callconv(.c) u32,
|
|
||||||
ValueCopy: *const fn (pdst: *Value, psrc: *const Value) callconv(.c) u32,
|
|
||||||
ValueIsolate: *const fn (pdst: *Value) callconv(.c) u32,
|
|
||||||
ValueType: *const fn (pval: *const Value, pType: *u32, pUnits: *u32) callconv(.c) u32,
|
|
||||||
ValueStringData: *const fn (pval: *const Value, pChars: *[*]const u16, pNumChars: *u32) callconv(.c) u32,
|
|
||||||
ValueStringDataSet: *const fn (pval: *Value, chars: [*]const u16, numChars: u32, units: u32) callconv(.c) u32,
|
|
||||||
ValueIntData: *const fn (pval: *const Value, pData: *i32) callconv(.c) u32,
|
|
||||||
ValueIntDataSet: *const fn (pval: *Value, data: i32, type: u32, units: u32) callconv(.c) u32,
|
|
||||||
ValueInt64Data: *const fn (pval: *const Value, pData: *i64) callconv(.c) u32,
|
|
||||||
ValueInt64DataSet: *const fn (pval: *Value, data: i64, type: u32, units: u32) callconv(.c) u32,
|
|
||||||
ValueFloatData: *const fn (pval: *const Value, pData: *f64) callconv(.c) u32,
|
|
||||||
ValueFloatDataSet: *const fn (pval: *Value, data: f64, type: u32, units: u32) callconv(.c) u32,
|
|
||||||
ValueBinaryData: *const fn (pval: *const Value, pBytes: *[*]const u8, pnBytes: *u32) callconv(.c) u32,
|
|
||||||
ValueBinaryDataSet: *const fn (pval: *Value, pBytes: [*]const u8, nBytes: u32, type: u32, units: u32) callconv(.c) u32,
|
|
||||||
ValueElementsCount: *const fn (pval: *const Value, pn: *i32) callconv(.c) u32,
|
|
||||||
ValueNthElementValue: *const fn (pval: *const Value, n: i32, pretval: *Value) callconv(.c) u32,
|
|
||||||
ValueNthElementValueSet: *const fn (pval: *Value, n: i32, pval_to_set: *const Value) callconv(.c) u32,
|
|
||||||
ValueNthElementKey: *const fn (pval: *const Value, n: i32, pretval: *Value) callconv(.c) u32,
|
|
||||||
ValueEnumElements: *const fn (pval: *const Value, penum: *const KeyValueCallback, param: ?*anyopaque) callconv(.c) u32,
|
|
||||||
ValueSetValueToKey: *const fn (pval: *Value, pkey: *const Value, pval_to_set: *const Value) callconv(.c) u32,
|
|
||||||
ValueGetValueOfKey: *const fn (pval: *const Value, pkey: *const Value, pretval: *Value) callconv(.c) u32,
|
|
||||||
ValueToString: *const fn (pval: *Value, how: ValueStringCvtType) callconv(.c) u32,
|
|
||||||
ValueFromString: *const fn (pval: *Value, str: [*]const u16, strLength: u32, how: ValueStringCvtType) callconv(.c) u32,
|
|
||||||
ValueInvoke: *const fn (pval: *const Value, pthis: *Value, argc: u32, argv: *const Value, pretval: *Value, url: CWStr) callconv(.c) u32,
|
|
||||||
ValueNativeFunctorSet: *const fn (pval: *Value, pinvoke: *const NativeFunctorInvoke, prelease: *const NativeFunctorRelease, tag: *void) callconv(.c) u32,
|
|
||||||
ValueIsNativeFunctor: *const fn (pval: *const Value) callconv(.c) Bool,
|
|
||||||
|
|
||||||
// Used to be script VM API
|
|
||||||
|
|
||||||
reserved1: *anyopaque,
|
|
||||||
reserved2: *anyopaque,
|
|
||||||
reserved3: *anyopaque,
|
|
||||||
reserved4: *anyopaque,
|
|
||||||
|
|
||||||
SciterOpenArchive: *const fn (archiveData: [*]const u8, archiveDataLength: u32) callconv(.c) HSArchive,
|
|
||||||
SciterGetArchiveItem: *const fn (harc: HSArchive, path: CWStr, pdata: *[*]const u8, pdataLength: *u32) callconv(.c) Bool,
|
|
||||||
SciterCloseArchive: *const fn (harc: HSArchive) callconv(.c) Bool,
|
|
||||||
|
|
||||||
SciterFireEvent: *const fn (evt: *const BehaviorEventParams, post: Bool, handled: *Bool) callconv(.c) DomResult,
|
|
||||||
|
|
||||||
SciterGetCallbackParam: *const fn (hwnd: HWindow) callconv(.c) ?*anyopaque,
|
|
||||||
SciterPostCallback: *const fn (hwnd: HWindow, wparam: isize, lparam: usize, timeoutms: u32) callconv(.c) isize,
|
|
||||||
|
|
||||||
GetSciterGraphicsAPI: *const fn () callconv(.c) *GraphicsAPI,
|
|
||||||
GetSciterRequestAPI: *const fn () callconv(.c) *RequestAPI,
|
|
||||||
|
|
||||||
/// IDXGISwapChain
|
|
||||||
SciterCreateOnDirectXWindow: *const fn (hwnd: HWindow, pSwapChain: *anyopaque) callconv(.c) Bool,
|
|
||||||
SciterRenderOnDirectXWindow: *const fn (hwnd: HWindow, elementToRenderOrNull: HElement, frontLayer: Bool) callconv(.c) Bool,
|
|
||||||
/// IDXGISurface
|
|
||||||
SciterRenderOnDirectXTexture: *const fn (hwnd: HWindow, elementToRenderOrNull: HElement, surface: *anyopaque) callconv(.c) Bool,
|
|
||||||
|
|
||||||
SciterProcX: *const fn (hwnd: HWindow, pMsg: *XMsg) callconv(.c) Bool,
|
|
||||||
|
|
||||||
SciterAtomValue: *const fn (name: CStr) callconv(.c) u64,
|
|
||||||
SciterAtomNameCB: *const fn (atomv: u64, rcv: *const StrReceiver, rcv_param: ?*anyopaque) callconv(.c) Bool,
|
|
||||||
|
|
||||||
SciterSetGlobalAsset: *const fn (pass: *OmAsset) callconv(.c) Bool,
|
|
||||||
SciterGetElementAsset: *const fn (el: HElement, nameAtom: u64, ppass: *?*OmAsset) callconv(.c) DomResult,
|
|
||||||
|
|
||||||
SciterSetVariable: *const fn (hwndOrNull: HWindow, name: CStr, pvalToSet: *const Value) callconv(.c) u32,
|
|
||||||
SciterGetVariable: *const fn (hwndOrNull: HWindow, name: CStr, pvalToGet: *Value) callconv(.c) u32,
|
|
||||||
|
|
||||||
SciterElementUnwrap: *const fn (pval: *const Value, ppElement: *HElement) callconv(.c) u32,
|
|
||||||
SciterElementWrap: *const fn (pval: *Value, pElement: HElement) callconv(.c) u32,
|
|
||||||
|
|
||||||
SciterNodeUnwrap: *const fn (pval: *const Value, ppNode: *HNode) callconv(.c) u32,
|
|
||||||
SciterNodeWrap: *const fn (pval: *Value, pNode: HNode) callconv(.c) u32,
|
|
||||||
|
|
||||||
SciterReleaseGlobalAsset: *const fn (pass: *OmAsset) callconv(.c) Bool,
|
|
||||||
|
|
||||||
SciterExec: *const fn (appCmd: u32, p1: usize, p2: usize) callconv(.c) isize,
|
|
||||||
SciterWindowExec: *const fn (hwnd: HWindow, windowCmd: u32, p1: usize, p2: usize) callconv(.c) isize,
|
|
||||||
|
|
||||||
SciterEGLGetProcAddress: *const fn (procName: CStr) callconv(.c) ?*anyopaque,
|
|
||||||
SciterEGLSendEvent: *const fn (he: HElement, eventCode: u32, reason: usize) callconv(.c) DomResult,
|
|
||||||
SciterRequestAnimationFrameEvent: *const fn (he: HElement, eventCode: u32, reason: usize) callconv(.c) DomResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO These APIs are actually well-known structs with function pointers in
|
|
||||||
// them, which may provide useful functionality
|
|
||||||
|
|
||||||
pub const GraphicsAPI = opaque {};
|
|
||||||
pub const RequestAPI = opaque {};
|
|
||||||
BIN
packages/sciter/vendor/sciter.dll
(Stored with Git LFS)
vendored
BIN
packages/sciter/vendor/sciter.dll
(Stored with Git LFS)
vendored
Binary file not shown.
@@ -1,7 +1,19 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
_ = b.addModule("vecmath", .{
|
const target = b.standardTargetOptions(.{});
|
||||||
|
|
||||||
|
const mod = b.addModule("vecmath", .{
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mod_tests = b.addTest(.{
|
||||||
|
.root_module = mod,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.{
|
.{
|
||||||
.name = .vecmath,
|
.name = .vecmath,
|
||||||
.version = "0.0.0",
|
.version = "0.0.0",
|
||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.16.0",
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"src",
|
"src",
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
|||||||
@@ -63,37 +63,41 @@ pub const Color = extern struct {
|
|||||||
@compileError("Invalid color literal: " ++ literal);
|
@compileError("Invalid color literal: " ++ literal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test l {
|
||||||
|
const i = Color.l("#012");
|
||||||
|
try std.testing.expectEqual(0x00, i.r);
|
||||||
|
try std.testing.expectEqual(0x11, i.g);
|
||||||
|
try std.testing.expectEqual(0x22, i.b);
|
||||||
|
try std.testing.expectEqual(0xFF, i.a);
|
||||||
|
|
||||||
|
const j = Color.l("#3456");
|
||||||
|
try std.testing.expectEqual(0x33, j.r);
|
||||||
|
try std.testing.expectEqual(0x44, j.g);
|
||||||
|
try std.testing.expectEqual(0x55, j.b);
|
||||||
|
try std.testing.expectEqual(0x66, j.a);
|
||||||
|
|
||||||
|
const k = Color.l("#F08040");
|
||||||
|
try std.testing.expectEqual(0xF0, k.r);
|
||||||
|
try std.testing.expectEqual(0x80, k.g);
|
||||||
|
try std.testing.expectEqual(0x40, k.b);
|
||||||
|
try std.testing.expectEqual(0xFF, k.a);
|
||||||
|
|
||||||
|
const m = Color.l("#20304050");
|
||||||
|
try std.testing.expectEqual(0x20, m.r);
|
||||||
|
try std.testing.expectEqual(0x30, m.g);
|
||||||
|
try std.testing.expectEqual(0x40, m.b);
|
||||||
|
try std.testing.expectEqual(0x50, m.a);
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn asArray(self: Color) Array {
|
pub inline fn asArray(self: Color) Array {
|
||||||
return @bitCast(self);
|
return @bitCast(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Color, w: *std.io.Writer) !void {
|
pub fn format(self: Color, w: *std.Io.Writer) !void {
|
||||||
try w.print("#{X:0>2}{X:0>2}{X:0>2}{X:0>2}", .{ self.r, self.g, self.b, self.a });
|
try w.print("#{X:0>2}{X:0>2}{X:0>2}{X:0>2}", .{ self.r, self.g, self.b, self.a });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test "l" {
|
|
||||||
const i: Color = .l("#012");
|
|
||||||
try std.testing.expectEqual(0x00, i.r);
|
|
||||||
try std.testing.expectEqual(0x11, i.g);
|
|
||||||
try std.testing.expectEqual(0x22, i.b);
|
|
||||||
try std.testing.expectEqual(0xFF, i.a);
|
|
||||||
|
|
||||||
const j: Color = .l("#3456");
|
|
||||||
try std.testing.expectEqual(0x33, j.r);
|
|
||||||
try std.testing.expectEqual(0x44, j.g);
|
|
||||||
try std.testing.expectEqual(0x55, j.b);
|
|
||||||
try std.testing.expectEqual(0x66, j.a);
|
|
||||||
|
|
||||||
const k: Color = .l("#F08040");
|
|
||||||
try std.testing.expectEqual(0xF0, k.r);
|
|
||||||
try std.testing.expectEqual(0x80, k.g);
|
|
||||||
try std.testing.expectEqual(0x40, k.b);
|
|
||||||
try std.testing.expectEqual(0xFF, k.a);
|
|
||||||
|
|
||||||
const l: Color = .l("#20304050");
|
|
||||||
try std.testing.expectEqual(0x20, l.r);
|
|
||||||
try std.testing.expectEqual(0x30, l.g);
|
|
||||||
try std.testing.expectEqual(0x40, l.b);
|
|
||||||
try std.testing.expectEqual(0x50, l.a);
|
|
||||||
}
|
|
||||||
|
|||||||
34
packages/vecmath/src/colors/ColorHdr.zig
Normal file
34
packages/vecmath/src/colors/ColorHdr.zig
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const vm = @import("../root.zig");
|
||||||
|
|
||||||
|
pub const ColorHdr = extern struct {
|
||||||
|
r: f16,
|
||||||
|
g: f16,
|
||||||
|
b: f16,
|
||||||
|
a: f16,
|
||||||
|
|
||||||
|
pub const Array = [4]f16;
|
||||||
|
|
||||||
|
pub const zero = init(0, 0, 0, 0);
|
||||||
|
pub const one = init(1, 1, 1, 1);
|
||||||
|
|
||||||
|
pub inline fn init(r: f16, g: f16, b: f16, a: f16) ColorHdr {
|
||||||
|
return .{ .r = r, .g = g, .b = b, .a = a };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn initArray(array: Array) ColorHdr {
|
||||||
|
return @bitCast(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn asArray(self: ColorHdr) Array {
|
||||||
|
return @bitCast(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(self: ColorHdr, w: *std.Io.Writer) !void {
|
||||||
|
try w.print("ColorHdr[{d}, {d}, {d}, {d}]", .{ self.r, self.g, self.b, self.a });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -252,4 +252,8 @@ pub const Matrix3x2 = extern struct {
|
|||||||
.ty = -inv_det * (self.tx * iy + self.ty * jy),
|
.ty = -inv_det * (self.tx * iy + self.ty * jy),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -406,4 +406,8 @@ pub const Matrix3x2x8 = struct {
|
|||||||
.ty = -inv_det * (self.tx * iy + self.ty * jy),
|
.ty = -inv_det * (self.tx * iy + self.ty * jy),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -507,4 +507,8 @@ pub const Matrix4x4 = extern struct {
|
|||||||
// zig fmt: on
|
// zig fmt: on
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -763,4 +763,8 @@ pub const Matrix4x4x8 = extern struct {
|
|||||||
// zig fmt: on
|
// zig fmt: on
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const std = @import("std");
|
|||||||
// --- COLORS ------------------------------------------------------------------
|
// --- COLORS ------------------------------------------------------------------
|
||||||
|
|
||||||
pub const Color = @import("colors/Color.zig").Color;
|
pub const Color = @import("colors/Color.zig").Color;
|
||||||
|
pub const ColorHdr = @import("colors/ColorHdr.zig").ColorHdr;
|
||||||
|
|
||||||
// --- MATRICES ----------------------------------------------------------------
|
// --- MATRICES ----------------------------------------------------------------
|
||||||
|
|
||||||
@@ -78,10 +79,36 @@ pub const cossin_x8 = trig.cossin_x8;
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
pub inline fn lerp(comptime T: type, a: T, b: T, t: T) T {
|
pub inline fn lerp(a: f32, b: f32, t: f32) f32 {
|
||||||
return @mulAdd(T, t, b, @mulAdd(T, -t, a, a));
|
return @mulAdd(f32, t, b, @mulAdd(f32, -t, a, a));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn lerpInt(a: i32, b: i32, t: f32) i32 {
|
||||||
|
const ab = b - a;
|
||||||
|
const ab_float: f32 = @floatFromInt(ab);
|
||||||
|
const d: i32 = @intFromFloat(@round(t * ab_float));
|
||||||
|
return a + d;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn lerpInt64(a: i64, b: i64, t: f32) i64 {
|
||||||
|
const ab = b - a;
|
||||||
|
const ab_float: f32 = @floatFromInt(ab);
|
||||||
|
const d: i64 = @intFromFloat(@round(t * ab_float));
|
||||||
|
return a + d;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn unlerp(a: f32, b: f32, x: f32) f32 {
|
||||||
|
return (x - a) / (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn unlerpInt(a: i32, b: i32, x: i32) f32 {
|
||||||
|
return @as(f32, @floatFromInt(x - a)) / @as(f32, @floatFromInt(b - a));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn unlerpInt64(a: i64, b: i64, x: i64) f32 {
|
||||||
|
return @as(f32, @floatFromInt(x - a)) / @as(f32, @floatFromInt(b - a));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "refAllDecls" {
|
test "refAllDecls" {
|
||||||
std.testing.refAllDeclsRecursive(@This());
|
std.testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,11 @@ pub const Complex = extern struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Complex, w: *std.io.Writer) !void {
|
pub fn format(self: Complex, w: *std.Io.Writer) !void {
|
||||||
try w.print("Complex[{d:.3}, {d:.3}]", .{ self.re, self.im });
|
try w.print("Complex[{d:.3}, {d:.3}]", .{ self.re, self.im });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -193,4 +193,8 @@ pub const Complex_x8 = struct {
|
|||||||
.im = @mulAdd(vm.f32x8, vm.ps(t), b.im, @mulAdd(vm.f32x8, -vm.ps(t), a.im, a.im)),
|
.im = @mulAdd(vm.f32x8, vm.ps(t), b.im, @mulAdd(vm.f32x8, -vm.ps(t), a.im, a.im)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -179,7 +179,11 @@ pub const Quaternion = extern struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Quaternion, w: *std.io.Writer) !void {
|
pub fn format(self: Quaternion, w: *std.Io.Writer) !void {
|
||||||
try w.print("Quaternion[{d:.3}, {d:.3}, {d:.3}, {d:.3}]", .{ self.x, self.y, self.z, self.w });
|
try w.print("Quaternion[{d:.3}, {d:.3}, {d:.3}, {d:.3}]", .{ self.x, self.y, self.z, self.w });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -278,4 +278,8 @@ pub const Quaternion_x8 = struct {
|
|||||||
.w = @mulAdd(vm.f32x8, vm.ps(t), b.w, @mulAdd(vm.f32x8, -vm.ps(t), a.w, a.w)),
|
.w = @mulAdd(vm.f32x8, vm.ps(t), b.w, @mulAdd(vm.f32x8, -vm.ps(t), a.w, a.w)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,3 +31,7 @@ pub inline fn epi64(value: i64) i64x4 {
|
|||||||
pub inline fn epu64(value: u64) u64x4 {
|
pub inline fn epu64(value: u64) u64x4 {
|
||||||
return @splat(value);
|
return @splat(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|||||||
@@ -235,3 +235,7 @@ test cossin_x8 {
|
|||||||
cossin_x8(.{ -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75 }),
|
cossin_x8(.{ -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75 }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ pub const Vector2 = extern struct {
|
|||||||
return @bitCast(self);
|
return @bitCast(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub inline fn asInt(self: Vector2) vm.Vector2Int {
|
||||||
|
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn withZ(self: Vector2, z: f32) vm.Vector3 {
|
pub inline fn withZ(self: Vector2, z: f32) vm.Vector3 {
|
||||||
return .{ .x = self.x, .y = self.y, .z = z };
|
return .{ .x = self.x, .y = self.y, .z = z };
|
||||||
}
|
}
|
||||||
@@ -163,7 +167,11 @@ pub const Vector2 = extern struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Vector2, w: *std.io.Writer) !void {
|
pub fn format(self: Vector2, w: *std.Io.Writer) !void {
|
||||||
try w.print("[{d}, {d}]", .{ self.x, self.y });
|
try w.print("[{d}, {d}]", .{ self.x, self.y });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ pub const Vector2Int = extern struct {
|
|||||||
return @bitCast(self);
|
return @bitCast(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub inline fn asFloat(self: Vector2Int) vm.Vector2 {
|
||||||
|
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn withZ(self: Vector2Int, z: i32) vm.Vector3Int {
|
pub inline fn withZ(self: Vector2Int, z: i32) vm.Vector3Int {
|
||||||
return .{ .x = self.x, .y = self.y, .z = z };
|
return .{ .x = self.x, .y = self.y, .z = z };
|
||||||
}
|
}
|
||||||
@@ -106,10 +110,14 @@ pub const Vector2Int = extern struct {
|
|||||||
return self.x * other.y - self.y * other.x;
|
return self.x * other.y - self.y * other.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Vector2Int, w: *std.io.Writer) !void {
|
pub fn format(self: Vector2Int, w: *std.Io.Writer) !void {
|
||||||
try w.print("[{X:0>8}, {X:0>8}]", .{
|
try w.print("[{X:0>8}, {X:0>8}]", .{
|
||||||
@as(u32, @bitCast(self.x)),
|
@as(u32, @bitCast(self.x)),
|
||||||
@as(u32, @bitCast(self.y)),
|
@as(u32, @bitCast(self.y)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ pub const Vector2Int_x8 = struct {
|
|||||||
|
|
||||||
// --- CONVERSION ----------------------------------------------------------
|
// --- CONVERSION ----------------------------------------------------------
|
||||||
|
|
||||||
|
pub inline fn asFloat(self: Vector2Int_x8) vm.Vector2x8 {
|
||||||
|
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn withZ(self: Vector2Int_x8, z: vm.i32x8) vm.Vector3Int_x8 {
|
pub inline fn withZ(self: Vector2Int_x8, z: vm.i32x8) vm.Vector3Int_x8 {
|
||||||
return .{ .x = self.x, .y = self.y, .z = z };
|
return .{ .x = self.x, .y = self.y, .z = z };
|
||||||
}
|
}
|
||||||
@@ -167,4 +171,8 @@ pub const Vector2Int_x8 = struct {
|
|||||||
pub inline fn cross(self: Vector2Int_x8, other: Vector2Int_x8) vm.i32x8 {
|
pub inline fn cross(self: Vector2Int_x8, other: Vector2Int_x8) vm.i32x8 {
|
||||||
return self.x * other.y - self.y * other.x;
|
return self.x * other.y - self.y * other.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ pub const Vector2x8 = struct {
|
|||||||
|
|
||||||
// --- CONVERSION ----------------------------------------------------------
|
// --- CONVERSION ----------------------------------------------------------
|
||||||
|
|
||||||
|
pub inline fn asInt(self: Vector2x8) vm.Vector2Int_x8 {
|
||||||
|
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn withZ(self: Vector2x8, z: vm.f32x8) vm.Vector3x8 {
|
pub inline fn withZ(self: Vector2x8, z: vm.f32x8) vm.Vector3x8 {
|
||||||
return .{ .x = self.x, .y = self.y, .z = z };
|
return .{ .x = self.x, .y = self.y, .z = z };
|
||||||
}
|
}
|
||||||
@@ -227,4 +231,8 @@ pub const Vector2x8 = struct {
|
|||||||
.y = self.x * vm.ps(m.iy) + self.y * vm.ps(m.jy),
|
.y = self.x * vm.ps(m.iy) + self.y * vm.ps(m.jy),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ pub const Vector3 = extern struct {
|
|||||||
return @bitCast(self);
|
return @bitCast(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub inline fn asInt(self: Vector3) vm.Vector3Int {
|
||||||
|
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y), .z = @intFromFloat(self.z) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn dropZ(self: Vector3) vm.Vector2 {
|
pub inline fn dropZ(self: Vector3) vm.Vector2 {
|
||||||
return .{ .x = self.x, .y = self.y };
|
return .{ .x = self.x, .y = self.y };
|
||||||
}
|
}
|
||||||
@@ -145,7 +149,7 @@ pub const Vector3 = extern struct {
|
|||||||
pub inline fn rotate_x8(self: Vector3, quaternion: vm.Quaternion_x8) vm.Vector3x8 {
|
pub inline fn rotate_x8(self: Vector3, quaternion: vm.Quaternion_x8) vm.Vector3x8 {
|
||||||
const w = quaternion.getScalar();
|
const w = quaternion.getScalar();
|
||||||
const xyz = quaternion.getVector();
|
const xyz = quaternion.getVector();
|
||||||
const self_x8: vm.Vector3x8 = .splat(self);
|
const self_x8 = vm.Vector3x8.splat(self);
|
||||||
|
|
||||||
return .add(
|
return .add(
|
||||||
self_x8,
|
self_x8,
|
||||||
@@ -188,7 +192,11 @@ pub const Vector3 = extern struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Vector3, w: *std.io.Writer) !void {
|
pub fn format(self: Vector3, w: *std.Io.Writer) !void {
|
||||||
try w.print("[{d}, {d}, {d}]", .{ self.x, self.y, self.z });
|
try w.print("[{d}, {d}, {d}]", .{ self.x, self.y, self.z });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ pub const Vector3Int = extern struct {
|
|||||||
return @bitCast(self);
|
return @bitCast(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub inline fn asFloat(self: Vector3Int) vm.Vector3 {
|
||||||
|
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y), .z = @floatFromInt(self.z) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn dropZ(self: Vector3Int) vm.Vector2Int {
|
pub inline fn dropZ(self: Vector3Int) vm.Vector2Int {
|
||||||
return .{ .x = self.x, .y = self.y };
|
return .{ .x = self.x, .y = self.y };
|
||||||
}
|
}
|
||||||
@@ -113,11 +117,15 @@ pub const Vector3Int = extern struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Vector3Int, w: *std.io.Writer) !void {
|
pub fn format(self: Vector3Int, w: *std.Io.Writer) !void {
|
||||||
try w.print("[{X:0>8}, {X:0>8}, {X:0>8}]", .{
|
try w.print("[{X:0>8}, {X:0>8}, {X:0>8}]", .{
|
||||||
@as(u32, @bitCast(self.x)),
|
@as(u32, @bitCast(self.x)),
|
||||||
@as(u32, @bitCast(self.y)),
|
@as(u32, @bitCast(self.y)),
|
||||||
@as(u32, @bitCast(self.z)),
|
@as(u32, @bitCast(self.z)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ pub const Vector3Int_x8 = struct {
|
|||||||
|
|
||||||
// --- CONVERSION ----------------------------------------------------------
|
// --- CONVERSION ----------------------------------------------------------
|
||||||
|
|
||||||
|
pub inline fn asFloat(self: Vector3Int_x8) vm.Vector3x8 {
|
||||||
|
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y), .z = @floatFromInt(self.z) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn dropZ(self: Vector3Int_x8) vm.Vector2Int_x8 {
|
pub inline fn dropZ(self: Vector3Int_x8) vm.Vector2Int_x8 {
|
||||||
return .{ .x = self.x, .y = self.y };
|
return .{ .x = self.x, .y = self.y };
|
||||||
}
|
}
|
||||||
@@ -176,4 +180,8 @@ pub const Vector3Int_x8 = struct {
|
|||||||
.z = self.x * other.y - self.y * other.x,
|
.z = self.x * other.y - self.y * other.x,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ pub const Vector3x8 = struct {
|
|||||||
|
|
||||||
// --- CONVERSION ----------------------------------------------------------
|
// --- CONVERSION ----------------------------------------------------------
|
||||||
|
|
||||||
|
pub inline fn asInt(self: Vector3x8) vm.Vector3Int_x8 {
|
||||||
|
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y), .z = @intFromFloat(self.z) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn dropZ(self: Vector3x8) vm.Vector2x8 {
|
pub inline fn dropZ(self: Vector3x8) vm.Vector2x8 {
|
||||||
return .{ .x = self.x, .y = self.y };
|
return .{ .x = self.x, .y = self.y };
|
||||||
}
|
}
|
||||||
@@ -254,4 +258,8 @@ pub const Vector3x8 = struct {
|
|||||||
.z = v.x * vm.ps(self.iz) + v.y * vm.ps(self.jz) + v.z * vm.ps(self.kz),
|
.z = v.x * vm.ps(self.iz) + v.y * vm.ps(self.jz) + v.z * vm.ps(self.kz),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ pub const Vector4 = extern struct {
|
|||||||
|
|
||||||
// --- CONVERSION ----------------------------------------------------------
|
// --- CONVERSION ----------------------------------------------------------
|
||||||
|
|
||||||
|
pub inline fn asInt(self: Vector4) vm.Vector4Int {
|
||||||
|
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y), .z = @intFromFloat(self.z), .w = @intFromFloat(self.w) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn asArray(self: Vector4) Array {
|
pub inline fn asArray(self: Vector4) Array {
|
||||||
return @bitCast(self);
|
return @bitCast(self);
|
||||||
}
|
}
|
||||||
@@ -143,7 +147,11 @@ pub const Vector4 = extern struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Vector4, w: *std.io.Writer) !void {
|
pub fn format(self: Vector4, w: *std.Io.Writer) !void {
|
||||||
try w.print("[{d}, {d}, {d}, {d}]", .{ self.x, self.y, self.z, self.w });
|
try w.print("[{d}, {d}, {d}, {d}]", .{ self.x, self.y, self.z, self.w });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ pub const Vector4Int = extern struct {
|
|||||||
return @bitCast(self);
|
return @bitCast(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub inline fn asFloat(self: Vector4Int) vm.Vector4 {
|
||||||
|
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y), .z = @floatFromInt(self.z), .w = @floatFromInt(self.w) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn dropW(self: Vector4Int) vm.Vector3Int {
|
pub inline fn dropW(self: Vector4Int) vm.Vector3Int {
|
||||||
return .{ .x = self.x, .y = self.y, .z = self.z };
|
return .{ .x = self.x, .y = self.y, .z = self.z };
|
||||||
}
|
}
|
||||||
@@ -108,7 +112,7 @@ pub const Vector4Int = extern struct {
|
|||||||
return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
|
return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Vector4Int, w: *std.io.Writer) !void {
|
pub fn format(self: Vector4Int, w: *std.Io.Writer) !void {
|
||||||
try w.print("[{X:0>8}, {X:0>8}, {X:0>8}, {X:0>8}]", .{
|
try w.print("[{X:0>8}, {X:0>8}, {X:0>8}, {X:0>8}]", .{
|
||||||
@as(u32, @bitCast(self.x)),
|
@as(u32, @bitCast(self.x)),
|
||||||
@as(u32, @bitCast(self.y)),
|
@as(u32, @bitCast(self.y)),
|
||||||
@@ -116,4 +120,8 @@ pub const Vector4Int = extern struct {
|
|||||||
@as(u32, @bitCast(self.w)),
|
@as(u32, @bitCast(self.w)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ pub const Vector4Int_x8 = struct {
|
|||||||
|
|
||||||
// --- CONVERSION ----------------------------------------------------------
|
// --- CONVERSION ----------------------------------------------------------
|
||||||
|
|
||||||
|
pub inline fn asFloat(self: Vector4Int_x8) vm.Vector4x8 {
|
||||||
|
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y), .z = @floatFromInt(self.z), .w = @floatFromInt(self.w) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn dropW(self: Vector4Int_x8) vm.Vector3Int_x8 {
|
pub inline fn dropW(self: Vector4Int_x8) vm.Vector3Int_x8 {
|
||||||
return .{ .x = self.x, .y = self.y, .z = self.z };
|
return .{ .x = self.x, .y = self.y, .z = self.z };
|
||||||
}
|
}
|
||||||
@@ -173,4 +177,8 @@ pub const Vector4Int_x8 = struct {
|
|||||||
pub inline fn dot(self: Vector4Int_x8, other: Vector4Int_x8) vm.i32x8 {
|
pub inline fn dot(self: Vector4Int_x8, other: Vector4Int_x8) vm.i32x8 {
|
||||||
return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
|
return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ pub const Vector4x8 = struct {
|
|||||||
|
|
||||||
// --- CONVERSION ----------------------------------------------------------
|
// --- CONVERSION ----------------------------------------------------------
|
||||||
|
|
||||||
|
pub inline fn asInt(self: Vector4x8) vm.Vector4Int_x8 {
|
||||||
|
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y), .z = @intFromFloat(self.z), .w = @intFromFloat(self.w) };
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn dropW(self: Vector4x8) vm.Vector3x8 {
|
pub inline fn dropW(self: Vector4x8) vm.Vector3x8 {
|
||||||
return .{ .x = self.x, .y = self.y, .z = self.z };
|
return .{ .x = self.x, .y = self.y, .z = self.z };
|
||||||
}
|
}
|
||||||
@@ -213,4 +217,8 @@ pub const Vector4x8 = struct {
|
|||||||
.w = self.x * vm.ps(m.iw) + self.y * vm.ps(m.jw) + self.z * vm.ps(m.kw) + self.w * vm.ps(m.tw),
|
.w = self.x * vm.ps(m.iw) + self.y * vm.ps(m.jw) + self.z * vm.ps(m.kw) + self.w * vm.ps(m.tw),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "refAllDecls" {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
52
packages/web/build.zig
Normal file
52
packages/web/build.zig
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const module = b.addModule("web", .{
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.linkSystemLibrary("ssl", .{});
|
||||||
|
module.linkSystemLibrary("crypto", .{});
|
||||||
|
|
||||||
|
const tests = b.addTest(.{
|
||||||
|
.root_module = module,
|
||||||
|
});
|
||||||
|
|
||||||
|
const exe_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "web", .module = module },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "web",
|
||||||
|
.root_module = exe_module,
|
||||||
|
});
|
||||||
|
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
const run_step = b.step("run", "Run the app");
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const run_tests = b.addRunArtifact(tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&run_tests.step);
|
||||||
|
}
|
||||||
11
packages/web/build.zig.zon
Normal file
11
packages/web/build.zig.zon
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.{
|
||||||
|
.name = .web,
|
||||||
|
.version = "0.0.0",
|
||||||
|
.minimum_zig_version = "0.15.2",
|
||||||
|
.paths = .{
|
||||||
|
"src",
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
},
|
||||||
|
.fingerprint = 0x15c938517148d390,
|
||||||
|
}
|
||||||
9
packages/web/cert.pem
Normal file
9
packages/web/cert.pem
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBPDCB76ADAgECAhRuWLL9k0QOIt4+BPNlFBzRcmvREDAFBgMrZXAwFDESMBAG
|
||||||
|
A1UEAwwJbG9jYWxob3N0MB4XDTI2MDMwODIwNDczNFoXDTM2MDMwNTIwNDczNFow
|
||||||
|
FDESMBAGA1UEAwwJbG9jYWxob3N0MCowBQYDK2VwAyEAdgB1CRIUYLCPclWp2+5c
|
||||||
|
X3I0aqoY7yuhZBE9NxKKbZ+jUzBRMB0GA1UdDgQWBBQXJ3/C7HMVOjXcnqvDKdWo
|
||||||
|
PWhzsTAfBgNVHSMEGDAWgBQXJ3/C7HMVOjXcnqvDKdWoPWhzsTAPBgNVHRMBAf8E
|
||||||
|
BTADAQH/MAUGAytlcANBAFirv6F5+TamfddV1sElbfI8jfhPLxWU+z6/mxxbzooX
|
||||||
|
IKdOh/FrCrPfeUavKH9C5Vr+ztPgoTdCaFqo5mT7MAw=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
3
packages/web/key.pem
Normal file
3
packages/web/key.pem
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MC4CAQAwBQYDK2VwBCIEIMDUsa31l2xEhX1gyB5w2WABgSbne3GHyUe0RWomq9C5
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
147
packages/web/src/Connection.zig
Normal file
147
packages/web/src/Connection.zig
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Connection = @This();
|
||||||
|
|
||||||
|
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||||
|
const openssl = @import("openssl.zig");
|
||||||
|
|
||||||
|
const iovec = std.posix.iovec;
|
||||||
|
const iovec_const = std.posix.iovec_const;
|
||||||
|
|
||||||
|
address: std.net.Address,
|
||||||
|
fd: FileDescriptor,
|
||||||
|
ssl: ?*openssl.Ssl,
|
||||||
|
node: std.DoublyLinkedList.Node = .{},
|
||||||
|
|
||||||
|
// TODO Consider proper usage of `send` syscall with `MSG_MORE` flag and setting
|
||||||
|
// the `TCP_CORK` option.
|
||||||
|
|
||||||
|
pub fn reinit(
|
||||||
|
self: *Connection,
|
||||||
|
address: std.net.Address,
|
||||||
|
fd: FileDescriptor,
|
||||||
|
ssl: ?*openssl.Ssl,
|
||||||
|
) void {
|
||||||
|
self.address = address;
|
||||||
|
self.fd = fd;
|
||||||
|
self.ssl = ssl;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Connection) void {
|
||||||
|
if (self.ssl) |x| x.free();
|
||||||
|
self.fd.close();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(self: *const Connection, buf: []u8) !usize {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
const bytes_read = try ssl.read(buf);
|
||||||
|
return bytes_read;
|
||||||
|
} else {
|
||||||
|
const bytes_read = try self.fd.read(buf);
|
||||||
|
return bytes_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readAll(self: *const Connection, buf: []u8) !void {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
try ssl.readAll(buf);
|
||||||
|
} else {
|
||||||
|
try self.fd.readAll(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readv(self: *const Connection, iov: []const iovec) !usize {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
var total_bytes_read: usize = 0;
|
||||||
|
for (iov) |io| {
|
||||||
|
const bytes_read = try ssl.read(io.base[0..io.len]);
|
||||||
|
total_bytes_read += bytes_read;
|
||||||
|
|
||||||
|
if (bytes_read < io.len) {
|
||||||
|
return total_bytes_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_bytes_read;
|
||||||
|
} else {
|
||||||
|
const bytes_read = try self.fd.readv(iov);
|
||||||
|
return bytes_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Might modify `iov` when `readv` syscall reads partially.
|
||||||
|
pub fn readvAll(self: *const Connection, iov: []iovec) !void {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
for (iov) |io| {
|
||||||
|
try ssl.readAll(io.base[0..io.len]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try self.fd.readvAll(iov);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self: *const Connection, buf: []const u8) !usize {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
const bytes_written = try ssl.write(buf);
|
||||||
|
return bytes_written;
|
||||||
|
} else {
|
||||||
|
const bytes_written = try self.fd.write(buf);
|
||||||
|
return bytes_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeAll(self: *const Connection, buf: []const u8) !void {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
try ssl.writeAll(buf);
|
||||||
|
} else {
|
||||||
|
try self.fd.writeAll(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writev(self: *const Connection, iov: []const iovec_const) !usize {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
var total_bytes_written: usize = 0;
|
||||||
|
for (iov) |io| {
|
||||||
|
const bytes_written = try ssl.write(io.base[0..io.len]);
|
||||||
|
total_bytes_written += bytes_written;
|
||||||
|
|
||||||
|
if (bytes_written < io.len) {
|
||||||
|
return total_bytes_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_bytes_written;
|
||||||
|
} else {
|
||||||
|
const bytes_written = try self.fd.writev(iov);
|
||||||
|
return bytes_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Might modify `iov` when `writev` syscall writes partially.
|
||||||
|
pub fn writevAll(self: *const Connection, iov: []iovec_const) !void {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
for (iov) |io| {
|
||||||
|
try ssl.writeAll(io.base[0..io.len]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try self.fd.writevAll(iov);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendfile(self: *const Connection, fd: FileDescriptor, offset: usize, len: usize) !usize {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
const bytes_written = try ssl.sendfile(fd, offset, len);
|
||||||
|
return bytes_written;
|
||||||
|
} else {
|
||||||
|
const bytes_written = try self.fd.sendfile(fd, offset, len);
|
||||||
|
return bytes_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendfileAll(self: *const Connection, fd: FileDescriptor, offset: usize, len: usize) !void {
|
||||||
|
if (self.ssl) |ssl| {
|
||||||
|
try ssl.sendfileAll(fd, offset, len);
|
||||||
|
} else {
|
||||||
|
try self.fd.sendfileAll(fd, offset, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
238
packages/web/src/FileDescriptor.zig
Normal file
238
packages/web/src/FileDescriptor.zig
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const linux = std.os.linux;
|
||||||
|
const errno = linux.E.init;
|
||||||
|
|
||||||
|
const iovec = std.posix.iovec;
|
||||||
|
const iovec_const = std.posix.iovec_const;
|
||||||
|
|
||||||
|
pub const FileDescriptor = enum(i32) {
|
||||||
|
stdin = 0,
|
||||||
|
stdout = 1,
|
||||||
|
stderr = 2,
|
||||||
|
_,
|
||||||
|
|
||||||
|
pub fn memfd_create(name: [*:0]const u8, flags: u32) !FileDescriptor {
|
||||||
|
const rc = linux.memfd_create(name, flags);
|
||||||
|
return switch (errno(rc)) {
|
||||||
|
.SUCCESS => @enumFromInt(@as(i32, @intCast(rc))),
|
||||||
|
else => error.SystemError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn socket(domain: u32, socket_type: u32, protocol: u32) !FileDescriptor {
|
||||||
|
const rc = linux.socket(domain, socket_type, protocol);
|
||||||
|
return switch (errno(rc)) {
|
||||||
|
.SUCCESS => @enumFromInt(@as(i32, @intCast(rc))),
|
||||||
|
else => error.SystemError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(self: FileDescriptor) void {
|
||||||
|
_ = linux.close(@intFromEnum(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept(self: FileDescriptor, noalias addr: ?*linux.sockaddr, noalias len: ?*linux.socklen_t) !FileDescriptor {
|
||||||
|
while (true) {
|
||||||
|
const rc = linux.accept(@intFromEnum(self), addr, len);
|
||||||
|
switch (errno(rc)) {
|
||||||
|
.SUCCESS => return @enumFromInt(@as(i32, @intCast(rc))),
|
||||||
|
.INPROGRESS, .AGAIN => continue,
|
||||||
|
.CONNABORTED => return error.ConnectionAborted,
|
||||||
|
.PERM => return error.BlockedByFirewall,
|
||||||
|
else => return error.SystemError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind(self: FileDescriptor, addr: *const linux.sockaddr, len: linux.socklen_t) !void {
|
||||||
|
const rc = linux.bind(@intFromEnum(self), addr, len);
|
||||||
|
return switch (errno(rc)) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
.ACCES, .PERM => error.AccessDenied,
|
||||||
|
.ADDRINUSE => error.AddressInUse,
|
||||||
|
.ADDRNOTAVAIL => error.AddressNotAvailable,
|
||||||
|
.AFNOSUPPORT => error.AddressFamilyNotSupported,
|
||||||
|
.LOOP => error.SymLinkLoop,
|
||||||
|
.NAMETOOLONG => error.NameTooLong,
|
||||||
|
.NOENT => error.FileNotFound,
|
||||||
|
.NOTDIR => error.NotDir,
|
||||||
|
.ROFS => error.ReadOnlyFileSystem,
|
||||||
|
else => error.SystemError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ftruncate(self: FileDescriptor, length: i64) !void {
|
||||||
|
const rc = linux.ftruncate(@intFromEnum(self), length);
|
||||||
|
return switch (errno(rc)) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
else => error.SystemError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getsockname(self: FileDescriptor, noalias addr: *linux.sockaddr, noalias len: *linux.socklen_t) !void {
|
||||||
|
const rc = linux.getsockname(@intFromEnum(self), addr, len);
|
||||||
|
return switch (errno(rc)) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
else => error.SystemError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen(self: FileDescriptor, backlog: u32) !void {
|
||||||
|
const rc = linux.listen(@intFromEnum(self), backlog);
|
||||||
|
return switch (errno(rc)) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
.ADDRINUSE => error.AddressInUse,
|
||||||
|
else => error.SystemError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(self: FileDescriptor, buf: []u8) !usize {
|
||||||
|
while (true) {
|
||||||
|
const rc = linux.read(@intFromEnum(self), buf.ptr, buf.len);
|
||||||
|
switch (errno(rc)) {
|
||||||
|
.SUCCESS => return rc,
|
||||||
|
.INTR => continue,
|
||||||
|
.AGAIN => return error.Timeout,
|
||||||
|
else => return error.SystemError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readAll(self: FileDescriptor, buf: []u8) !void {
|
||||||
|
var total_bytes_read: usize = 0;
|
||||||
|
|
||||||
|
while (total_bytes_read < buf.len) {
|
||||||
|
const chunk = buf[total_bytes_read..];
|
||||||
|
const bytes_read = try self.read(chunk);
|
||||||
|
total_bytes_read += bytes_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readv(self: FileDescriptor, iov: []const iovec) !usize {
|
||||||
|
while (true) {
|
||||||
|
const rc = linux.readv(@intFromEnum(self), iov.ptr, iov.len);
|
||||||
|
switch (errno(rc)) {
|
||||||
|
.SUCCESS => return rc,
|
||||||
|
.INTR => continue,
|
||||||
|
.AGAIN, .WOULDBLOCK => error.Timeout,
|
||||||
|
else => return error.SystemError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Might modify `iov` when `readv` syscall reads partially.
|
||||||
|
pub fn readvAll(self: FileDescriptor, iov: []iovec) !void {
|
||||||
|
var total_bytes_read: usize = 0;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < iov.len) {
|
||||||
|
var bytes_read = try self.readv(iov[i..]);
|
||||||
|
total_bytes_read += bytes_read;
|
||||||
|
|
||||||
|
// skip whole buffers
|
||||||
|
while (i < iov.len and bytes_read >= iov[i].len) {
|
||||||
|
bytes_read -= iov[i].len;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip part of a buffer
|
||||||
|
if (bytes_read > 0) {
|
||||||
|
iov[i].base += bytes_read;
|
||||||
|
iov[i].len -= bytes_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setsockopt(self: FileDescriptor, level: i32, optname: u32, opt: []const u8) !void {
|
||||||
|
const rc = linux.setsockopt(@intFromEnum(self), level, optname, opt.ptr, @intCast(opt.len));
|
||||||
|
return switch (errno(rc)) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
else => error.SystemError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self: FileDescriptor, buf: []const u8) !usize {
|
||||||
|
while (true) {
|
||||||
|
const rc = linux.write(@intFromEnum(self), buf.ptr, buf.len);
|
||||||
|
switch (errno(rc)) {
|
||||||
|
.SUCCESS => return rc,
|
||||||
|
.INTR => continue,
|
||||||
|
else => return error.SystemError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeAll(self: FileDescriptor, buf: []const u8) !void {
|
||||||
|
var total_bytes_written: usize = 0;
|
||||||
|
|
||||||
|
while (total_bytes_written < buf.len) {
|
||||||
|
const chunk = buf[total_bytes_written..];
|
||||||
|
const bytes_written = try self.write(chunk);
|
||||||
|
total_bytes_written += bytes_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writev(self: FileDescriptor, iov: []const iovec_const) !usize {
|
||||||
|
while (true) {
|
||||||
|
const rc = linux.writev(@intFromEnum(self), iov.ptr, iov.len);
|
||||||
|
switch (errno(rc)) {
|
||||||
|
.SUCCESS => return rc,
|
||||||
|
.INTR => continue,
|
||||||
|
else => return error.SystemError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Might modify `iov` when `writev` syscall writes partially.
|
||||||
|
pub fn writevAll(self: FileDescriptor, iov: []iovec_const) !void {
|
||||||
|
var total_bytes_written: usize = 0;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < iov.len) {
|
||||||
|
var bytes_written = try self.writev(iov[i..]);
|
||||||
|
total_bytes_written += bytes_written;
|
||||||
|
|
||||||
|
// skip whole buffers
|
||||||
|
while (i < iov.len and bytes_written >= iov[i].len) {
|
||||||
|
bytes_written -= iov[i].len;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip part of a buffer
|
||||||
|
if (bytes_written > 0) {
|
||||||
|
iov[i].base += bytes_written;
|
||||||
|
iov[i].len -= bytes_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls `fstat` and returns total size in bytes.
|
||||||
|
pub fn size(self: FileDescriptor) !usize {
|
||||||
|
var stat = std.mem.zeroes(linux.Stat);
|
||||||
|
const rc = linux.fstat(@intFromEnum(self), &stat);
|
||||||
|
return switch (errno(rc)) {
|
||||||
|
.SUCCESS => @intCast(stat.size),
|
||||||
|
else => error.SystemError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendfile(self: FileDescriptor, fd: FileDescriptor, offset: usize, len: usize) !usize {
|
||||||
|
var offset_mut = offset;
|
||||||
|
while (true) {
|
||||||
|
const rc = linux.sendfile(@intFromEnum(self), @intFromEnum(fd), @ptrCast(&offset_mut), len);
|
||||||
|
switch (errno(rc)) {
|
||||||
|
.SUCCESS => return rc,
|
||||||
|
.INTR => continue,
|
||||||
|
else => return error.SystemError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendfileAll(self: FileDescriptor, fd: FileDescriptor, offset: usize, len: usize) !void {
|
||||||
|
var total_bytes_sent: usize = 0;
|
||||||
|
|
||||||
|
while (total_bytes_sent < len) {
|
||||||
|
const bytes_sent = try self.sendfile(fd, offset + total_bytes_sent, len - total_bytes_sent);
|
||||||
|
total_bytes_sent += bytes_sent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
41
packages/web/src/Id.zig
Normal file
41
packages/web/src/Id.zig
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const UUID = @import("UUID.zig");
|
||||||
|
|
||||||
|
const decoder = &std.base64.url_safe_no_pad.Decoder;
|
||||||
|
const encoder = &std.base64.url_safe_no_pad.Encoder;
|
||||||
|
|
||||||
|
pub fn Id(comptime _tag: @Type(.enum_literal)) type {
|
||||||
|
return struct {
|
||||||
|
pub const tag = _tag;
|
||||||
|
|
||||||
|
uuid: UUID,
|
||||||
|
|
||||||
|
pub fn init(uuid: UUID) @This() {
|
||||||
|
return .{ .uuid = uuid };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eql(a: @This(), b: @This()) bool {
|
||||||
|
return std.mem.eql(u8, &a.bytes, &b.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(encoded: *const [22]u8) !@This() {
|
||||||
|
var bytes: [16]u8 = undefined;
|
||||||
|
decoder.decode(&bytes, encoded) catch |err| switch (err) {
|
||||||
|
error.InvalidCharacter => return error.InvalidId,
|
||||||
|
error.InvalidPadding, error.NoSpaceLeft => unreachable,
|
||||||
|
};
|
||||||
|
return .{ .bytes = bytes };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode(self: @This()) [22]u8 {
|
||||||
|
var text: [22]u8 = undefined;
|
||||||
|
self.encodeInto(&text);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encodeInto(self: @This(), text: *[22]u8) void {
|
||||||
|
encoder.encode(text, self.bytes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
66
packages/web/src/Request.zig
Normal file
66
packages/web/src/Request.zig
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Request = @This();
|
||||||
|
|
||||||
|
const http = @import("http.zig");
|
||||||
|
|
||||||
|
method: http.Method,
|
||||||
|
pathname: []const u8,
|
||||||
|
headers: *HeaderHashMap,
|
||||||
|
body: []const u8,
|
||||||
|
|
||||||
|
pub const HeaderHashMap = std.HashMapUnmanaged(
|
||||||
|
http.FieldName,
|
||||||
|
HeaderList,
|
||||||
|
http.FieldName.HashMapContext,
|
||||||
|
std.hash_map.default_max_load_percentage,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub const HeaderList = struct {
|
||||||
|
list: std.SinglyLinkedList,
|
||||||
|
len: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const HeaderValue = struct {
|
||||||
|
value: []const u8,
|
||||||
|
node: std.SinglyLinkedList.Node,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Gets a header field value of a given `name`. When there is no such header,
|
||||||
|
/// `null` is returned. When there is more than one header with the same name,
|
||||||
|
/// the value of the field that came later will be returned.
|
||||||
|
pub fn getHeader(self: *const Request, name: http.FieldName) ?[]const u8 {
|
||||||
|
if (self.headers.get(name)) |list| {
|
||||||
|
if (list.list.first) |head| {
|
||||||
|
const header_value: *HeaderValue = @fieldParentPtr("node", head);
|
||||||
|
return header_value.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getHeaderKnown(self: *const Request, known: http.KnownFieldName) ?[]const u8 {
|
||||||
|
return self.getHeader(.initKnonw(known));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getHeaders(self: *const Request, name: http.FieldName, values: [][]const u8) []const []const u8 {
|
||||||
|
var i: usize = 0;
|
||||||
|
var node: ?*std.SinglyLinkedList.Node = if (self.headers.get(name)) |list| list.list else null;
|
||||||
|
|
||||||
|
while (i < values.len) {
|
||||||
|
if (node) |n| {
|
||||||
|
const header_value: *HeaderValue = @fieldParentPtr("node", n);
|
||||||
|
values[i] = header_value.value;
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
node = n.next;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values[0..i];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getHeaderCount(self: *const Request, name: http.FieldName) usize {
|
||||||
|
const list = self.headers.get(name) orelse return 0;
|
||||||
|
return list.len;
|
||||||
|
}
|
||||||
16
packages/web/src/RequestHandler.zig
Normal file
16
packages/web/src/RequestHandler.zig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const RequestHandler = @This();
|
||||||
|
|
||||||
|
const Request = @import("Request.zig");
|
||||||
|
const Response = @import("Response.zig");
|
||||||
|
|
||||||
|
ptr: *anyopaque,
|
||||||
|
vtable: *const VTable,
|
||||||
|
|
||||||
|
pub const VTable = struct {
|
||||||
|
handle: *const fn (self: *anyopaque, request: *Request, response: *Response) anyerror!void,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub inline fn handle(self: RequestHandler, request: *Request, response: *Response) anyerror!void {
|
||||||
|
try self.vtable.handle(self.ptr, request, response);
|
||||||
|
}
|
||||||
85
packages/web/src/Response.zig
Normal file
85
packages/web/src/Response.zig
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Response = @This();
|
||||||
|
|
||||||
|
const Connection = @import("Connection.zig");
|
||||||
|
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||||
|
const http = @import("http.zig");
|
||||||
|
|
||||||
|
const iovec = std.posix.iovec;
|
||||||
|
const iovec_const = std.posix.iovec_const;
|
||||||
|
|
||||||
|
pub const State = union(enum) {
|
||||||
|
init: void,
|
||||||
|
sent: void,
|
||||||
|
errored: anyerror,
|
||||||
|
|
||||||
|
pub fn initErrored(err: anyerror) State {
|
||||||
|
return .{ .errored = err };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
connection: *Connection,
|
||||||
|
header_writer: std.Io.Writer,
|
||||||
|
body_writer: std.Io.Writer,
|
||||||
|
state: State,
|
||||||
|
|
||||||
|
pub fn init(connection: *Connection, header_write_buffer: []u8, body_write_buffer: []u8) Response {
|
||||||
|
return .{
|
||||||
|
.connection = connection,
|
||||||
|
.header_writer = .fixed(header_write_buffer),
|
||||||
|
.body_writer = .fixed(body_write_buffer),
|
||||||
|
.state = .init,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendHeadersOnly(self: *Response) void {
|
||||||
|
std.debug.assert(self.state == .init);
|
||||||
|
|
||||||
|
const headers_slice = self.header_writer.buffered();
|
||||||
|
std.debug.assert(headers_slice.len > 0);
|
||||||
|
|
||||||
|
if (self.connection.writeAll(headers_slice)) {
|
||||||
|
self.state = .sent;
|
||||||
|
} else |err| {
|
||||||
|
self.state = .initErrored(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendHeadersAndBody(self: *Response) void {
|
||||||
|
std.debug.assert(self.state == .init);
|
||||||
|
|
||||||
|
const headers_slice = self.header_writer.buffered();
|
||||||
|
const body_slice = self.body_writer.buffered();
|
||||||
|
std.debug.assert(headers_slice.len > 0);
|
||||||
|
std.debug.assert(body_slice.len > 0);
|
||||||
|
|
||||||
|
var iov = [_]iovec_const{
|
||||||
|
.{ .base = headers_slice.ptr, .len = headers_slice.len },
|
||||||
|
.{ .base = body_slice.ptr, .len = body_slice.len },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (self.connection.writevAll(&iov)) {
|
||||||
|
self.state = .sent;
|
||||||
|
} else |err| {
|
||||||
|
self.state = .initErrored(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendHeadersAndFile(self: *Response, fd: FileDescriptor, offset: usize, maybe_len: ?usize) void {
|
||||||
|
std.debug.assert(self.state == .init);
|
||||||
|
|
||||||
|
const headers_slice = self.header_writer.buffered();
|
||||||
|
std.debug.assert(headers_slice.len > 0);
|
||||||
|
|
||||||
|
const len = maybe_len orelse (try fd.size()) - offset;
|
||||||
|
|
||||||
|
if (self.connection.writeAll(headers_slice)) {
|
||||||
|
if (self.connection.sendfileAll(fd, offset, len)) {
|
||||||
|
self.state = .sent;
|
||||||
|
} else |err2| {
|
||||||
|
self.state = .initErrored(err2);
|
||||||
|
}
|
||||||
|
} else |err1| {
|
||||||
|
self.state = .initErrored(err1);
|
||||||
|
}
|
||||||
|
}
|
||||||
375
packages/web/src/Server.zig
Normal file
375
packages/web/src/Server.zig
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Server = @This();
|
||||||
|
|
||||||
|
const Connection = @import("Connection.zig");
|
||||||
|
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||||
|
const http = @import("http.zig");
|
||||||
|
const openssl = @import("openssl.zig");
|
||||||
|
const Request = @import("Request.zig");
|
||||||
|
const RequestHandler = @import("RequestHandler.zig");
|
||||||
|
const Worker = @import("Worker.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.Server);
|
||||||
|
const linux = std.os.linux;
|
||||||
|
const errno = linux.E.init;
|
||||||
|
|
||||||
|
fd: FileDescriptor,
|
||||||
|
address: std.net.Address,
|
||||||
|
ssl_ctx: ?*openssl.SslContext,
|
||||||
|
workers: []Worker,
|
||||||
|
threads: []std.Thread,
|
||||||
|
request_handler: RequestHandler,
|
||||||
|
read_timeout_us: u64,
|
||||||
|
|
||||||
|
connection_queue: std.DoublyLinkedList,
|
||||||
|
// NOTE Connection pool has no need for being doubly-linked, but the queue has
|
||||||
|
// (as it's FIFO) and we want a single intrusive Node to be able to participate
|
||||||
|
// in both lists. This is possible because a connection will never belong to
|
||||||
|
// both lists at the same time.
|
||||||
|
connection_pool: std.DoublyLinkedList,
|
||||||
|
connection_buffer: []Connection,
|
||||||
|
|
||||||
|
mutex: std.Thread.Mutex,
|
||||||
|
cond_connection_queued: std.Thread.Condition,
|
||||||
|
cond_connection_freed: std.Thread.Condition,
|
||||||
|
|
||||||
|
/// 4 kiB
|
||||||
|
const page_size = 4 * 1024;
|
||||||
|
/// 2 MiB
|
||||||
|
const huge_page_size = 2 * 1024 * 1024;
|
||||||
|
|
||||||
|
pub const Options = struct {
|
||||||
|
request_handler: RequestHandler,
|
||||||
|
address: std.net.Address = .initIp4(.{ 127, 0, 0, 1 }, 8000),
|
||||||
|
/// If not `null`, the server will use TLS with the provided OpenSSL
|
||||||
|
/// context.
|
||||||
|
ssl_ctx: ?*openssl.SslContext = null,
|
||||||
|
max_connections: u32 = 128,
|
||||||
|
/// The number of worker threads. If set to `0`, the number of worker
|
||||||
|
/// threads will be equal to the number of logical CPU cores.
|
||||||
|
worker_count: u32 = 0,
|
||||||
|
/// The maximum number of header fields the `Request` object will be able to
|
||||||
|
/// store. An HTTP request will be rejected if it has more header fields
|
||||||
|
/// than the capacity.
|
||||||
|
max_header_fields: u32 = 256,
|
||||||
|
/// The number of 2 MiB pages reserved for a single read buffer. Each worker
|
||||||
|
/// has its own read buffer. An HTTP request (headers and content combined)
|
||||||
|
/// will be rejected if it is larger than the read buffer.
|
||||||
|
read_buffer_huge_pages: u32 = 1,
|
||||||
|
/// The number of 4 kiB pages reserved for a single header write buffer.
|
||||||
|
/// Each worker has its own header write buffer. The HTTP status line, all
|
||||||
|
/// header fields and the CRLF terminator must all fit in the header write
|
||||||
|
/// buffer.
|
||||||
|
header_write_buffer_pages: u32 = 1,
|
||||||
|
/// The number of 2 MiB pages reserved for a single body write buffer. Each
|
||||||
|
/// worker has its own body write buffer. The HTTP response body must fit
|
||||||
|
/// entirely within the body write buffer. This restriction only applies to
|
||||||
|
/// bodies generated with the body writer and not to bodies sent with
|
||||||
|
/// `sendfile`.
|
||||||
|
body_write_buffer_huge_pages: u32 = 1,
|
||||||
|
/// How much time should a worker wait on an idle connection before closing
|
||||||
|
/// it. Specifically, how much time can a `read` syscall block for, before
|
||||||
|
/// the connection is forcefully closed.
|
||||||
|
read_timeout_us: u64 = 1 * std.time.us_per_s,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
|
||||||
|
const worker_count = if (options.worker_count > 0) options.worker_count else try std.Thread.getCpuCount();
|
||||||
|
|
||||||
|
// Create socket fd
|
||||||
|
|
||||||
|
const fd: FileDescriptor = try .socket(
|
||||||
|
options.address.any.family,
|
||||||
|
linux.SOCK.STREAM | linux.SOCK.CLOEXEC,
|
||||||
|
if (options.address.any.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP,
|
||||||
|
);
|
||||||
|
errdefer fd.close();
|
||||||
|
|
||||||
|
const opt = std.mem.toBytes(@as(c_int, 1));
|
||||||
|
try fd.setsockopt(linux.SOL.SOCKET, linux.SO.REUSEADDR, &opt);
|
||||||
|
try fd.setsockopt(linux.SOL.SOCKET, linux.SO.REUSEPORT, &opt);
|
||||||
|
|
||||||
|
var socklen = options.address.getOsSockLen();
|
||||||
|
try fd.bind(&options.address.any, socklen);
|
||||||
|
try fd.listen(options.max_connections);
|
||||||
|
|
||||||
|
var listen_address = options.address;
|
||||||
|
try fd.getsockname(&listen_address.any, &socklen);
|
||||||
|
|
||||||
|
// Allocate arrays
|
||||||
|
|
||||||
|
const workers = try allocator.alloc(Worker, worker_count);
|
||||||
|
errdefer allocator.free(workers);
|
||||||
|
|
||||||
|
const connection_buffer = try allocator.alloc(Connection, options.max_connections);
|
||||||
|
errdefer allocator.free(connection_buffer);
|
||||||
|
|
||||||
|
const threads = try allocator.alloc(std.Thread, worker_count);
|
||||||
|
errdefer allocator.free(threads);
|
||||||
|
|
||||||
|
// Allocate and remap read buffers
|
||||||
|
|
||||||
|
const single_read_buffer_size = @as(usize, options.read_buffer_huge_pages) * huge_page_size;
|
||||||
|
const all_read_buffers_size = worker_count * single_read_buffer_size;
|
||||||
|
|
||||||
|
const double_single_read_buffer_size = 2 * single_read_buffer_size;
|
||||||
|
const double_all_read_buffers_size = 2 * all_read_buffers_size;
|
||||||
|
|
||||||
|
const read_buffer_fd: FileDescriptor = try .memfd_create("read_buffer", 0);
|
||||||
|
defer read_buffer_fd.close();
|
||||||
|
|
||||||
|
try read_buffer_fd.ftruncate(@intCast(all_read_buffers_size));
|
||||||
|
|
||||||
|
const read_buffer_ptr = try errOrPtr(linux.mmap(
|
||||||
|
null,
|
||||||
|
double_all_read_buffers_size,
|
||||||
|
linux.PROT.NONE,
|
||||||
|
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
errdefer _ = linux.munmap(read_buffer_ptr, double_all_read_buffers_size);
|
||||||
|
_ = linux.madvise(read_buffer_ptr, double_all_read_buffers_size, linux.MADV.HUGEPAGE);
|
||||||
|
|
||||||
|
for (0..worker_count) |i| {
|
||||||
|
const offset = i * single_read_buffer_size;
|
||||||
|
const double_offset = i * double_single_read_buffer_size;
|
||||||
|
|
||||||
|
try err(linux.mmap(
|
||||||
|
read_buffer_ptr + double_offset,
|
||||||
|
single_read_buffer_size,
|
||||||
|
linux.PROT.READ | linux.PROT.WRITE,
|
||||||
|
linux.MAP{ .TYPE = .SHARED, .FIXED = true },
|
||||||
|
@intFromEnum(read_buffer_fd),
|
||||||
|
@intCast(offset),
|
||||||
|
));
|
||||||
|
|
||||||
|
try err(linux.mmap(
|
||||||
|
read_buffer_ptr + double_offset + single_read_buffer_size,
|
||||||
|
single_read_buffer_size,
|
||||||
|
linux.PROT.READ | linux.PROT.WRITE,
|
||||||
|
linux.MAP{ .TYPE = .SHARED, .FIXED = true },
|
||||||
|
@intFromEnum(read_buffer_fd),
|
||||||
|
@intCast(offset),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate header write buffer
|
||||||
|
|
||||||
|
const single_header_write_buffer_size = @as(usize, options.header_write_buffer_pages) * page_size;
|
||||||
|
const all_header_write_buffers_size = worker_count * single_header_write_buffer_size;
|
||||||
|
|
||||||
|
const header_write_buffer_ptr = try errOrPtr(linux.mmap(
|
||||||
|
null,
|
||||||
|
all_header_write_buffers_size,
|
||||||
|
linux.PROT.READ | linux.PROT.WRITE,
|
||||||
|
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
errdefer _ = linux.munmap(header_write_buffer_ptr, all_header_write_buffers_size);
|
||||||
|
|
||||||
|
// Allocate body write buffer
|
||||||
|
|
||||||
|
const single_body_write_buffer_size = @as(usize, options.body_write_buffer_huge_pages) * huge_page_size;
|
||||||
|
const all_body_write_buffers_size = worker_count * single_body_write_buffer_size;
|
||||||
|
|
||||||
|
const body_write_buffer_ptr = try errOrPtr(linux.mmap(
|
||||||
|
null,
|
||||||
|
all_body_write_buffers_size,
|
||||||
|
linux.PROT.READ | linux.PROT.WRITE,
|
||||||
|
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
errdefer _ = linux.munmap(body_write_buffer_ptr, all_body_write_buffers_size);
|
||||||
|
_ = linux.madvise(body_write_buffer_ptr, all_body_write_buffers_size, linux.MADV.HUGEPAGE);
|
||||||
|
|
||||||
|
// Initialize workers
|
||||||
|
|
||||||
|
var workers_initialized: usize = 0;
|
||||||
|
errdefer {
|
||||||
|
for (workers[0..workers_initialized]) |*worker| {
|
||||||
|
worker.deinit(allocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (workers, 0..) |*worker, i| {
|
||||||
|
const read_offset = i * double_single_read_buffer_size;
|
||||||
|
const header_write_offset = i * single_header_write_buffer_size;
|
||||||
|
const body_write_offset = i * single_body_write_buffer_size;
|
||||||
|
worker.* = try Worker.init(allocator, .{
|
||||||
|
.worker_id = i,
|
||||||
|
|
||||||
|
.max_header_fields = options.max_header_fields,
|
||||||
|
|
||||||
|
.read_buffer_ptr = read_buffer_ptr + read_offset,
|
||||||
|
.read_buffer_size = single_read_buffer_size,
|
||||||
|
|
||||||
|
.header_write_buffer = (header_write_buffer_ptr + header_write_offset)[0..single_header_write_buffer_size],
|
||||||
|
.body_write_buffer = (body_write_buffer_ptr + body_write_offset)[0..single_body_write_buffer_size],
|
||||||
|
});
|
||||||
|
workers_initialized += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill connection pool
|
||||||
|
|
||||||
|
var connection_pool: std.DoublyLinkedList = .{};
|
||||||
|
for (connection_buffer) |*c| {
|
||||||
|
connection_pool.prepend(&c.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.fd = fd,
|
||||||
|
.address = listen_address,
|
||||||
|
.ssl_ctx = options.ssl_ctx,
|
||||||
|
.workers = workers,
|
||||||
|
.threads = threads,
|
||||||
|
.request_handler = options.request_handler,
|
||||||
|
.read_timeout_us = options.read_timeout_us,
|
||||||
|
|
||||||
|
.connection_queue = .{},
|
||||||
|
.connection_pool = connection_pool,
|
||||||
|
.connection_buffer = connection_buffer,
|
||||||
|
|
||||||
|
.mutex = .{},
|
||||||
|
.cond_connection_queued = .{},
|
||||||
|
.cond_connection_freed = .{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Server, allocator: std.mem.Allocator) void {
|
||||||
|
log.debug("Deinitializing Server.", .{});
|
||||||
|
const worker_count = self.workers.len;
|
||||||
|
|
||||||
|
const single_read_buffers_size = self.workers[0].read_buffer_size;
|
||||||
|
const all_read_buffers_size = worker_count * single_read_buffers_size;
|
||||||
|
const double_all_read_buffers_size = 2 * all_read_buffers_size;
|
||||||
|
|
||||||
|
const single_header_write_buffer_size = self.workers[0].header_write_buffer.len;
|
||||||
|
const all_header_write_buffers_size = worker_count * single_header_write_buffer_size;
|
||||||
|
|
||||||
|
const single_body_write_buffer_size = self.workers[0].body_write_buffer.len;
|
||||||
|
const all_body_write_buffers_size = worker_count * single_body_write_buffer_size;
|
||||||
|
|
||||||
|
const read_buffer_ptr = self.workers[0].read_buffer_ptr;
|
||||||
|
const header_write_buffer_ptr = self.workers[0].header_write_buffer.ptr;
|
||||||
|
const body_write_buffer_ptr = self.workers[0].body_write_buffer.ptr;
|
||||||
|
|
||||||
|
for (self.workers) |*worker| {
|
||||||
|
worker.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = linux.munmap(body_write_buffer_ptr, all_body_write_buffers_size);
|
||||||
|
_ = linux.munmap(header_write_buffer_ptr, all_header_write_buffers_size);
|
||||||
|
_ = linux.munmap(read_buffer_ptr, double_all_read_buffers_size);
|
||||||
|
|
||||||
|
allocator.free(self.threads);
|
||||||
|
allocator.free(self.connection_buffer);
|
||||||
|
allocator.free(self.workers);
|
||||||
|
|
||||||
|
self.fd.close();
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method block until the server is stopped, which is achieved by storing
|
||||||
|
/// `false` into `running`. You should use another thread or interruption
|
||||||
|
/// handler to be able to stop the server.
|
||||||
|
pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
|
||||||
|
var worker_running: std.atomic.Value(bool) = .init(running.load(.acquire));
|
||||||
|
|
||||||
|
var spawned: usize = 0;
|
||||||
|
defer {
|
||||||
|
log.debug("Storing `false` into worker_running.", .{});
|
||||||
|
worker_running.store(false, .release);
|
||||||
|
log.debug("Broadcasting connection queued condition variable.", .{});
|
||||||
|
self.cond_connection_queued.broadcast();
|
||||||
|
for (self.threads[0..spawned], 0..) |*thread, i| {
|
||||||
|
log.debug("Joining the thread of worker #{d}.", .{i});
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.workers, 0..) |*worker, i| {
|
||||||
|
log.debug("Spawning thread for worker #{d}.", .{i});
|
||||||
|
self.threads[i] = try std.Thread.spawn(.{}, Worker.worker, .{ worker, self, &worker_running });
|
||||||
|
spawned += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (running.load(.acquire)) {
|
||||||
|
var address: std.net.Address = undefined;
|
||||||
|
var address_size: u32 = @sizeOf(std.net.Address);
|
||||||
|
|
||||||
|
log.debug("Accepting connection.", .{});
|
||||||
|
const fd = self.fd.accept(&address.any, &address_size) catch |e| {
|
||||||
|
log.err("Error while accepting connection: {}", .{e});
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
log.debug("Accepted connection from {f}", .{address});
|
||||||
|
|
||||||
|
const timeout: linux.timeval = .{
|
||||||
|
.sec = @intCast(self.read_timeout_us / std.time.us_per_s),
|
||||||
|
.usec = @intCast(self.read_timeout_us % std.time.us_per_s),
|
||||||
|
};
|
||||||
|
try fd.setsockopt(linux.SOL.SOCKET, linux.SO.RCVTIMEO, std.mem.asBytes(&timeout));
|
||||||
|
|
||||||
|
const ssl: ?*openssl.Ssl = self.maybeInitSsl(fd) catch |e| {
|
||||||
|
log.err("Error while estabilishing SSL connection: {}", .{e});
|
||||||
|
fd.close();
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
log.debug("Acquiring mutex.", .{});
|
||||||
|
self.mutex.lock();
|
||||||
|
log.debug("Acquired mutex.", .{});
|
||||||
|
defer {
|
||||||
|
log.debug("Unlocking mutex.", .{});
|
||||||
|
self.mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (self.connection_pool.pop()) |node| {
|
||||||
|
const connection: *Connection = @fieldParentPtr("node", node);
|
||||||
|
connection.reinit(address, fd, ssl);
|
||||||
|
log.debug("Adding connection to {f} to the connection queue.", .{connection.address});
|
||||||
|
self.connection_queue.prepend(node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Waiting on connection freed condition variable.", .{});
|
||||||
|
self.cond_connection_freed.wait(&self.mutex);
|
||||||
|
log.debug("Woken up on connection freed condition variable.", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Signaling connection queued condition variable.", .{});
|
||||||
|
self.cond_connection_queued.signal();
|
||||||
|
} else {
|
||||||
|
log.debug("Loaded `false` from running, the accept loop exited.", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybeInitSsl(self: *const Server, fd: FileDescriptor) !?*openssl.Ssl {
|
||||||
|
if (self.ssl_ctx) |ssl_ctx| {
|
||||||
|
const ssl = try openssl.Ssl.new(ssl_ctx);
|
||||||
|
try ssl.setFd(fd);
|
||||||
|
log.debug("Accepting SSL layer.", .{});
|
||||||
|
try ssl.accept();
|
||||||
|
log.debug("Accepted SSL layer.", .{});
|
||||||
|
return ssl;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn err(rc: usize) !void {
|
||||||
|
const e = errno(rc);
|
||||||
|
return if (e != .SUCCESS) error.SystemError else {};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn errOrPtr(rc: usize) ![*]u8 {
|
||||||
|
const e = errno(rc);
|
||||||
|
return if (e != .SUCCESS) error.SystemError else @ptrFromInt(rc);
|
||||||
|
}
|
||||||
87
packages/web/src/UUID.zig
Normal file
87
packages/web/src/UUID.zig
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const UUID = @This();
|
||||||
|
|
||||||
|
bytes: [16]u8,
|
||||||
|
|
||||||
|
pub const zero: UUID = .{ .bytes = @splat(0) };
|
||||||
|
|
||||||
|
pub fn v4() UUID {
|
||||||
|
var bytes: [16]u8 = undefined;
|
||||||
|
std.crypto.random.bytes(&bytes);
|
||||||
|
bytes[6] = (bytes[6] & 0x0F) | 0x40;
|
||||||
|
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||||
|
return .{ .bytes = bytes };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const namespaces = struct {
|
||||||
|
/// Name string is a fully-qualified domain name
|
||||||
|
///
|
||||||
|
/// `6ba7b810-9dad-11d1-80b4-00c04fd430c8`
|
||||||
|
pub const dns: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x10, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
|
||||||
|
/// Name string is a URL
|
||||||
|
///
|
||||||
|
/// `6ba7b811-9dad-11d1-80b4-00c04fd430c8`
|
||||||
|
pub const url: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x11, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
|
||||||
|
/// Name string is an ISO OID
|
||||||
|
///
|
||||||
|
/// `6ba7b812-9dad-11d1-80b4-00c04fd430c8`
|
||||||
|
pub const oid: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x12, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
|
||||||
|
/// Name string is an X.500 DN (in DER or a text output format)
|
||||||
|
///
|
||||||
|
/// `6ba7b814-9dad-11d1-80b4-00c04fd430c8`
|
||||||
|
pub const x500: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x14, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn v5(namespace: UUID, name: []const u8) UUID {
|
||||||
|
const hash = blk: {
|
||||||
|
var hasher = std.crypto.hash.Sha1.init(.{});
|
||||||
|
hasher.update(namespace);
|
||||||
|
hasher.update(name);
|
||||||
|
break :blk hasher.finalResult();
|
||||||
|
};
|
||||||
|
|
||||||
|
var bytes: [16]u8 = hash[0..16].*;
|
||||||
|
bytes[6] = (bytes[6] & 0x0F) | 0x50;
|
||||||
|
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||||
|
|
||||||
|
return .{ .bytes = bytes };
|
||||||
|
}
|
||||||
|
|
||||||
|
var v7_lock: std.Thread.Mutex = .{};
|
||||||
|
var v7_last_timestamp: std.atomic.Value(u64) = .{ .raw = 0 };
|
||||||
|
var v7_counter: std.atomic.Value(u32) = .{ .raw = 0 };
|
||||||
|
|
||||||
|
fn getCount(timestamp: u64) u32 {
|
||||||
|
v7_lock.lock();
|
||||||
|
defer v7_lock.unlock();
|
||||||
|
|
||||||
|
if (v7_last_timestamp.swap(timestamp, .monotonic) != timestamp) {
|
||||||
|
v7_counter.store(0, .monotonic);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v7_counter.fetchAdd(1, .monotonic) % 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn v7() UUID {
|
||||||
|
const timestamp: u64 = @intCast(@max(0, std.time.milliTimestamp()));
|
||||||
|
const count = getCount(timestamp);
|
||||||
|
const random = blk: {
|
||||||
|
var bytes: [8]u8 = undefined;
|
||||||
|
std.crypto.random.bytes(&bytes);
|
||||||
|
break :blk bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
var bytes: [16]u8 = undefined;
|
||||||
|
bytes[0] = @truncate(timestamp >> 40);
|
||||||
|
bytes[1] = @truncate(timestamp >> 32);
|
||||||
|
bytes[2] = @truncate(timestamp >> 24);
|
||||||
|
bytes[3] = @truncate(timestamp >> 16);
|
||||||
|
bytes[4] = @truncate(timestamp >> 8);
|
||||||
|
bytes[5] = @truncate(timestamp);
|
||||||
|
bytes[6] = (@as(u8, 7) << 4) | @as(u8, @truncate((count >> 8) & 0x0F));
|
||||||
|
bytes[7] = @truncate(count);
|
||||||
|
bytes[8] = (random[0] & 0x3F) | 0x80;
|
||||||
|
@memcpy(bytes[9..16], random[1..8]);
|
||||||
|
|
||||||
|
return .{ .bytes = bytes };
|
||||||
|
}
|
||||||
321
packages/web/src/Worker.zig
Normal file
321
packages/web/src/Worker.zig
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Worker = @This();
|
||||||
|
|
||||||
|
const Connection = @import("Connection.zig");
|
||||||
|
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||||
|
const http = @import("http.zig");
|
||||||
|
const Request = @import("Request.zig");
|
||||||
|
const RequestHandler = @import("RequestHandler.zig");
|
||||||
|
const Response = @import("Response.zig");
|
||||||
|
const Server = @import("Server.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.Worker);
|
||||||
|
|
||||||
|
/// Integer unique for this worker. Has no functional meaning. Can be used for
|
||||||
|
/// debugging and profiling.
|
||||||
|
worker_id: usize,
|
||||||
|
|
||||||
|
read_buffer_ptr: [*]u8,
|
||||||
|
read_buffer_size: usize,
|
||||||
|
read_head: usize,
|
||||||
|
read_tail: usize,
|
||||||
|
|
||||||
|
header_write_buffer: []u8,
|
||||||
|
body_write_buffer: []u8,
|
||||||
|
|
||||||
|
header_hash_map: Request.HeaderHashMap,
|
||||||
|
header_value_buffer: []Request.HeaderValue,
|
||||||
|
|
||||||
|
pub const Options = struct {
|
||||||
|
worker_id: usize,
|
||||||
|
|
||||||
|
max_header_fields: u32,
|
||||||
|
|
||||||
|
read_buffer_ptr: [*]u8,
|
||||||
|
read_buffer_size: usize,
|
||||||
|
|
||||||
|
header_write_buffer: []u8,
|
||||||
|
body_write_buffer: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, options: Options) !Worker {
|
||||||
|
var header_hash_map: Request.HeaderHashMap = .empty;
|
||||||
|
try header_hash_map.ensureTotalCapacity(allocator, options.max_header_fields);
|
||||||
|
errdefer header_hash_map.deinit(allocator);
|
||||||
|
|
||||||
|
const header_value_buffer = try allocator.alloc(Request.HeaderValue, options.max_header_fields);
|
||||||
|
errdefer allocator.free(header_value_buffer);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.worker_id = options.worker_id,
|
||||||
|
|
||||||
|
.read_buffer_ptr = options.read_buffer_ptr,
|
||||||
|
.read_buffer_size = options.read_buffer_size,
|
||||||
|
.read_head = 0,
|
||||||
|
.read_tail = 0,
|
||||||
|
|
||||||
|
.header_write_buffer = options.header_write_buffer,
|
||||||
|
.body_write_buffer = options.body_write_buffer,
|
||||||
|
|
||||||
|
.header_hash_map = header_hash_map,
|
||||||
|
.header_value_buffer = header_value_buffer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Worker, allocator: std.mem.Allocator) void {
|
||||||
|
log.debug("[#{d}] Deinitializing Worker.", .{self.worker_id});
|
||||||
|
|
||||||
|
allocator.free(self.header_value_buffer);
|
||||||
|
self.header_hash_map.deinit(allocator);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn worker(
|
||||||
|
self: *Worker,
|
||||||
|
server: *Server,
|
||||||
|
running: *const std.atomic.Value(bool),
|
||||||
|
) void {
|
||||||
|
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id});
|
||||||
|
server.mutex.lock();
|
||||||
|
log.debug("[#{d}] Acquired mutex.", .{self.worker_id});
|
||||||
|
defer {
|
||||||
|
log.debug("[#{d}] Unlocking mutex.", .{self.worker_id});
|
||||||
|
server.mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (running.load(.acquire)) {
|
||||||
|
if (server.connection_queue.pop()) |node| {
|
||||||
|
const connection: *Connection = @fieldParentPtr("node", node);
|
||||||
|
log.debug("[#{d}] Popped connection to {f} from the connection queue.", .{ self.worker_id, connection.address });
|
||||||
|
|
||||||
|
log.debug("[#{d}] Unlocking mutex.", .{self.worker_id});
|
||||||
|
server.mutex.unlock();
|
||||||
|
defer {
|
||||||
|
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id});
|
||||||
|
server.mutex.lock();
|
||||||
|
log.debug("[#{d}] Acquired mutex.", .{self.worker_id});
|
||||||
|
|
||||||
|
log.debug("[#{d}] Returning connection to connection pool.", .{self.worker_id});
|
||||||
|
server.connection_pool.append(&connection.node);
|
||||||
|
log.debug("[#{d}] Signaling connection freed condition variable.", .{self.worker_id});
|
||||||
|
server.cond_connection_freed.signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("[#{d}] Handling connection to {f}.", .{ self.worker_id, connection.address });
|
||||||
|
self.handleConnection(server.request_handler, connection, running) catch |err| {
|
||||||
|
log.err("[#{d}] Error while handling connection: {}", .{ self.worker_id, err });
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
log.debug("[#{d}] Waiting on connection queued condition variable.", .{self.worker_id});
|
||||||
|
server.cond_connection_queued.wait(&server.mutex);
|
||||||
|
log.debug("[#{d}] Woken up on connection queued condition variable.", .{self.worker_id});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("[#{d}] Loaded `false` from running, the worker loop exited.", .{self.worker_id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleConnection(
|
||||||
|
self: *Worker,
|
||||||
|
request_handler: RequestHandler,
|
||||||
|
connection: *Connection,
|
||||||
|
running: *const std.atomic.Value(bool),
|
||||||
|
) !void {
|
||||||
|
defer connection.deinit();
|
||||||
|
|
||||||
|
while (running.load(.acquire)) {
|
||||||
|
const res = self.handleRequest(request_handler, connection) catch |err| {
|
||||||
|
log.err("[#{d}] Error while handling request: {}", .{ self.worker_id, err });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
log.debug("[#{d}] Request handler indicated to stop handling the connection to {f}.", .{ self.worker_id, connection.address });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("[#{d}] Loaded `false` from running, the connection handler loop exited.", .{self.worker_id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleRequest(
|
||||||
|
self: *Worker,
|
||||||
|
request_handler: RequestHandler,
|
||||||
|
connection: *Connection,
|
||||||
|
) !bool {
|
||||||
|
self.header_hash_map.clearRetainingCapacity();
|
||||||
|
|
||||||
|
var request: Request = .{
|
||||||
|
.method = undefined,
|
||||||
|
.pathname = undefined,
|
||||||
|
.headers = &self.header_hash_map,
|
||||||
|
.body = undefined,
|
||||||
|
};
|
||||||
|
var response: Response = .init(connection, self.header_write_buffer, self.body_write_buffer);
|
||||||
|
var parser: http.Parser = .init();
|
||||||
|
|
||||||
|
var next_header_index: usize = 0;
|
||||||
|
var ignore: bool = false;
|
||||||
|
var client_closed: bool = false;
|
||||||
|
|
||||||
|
var leftover_bytes = self.read_tail - self.read_head;
|
||||||
|
const max_read_tail = self.read_head + self.read_buffer_size;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var bytes_read: usize = undefined;
|
||||||
|
var chunk: []const u8 = undefined;
|
||||||
|
|
||||||
|
if (leftover_bytes > 0) {
|
||||||
|
bytes_read = leftover_bytes;
|
||||||
|
chunk = self.read_buffer_ptr[self.read_tail - leftover_bytes .. self.read_tail];
|
||||||
|
leftover_bytes = 0;
|
||||||
|
} else {
|
||||||
|
const read_tail = self.read_tail;
|
||||||
|
bytes_read = connection.read(self.read_buffer_ptr[read_tail..max_read_tail]) catch |err| switch (err) {
|
||||||
|
error.Timeout => {
|
||||||
|
log.debug("[#{d}] Connection to {f} timed out.", .{ self.worker_id, connection.address });
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
if (bytes_read == 0) {
|
||||||
|
log.debug("[#{d}] Read zero bytes from connection to {f}.", .{ self.worker_id, connection.address });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
chunk = self.read_buffer_ptr[read_tail .. read_tail + bytes_read];
|
||||||
|
self.read_tail += bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = parser.consume(chunk) catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.MethodNotSupported => try closeWith(&response, http.status.method_not_allowed),
|
||||||
|
error.HttpVersionNotSupported => try closeWith(&response, http.status.http_version_not_supported),
|
||||||
|
error.SyntaxError => try closeWith(&response, http.status.bad_request),
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const done = if (res.result) |result| std.meta.activeTag(result) == .body else false;
|
||||||
|
|
||||||
|
if (self.read_tail - self.read_head >= self.read_buffer_size and !done) {
|
||||||
|
if (parser.state == .body) {
|
||||||
|
try closeWith(&response, http.status.content_too_large);
|
||||||
|
} else {
|
||||||
|
try closeWith(&response, http.status.request_header_fields_too_large);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.result) |result| {
|
||||||
|
switch (result) {
|
||||||
|
.method => |method| request.method = method,
|
||||||
|
.pathname => |pathname| request.pathname = pathname,
|
||||||
|
.header => |header| blk: {
|
||||||
|
if (header.isNamedKnown(.Connection) and std.mem.eql(u8, header.value, "close")) {
|
||||||
|
client_closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignore) {
|
||||||
|
break :blk;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_header_index >= self.header_value_buffer.len or self.header_hash_map.available == 0) {
|
||||||
|
// TODO Here, we could ignore, but make sure this does
|
||||||
|
// not clash with the other "request too long" checks
|
||||||
|
// (i.e. be careful not to double respond).
|
||||||
|
_ = &ignore;
|
||||||
|
|
||||||
|
try closeWith(&response, http.status.request_header_fields_too_large);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
const entry = self.header_hash_map.getOrPutAssumeCapacity(header.name);
|
||||||
|
const header_value = &self.header_value_buffer[next_header_index];
|
||||||
|
header_value.* = .{ .node = .{}, .value = header.value };
|
||||||
|
next_header_index += 1;
|
||||||
|
|
||||||
|
if (!entry.found_existing) {
|
||||||
|
entry.value_ptr.* = .{
|
||||||
|
.len = 0,
|
||||||
|
.list = .{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.value_ptr.list.prepend(&header_value.node);
|
||||||
|
entry.value_ptr.len += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.end_of_headers => {},
|
||||||
|
.body => |body| {
|
||||||
|
request.body = body;
|
||||||
|
|
||||||
|
if (!ignore) {
|
||||||
|
request_handler.handle(&request, &response) catch |err| {
|
||||||
|
if (response.state == .init) {
|
||||||
|
response.header_writer.end = 0;
|
||||||
|
response.body_writer.end = 0;
|
||||||
|
|
||||||
|
const error_name = @errorName(err);
|
||||||
|
|
||||||
|
try response.body_writer.print("Internal Server Error\n{s}\n", .{error_name});
|
||||||
|
|
||||||
|
try response.header_writer.writeAll(http.status.internal_server_error);
|
||||||
|
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\n");
|
||||||
|
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
|
||||||
|
try response.header_writer.writeAll("\r\n");
|
||||||
|
|
||||||
|
response.sendHeadersAndBody();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.state == .init) {
|
||||||
|
const no_headers = response.header_writer.end > 0;
|
||||||
|
const no_body = response.body_writer.end > 0;
|
||||||
|
|
||||||
|
if (no_headers) {
|
||||||
|
if (no_body) {
|
||||||
|
try response.header_writer.writeAll(http.status.no_content);
|
||||||
|
try response.header_writer.writeAll("\r\n");
|
||||||
|
|
||||||
|
response.sendHeadersOnly();
|
||||||
|
} else {
|
||||||
|
try response.header_writer.writeAll(http.status.ok);
|
||||||
|
try response.header_writer.writeAll("Content-Type: application/octet-stream");
|
||||||
|
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
|
||||||
|
try response.header_writer.writeAll("\r\n");
|
||||||
|
|
||||||
|
response.sendHeadersAndBody();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (no_body) {
|
||||||
|
response.sendHeadersOnly();
|
||||||
|
} else {
|
||||||
|
response.sendHeadersAndBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leftover_bytes = bytes_read - res.consumed;
|
||||||
|
self.read_head = (self.read_tail - leftover_bytes) & ~(self.read_buffer_size - 1);
|
||||||
|
self.read_tail = self.read_head + leftover_bytes;
|
||||||
|
return !client_closed;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leftover_bytes = bytes_read - res.consumed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn closeWith(response: *Response, status_line: []const u8) !void {
|
||||||
|
// This function is meant to be called before a request handler gets to do
|
||||||
|
// anything.
|
||||||
|
std.debug.assert(response.header_writer.end == 0);
|
||||||
|
std.debug.assert(response.body_writer.end == 0);
|
||||||
|
std.debug.assert(response.state == .init);
|
||||||
|
|
||||||
|
try response.header_writer.writeAll(status_line);
|
||||||
|
try response.header_writer.writeAll("Connection: close\r\n");
|
||||||
|
try response.header_writer.writeAll("\r\n");
|
||||||
|
}
|
||||||
8
packages/web/src/http.zig
Normal file
8
packages/web/src/http.zig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const FieldName = @import("http/FieldName.zig").FieldName;
|
||||||
|
pub const Header = @import("http/Header.zig");
|
||||||
|
pub const KnownFieldName = @import("http/KnownFieldName.zig").KnownFieldName;
|
||||||
|
pub const Method = @import("http/Method.zig").Method;
|
||||||
|
pub const Parser = @import("http/Parser.zig");
|
||||||
|
pub const status = @import("http/status.zig");
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user