diff options
Diffstat (limited to 'src/javascript/jsc/javascript.zig')
-rw-r--r-- | src/javascript/jsc/javascript.zig | 1264 |
1 files changed, 1075 insertions, 189 deletions
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index aff505f22..1b71c0197 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -3,10 +3,16 @@ const std = @import("std"); usingnamespace @import("../../global.zig"); const Fs = @import("../../fs.zig"); const resolver = @import("../../resolver/resolver.zig"); -const ast = @import("../../ast/base.zig"); +const ast = @import("../../import_record.zig"); const NodeModuleBundle = @import("../../node_module_bundle.zig").NodeModuleBundle; const WTFString = @import("../../wtf_string_mutable.zig").WTFStringMutable; const logger = @import("../../logger.zig"); +const Api = @import("../../api/schema.zig").Api; +const options = @import("../../options.zig"); +const Bundler = @import("../../bundler.zig").ServeBundler; + +const hash_map = @import("../../hash_map.zig"); + pub const ExportJavaScript = union(Tag) { Module: *Module, String: *String, @@ -21,7 +27,7 @@ pub const ExportJavaScript = union(Tag) { pub const ResolveFunctionType = fn (ctx: anytype, source_dir: string, import_path: string, import_kind: ast.ImportKind) anyerror!resolver.Result; pub const TranspileFunctionType = fn (ctx: anytype, resolve_result: resolver.Result) anyerror![:0]const u8; - +const ExceptionValueRef = [*c]js.JSValueRef; const JSStringMapContext = struct { pub fn hash(self: @This(), s: js.JSStringRef) u64 { return hashString(s); @@ -35,61 +41,128 @@ pub fn JSStringMap(comptime V: type) type { return std.HashMap(js.JSStringRef, V, JSStringMapContext, 60); } +pub fn configureTransformOptionsForSpeedy(allocator: *std.mem.Allocator, _args: Api.TransformOptions) !Api.TransformOptions { + var args = _args; + + args.platform = Api.Platform.speedy; + args.serve = false; + args.write = false; + args.resolve = Api.ResolveMode.lazy; + args.generate_node_module_bundle = false; + + // We inline process.env.* at bundle time but process.env is a proxy object which will otherwise return undefined. + + var env_map = try getNodeEnvMap(allocator); + var env_count = env_map.count(); + + if (args.define) |def| { + for (def.keys) |key| { + env_count += @boolToInt((env_map.get(key) == null)); + } + } + var needs_node_env = env._map.get("NODE_ENV") == null; + + var needs_regenerate = args.define == null and env_count > 0; + if (args.define) |def| { + if (def.keys.len != env_count) { + needs_regenerate = true; + } + for (def.keys) |key| { + if (strings.eql(key, "process.env.NODE_ENV")) { + needs_node_env = false; + } + } + } + + if (needs_regenerate) { + var new_list = try allocator.alloc([]u8, env_count * 2 + @boolToInt(needs_node_env) * 2); + var new_map = Api.StringMap{ + .keys = new_list[0..env_count], + .values = new_list[env_count..], + }; + var iter = env_map.iterator(); + + var last: usize = 0; + while (iter.next()) |entry| { + new_map.keys[last] = entry.key_ptr.*; + var value = entry.value_ptr.*; + if (value.len == 0 or value.len[0] != '"' or value.len[value.len - 1] != '"') { + value = try std.fmt.allocPrint(allocator, "\"{s}\"", .{value}); + } + new_map.values[last] = value; + last += 1; + } + + if (args.define) |def| { + var from_env = new_map.keys[0..last]; + + for (def.keys) |pre, i| { + if (env_map.get(pre) != null) { + for (from_env) |key, i| { + if (srings.eql(key, pre)) { + new_map.values[i] = def.values[i]; + } + } + } else { + new_map.keys[last] = pre; + new_map.values[last] = def.values[i]; + last += 1; + } + } + } + + if (needs_node_env) { + new_map.keys[last] = options.DefaultUserDefines.NodeEnv.Key; + new_map.values[last] = options.DefaultUserDefines.NodeEnv.Value; + } + } + + return args; +} + // If you read JavascriptCore/API/JSVirtualMachine.mm - https://github.com/WebKit/WebKit/blob/acff93fb303baa670c055cb24c2bad08691a01a0/Source/JavaScriptCore/API/JSVirtualMachine.mm#L101 // We can see that it's sort of like std.mem.Allocator but for JSGlobalContextRef, to support Automatic Reference Counting // Its unavailable on Linux pub const VirtualMachine = struct { - const RequireCacheType = std.AutoHashMap(u64, Module); - ctx: js.JSGlobalContextRef, + const RequireCacheType = std.AutoHashMap(u32, Module); + root: js.JSGlobalContextRef, + ctx: js.JSGlobalContextRef = undefined, group: js.JSContextGroupRef, allocator: *std.mem.Allocator, - transpile_ctx: *c_void = undefined, - transpile: *TranspileFunctionType = undefined, require_cache: RequireCacheType, - resolve_: *ResolveFunctionType = undefined, - resolve_ctx: *c_void = undefined, node_modules: ?*NodeModuleBundle = null, node_modules_ref: js.JSObjectRef = null, - global: GlobalObject, + global: *GlobalObject, + bundler: Bundler, + log: *logger.Log, - pub fn init(allocator: *std.mem.Allocator) !*VirtualMachine { + pub fn init( + allocator: *std.mem.Allocator, + _args: Api.TransformOptions, + ) !*VirtualMachine { var group = js.JSContextGroupCreate(); var ctx = js.JSGlobalContextCreateInGroup(group, null); - - Properties.init(); + var log = try allocator.create(logger.Log); var vm = try allocator.create(VirtualMachine); + var global = try allocator.create(GlobalObject); vm.* = .{ .allocator = allocator, + .bundler = try Bundler.init(allocator, log, try configureTransformOptionsForSpeedy(allocator, _args)), .group = group, - .ctx = ctx, + .root = ctx, .require_cache = RequireCacheType.init(allocator), - .global = undefined, + .global = global, }; - vm.global = GlobalObject{ .vm = undefined }; - return vm; - } - - pub fn setupGlobals(this: *VirtualMachine) void {} + Properties.init(); + vm.bundler.configureLinker(); - pub fn resolve( - this: *VirtualMachine, - from: *const Module, - to: string, - ) !js.JSValueRef { - return (try this.resolve_(this.resolve_ctx, from.path.dir, to, .require)) orelse return error.ModuleNotFound; - } - threadlocal var require_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + global.* = GlobalObject{ .vm = vm }; + try vm.global.boot(); + vm.ctx = vm.global.ctx; - // Assume bundle is already transpiled, so we skip transpiling in here. - pub fn requireFromBundle(this: *VirtualMachine, import_path: string) !js.JSValueRef {} + Module.boot(vm); - pub fn require( - this: *VirtualMachine, - module: *const Module, - path_value: js.JSValueRef, - ) !js.JSValueRef { - var import_path = To.Zig.str(path_value, &require_buf); - var resolve_result = try this.resolve(module, import_path); + return vm; } threadlocal var eval_buf: WTFString = undefined; @@ -98,61 +171,46 @@ pub const VirtualMachine = struct { pub fn evalUtf8( this: *VirtualMachine, path_text: string, - contents: string, + contents: [:0]const u8, ) !js.JSValueRef { - if (!eval_buf_loaded) { - eval_buf = try WTFString.init(this.allocator, contents.len + path.text.len + 2); - } else { - eval_buf.reset(); - try eval_buf.growIfNeeded(contents.len + path.text.len + 2); - } - try eval_buf.append(contents); - eval_buf.list.append(eval_buf.allocator, 0); - var script_len = eval_buf.list.items.len; - if (path_text.len > 0) { - try eval_buf.append(path_text); - eval_buf.list.append(eval_buf.allocator, 0); - } + // if (!eval_buf_loaded) { + // eval_buf = try WTFString.init(this.allocator, contents.len + path_text.len); + // } else { + // eval_buf.reset(); + // try eval_buf.growIfNeeded(contents.len + path_text.len); + // } - var buf = eval_buf.toOwnedSliceLeaky(); - var script = js.JSStringCreateWithCharactersNoCopy(buf[0..script_len].ptr, script_len - 1); - var sourceURL: js.JSStringRef = null; + // try eval_buf.append(contents); + // var script_len = eval_buf.list.items.len; + // if (path_text.len > 0) { + // try eval_buf.append(path_text); + // } - if (path_text.len > 0) { - sourceURL = js.JSStringCreateWithCharactersNoCopy( - buf[script_len + 1 ..].ptr, - buf[script_len + 1 ..].len - 1, - ); - } + // var buf = eval_buf.toOwnedSliceLeaky(); + // var script = js.JSStringCreateWithCharactersNoCopy(@as([*c]js.JSChar, buf[0..script_len].ptr), script_len); + // script = js.JSStringRetain(script); + // var sourceURL: js.JSStringRef = null; - return js.JSEvaluateScript( + // if (path_text.len > 0) { + // sourceURL = js.JSStringCreateWithCharactersNoCopy( + // @as([*c]js.JSChar, buf[script_len + 1 ..].ptr), + // buf[script_len + 1 ..].len, + // ); + // sourceURL = js.JSStringRetain(sourceURL); + // } + var exception: js.JSObjectRef = null; + var val = js.JSEvaluateScript( this.ctx, - script, - js.JSValueMakeUndefined(this.ctx), - sourceURL, - 0, + js.JSStringCreateWithUTF8CString(contents.ptr), + this.global.ref, null, + 0, + &exception, ); - } -}; - -pub const BundleLoader = struct { - bundle: *const NodeModuleBundle, - allocator: *std.mem.Allocator, - vm: *VirtualMachine, - loaded: bool = false, - ref: js.JSObjectRef = null, - pub fn init(bundle: *const NodeModuleBundle, allocator: *std.mem.Allocator, vm: *VirtualMachine) BundleLoader { - return BundleLoader{ - .bundle = bundle, - .allocator = allocator, - .vm = vm, - }; + return exception; } - - pub fn loadBundle(this: *BundleLoader) !void {} }; pub const To = struct { @@ -171,12 +229,15 @@ pub const To = struct { ctx: js.JSContextRef, function: js.JSObjectRef, thisObject: js.JSObjectRef, - arguments: []js.JSValueRef, - exception: [*c]js.JSValueRef, + arguments: []const js.JSValueRef, + exception: js.JSValueRef, ) js.JSValueRef, ) js.JSObjectRef { - var function = js.JSObjectMakeFunctionWithCallback(ctx, name, Callback(ZigContextType, callback)); - js.JSObjectSetPrivate(function, @ptrCast(*c_void, zig)); + var function = js.JSObjectMakeFunctionWithCallback(ctx, name, Callback(ZigContextType, callback).rfn); + js.JSObjectSetPrivate( + function, + @ptrCast(*c_void, @alignCast(@alignOf(*c_void), global)), + ); return function; } @@ -187,30 +248,37 @@ pub const To = struct { ctx: js.JSContextRef, function: js.JSObjectRef, thisObject: js.JSObjectRef, - arguments: []js.JSValueRef, - exception: [*c]js.JSValueRef, + arguments: []const js.JSValueRef, + exception: js.JSValueRef, ) js.JSValueRef, ) type { return struct { - pub fn run( + pub fn rfn( ctx: js.JSContextRef, function: js.JSObjectRef, thisObject: js.JSObjectRef, argumentCount: usize, arguments: [*c]const js.JSValueRef, - exception: [*c]js.JSValueRef, + exception: ExceptionValueRef, ) callconv(.C) js.JSValueRef { - var object_ptr = js.JSObjectGetPrivate(function) orelse { + var object_ptr_ = js.JSObjectGetPrivate(function); + if (object_ptr_ == null) { + object_ptr_ = js.JSObjectGetPrivate(thisObject); + } + + if (object_ptr_ == null) { return js.JSValueMakeUndefined(ctx); - }; + } + + var object_ptr = object_ptr_.?; return ctxfn( - @intToPtr(ZigContextType, object_ptr), + @ptrCast(*ZigContextType, @alignCast(@alignOf(*ZigContextType), object_ptr)), ctx, function, thisObject, - arguments[0..argumentCount], - exception, + if (arguments) |args| args[0..argumentCount] else &[_]js.JSValueRef{}, + null, ); } }; @@ -237,6 +305,7 @@ pub const Properties = struct { pub const exports = "exports"; pub const log = "log"; pub const debug = "debug"; + pub const name = "name"; pub const info = "info"; pub const error_ = "error"; pub const warn = "warn"; @@ -246,38 +315,45 @@ pub const Properties = struct { }; pub const UTF16 = struct { - pub const module = std.unicode.utf8ToUtf16LeStringLiteral("module"); - pub const globalThis = std.unicode.utf8ToUtf16LeStringLiteral("globalThis"); - pub const exports = std.unicode.utf8ToUtf16LeStringLiteral("exports"); - pub const log = std.unicode.utf8ToUtf16LeStringLiteral("log"); - pub const debug = std.unicode.utf8ToUtf16LeStringLiteral("debug"); - pub const info = std.unicode.utf8ToUtf16LeStringLiteral("info"); - pub const error_ = std.unicode.utf8ToUtf16LeStringLiteral("error"); - pub const warn = std.unicode.utf8ToUtf16LeStringLiteral("warn"); - pub const console = std.unicode.utf8ToUtf16LeStringLiteral("console"); - pub const require = std.unicode.utf8ToUtf16LeStringLiteral("require"); - pub const description = std.unicode.utf8ToUtf16LeStringLiteral("description"); + pub const module: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("module"); + pub const globalThis: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("globalThis"); + pub const exports: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("exports"); + pub const log: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("log"); + pub const debug: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("debug"); + pub const info: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("info"); + pub const error_: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("error"); + pub const warn: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("warn"); + pub const console: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("console"); + pub const require: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("require"); + pub const description: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("description"); + pub const name: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral("name"); }; pub const Refs = struct { - pub var module: js.JSStringRef = undefined; - pub var globalThis: js.JSStringRef = undefined; - pub var exports: js.JSStringRef = undefined; - pub var log: js.JSStringRef = undefined; - pub var debug: js.JSStringRef = undefined; - pub var info: js.JSStringRef = undefined; - pub var error_: js.JSStringRef = undefined; - pub var warn: js.JSStringRef = undefined; - pub var console: js.JSStringRef = undefined; - pub var require: js.JSStringRef = undefined; - pub var description: js.JSStringRef = undefined; + pub var module: js.JSStringRef = null; + pub var globalThis: js.JSStringRef = null; + pub var exports: js.JSStringRef = null; + pub var log: js.JSStringRef = null; + pub var debug: js.JSStringRef = null; + pub var info: js.JSStringRef = null; + pub var error_: js.JSStringRef = null; + pub var warn: js.JSStringRef = null; + pub var console: js.JSStringRef = null; + pub var require: js.JSStringRef = null; + pub var description: js.JSStringRef = null; + pub var name: js.JSStringRef = null; }; pub fn init() void { inline for (std.meta.fieldNames(UTF8)) |name| { - @field(Refs, name) = js.JSStringCreateWithCharactersNoCopy( - &@field(StringStore.UTF16, name), - @field(StringStore.UTF16, name).len, + @field(Refs, name) = js.JSStringRetain( + js.JSStringCreateWithCharactersNoCopy( + @field(StringStore.UTF16, name).ptr, + @field(StringStore.UTF16, name).len - 1, + ), + ); + std.debug.assert( + js.JSStringIsEqualToUTF8CString(@field(Refs, name), @field(UTF8, name)[0.. :0]), ); } } @@ -291,7 +367,7 @@ pub const String = struct { ref: js.JSStringRef, len: usize, - pub fn chars(this: *const String) []js.JSChar { + pub fn chars(this: *const String) []const js.JSChar { return js.JSStringGetCharactersPtr(this.ref)[0..js.JSStringGetLength(this.ref)]; } @@ -300,30 +376,571 @@ pub const String = struct { } }; +const GetterFn = fn ( + this: anytype, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: [*c]JSValueRef, +) js.JSValueRef; +const SetterFn = fn ( + this: anytype, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + value: js.JSValueRef, + exception: [*c]JSValueRef, +) js.JSValueRef; + +const JSProp = struct { + get: ?GetterFn = null, + set: ?SetterFn = null, + ro: bool = false, +}; + pub const Module = struct { - path: Fs.PathName, + path: Fs.Path, - require: RequireObject, - hash: u64, + hashid: u32, ref: js.JSObjectRef, + id: js.JSValueRef = null, + exports: js.JSValueRef = null, + + global_ref: js.JSValueRef = null, + + vm: *VirtualMachine, + + pub var module_class: js.JSClassRef = undefined; + pub var module_global_class: js.JSClassRef = undefined; + pub var module_global_class_def: js.JSClassDefinition = undefined; + pub var module_class_def: js.JSClassDefinition = undefined; + + pub const NodeModuleList = struct { + tempbuf: []u8, + property_names: [*]u8, + property_getters: []js.JSObjectRef, + module_property_map: ModuleIDMap, + node_module_global_class: js.JSClassRef, + node_module_global_class_def: js.JSClassDefinition, + bundle_ctx: js.JSGlobalContextRef, + vm: *VirtualMachine, + + pub const Instance = struct { + module: Module, + ref: js.JSObjectRef, + ctx: js.JSGlobalContextRef, + node_module_list: *NodeModuleList, + + const NodeModuleInstanceClassName = "NodeModule"; + const ModuleLoadStaticFunctionName = "$$m"; + + var instance_class_definition: js.JSClassDefinition = undefined; + + var instance_class_ref: js.JSClassRef = undefined; + var instance_class_loaded = false; + + threadlocal var source_code_buffer: MutableString = undefined; + threadlocal var source_code_buffer_loaded = false; + pub fn evalBundledModule( + allocator: *std.mem.Allocator, + vm: *VirtualMachine, + node_module_list: *NodeModuleList, + ctx: js.JSContextRef, + id: u32, + ) !Instance { + const bundled_module = &vm.node_modules.?.bundle.modules[id]; + if (!source_code_buffer_loaded) { + source_code_buffer = try MutableString.init(allocator, bundled_module.code.length + 1); + source_code_buffer_loaded = true; + } else { + source_code_buffer.reset(); + source_code_buffer.growIfNeeded(bundled_module.code.length + 1); + } + + var node_module_file = std.fs.File{ .handle = vm.node_modules.?.fd }; + const read = try node_module_file.pread(source_code_buffer.list.items, bundled_module.code.offset); + source_code_buffer.list.items[read] = 0; + var buf = source_code_buffer.list.items[0..read :0]; + + const bundled_package = &vm.node_modules.?.bundle.packages[bundled_module.package_id]; + // We want linear because we expect it to virtually always be at 0 + // However, out of caution we check. + var start_at: usize = std.mem.indexOfPosLinear(u8, buf, 0, "export var $") orelse return error.FailedCorruptNodeModuleMissingExport; + start_at += "export var $".len; + start_at = std.mem.indexOfPosLinear(u8, "$$m(", start_at, buf) orelse return error.FailedCorruptNodeModuleMissingModuleWrapper; + var source_buf = source_code_buffer.list.items[start_at..read :0]; + var source_string = js.JSStringCreateWithUTF8CString(source_buf.ptr); + defer js.JSStringRelease(source_string); + var source_url_buf = try std.fmt.allocPrintZ( + allocator, + "node_modules.jsb/{s}/{s}", + .{ + vm.node_modules.?.str(bundled_package.path), + vm.node_modules.?.str(bundled_module.path), + }, + ); + defer allocator.free(source_url_buf); + var source_url = js.JSStringCreateWithUTF8CString(source_url_buf); + defer js.JSStringRelease(source_url); + var exception: js.JSValueRef = null; + var return_value = js.JSEvaluateScript(node_module_list.bundle_ctx, source_string, null, source_url, 1, &exception); + if (exception != null) { + var message = js.JSValueToStringCopy(node_module_list.bundle_ctx, exception.?, null); + defer js.JSStringRelease(message); + var message_str_size = js.JSStringGetMaximumUTF8CStringSize(message); + var message_str_buf = try allocator.alloc(u8, message_str_size); + defer allocator.free(message_str_buf); + var message_str_read = js.JSStringGetUTF8CString(message, message_str_buf, message_str_size); + defer Output.flush(); + Output.prettyErrorln("<r>{s}\n--<r><red>error<r> loading <cyan>\"{s}/{s}\"<r>--", .{ + message_str_buf[0..message_str_read], + vm.node_modules.?.str(bundled_package.name), + vm.node_modules.?.str(bundled_module.path), + }); + return error.FailedException; + } + + + + + + + + if (!js.JSValueIsObject(node_module_list.bundle_ctx, return_value) or js.JSObjectGetPrivate(return_value) == null) { + Output.prettyErrorln( + \\\<r><red>Failed<r> to load <cyan>"{s}/{s}"<r>.\n + \\This is an internal error. Every module in node_modules.jsb is expected to call a function + \\initializing the module on load, and that function is supposed to return an object. + \\It didn't return an object. + \\That doesn't mean there was a syntax error (syntax errors occur earlier). + \\If you weren't poking around in node_modules.jsb (or messing with object prototypes), + \\please file an issue and include your node_modules.jsb. + , .{ + vm.node_modules.?.str(bundled_package.name), + vm.node_modules.?.str(bundled_module.path), + }); + Output.flush(); + return error.FailedReturnValueInvalid; + } + + + + + + + + + } + }; + + pub const RequireBundledModule = struct { + id: u32, + list: *NodeModuleList, + }; + + // key: hash of module.path + // value: index of module + const ModuleIDMap = hash_map.AutoHashMap(u64, u32); + + pub fn initializeGlobal(ctx: JSContextRef, obj: JSObjectRef) callconv(.C) void {} + + pub fn getRequireFromBundleProperty(ctx: js.JSContextRef, thisObject: js.JSObjectRef, prop: js.JSStringRef, exception: [*c]js.JSValueRef) js.JSValueRef { + var thisPtr = js.JSObjectGetPrivate(thisObject); + if (thisPtr == null) return null; + + var this = @ptrCast( + *NodeModuleList, + @alignCast( + @alignOf( + *NodeModuleList, + ), + thisPtr.?, + ), + ); + + const size = js.JSStringGetUTF8CString(prop, this.tempbuf.ptr, this.tempbuf.len); + const key = std.hash.Wyhash.hash(0, this.tempbuf[0..size]); + const id = this.module_property_map.get(id) orelse return null; + + if (this.property_getters[id] == null) { + var require_bundled = try this.vm.allocator.create(RequireBundledModule); + require_bundled.* = RequireBundledModule{ .id = id, .list = this }; + this.property_getters[id] = To.JS.functionWithCallback( + RequireBundledModule, + require_bundled, + prop, + ctx, + requireBundledModule, + ); + } + + return this.property_getters[id]; + } + + // this is what $aosdi123() inside a node_modules.jsb calls + pub fn requireBundledModule( + obj: *const RequireBundledModule, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: [*c]js.JSValueRef, + ) js.JSValueRef { + const bundle = &obj.list.vm.node_modules.?.bundle; + const bundled_module = &bundle.modules[obj.id]; + const bundled_pkg = &bundle.packages[bundled_module.package_id]; + + const result = loadBundledModuleById(obj.list.vm, ctx, obj.id) catch |err| { + Output.prettyErrorln("<r><red>RequireError<r>: <b>{s}<r> in \"<cyan>{s}/{s}<r>\"", .{ + @errorName(err), + obj.list.vm.node_modules.?.str(bundled_pkg.name), + obj.list.vm.node_modules.?.str(bundled_module.path), + }); + var message = std.fmt.allocPrintZ(obj.list.vm.allocator, "RequireError: {s} in \"{s}/{s}\"", .{ + @errorName(err), + obj.list.vm.node_modules.?.str(bundled_pkg.name), + obj.list.vm.node_modules.?.str(bundled_module.path), + }) catch unreachable; + defer Output.flush(); + defer obj.list.vm.allocator.free(message); + var args = obj.list.vm.allocator.alloc(js.JSStringRef, 1) catch unreachable; + args[0] = js.JSStringCreateWithUTF8CString(message.ptr); + exception.* = js.JSObjectMakeError(ctx, 1, args, null); + return js.JSValueMakeUndefined(ctx); + }; + + return result.Module.internalGetExports(); + } + + pub fn init(vm: *VirtualMachine, ctx: js.JSContextRef, bundle: *const NodeModuleBundle) !NodeModuleList { + var size: usize = 0; + var longest_size: usize = 0; + for (bundle.bundle.modules) |module, i| { + var hasher = std.hash.Wyhash.init(0); + hasher.update(bundle.str(module.path)); + hasher.update( + std.mem.asBytes( + &bundle.bundle.packages[module.package_id].hash, + ), + ); + // Add one for null-terminated string offset + const this_size = std.fmt.count( + "${x}", + .{ + @truncate( + u32, + hasher.final(), + ), + }, + ) + 1; + size += this_size; + longest_size = std.math.max(this_size, longest_size); + } + var static_properties = try vm.allocator.alloc(js.JSStaticValue, bundle.bundle.modules.len); + var utf8 = try vm.allocator.alloc(u8, size + longest_size); + + var tempbuf = utf8[size..]; + + var names_buf = utf8[0..size]; + var module_property_map = ModuleIDMap.init(vm.allocator); + try module_property_map.ensureCapacity(bundle.bundle.modules.len); + + for (bundle.bundle.modules) |module, i| { + var hasher = std.hash.Wyhash.init(0); + hasher.update(bundle.str(module.path)); + hasher.update( + std.mem.asBytes( + &bundle.bundle.packages[module.package_id].hash, + ), + ); + + const hash = @truncate( + u32, + hasher.final(), + ); + + // The variable name is the hash of the module path + var name = std.fmt.bufPrint(names_buf, "${x}", .{hash}) catch unreachable; + + // But we don't store that for the hash map. Instead, we store the hash of name. + // This lets us avoid storing pointers to the name in the hash table, so if we free it later + // or something it won't cause issues. + hasher = std.hash.Wyhash.init(0); + hasher.update(name); + var property_key = hasher.final(); + + name.ptr[name.len] = 0; + const name_len = name.len; + + static_properties[i] = js.JSStaticValue{ + .name = name[0.. :0], + .getProperty = getRequireFromBundleProperty, + .attributes = .kJSPropertyAttributeReadOnly, + }; + names_buf = names_buf[name_len..]; + module_property_map.putAssumeCapacityNoClobberWithHash(property_key, property_key, @truncate(u32, i)); + } + + var node_module_global_class_def = js.kJSClassDefinitionEmpty; + node_module_global_class_def.staticValues = static_properties; + node_module_global_class_def.className = node_module_global_class_name[0.. :0]; + node_module_global_class_def.parentClass = vm.global.global_class; + + var property_getters = try vm.allocator.alloc(js.JSObjectRef, bundle.bundle.modules.len); + std.mem.set(js.JSObjectRef, property_getters, null); + + return NodeModuleList{ + .module_property_map = module_property_map, + .node_module_global_class_def = node_module_global_class_def, + .vm = vm, + .tempbuf = tempbuf, + .property_getters = property_getters, + .node_module_global_class = js.JSClassCreate(node_module_global_class_def), + }; + } + }; + pub const node_module_global_class_name = "NodeModuleGlobal"; + pub const ModuleGlobalClass = NewClass( + Module, + "ModuleGlobal", + .{ .@"require" = require }, + .{}, + false, + false, + ); + + const JSExport = NewClass( + Module, + "Module", + .{ .@"require" = require }, + .{ + .@"id" = JSProp{ + .get = getId, + .ro = true, + }, + .@"exports" = JSProp{ + .get = getExports, + .set = setExports, + .ro = false, + }, + }, + false, + false, + ); + + pub fn boot(vm: *VirtualMachine) void { + module_global_class_def = ModuleGlobalClass.define(vm.root); + module_global_class_def.parentClass = vm.global.global_class; + module_global_class = js.JSClassRetain(js.JSClassCreate(&module_global_class_def)); + module_class_def = JSExport.define(vm.root); + module_class = js.JSClassRetain(js.JSClassCreate(&module_class_def)); + } + + pub const LoadResult = union(Tag) { + Module: *Module, + Path: Fs.Path, + + pub const Tag = enum { + Module, + Path, + }; + }; + + pub fn loadBundledModuleById(vm: *VirtualMachine, ctx: js.JSContextRef, id: u32) !LoadResult {} + + pub fn loadFromResolveResult(vm: *VirtualMachine, ctx: js.JSContextRef, resolved: resolver.Result) !LoadResult { + var hash = @truncate(u32, std.hash.Wyhash.hash(0, resolved.path_pair.primary.text)); + if (vm.require_cache.getPtr(hash)) |mod| { + return .{ .Module = mod }; + } + + const path = resolved.path_pair.primary; + const loader = vm.bundler.options.loaders.get(path.name.ext) orelse .file; + switch (loader) { + .js, + .jsx, + .ts, + .tsx, + .json, + => { + if (resolved.package_json) |package_json| { + if (package_json.hash > 0) { + if (vm.node_modules) |node_modules| { + if (node_modules.getPackageIDByHash(package_json.hash)) |package_id| { + const package_relative_path = vm.bundler.fs.relative( + package_json.source.path.name.dirWithTrailingSlash(), + path.text, + ); + + if (node_modules.findModuleInPackage( + &node_modules.bundle.packages[package_id], + package_relative_path, + )) |found_module| {} + } + } + } + } + + vm.bundler.resetStore(); + var result = vm.bundler.parse( + vm.bundler.allocator, + path, + loader, + result.dirname_fd, + null, + null, + ) orelse { + return error.ParseError; + }; + }, + + // Replace imports to non-executables with paths to those files. + // In SSR or on web, these become URLs. + // Otherwise, absolute file paths. + else => { + switch (vm.bundler.options.import_path_format) { + .absolute_path => { + return LoadResult{ .Path = path }; + }, + .absolute_url => { + var fs = vm.bundler.fs; + + var base = fs.relativeTo(path.text); + if (strings.lastIndexOfChar(base, '.')) |dot| { + base = base[0..dot]; + } + + var dirname = std.fs.path.dirname(base) orelse ""; + + var basename = std.fs.path.basename(base); + + const needs_slash = dirname.len > 0 and dirname[dirname.len - 1] != '/'; + + if (needs_slash) { + const absolute_url = try std.fmt.allocPrint( + vm.allocator, + "{s}{s}/{s}{s}", + .{ + vm.bundler.options.public_url, + dirname, + basename, + path.name.ext, + }, + ); + + return LoadResult{ + .Path = Fs.Path.initWithPretty(absolute_url, absolute_url), + }; + } else { + const absolute_url = try std.fmt.allocPrint( + vm.allocator, + "{s}{s}{s}{s}", + .{ + vm.bundler.options.public_url, + dirname, + basename, + path.name.ext, + }, + ); + + return LoadResult{ + .Path = Fs.Path.initWithPretty(absolute_url, absolute_url), + }; + } + }, + } + }, + } + } + + pub fn getId( + this: *Module, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: [*c]js.JSValueRef, + ) js.JSValueRef { + if (this.id == null) { + this.id = js.JSStringCreateWithUTF8CString(this.path.text[0.. :0]); + } + + return this.id; + } + + pub fn getExports( + this: *Module, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: [*c]js.JSValueRef, + ) js.JSValueRef { + return this.internalGetExports(); + } + + pub fn internalGetExports(this: *Module) js.JSValueRef { + if (this.exports == null) { + this.exports = js.JSObjectMake(ctx, null, null); + } + + return this.exports; + } + + pub fn setExports( + this: *Module, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + value: js.JSValueRef, + exception: [*c]JSValueRef, + ) JSValueRef { + if (this.exports != null) { + if (js.JSValueIsString(this.exports.?)) { + js.JSStringRelease(this.exports.?); + } + } + + this.exports = value; + } + pub const RequireObject = struct {}; pub fn require( this: *Module, - arguments: [*c]const js.JSValueRef, - arguments_len: usize, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + arguments: []js.JSValueRef, exception: [*c]JSValueRef, - ) js.JSValueRef {} + ) js.JSValueRef { + if (arguments.len == 0 or arguments.len > 1 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringGetLength(arguments[0]) == 0) { + defer Output.flush(); + if (arguments.len == 0) { + Output.prettyErrorln("<r><red>error<r>: <s><b>require<r> needs a string, e.g. require(\"left-pad\")", .{}); + } else if (arguments.len > 1) { + Output.prettyErrorln("<r><red>error<r>: <s><b>require<r> only accepts one argument and it must be a string, e.g. require(\"left-pad\")", .{}); + } else if (!js.JSValueIsString(ctx, arguments[0])) { + Output.prettyErrorln("<r><red>error<r>: <s><b>require<r> only supports a string, e.g. require(\"left-pad\")", .{}); + } else { + Output.prettyErrorln("<r><red>error<r>: <s><b>require(\"\")<r> string cannot be empty.", .{}); + } + exception.* = js.JSObjectMakeError(ctx, 0, null, null); + return null; + } + } }; pub const GlobalObject = struct { ref: js.JSObjectRef = undefined, vm: *VirtualMachine, - console: js.JSClassRef = undefined, + ctx: js.JSGlobalContextRef = undefined, + console_class: js.JSClassRef = undefined, + console: js.JSObjectRef = undefined, console_definition: js.JSClassDefinition = undefined, + global_class_def: js.JSClassDefinition = undefined, + global_class: js.JSClassRef = undefined, + root_obj: js.JSObjectRef = undefined, - pub const ConsoleClass = NewSingletonClass( + pub const ConsoleClass = NewClass( GlobalObject, "Console", .{ @@ -335,15 +952,53 @@ pub const GlobalObject = struct { .@"error" = stderr, .@"warn" = stderr, }, + .{}, // people sometimes modify console.log, let them. false, + true, + ); + + pub const GlobalClass = NewClass( + GlobalObject, + "Global", + .{}, + .{ + .@"console" = getConsole, + }, + false, + false, ); - pub fn load(global: *GlobalObject) !void { - global.console_definition = ConsoleClass.define(global, global.vm.ctx); - global.console = js.JSClassCreate(&global.console_definition); + pub fn getConsole(global: *GlobalObject, ctx: js.JSContextRef, obj: js.JSObjectRef, exception: ExceptionValueRef) js.JSValueRef { + return global.console; } + pub fn onMissingProperty(global: *GlobalObject, ctx: js.JSContextRef, obj: js.JSObjectRef, prop: js.JSStringRef, exception: ExceptionValueRef) js.JSValueRef { + if (js.JSObjectHasProperty(ctx, global.root_obj, prop)) { + return js.JSObjectGetProperty(ctx, global.root_obj, prop, exception); + } else { + return js.JSValueMakeUndefined(ctx); + } + } + + pub fn boot(global: *GlobalObject) !void { + var private: ?*c_void = global; + global.root_obj = js.JSContextGetGlobalObject(global.vm.root); + + global.console_definition = ConsoleClass.define(global.vm.root); + global.console_class = js.JSClassRetain(js.JSClassCreate(&global.console_definition)); + global.console = js.JSObjectMake(global.vm.root, global.console_class, private); + + global.global_class_def = GlobalClass.define(global.vm.root); + global.global_class = js.JSClassRetain(js.JSClassCreate(&global.global_class_def)); + + global.ctx = js.JSGlobalContextRetain(js.JSGlobalContextCreateInGroup(global.vm.group, global.global_class)); + + std.debug.assert(js.JSObjectSetPrivate(js.JSContextGetGlobalObject(global.ctx), private)); + global.ref = js.JSContextGetGlobalObject(global.ctx); + } + + threadlocal var printer_buf: [4092]u8 = undefined; fn valuePrinter(comptime ValueType: js.JSType, ctx: js.JSContextRef, arg: js.JSValueRef, writer: anytype) !void { switch (ValueType) { .kJSTypeUndefined => { @@ -360,29 +1015,14 @@ pub const GlobalObject = struct { } }, .kJSTypeNumber => { - try writer.print("{d}", js.JSValueToNumber(ctx, arg, null)); + try writer.print( + "{d}", + .{js.JSValueToNumber(ctx, arg, null)}, + ); }, .kJSTypeString => { - var str = String{ .ref = @as(js.JSStringRef, arg) }; - var chars = str.chars(); - switch (chars.len) { - 0 => { - try writer.writeAll("\"\""); - }, - else => { - for (chars) |c, i| { - switch (c) { - 0...127 => { - // Since we're writing character by character - // it will be really slow if we check for an error every time - _ = writer.write(@intCast(u8, c)) catch 0; - }, - // TODO: - else => {}, - } - } - }, - } + const used = js.JSStringGetUTF8CString(arg, (&printer_buf), printer_buf.len); + try writer.writeAll(printer_buf[0..used]); }, .kJSTypeObject => { // TODO: @@ -391,8 +1031,8 @@ pub const GlobalObject = struct { .kJSTypeSymbol => { var description = js.JSObjectGetPropertyForKey(ctx, arg, Properties.Refs.description, null); return switch (js.JSValueGetType(ctx, description)) { - .kJSTypeString => try valuePrinter(.kJSTypeString, ctx, description, writer), - else => try valuePrinter(.kJSTypeUndefined, ctx, description, writer), + .kJSTypeString => try valuePrinter(.kJSTypeString, ctx, js.JSStringRetain(description), writer), + else => try valuePrinter(.kJSTypeUndefined, ctx, js.JSStringRetain(description), writer), }; }, else => {}, @@ -402,15 +1042,16 @@ pub const GlobalObject = struct { fn output( writer: anytype, ctx: js.JSContextRef, - arguments: []js.JSValueRef, + arguments: []const js.JSValueRef, ) !void { defer Output.flush(); // console.log(); if (arguments.len == 0) { - return js.JSValueMakeUndefined(ctx); + return; } const last = arguments.len - 1; + defer writer.writeAll("\n") catch {}; for (arguments) |arg, i| { switch (js.JSValueGetType(ctx, arg)) { @@ -459,8 +1100,6 @@ pub const GlobalObject = struct { else => {}, } } - - return js.JSValueMakeUndefined(ctx); } pub fn stdout( @@ -468,9 +1107,11 @@ pub const GlobalObject = struct { ctx: js.JSContextRef, function: js.JSObjectRef, thisObject: js.JSObjectRef, - arguments: []js.JSValueRef, + arguments: []const js.JSValueRef, + exception: js.JSValueRef, ) js.JSValueRef { - return try output(Output.writer(), ctx, arguments); + output(Output.writer(), ctx, arguments) catch {}; + return js.JSValueMakeUndefined(ctx); } pub fn stderr( @@ -478,57 +1119,302 @@ pub const GlobalObject = struct { ctx: js.JSContextRef, function: js.JSObjectRef, thisObject: js.JSObjectRef, - arguments: []js.JSValueRef, + arguments: []const js.JSValueRef, + exception: js.JSValueRef, ) js.JSValueRef { - return try output(Output.errorWriter(), ctx, arguments); + output(Output.errorWriter(), ctx, arguments) catch {}; + return js.JSValueMakeUndefined(ctx); // js.JSObjectMakeFunctionWithCallback(ctx: JSContextRef, name: JSStringRef, callAsFunction: JSObjectCallAsFunctionCallback) } }; -pub fn NewSingletonClass( +pub fn NewClass( comptime ZigType: type, comptime name: string, - comptime functions: anytype, + comptime staticFunctions: anytype, + comptime properties: anytype, comptime read_only: bool, + comptime singleton: bool, ) type { return struct { const ClassDefinitionCreator = @This(); - const function_names = std.meta.fieldNames(functions); - const function_name_literals: [function_names.len][]js.JSChar = brk: { - var names = std.mem.zeroes([function_names.len][]js.JSChar); - + const function_names = std.meta.fieldNames(@TypeOf(staticFunctions)); + const names_buf = brk: { + var total_len: usize = 0; + for (function_names) |field, i| { + total_len += std.unicode.utf8ToUtf16LeStringLiteral(field).len; + } + var offset: usize = 0; + var names_buf_ = std.mem.zeroes([total_len]u16); for (function_names) |field, i| { - names[i] = std.unicode.utf8ToUtf16LeStringLiteral(field); + var name_ = std.unicode.utf8ToUtf16LeStringLiteral(field); + std.mem.copy(u16, names_buf_[offset .. name_.len + offset], name_[0..]); + offset += name_.len; + } + break :brk names_buf_; + }; + const function_name_literals: [function_names.len][]const js.JSChar = brk: { + var names = std.mem.zeroes([function_names.len][]const js.JSChar); + var len: usize = 0; + for (function_names) |field, i| { + const end = len + std.unicode.utf8ToUtf16LeStringLiteral(field).len; + names[i] = names_buf[len..end]; + len = end; } break :brk names; }; var function_name_refs: [function_names.len]js.JSStringRef = undefined; + var class_name_str = name[0.. :0].ptr; const class_name_literal = std.unicode.utf8ToUtf16LeStringLiteral(name); - var static_functions: [function_name_refs.len + 1:0]js.JSStaticFunction = undefined; - - pub fn define(zig: *ZigType, ctx: js.JSContextRef) !js.JSClassDefinition { - var def = std.mem.zeroes(js.JSClassDefinition); - - inline for (function_name_literals) |function_name, i| { - function_name_refs[i] = js.JSStringCreateWithCharactersNoCopy(&function_name, function_name.len); - static_functions[i] = js.JSStaticFunction{ - .name = (function_names[i][0.. :0]).ptr, - .callAsFunction = To.JS.functionWithCallback( - ZigType, - zig, - function_name_refs[i], - ctx, - @field(functions, function_names[i]), + var static_functions: [function_name_refs.len + 1]js.JSStaticFunction = undefined; + var instance_functions: [function_names.len]js.JSObjectRef = undefined; + const property_names = std.meta.fieldNames(@TypeOf(properties)); + var property_name_refs: [property_names.len]js.JSStringRef = undefined; + const property_name_literals: [property_names.len][]const js.JSChar = brk: { + var list = std.mem.zeroes([property_names.len][]const js.JSChar); + for (property_names) |prop_name, i| { + list[i] = std.unicode.utf8ToUtf16LeStringLiteral(prop_name); + } + break :brk list; + }; + var static_properties: [property_names.len]js.JSStaticValue = undefined; + + pub fn getPropertyCallback(ctx: js.JSContextRef, obj: js.JSObjectRef, prop: js.JSStringRef, exception: ExceptionValueRef) callconv(.C) js.JSValueRef { + var instance_pointer_ = js.JSObjectGetPrivate(obj); + if (instance_pointer_ == null) return js.JSValueMakeUndefined(ctx); + var instance_pointer = instance_pointer_.?; + var ptr = @ptrCast( + *ZigType, + @alignCast( + @alignOf( + *ZigType, ), - .attributes = comptime if (read_only) js.JSPropertyAttributes.kJSPropertyAttributeReadOnly else js.JSPropertyAttributes.kJSPropertyAttributeNone, - }; + instance_pointer, + ), + ); + + if (singleton) { + inline for (function_names) |propname, i| { + if (js.JSStringIsEqual(prop, function_name_refs[i])) { + return instance_functions[i]; + } + } + if (comptime std.meta.trait.hasFn("onMissingProperty")(ZigType)) { + return ptr.onMissingProperty(ctx, obj, prop, exception); + } + } else { + inline for (property_names) |propname, i| { + if (js.JSStringIsEqual(prop, property_name_refs[i])) { + return @field( + properties, + propname, + )(ptr, ctx, obj, exception); + } + } + + if (comptime std.meta.trait.hasFn("onMissingProperty")(ZigType)) { + return ptr.onMissingProperty(ctx, obj, prop, exception); + } } - static_functions[function_name_literals.len] = std.mem.zeroes(js.JSStaticFunction); - def.staticFunctions = static_functions[0.. :0].ptr; - def.className = name[0.. :0].ptr; + + return js.JSValueMakeUndefined(ctx); + } + + fn StaticProperty(comptime id: usize) type { + return struct { + pub fn getter(ctx: js.JSContextRef, obj: js.JSObjectRef, prop: js.JSStringRef, exception: [*c]js.JSValueRef) callconv(.C) js.JSValueRef { + var instance_pointer_ = js.JSObjectGetPrivate(obj); + if (instance_pointer_ == null) return js.JSValueMakeUndefined(ctx); + var ptr = @ptrCast( + *ZigType, + @alignCast( + @alignOf( + *ZigType, + ), + instance_pointer_.?, + ), + ); + + return @field( + properties, + property_names[id], + )(ptr, ctx, obj, exception); + } + }; + } + + pub fn define(ctx: js.JSContextRef) js.JSClassDefinition { + var def = js.kJSClassDefinitionEmpty; + + if (static_functions.len > 0) { + inline for (function_name_literals) |function_name, i| { + var callback = To.JS.Callback(ZigType, @field(staticFunctions, function_names[i])).rfn; + function_name_refs[i] = js.JSStringCreateWithCharactersNoCopy( + function_name.ptr, + function_name.len, + ); + + static_functions[i] = js.JSStaticFunction{ + .name = (function_names[i][0.. :0]).ptr, + .callAsFunction = callback, + .attributes = comptime if (read_only) js.JSPropertyAttributes.kJSPropertyAttributeReadOnly else js.JSPropertyAttributes.kJSPropertyAttributeNone, + }; + // if (singleton) { + // var function = js.JSObjectMakeFunctionWithCallback(ctx, function_name_refs[i], callback); + // instance_functions[i] = function; + // } + } + def.staticFunctions = &static_functions; + } + + if (property_names.len > 0) { + inline for (comptime property_name_literals) |prop_name, i| { + property_name_refs[i] = js.JSStringCreateWithCharactersNoCopy( + prop_name.ptr, + prop_name.len, + ); + static_properties[i] = std.mem.zeroes(js.JSStaticValue); + static_properties[i].getProperty = StaticProperty(i).getter; + static_properties[i].name = property_names[i][0.. :0]; + } + + def.staticValues = (&static_properties); + } + + def.className = class_name_str; + // def.getProperty = getPropertyCallback; return def; } }; } + +// This makes it so we get the defines already formatted from the user's environment with the "process.env." prefix set +// This also normalizes quoting +// Currently, it truncates any environment variables to a max of 1024 bytes +const NodeEnvBufMap = struct { + backing: std.BufMap, + pub fn init(allocator: *std.mem.Allocator) NodeEnvBufMap { + return NodeEnvBufMap{ .backing = std.BufMap.init(allocator) }; + } + pub fn get(this: *const NodeEnvBufMap, key: string) ?string { + return this.backing.get(key); + } + pub threadlocal var bufkeybuf: [1024]u8 = undefined; + pub threadlocal var bufkeybuf_first = true; + pub fn put(this: *NodeEnvBufMap, key: string, value: string) !void { + if (value.len == 0) { + return; + } + + if (bufkeybuf_first) { + bufkeybuf[0.."process.env.".len].* = "process.env."; + bufkeybuf_first = false; + } + std.mem.copy(u8, bufkeybuf_first["process.env.".len..], key); + var key_slice = bufkeybuf[0 .. key.len + "process.env.".len]; + var value_slice = value; + const max_value_slice_len = std.math.min(value.len, bufkeybuf.len - key_slice.len); + if (value[0] != '"' and value[value.len - 1] != '"') { + value_slice = bufkeybuf[key_slice.len..][0 .. max_value_slice_len + 2]; + value_slice[0] = '"'; + std.mem.copy(u8, value_slice[1..], value[0..max_value_slice_len]); + value_slice[value_slice.len - 1] = '"'; + } else if (value[0] != '"') { + value_slice[0] = '"'; + std.mem.copy(u8, value_slice[1..], value[0..max_value_slice_len]); + } else if (value[value.len - 1] != '"') { + std.mem.copy(u8, value_slice[1..], value[0..max_value_slice_len]); + value_slice[value_slice.len - 1] = '"'; + } + + return this.backing.put(key_slice, value_slice); + } +}; + +pub fn getNodeEnvMap(allocator: *Allocator) !NodeEnvBufMap { + var result = NodeEnvBufMap.init(allocator); + errdefer result.deinit(); + + if (builtin.os.tag == .windows) { + const ptr = os.windows.peb().ProcessParameters.Environment; + + var i: usize = 0; + while (ptr[i] != 0) { + const key_start = i; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const key_w = ptr[key_start..i]; + const key = try std.unicode.utf16leToUtf8Alloc(allocator, key_w); + errdefer allocator.free(key); + + if (ptr[i] == '=') i += 1; + + const value_start = i; + while (ptr[i] != 0) : (i += 1) {} + const value_w = ptr[value_start..i]; + const value = try std.unicode.utf16leToUtf8Alloc(allocator, value_w); + errdefer allocator.free(value); + + i += 1; // skip over null byte + + try result.putMove(key, value); + } + return result; + } else if (builtin.os.tag == .wasi) { + var environ_count: usize = undefined; + var environ_buf_size: usize = undefined; + + const environ_sizes_get_ret = os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); + if (environ_sizes_get_ret != os.wasi.ESUCCESS) { + return os.unexpectedErrno(environ_sizes_get_ret); + } + + var environ = try allocator.alloc([*:0]u8, environ_count); + defer allocator.free(environ); + var environ_buf = try allocator.alloc(u8, environ_buf_size); + defer allocator.free(environ_buf); + + const environ_get_ret = os.wasi.environ_get(environ.ptr, environ_buf.ptr); + if (environ_get_ret != os.wasi.ESUCCESS) { + return os.unexpectedErrno(environ_get_ret); + } + + for (environ) |env| { + const pair = mem.spanZ(env); + var parts = mem.split(pair, "="); + const key = parts.next().?; + const value = parts.next().?; + try result.put(key, value); + } + return result; + } else if (builtin.link_libc) { + var ptr = std.c.environ; + while (ptr.*) |line| : (ptr += 1) { + var line_i: usize = 0; + while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} + const key = line[0..line_i]; + + var end_i: usize = line_i; + while (line[end_i] != 0) : (end_i += 1) {} + const value = line[line_i + 1 .. end_i]; + + try result.put(key, value); + } + return result; + } else { + for (os.environ) |line| { + var line_i: usize = 0; + while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} + const key = line[0..line_i]; + + var end_i: usize = line_i; + while (line[end_i] != 0) : (end_i += 1) {} + const value = line[line_i + 1 .. end_i]; + + try result.put(key, value); + } + return result; + } +} |