diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index a9ea5c0c..bdbc3942 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,10 +1,52 @@
 on:
   push:
-    branches: [main]
+    branches: [main, windows]
   pull_request:
     branches: [main]
 
 jobs:
+  test-windows:
+    runs-on: windows-latest
+    steps:
+      - name: Install homebrew
+        run: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+      - name: Checkout project
+        uses: actions/checkout@v3.0.0
+      - name: Checkout submodules
+        run: git submodule update --init --recursive
+      - name: Setup nightly Zig
+        uses: mlugg/setup-zig@v1
+        with:
+          version: master
+      - name: Build test ffi lib
+        run: zig build-lib -dynamic tests/utils/foreign.zig && mv libforeign.* tests/utils/
+
+      - name: Run tests Debug
+        run: zig build test
+      - name: Cleanup
+        run: rm -rf zig-out zig-cache
+      - name: Run tests Debug with JIT always on
+        run: zig build -Djit_always_on test
+      - name: Cleanup
+        run: rm -rf zig-out zig-cache
+
+      - name: Run tests ReleaseSafe
+        run: zig build -Doptimize=ReleaseSafe test
+      - name: Cleanup
+        run: rm -rf zig-out zig-cache
+      - name: Run tests ReleaseSafe with JIT always on
+        run: zig build -Doptimize=ReleaseSafe -Djit_always_on test
+      - name: Cleanup
+        run: rm -rf zig-out zig-cache
+
+      - name: Run tests ReleaseFast
+        run: zig build -Doptimize=ReleaseFast test
+      - name: Cleanup
+        run: rm -rf zig-out zig-cache
+      - name: Run tests ReleaseFast with JIT always on
+        run: zig build -Doptimize=ReleaseFast -Djit_always_on test
+      - name: Cleanup
+        run: rm -rf zig-out zig-cache
   test-macos:
     runs-on: macos-latest
     steps:
diff --git a/build.zig b/build.zig
index 56bb211a..cbd78f12 100644
--- a/build.zig
+++ b/build.zig
@@ -95,11 +95,10 @@ const BuildOptions = struct {
     };
 };
 
-fn getBuzzPrefix(b: *Build) ![]const u8 {
-    return std.posix.getenv("BUZZ_PATH") orelse std.fs.path.dirname(b.exe_dir).?;
-}
-
 pub fn build(b: *Build) !void {
+    var envMap = try std.process.getEnvMap(b.allocator);
+    defer envMap.deinit();
+
     // Check minimum zig version
     const current_zig = builtin.zig_version;
     const min_zig = std.SemanticVersion.parse("0.14.0-dev.2851+b074fb7dd") catch return;
@@ -116,8 +115,8 @@ pub fn build(b: *Build) !void {
         .target = target,
         .version = std.SemanticVersion{ .major = 0, .minor = 5, .patch = 0 },
         // Current commit sha
-        .sha = std.posix.getenv("GIT_SHA") orelse
-            std.posix.getenv("GITHUB_SHA") orelse std.mem.trim(
+        .sha = envMap.get("GIT_SHA") orelse
+            envMap.get("GITHUB_SHA") orelse std.mem.trim(
             u8,
             b.run(
                 &.{
@@ -264,22 +263,6 @@ pub fn build(b: *Build) !void {
 
     const build_option_module = build_options.step(b);
 
-    var sys_libs = std.ArrayList([]const u8).init(b.allocator);
-    defer sys_libs.deinit();
-    var includes = std.ArrayList([]const u8).init(b.allocator);
-    defer includes.deinit();
-    var llibs = std.ArrayList([]const u8).init(b.allocator);
-    defer llibs.deinit();
-
-    includes.appendSlice(&[_][]const u8{
-        "./vendors/mir",
-        "./vendors/mimalloc/include",
-    }) catch unreachable;
-
-    llibs.appendSlice(&[_][]const u8{
-        "./vendors/mir",
-    }) catch unreachable;
-
     const lib_pcre2 = if (!is_wasm)
         try buildPcre2(b, target, build_mode)
     else
@@ -288,7 +271,7 @@ pub fn build(b: *Build) !void {
         try buildMimalloc(b, target, build_mode)
     else
         null;
-    const lib_linenoise = if (!is_wasm)
+    const lib_linenoise = if (!is_wasm and target.result.os.tag != .windows)
         try buildLinenoise(b, target, build_mode)
     else
         null;
@@ -337,20 +320,6 @@ pub fn build(b: *Build) !void {
     }
     b.step("run", "run buzz").dependOn(&run_exe.step);
 
-    for (includes.items) |include| {
-        exe.addIncludePath(b.path(include));
-        exe_check.addIncludePath(b.path(include));
-    }
-    for (llibs.items) |lib| {
-        exe.addLibraryPath(b.path(lib));
-        exe_check.addLibraryPath(b.path(lib));
-    }
-    for (sys_libs.items) |slib| {
-        // FIXME: if mir is linked as static library (libmir.a), here also need to link libc
-        // it's better to built it with Zig's build system
-        exe.linkSystemLibrary(slib);
-        exe_check.linkSystemLibrary(slib);
-    }
     if (build_options.needLibC()) {
         exe.linkLibC();
         exe_check.linkLibC();
@@ -372,15 +341,6 @@ pub fn build(b: *Build) !void {
 
         b.installArtifact(lib);
 
-        for (includes.items) |include| {
-            lib.addIncludePath(b.path(include));
-        }
-        for (llibs.items) |llib| {
-            lib.addLibraryPath(b.path(llib));
-        }
-        for (sys_libs.items) |slib| {
-            lib.linkSystemLibrary(slib);
-        }
         if (build_options.needLibC()) {
             lib.linkLibC();
         }
@@ -392,17 +352,21 @@ pub fn build(b: *Build) !void {
 
         if (lib_pcre2) |pcre| {
             lib.linkLibrary(pcre);
+            exe.linkLibrary(pcre);
         }
 
         if (lib_mimalloc) |mimalloc| {
             lib.linkLibrary(mimalloc);
+            exe.linkLibrary(mimalloc);
             if (lib.root_module.resolved_target.?.result.os.tag == .windows) {
                 lib.linkSystemLibrary("bcrypt");
+                exe.linkSystemLibrary("bcrypt");
             }
         }
 
         if (lib_mir) |mir| {
             lib.linkLibrary(mir);
+            exe.linkLibrary(mir);
         }
 
         // So that JIT compiled function can reference buzz_api
@@ -473,15 +437,6 @@ pub fn build(b: *Build) !void {
             artifact.dest_dir = .{ .custom = "lib/buzz" };
 
             // No need to link anything when building for wasm since everything is static
-            for (includes.items) |include| {
-                std_lib.addIncludePath(b.path(include));
-            }
-            for (llibs.items) |llib| {
-                std_lib.addLibraryPath(b.path(llib));
-            }
-            for (sys_libs.items) |slib| {
-                std_lib.linkSystemLibrary(slib);
-            }
             if (build_options.needLibC()) {
                 std_lib.linkLibC();
             }
@@ -515,15 +470,6 @@ pub fn build(b: *Build) !void {
         .target = target,
         .optimize = build_mode,
     });
-    for (includes.items) |include| {
-        tests.addIncludePath(b.path(include));
-    }
-    for (llibs.items) |llib| {
-        tests.addLibraryPath(b.path(llib));
-    }
-    for (sys_libs.items) |slib| {
-        tests.linkSystemLibrary(slib);
-    }
     if (build_options.needLibC()) {
         tests.linkLibC();
     }
@@ -544,7 +490,7 @@ pub fn build(b: *Build) !void {
     const test_step = b.step("test", "Run all the tests");
     const run_tests = b.addRunArtifact(tests);
     run_tests.cwd = b.path(".");
-    run_tests.setEnvironmentVariable("BUZZ_PATH", try getBuzzPrefix(b));
+    run_tests.setEnvironmentVariable("BUZZ_PATH", envMap.get("BUZZ_PATH") orelse std.fs.path.dirname(b.exe_dir).?);
     run_tests.step.dependOn(install_step); // wait for libraries to be installed
     test_step.dependOn(&run_tests.step);
 }
@@ -757,5 +703,10 @@ pub fn buildMir(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.O
         },
     );
 
+    if (target.result.os.tag == .windows) {
+        lib.linkSystemLibrary("kernel32");
+        lib.linkSystemLibrary("psapi");
+    }
+
     return lib;
 }
diff --git a/src/Parser.zig b/src/Parser.zig
index 6412f0e0..68aa1ddb 100644
--- a/src/Parser.zig
+++ b/src/Parser.zig
@@ -145,9 +145,14 @@ pub fn defaultBuzzPrefix() []const u8 {
 }
 
 var _buzz_path_buffer: [4096]u8 = undefined;
-pub fn buzzPrefix() []const u8 {
+pub fn buzzPrefix(allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 {
     // FIXME: don't use std.posix directly
-    if (std.posix.getenv("BUZZ_PATH")) |buzz_path| {
+    if (std.process.getEnvVarOwned(allocator, "BUZZ_PATH") catch |err| env: {
+        switch (err) {
+            error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :env null,
+            else => return error.OutOfMemory,
+        }
+    }) |buzz_path| {
         return buzz_path;
     }
 
@@ -163,8 +168,9 @@ pub fn buzzPrefix() []const u8 {
 
 var _buzz_path_buffer2: [4096]u8 = undefined;
 /// the returned string can be used only until next call to this function
-pub fn buzzLibPath() ![]const u8 {
-    const path2 = buzzPrefix();
+pub fn buzzLibPath(allocator: std.mem.Allocator) ![]const u8 {
+    const path2 = try buzzPrefix(allocator);
+    defer allocator.free(path2);
     const sep = std.fs.path.sep_str;
     return std.fmt.bufPrint(
         &_buzz_path_buffer2,
@@ -7597,7 +7603,7 @@ fn searchPaths(self: *Self, file_name: []const u8) ![][]const u8 {
             self.gc.allocator,
             suffixed,
             "$",
-            try buzzLibPath(),
+            try buzzLibPath(self.gc.allocator),
         );
 
         try paths.append(prefixed);
@@ -7636,7 +7642,7 @@ fn searchLibPaths(self: *Self, file_name: []const u8) !std.ArrayList([]const u8)
             self.gc.allocator,
             suffixed,
             "$",
-            try buzzLibPath(),
+            try buzzLibPath(self.gc.allocator),
         );
 
         try paths.append(prefixed);
@@ -8137,7 +8143,7 @@ fn importLibSymbol(self: *Self, full_file_name: []const u8, symbol: []const u8)
         "External library `{s}` not found: {s}{s}\n",
         .{
             file_basename,
-            if (builtin.link_libc)
+            if (builtin.link_libc and builtin.os.tag != .windows)
                 std.mem.sliceTo(dlerror(), 0)
             else
                 "",
@@ -8348,7 +8354,7 @@ fn zdefStatement(self: *Self) Error!Ast.Node.Index {
                         "External library `{s}` not found: {s}{s}\n",
                         .{
                             lib_name_str,
-                            if (builtin.link_libc)
+                            if (builtin.link_libc and builtin.os.tag != .windows)
                                 std.mem.sliceTo(dlerror(), 0)
                             else
                                 "",
diff --git a/src/buzz_api.zig b/src/buzz_api.zig
index b3971e65..4fded863 100644
--- a/src/buzz_api.zig
+++ b/src/buzz_api.zig
@@ -1127,7 +1127,7 @@ export fn bz_rethrow(vm: *VM) void {
     if ((vm.currentFrame() == null or vm.currentFrame().?.in_native_call) and vm.current_fiber.try_context != null) {
         // FIXME: close try scope
 
-        if (builtin.os.tag == .macos or builtin.os.tag == .linux or builtin.os.windows) {
+        if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
             jmp._longjmp(&vm.current_fiber.try_context.?.env, 1);
         } else {
             jmp.longjmp(&vm.current_fiber.try_context.?.env, 1);
diff --git a/src/lib/buzz_io.zig b/src/lib/buzz_io.zig
index 76744176..cb54064d 100644
--- a/src/lib/buzz_io.zig
+++ b/src/lib/buzz_io.zig
@@ -1,29 +1,55 @@
 const std = @import("std");
 const api = @import("buzz_api.zig");
 const io = @import("io.zig");
+const builtin = @import("builtin");
 
 pub export fn getStdIn(ctx: *api.NativeCtx) c_int {
-    ctx.vm.bz_push(api.Value.fromInteger(@intCast(std.io.getStdIn().handle)));
+    ctx.vm.bz_push(
+        api.Value.fromInteger(
+            if (builtin.os.tag == .windows)
+                @intCast(@intFromPtr(std.io.getStdIn().handle))
+            else
+                @intCast(std.io.getStdIn().handle),
+        ),
+    );
 
     return 1;
 }
 
 pub export fn getStdOut(ctx: *api.NativeCtx) c_int {
-    ctx.vm.bz_push(api.Value.fromInteger(@intCast(std.io.getStdOut().handle)));
+    ctx.vm.bz_push(
+        api.Value.fromInteger(
+            if (builtin.os.tag == .windows)
+                @intCast(@intFromPtr(std.io.getStdOut().handle))
+            else
+                @intCast(std.io.getStdOut().handle),
+        ),
+    );
 
     return 1;
 }
 
 pub export fn getStdErr(ctx: *api.NativeCtx) c_int {
-    ctx.vm.bz_push(api.Value.fromInteger(@intCast(std.io.getStdErr().handle)));
+    ctx.vm.bz_push(
+        api.Value.fromInteger(
+            if (builtin.os.tag == .windows)
+                @intCast(@intFromPtr(std.io.getStdErr().handle))
+            else
+                @intCast(std.io.getStdErr().handle),
+        ),
+    );
 
     return 1;
 }
 
 pub export fn FileIsTTY(ctx: api.NativeCtx) c_int {
-    const handle: std.fs.File.Handle = @intCast(
-        ctx.vm.bz_peek(0).integer(),
-    );
+    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(),
+        );
 
     ctx.vm.bz_push(api.Value.fromBoolean(std.posix.isatty(handle)));
 
@@ -94,15 +120,26 @@ pub export fn FileOpen(ctx: *api.NativeCtx) c_int {
         },
     };
 
-    ctx.vm.bz_push(api.Value.fromInteger(@intCast(file.handle)));
+    ctx.vm.bz_push(
+        api.Value.fromInteger(
+            if (builtin.os.tag == .windows)
+                @intCast(@intFromPtr(file.handle))
+            else
+                @intCast(file.handle),
+        ),
+    );
 
     return 1;
 }
 
 pub export fn FileClose(ctx: *api.NativeCtx) c_int {
-    const handle: std.fs.File.Handle = @intCast(
-        ctx.vm.bz_peek(0).integer(),
-    );
+    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 = std.fs.File{ .handle = handle };
     file.close();
@@ -144,9 +181,13 @@ fn handleFileReadWriteError(ctx: *api.NativeCtx, err: anytype) void {
 }
 
 pub export fn FileReadAll(ctx: *api.NativeCtx) c_int {
-    const handle: std.fs.File.Handle = @intCast(
-        ctx.vm.bz_peek(1).integer(),
-    );
+    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 max_size = ctx.vm.bz_peek(0);
 
     const file: std.fs.File = std.fs.File{ .handle = handle };
@@ -212,9 +253,13 @@ fn handleFileReadLineError(ctx: *api.NativeCtx, err: anytype) void {
 }
 
 pub export fn FileReadLine(ctx: *api.NativeCtx) c_int {
-    const handle: std.fs.File.Handle = @intCast(
-        ctx.vm.bz_peek(1).integer(),
-    );
+    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 max_size = ctx.vm.bz_peek(0);
 
     const file: std.fs.File = std.fs.File{ .handle = handle };
@@ -284,9 +329,13 @@ pub export fn FileRead(ctx: *api.NativeCtx) c_int {
         return -1;
     }
 
-    const handle: std.fs.File.Handle = @intCast(
-        ctx.vm.bz_peek(1).integer(),
-    );
+    const handle: std.fs.File.Handle =
+        if (builtin.os.tag == .windows)
+        @ptrFromInt(@as(usize, @intCast(ctx.vm.bz_peek(1).integer())))
+    else
+        @intCast(
+            ctx.vm.bz_peek(1).integer(),
+        );
 
     const file: std.fs.File = std.fs.File{ .handle = handle };
     const reader = file.reader();
@@ -322,9 +371,13 @@ pub export fn FileRead(ctx: *api.NativeCtx) c_int {
 
 // extern fun File_write(int fd, [int] bytes) > void;
 pub export fn FileWrite(ctx: *api.NativeCtx) c_int {
-    const handle: std.fs.File.Handle = @intCast(
-        ctx.vm.bz_peek(1).integer(),
-    );
+    const handle: std.fs.File.Handle =
+        if (builtin.os.tag == .windows)
+        @ptrFromInt(@as(usize, @intCast(ctx.vm.bz_peek(1).integer())))
+    else
+        @intCast(
+            ctx.vm.bz_peek(1).integer(),
+        );
 
     const file: std.fs.File = std.fs.File{ .handle = handle };
 
diff --git a/src/lib/buzz_os.zig b/src/lib/buzz_os.zig
index fcaf868f..844396ef 100644
--- a/src/lib/buzz_os.zig
+++ b/src/lib/buzz_os.zig
@@ -31,7 +31,12 @@ pub export fn env(ctx: *api.NativeCtx) c_int {
     defer api.VM.allocator.free(key_slice);
 
     // FIXME: don't use std.posix directly
-    if (std.posix.getenv(key_slice)) |value| {
+    if (std.process.getEnvVarOwned(api.VM.allocator, "key_slice") catch |err| env: {
+        switch (err) {
+            error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :env null,
+            else => @panic("Out of memory"),
+        }
+    }) |value| {
         ctx.vm.bz_push(
             api.VM.bz_stringToValue(
                 ctx.vm,
@@ -40,6 +45,8 @@ pub export fn env(ctx: *api.NativeCtx) c_int {
             ),
         );
 
+        api.VM.allocator.free(value);
+
         return 1;
     }
 
@@ -121,7 +128,7 @@ pub export fn tmpFilename(ctx: *api.NativeCtx) c_int {
     return 1;
 }
 
-// If it was named `exit` it would be considered by zig as a callback when std.posix.exit is called
+// If it was named `exit` it would be considered by zig as a callback when std.process.exit is called
 pub export fn buzzExit(ctx: *api.NativeCtx) c_int {
     const exitCode: i32 = ctx.vm.bz_peek(0).integer();
 
@@ -347,7 +354,14 @@ pub export fn SocketConnect(ctx: *api.NativeCtx) c_int {
                 return -1;
             };
 
-            ctx.vm.bz_push(api.Value.fromInteger(@intCast(stream.handle)));
+            ctx.vm.bz_push(
+                api.Value.fromInteger(
+                    if (builtin.os.tag == .windows)
+                        @intCast(@intFromPtr(stream.handle))
+                    else
+                        @intCast(stream.handle),
+                ),
+            );
 
             return 1;
         },
@@ -358,6 +372,13 @@ pub export fn SocketConnect(ctx: *api.NativeCtx) c_int {
             return -1;
         },
         2 => {
+            // Unix socket not available on windows
+            if (builtin.os.tag == .windows) {
+                ctx.vm.pushError("errors.InvalidArgumentError", null);
+
+                return -1;
+            }
+
             const stream = std.net.connectUnixSocket(address) catch |err| {
                 handleConnectUnixError(ctx, err);
 
@@ -377,9 +398,12 @@ pub export fn SocketConnect(ctx: *api.NativeCtx) c_int {
 }
 
 pub export fn SocketClose(ctx: *api.NativeCtx) c_int {
-    const socket: std.posix.socket_t = @intCast(
-        ctx.vm.bz_peek(0).integer(),
-    );
+    const socket: std.posix.socket_t = if (builtin.os.tag == .windows)
+        @ptrFromInt(@as(usize, @intCast(ctx.vm.bz_peek(0).integer())))
+    else
+        @intCast(
+            ctx.vm.bz_peek(0).integer(),
+        );
 
     std.posix.shutdown(socket, .both) catch @panic("Could not stop socket");
 
@@ -421,9 +445,12 @@ pub export fn SocketRead(ctx: *api.NativeCtx) c_int {
         return -1;
     }
 
-    const handle: std.posix.socket_t = @intCast(
-        ctx.vm.bz_peek(1).integer(),
-    );
+    const handle: std.posix.socket_t = if (builtin.os.tag == .windows)
+        @ptrFromInt(@as(usize, @intCast(ctx.vm.bz_peek(1).integer())))
+    else
+        @intCast(
+            ctx.vm.bz_peek(1).integer(),
+        );
 
     const stream: std.net.Stream = .{ .handle = handle };
     const reader = stream.reader();
@@ -494,9 +521,12 @@ fn handleReadLineError(ctx: *api.NativeCtx, err: anytype) void {
 }
 
 pub export fn SocketReadLine(ctx: *api.NativeCtx) c_int {
-    const handle: std.posix.socket_t = @intCast(
-        ctx.vm.bz_peek(1).integer(),
-    );
+    const handle: std.posix.socket_t = if (builtin.os.tag == .windows)
+        @ptrFromInt(@as(usize, @intCast(ctx.vm.bz_peek(1).integer())))
+    else
+        @intCast(
+            ctx.vm.bz_peek(1).integer(),
+        );
     const max_size = ctx.vm.bz_peek(0);
 
     const stream: std.net.Stream = .{ .handle = handle };
@@ -537,9 +567,12 @@ pub export fn SocketReadLine(ctx: *api.NativeCtx) c_int {
 }
 
 pub export fn SocketReadAll(ctx: *api.NativeCtx) c_int {
-    const handle: std.posix.socket_t = @intCast(
-        ctx.vm.bz_peek(1).integer(),
-    );
+    const handle: std.posix.socket_t = if (builtin.os.tag == .windows)
+        @ptrFromInt(@as(usize, @intCast(ctx.vm.bz_peek(1).integer())))
+    else
+        @intCast(
+            ctx.vm.bz_peek(1).integer(),
+        );
     const max_size = ctx.vm.bz_peek(0);
 
     const stream: std.net.Stream = .{ .handle = handle };
@@ -581,9 +614,12 @@ pub export fn SocketReadAll(ctx: *api.NativeCtx) c_int {
 }
 
 pub export fn SocketWrite(ctx: *api.NativeCtx) c_int {
-    const handle: std.posix.socket_t = @intCast(
-        ctx.vm.bz_peek(1).integer(),
-    );
+    const handle: std.posix.socket_t = if (builtin.os.tag == .windows)
+        @ptrFromInt(@as(usize, @intCast(ctx.vm.bz_peek(1).integer())))
+    else
+        @intCast(
+            ctx.vm.bz_peek(1).integer(),
+        );
 
     const stream: std.net.Stream = .{ .handle = handle };
 
@@ -687,7 +723,14 @@ pub export fn SocketServerStart(ctx: *api.NativeCtx) c_int {
         return -1;
     };
 
-    ctx.vm.bz_push(api.Value.fromInteger(@intCast(server.stream.handle)));
+    ctx.vm.bz_push(
+        api.Value.fromInteger(
+            if (builtin.os.tag == .windows)
+                @intCast(@intFromPtr(server.stream.handle))
+            else
+                @intCast(server.stream.handle),
+        ),
+    );
 
     return 1;
 }
@@ -696,9 +739,12 @@ pub export fn SocketServerAccept(ctx: *api.NativeCtx) c_int {
     var server = std.net.Server{
         .listen_address = undefined, // FIXME: we lose this
         .stream = std.net.Stream{
-            .handle = @intCast(
-                ctx.vm.bz_peek(0).integer(),
-            ),
+            .handle = if (builtin.os.tag == .windows)
+                @ptrFromInt(@as(usize, @intCast(ctx.vm.bz_peek(0).integer())))
+            else
+                @intCast(
+                    ctx.vm.bz_peek(0).integer(),
+                ),
         },
     };
 
@@ -723,7 +769,14 @@ pub export fn SocketServerAccept(ctx: *api.NativeCtx) c_int {
         return -1;
     };
 
-    ctx.vm.bz_push(api.Value.fromInteger(@intCast(connection.stream.handle)));
+    ctx.vm.bz_push(
+        api.Value.fromInteger(
+            if (builtin.os.tag == .windows)
+                @intCast(@intFromPtr(connection.stream.handle))
+            else
+                @intCast(connection.stream.handle),
+        ),
+    );
 
     return 1;
 }
diff --git a/src/repl.zig b/src/repl.zig
index 7b4037cf..b2446484 100644
--- a/src/repl.zig
+++ b/src/repl.zig
@@ -31,7 +31,7 @@ const ObjForeignContainer = _obj.ObjForeignContainer;
 const Parser = @import("Parser.zig");
 const CompileError = Parser.CompileError;
 const JIT = @import("Jit.zig");
-const ln = @import("linenoise.zig");
+const ln = if (builtin.os.tag != .windows) @import("linenoise.zig") else void;
 const Value = @import("value.zig").Value;
 const disassembler = @import("disassembler.zig");
 const dumpStack = disassembler.dumpStack;
@@ -89,7 +89,9 @@ pub fn printBanner(out: anytype, full: bool) void {
 }
 
 pub fn repl(allocator: std.mem.Allocator) !void {
-    const colorterm = std.posix.getenv("COLORTERM");
+    var envMap = try std.process.getEnvMap(allocator);
+    defer envMap.deinit();
+    const colorterm = envMap.get("COLORTERM");
     const true_color = if (colorterm) |ct|
         std.mem.eql(u8, ct, "24bit") or std.mem.eql(u8, ct, "truecolor")
     else
@@ -144,11 +146,13 @@ pub fn repl(allocator: std.mem.Allocator) !void {
 
     try buzz_history_path.writer().print(
         "{s}/.buzz_history\x00",
-        .{std.posix.getenv("HOME") orelse "."},
+        .{envMap.get("HOME") orelse "."},
     );
 
-    _ = ln.linenoiseHistorySetMaxLen(100);
-    _ = ln.linenoiseHistoryLoad(@ptrCast(buzz_history_path.items.ptr));
+    if (builtin.os.tag != .windows) {
+        _ = ln.linenoiseHistorySetMaxLen(100);
+        _ = ln.linenoiseHistoryLoad(@ptrCast(buzz_history_path.items.ptr));
+    }
 
     // Import std and debug as commodity
     _ = runSource(
@@ -165,20 +169,37 @@ pub fn repl(allocator: std.mem.Allocator) !void {
     var previous_globals = try vm.globals.clone();
     var previous_type_registry = try gc.type_registry.registry.clone();
     var previous_input: ?[]u8 = null;
+    const stdin_buffer = if (builtin.os.tag == .windows)
+        gc.allocator.alloc(u8, 2048) catch @panic("Out of memory")
+    else
+        null;
 
     while (true) {
-        const read_source = ln.linenoise(
-            if (previous_input != null)
-                MULTILINE_PROMPT
-            else
-                PROMPT,
-        );
+        if (builtin.os.tag == .windows) {
+            std.io.getStdOut().writeAll(
+                if (previous_input != null)
+                    MULTILINE_PROMPT
+                else
+                    PROMPT,
+            ) catch @panic("Could not write to stdout");
+        }
+
+        const read_source = if (builtin.os.tag != .windows)
+            ln.linenoise(
+                if (previous_input != null)
+                    MULTILINE_PROMPT
+                else
+                    PROMPT,
+            )
+        else
+            std.io.getStdIn().reader()
+                .readUntilDelimiterOrEof(stdin_buffer, '\n') catch @panic("Could not read stdin");
 
         if (read_source == null) {
             std.process.exit(0);
         }
 
-        var source = std.mem.span(read_source.?);
+        var source = if (builtin.os.tag == .windows) read_source.? else std.mem.span(read_source.?);
         const original_source = source;
 
         if (source.len > 0) {
@@ -232,8 +253,10 @@ pub fn repl(allocator: std.mem.Allocator) !void {
             };
 
             if (parser.reporter.last_error == null and codegen.reporter.last_error == null) {
-                _ = ln.linenoiseHistoryAdd(source);
-                _ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.items.ptr));
+                if (builtin.os.tag != .windows) {
+                    _ = ln.linenoiseHistoryAdd(source);
+                    _ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.items.ptr));
+                }
                 // FIXME: why can't I deinit those?
                 // previous_parser_globals.deinit();
                 previous_parser_globals = try parser.globals.clone();
@@ -284,7 +307,7 @@ pub fn repl(allocator: std.mem.Allocator) !void {
                 if (parser.reporter.last_error == .unclosed) {
                     previous_input = gc.allocator.alloc(u8, source.len) catch @panic("Out of memory");
                     std.mem.copyForwards(u8, previous_input.?, source);
-                } else {
+                } else if (builtin.os.tag != .windows) {
                     _ = ln.linenoiseHistoryAdd(source);
                     _ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.items.ptr));
                 }