Skip to content

Commit

Permalink
feat: io.FilePoller
Browse files Browse the repository at this point in the history
  • Loading branch information
giann committed Feb 2, 2025
1 parent 5b69603 commit ecc656e
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 15 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# Unreleased

# 0.5.0
# Added

- File polling API: `File.getPoller`, `FilePoller` (see example https://github.com/buzz-language/buzz/blob/main/tests/manual/007-fd-poller.buzz)
- _Shortcut_ operators: `+=`, `-=`, `*=`, etc. (https://github.com/buzz-language/buzz/issues/78)
- `rg.contains`

# Changed

- `main` signature can omit `args` argument
- Maximum number of enum cases is now 16 777 215 instead of 255

# 0.5.0 (01-24-2025)

## Major syntax changes
- Types are now specified *after* the identifier + `:` (https://github.com/buzz-language/buzz/issues/310). This includes:
Expand Down
2 changes: 1 addition & 1 deletion src/disassembler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ pub const DumpState = struct {
defer state.vm.gc.allocator.free(enum_value_type_def);

out.print(
"enum({s}) {s} {{\n",
"enum<{s}> {s} {{\n",
.{
enum_value_type_def,
enum_type_def.name.string,
Expand Down
133 changes: 122 additions & 11 deletions src/lib/buzz_io.zig
Original file line number Diff line number Diff line change
Expand Up @@ -345,20 +345,15 @@ pub export fn FileRead(ctx: *api.NativeCtx) callconv(.c) c_int {
const reader = file.reader();

// Avoid heap allocation if we read less than 255 bytes
var buffer = if (n > 255)
api.VM.allocator.alloc(u8, @as(usize, @intCast(n))) catch {
ctx.vm.bz_panic("Out of memory", "Out of memory".len);
unreachable;
}
else
@constCast(&([_]u8{0} ** 255));
var buffer = api.VM.allocator.alloc(u8, @as(usize, @intCast(n))) catch {
ctx.vm.bz_panic("Out of memory", "Out of memory".len);
unreachable;
};

// bz_stringToValue will copy it
defer api.VM.allocator.free(buffer);

const read = reader.readAll(
buffer[0..@intCast(@min(255, n))],
) catch |err| {
const read = reader.readAll(buffer[0..@intCast(n)]) catch |err| {
handleFileReadAllError(ctx, err);

return -1;
Expand Down Expand Up @@ -415,7 +410,6 @@ fn fileSmallRead(ctx: *api.NativeCtx, handle: std.fs.File.Handle, n: usize) c_in
return 1;
}

// extern fun File_write(int fd, [int] bytes) > void;
pub export fn FileWrite(ctx: *api.NativeCtx) callconv(.c) c_int {
const handle: std.fs.File.Handle =
if (builtin.os.tag == .windows)
Expand Down Expand Up @@ -467,6 +461,123 @@ pub export fn FileWrite(ctx: *api.NativeCtx) callconv(.c) c_int {
return 0;
}

const FileEnum = enum {
file,
};

pub export fn FileGetPoller(ctx: *api.NativeCtx) callconv(.c) c_int {
const handle: std.fs.File.Handle = if (builtin.os.tag == .windows)
@ptrFromInt(@as(usize, @intCast(ctx.vm.bz_peek(0).integer())))
else
@intCast(
ctx.vm.bz_peek(0).integer(),
);
const file = std.fs.File{ .handle = handle };

const poller = api.VM.allocator.create(std.io.Poller(FileEnum)) catch {
ctx.vm.bz_panic("Out of memory", "Out of memory".len);
unreachable;
};

poller.* = std.io.poll(
api.VM.allocator,
FileEnum,
.{ .file = file },
);

ctx.vm.bz_push(
ctx.vm.bz_newUserData(@intFromPtr(poller)),
);

return 1;
}

fn pollerFromUserData(userdata: u64) *std.io.Poller(FileEnum) {
return @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(@as(usize, @truncate(userdata))))));
}

pub export fn PollerPoll(ctx: *api.NativeCtx) callconv(.c) c_int {
const poller = pollerFromUserData(
ctx.vm.bz_peek(1).bz_getUserDataPtr(),
);
const timeout_value = ctx.vm.bz_peek(0);
const timeout: u64 = @as(
u64,
@intCast(
if (timeout_value.isInteger())
timeout_value.integer()
else
0,
),
) * 1_000_000;

const got_something = poller.pollTimeout(timeout) catch |err| {
handlePollError(ctx, err);

return -1;
};

if (got_something) {
const fifo = poller.fifo(.file);
const len = fifo.readableLength();

if (len > 0) {
const read = fifo.readableSliceOfLen(len);

ctx.vm.bz_push(
ctx.vm.bz_stringToValue(read.ptr, len),
);

fifo.discard(len);
} else {
ctx.vm.bz_push(api.Value.Null);
}
} else {
ctx.vm.bz_push(api.Value.Null);
}

return 1;
}

pub export fn PollerDeinit(ctx: *api.NativeCtx) callconv(.c) c_int {
const poller = pollerFromUserData(
ctx.vm.bz_peek(0).bz_getUserDataPtr(),
);

poller.deinit();
api.VM.allocator.destroy(poller);

return 0;
}

fn handlePollError(ctx: *api.NativeCtx, err: anytype) void {
switch (err) {
error.InputOutput,
error.AccessDenied,
error.SystemResources,
error.WouldBlock,
error.IsDir,
=> ctx.vm.pushErrorEnum("errors.FileSystemError", @errorName(err)),
error.OperationAborted,
error.LockViolation,
error.NotOpenForReading,
=> ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)),
error.ConnectionResetByPeer,
error.ConnectionTimedOut,
error.SocketNotConnected,
error.Canceled,
error.NetworkSubsystemFailed,
=> ctx.vm.pushErrorEnum("errors.SocketError", @errorName(err)),
error.ProcessNotFound,
=> ctx.vm.pushErrorEnum("errors.ExecError", @errorName(err)),
error.Unexpected => ctx.vm.pushError("errors.UnexpectedError", null),
error.OutOfMemory => {
ctx.vm.bz_panic("Out of memory", "Out of memory".len);
unreachable;
},
}
}

pub export fn runFile(ctx: *api.NativeCtx) callconv(.c) c_int {
// Read file
var len: usize = 0;
Expand Down
33 changes: 33 additions & 0 deletions src/lib/io.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ extern fun getStdOut() > int;
extern fun getStdErr() > int;
/// @private
extern fun FileIsTTY(fd: int) > bool;
/// @private
extern fun FileGetPoller(fd: int) > ud !> errors\FileSystemError, errors\ReadWriteError, errors\SocketError, errors\ExecError, error\UnexpectedError;
/// @private
extern fun PollerPoll(poller: ud, timeout: int?) > str? !> errors\ReadWriteError;
/// @private
extern fun PollerDeinit(poller: ud) > void;

/// File mode with which you can open a file
export enum FileMode {
Expand Down Expand Up @@ -83,6 +89,33 @@ export object File {
fun isTTY() > bool {
return FileIsTTY(this.fd);
}

/// @return FilePoller that can be used to poll incoming data on that file
fun getPoller() > FilePoller !> errors\FileSystemError, errors\ReadWriteError, errors\SocketError, errors\ExecError, error\UnexpectedError {
return FilePoller{
poller = FileGetPoller(this.fd),
};
}
}

export object FilePoller {
/// Underlying zig poller
poller: ud,

// Poll file, blocking for at most `timeout` before returning
// @returns The string read if any, `null` otherwise
fun poll(timeout: int?) > str? !> errors\ReadWriteError {
return PollerPoll(this.poller, timeout);
}

fun collect() > void {
this.deinit();
}

fun deinit() > void {
PollerDeinit(this.poller);
}

}

/// stdin
Expand Down
2 changes: 1 addition & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ else

fn printBanner(out: anytype, full: bool) void {
out.print(
"\n👨‍🚀 buzz {}-{s} Copyright (C) 2021-present Benoit Giannangeli\n",
"👨‍🚀 buzz {}-{s} Copyright (C) 2021-present Benoit Giannangeli\n",
.{
BuildOptions.version,
BuildOptions.sha,
Expand Down
2 changes: 1 addition & 1 deletion src/repl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub const MULTILINE_PROMPT = "... ";

pub fn printBanner(out: anytype, full: bool) void {
out.print(
"\n👨‍🚀 buzz {}-{s} Copyright (C) 2021-present Benoit Giannangeli\n",
"👨‍🚀 buzz {}-{s} Copyright (C) 2021-present Benoit Giannangeli\n",
.{
BuildOptions.version,
BuildOptions.sha,
Expand Down
18 changes: 18 additions & 0 deletions tests/manual/007-fd-poller.buzz
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "std";
import "io";

fun main(_: [str]) > void !> any {
final poller = io\stdin.getPoller();

while (true) {
if (poller.poll(100) -> polled) {
std\print("> {polled}");

if (polled == "q") {
break;
}
} else {
std\print("nothing...");
}
}
}

0 comments on commit ecc656e

Please sign in to comment.