mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
File diff suppressed because it is too large
Load Diff
191
src/defines.zig
191
src/defines.zig
@@ -1,8 +1,12 @@
|
||||
const std = @import("std");
|
||||
const js_ast = @import("./js_ast.zig");
|
||||
const alloc = @import("alloc.zig");
|
||||
|
||||
const logger = @import("logger.zig");
|
||||
const js_lexer = @import("js_lexer.zig");
|
||||
const json_parser = @import("json_parser.zig");
|
||||
const fs = @import("fs.zig");
|
||||
usingnamespace @import("strings.zig");
|
||||
usingnamespace @import("ast/base.zig");
|
||||
|
||||
const GlobalDefinesKey = @import("./defines-table.zig").GlobalDefinesKey;
|
||||
|
||||
@@ -15,10 +19,17 @@ const Globals = struct {
|
||||
|
||||
pub const Infinity = js_ast.E.Number{ .value = std.math.inf(f64) };
|
||||
pub const InfinityPtr = &Globals.Infinity;
|
||||
pub const UndefinedData = js_ast.Expr.Data{ .e_undefined = Globals.UndefinedPtr };
|
||||
pub const NaNData = js_ast.Expr.Data{ .e_number = Globals.NanPtr };
|
||||
pub const InfinityData = js_ast.Expr.Data{ .e_number = Globals.InfinityPtr };
|
||||
};
|
||||
|
||||
pub const RawDefines = std.StringHashMap(string);
|
||||
pub const UserDefines = std.StringHashMap(DefineData);
|
||||
|
||||
pub const DefineData = struct {
|
||||
value: js_ast.Expr.Data = DefaultValue,
|
||||
value: js_ast.Expr.Data,
|
||||
original_name: ?string = null,
|
||||
|
||||
// True if accessing this value is known to not have any side effects. For
|
||||
// example, a bare reference to "Object.create" can be removed because it
|
||||
@@ -30,8 +41,6 @@ pub const DefineData = struct {
|
||||
// have any observable side effects.
|
||||
call_can_be_unwrapped_if_unused: bool = false,
|
||||
|
||||
pub const DefaultValue = js_ast.Expr.Data{ .e_undefined = Globals.UndefinedPtr };
|
||||
|
||||
// All the globals have the same behavior.
|
||||
// So we can create just one struct for it.
|
||||
pub const GlobalDefineData = DefineData{};
|
||||
@@ -39,13 +48,73 @@ pub const DefineData = struct {
|
||||
pub fn merge(a: DefineData, b: DefineData) DefineData {
|
||||
return DefineData{
|
||||
.value = b.value,
|
||||
.can_be_removed_if_unsued = a.can_be_removed_if_unsued,
|
||||
.can_be_removed_if_unused = a.can_be_removed_if_unused,
|
||||
.call_can_be_unwrapped_if_unused = a.call_can_be_unwrapped_if_unused,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn from_input(defines: RawDefines, log: *logger.Log, allocator: *std.mem.Allocator) !UserDefines {
|
||||
var user_defines = UserDefines.init(allocator);
|
||||
try user_defines.ensureCapacity(defines.count());
|
||||
|
||||
var iter = defines.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
var splitter = std.mem.split(entry.key, ".");
|
||||
while (splitter.next()) |part| {
|
||||
if (!js_lexer.isIdentifier(part)) {
|
||||
if (strings.eql(part, entry.key)) {
|
||||
try log.addErrorFmt(null, logger.Loc{}, allocator, "The define key \"{s}\" must be a valid identifier", .{entry.key});
|
||||
} else {
|
||||
try log.addErrorFmt(null, logger.Loc{}, allocator, "The define key \"{s}\" contains invalid identifier \"{s}\"", .{ part, entry.key });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (js_lexer.isIdentifier(entry.value) and !js_lexer.Keywords.has(entry.value)) {
|
||||
var ident: *js_ast.E.Identifier = try allocator.create(js_ast.E.Identifier);
|
||||
ident.ref = Ref.None;
|
||||
ident.can_be_removed_if_unused = true;
|
||||
user_defines.putAssumeCapacity(
|
||||
entry.key,
|
||||
DefineData{
|
||||
.value = js_ast.Expr.Data{ .e_identifier = ident },
|
||||
.original_name = entry.value,
|
||||
.can_be_removed_if_unused = true,
|
||||
},
|
||||
);
|
||||
// user_defines.putAssumeCapacity(
|
||||
// entry.key,
|
||||
// DefineData{ .value = js_ast.Expr.Data{.e_identifier = } },
|
||||
// );
|
||||
continue;
|
||||
}
|
||||
var _log = log;
|
||||
var source = logger.Source{ .contents = entry.value, .path = fs.Path.init("/internal/defines.json"), .identifier_name = "" };
|
||||
var expr = try json_parser.ParseJSON(&source, _log, allocator);
|
||||
var data: js_ast.Expr.Data = undefined;
|
||||
switch (expr.data) {
|
||||
.e_missing => {
|
||||
continue;
|
||||
},
|
||||
.e_null, .e_boolean, .e_string, .e_number, .e_object, .e_array => {
|
||||
data = expr.data;
|
||||
},
|
||||
else => {
|
||||
continue;
|
||||
},
|
||||
}
|
||||
|
||||
user_defines.putAssumeCapacity(entry.key, DefineData{
|
||||
.value = data,
|
||||
});
|
||||
}
|
||||
|
||||
return user_defines;
|
||||
}
|
||||
};
|
||||
|
||||
fn arePartsEqual(a: []string, b: []string) bool {
|
||||
fn arePartsEqual(a: []const string, b: []const string) bool {
|
||||
if (a.len != b.len) {
|
||||
return false;
|
||||
}
|
||||
@@ -63,44 +132,73 @@ fn arePartsEqual(a: []string, b: []string) bool {
|
||||
pub const IdentifierDefine = DefineData;
|
||||
|
||||
pub const DotDefine = struct {
|
||||
parts: []string,
|
||||
parts: []const string,
|
||||
data: DefineData,
|
||||
};
|
||||
|
||||
pub const Define = struct {
|
||||
identifiers: std.StringHashMapUnmanaged(IdentifierDefine),
|
||||
dots: std.StringHashMapUnmanaged([]DotDefine),
|
||||
identifiers: std.StringHashMap(IdentifierDefine),
|
||||
dots: std.StringHashMap([]DotDefine),
|
||||
allocator: *std.mem.Allocator,
|
||||
|
||||
pub fn init(allocator: *std.mem.Allocator, user_defines: std.StringHashMap(DefineData)) !*@This() {
|
||||
pub fn init(allocator: *std.mem.Allocator, user_defines: UserDefines) !*@This() {
|
||||
var define = try allocator.create(Define);
|
||||
define.allocator = allocator;
|
||||
try define.identifiers.ensureCapacity(allocator, 641);
|
||||
try define.dots.ensureCapacity(allocator, 38);
|
||||
define.identifiers = std.StringHashMap(IdentifierDefine).init(allocator);
|
||||
define.dots = std.StringHashMap([]DotDefine).init(allocator);
|
||||
try define.identifiers.ensureCapacity(641);
|
||||
try define.dots.ensureCapacity(64);
|
||||
|
||||
var undefined_val = try allocator.create(js_ast.E.Undefined);
|
||||
var val = js_ast.Expr.Data{ .e_undefined = undefined_val };
|
||||
var ident_define = IdentifierDefine{
|
||||
.value = val,
|
||||
};
|
||||
var value_define = DefineData{ .value = val };
|
||||
// Step 1. Load the globals into the hash tables
|
||||
for (GlobalDefinesKey) |global| {
|
||||
if (global.len == 1) {
|
||||
|
||||
// TODO: when https://github.com/ziglang/zig/pull/8596 is merged, switch to putAssumeCapacityNoClobber
|
||||
define.identifiers.putAssumeCapacity(global[0], IdentifierDefine.GlobalDefineData);
|
||||
try define.identifiers.put(global[0], value_define);
|
||||
} else {
|
||||
const key = global[global.len - 1];
|
||||
// TODO: move this to comptime
|
||||
// TODO: when https://github.com/ziglang/zig/pull/8596 is merged, switch to putAssumeCapacityNoClobber
|
||||
define.dots.putAssumeCapacity(global[global.len - 1], DotDefine{
|
||||
.parts = global[0 .. global.len - 1],
|
||||
.data = DefineData.GlobalDefineData,
|
||||
});
|
||||
if (define.dots.getEntry(key)) |entry| {
|
||||
var list = try std.ArrayList(DotDefine).initCapacity(allocator, entry.value.len + 1);
|
||||
list.appendSliceAssumeCapacity(entry.value);
|
||||
list.appendAssumeCapacity(DotDefine{
|
||||
.parts = global[0 .. global.len - 1],
|
||||
.data = value_define,
|
||||
});
|
||||
|
||||
define.dots.putAssumeCapacity(key, list.toOwnedSlice());
|
||||
} else {
|
||||
var list = try std.ArrayList(DotDefine).initCapacity(allocator, 1);
|
||||
list.appendAssumeCapacity(DotDefine{
|
||||
.parts = global[0 .. global.len - 1],
|
||||
.data = value_define,
|
||||
});
|
||||
|
||||
define.dots.putAssumeCapacity(key, list.toOwnedSlice());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var nan_val = try allocator.create(js_ast.E.Number);
|
||||
nan_val.value = std.math.nan_f64;
|
||||
|
||||
var inf_val = try allocator.create(js_ast.E.Number);
|
||||
inf_val.value = std.math.inf_f64;
|
||||
|
||||
// Step 2. Swap in certain literal values because those can be constant folded
|
||||
define.identifiers.putAssumeCapacity("undefined", DefineData{
|
||||
.value = js_ast.Expr.Data{ .e_undefined = Globals.UndefinedPtr },
|
||||
});
|
||||
define.identifiers.putAssumeCapacity("undefined", value_define);
|
||||
define.identifiers.putAssumeCapacity("NaN", DefineData{
|
||||
.value = js_ast.Expr.Data{ .e_number = Globals.NanPtr },
|
||||
.value = js_ast.Expr.Data{ .e_number = nan_val },
|
||||
});
|
||||
define.identifiers.putAssumeCapacity("Infinity", DefineData{
|
||||
.value = js_ast.Expr.Data{ .e_number = Globals.InfinityPtr },
|
||||
.value = js_ast.Expr.Data{ .e_number = inf_val },
|
||||
});
|
||||
|
||||
// Step 3. Load user data into hash tables
|
||||
@@ -112,12 +210,20 @@ pub const Define = struct {
|
||||
// e.g. process.env.NODE_ENV
|
||||
if (strings.lastIndexOfChar(user_define.key, '.')) |last_dot| {
|
||||
const tail = user_define.key[last_dot + 1 .. user_define.key.len];
|
||||
const parts = std.mem.tokenize(user_define.key[0..last_dot], ".").rest();
|
||||
const remainder = user_define.key[0..last_dot];
|
||||
const count = std.mem.count(u8, remainder, ".") + 1;
|
||||
var parts = try allocator.alloc(string, count);
|
||||
var splitter = std.mem.split(remainder, ".");
|
||||
var i: usize = 0;
|
||||
while (splitter.next()) |split| : (i += 1) {
|
||||
parts[i] = split;
|
||||
}
|
||||
|
||||
var didFind = false;
|
||||
var initial_values = &([_]DotDefine{});
|
||||
var initial_values: []DotDefine = &([_]DotDefine{});
|
||||
|
||||
// "NODE_ENV"
|
||||
if (define.dots.getEntry()) |entry| {
|
||||
if (define.dots.getEntry(tail)) |entry| {
|
||||
for (entry.value) |*part| {
|
||||
// ["process", "env"] == ["process", "env"]
|
||||
if (arePartsEqual(part.parts, parts)) {
|
||||
@@ -141,7 +247,7 @@ pub const Define = struct {
|
||||
// TODO: do we need to allocate this?
|
||||
.parts = parts,
|
||||
});
|
||||
try define.dots.put(allocator, tail, list.toOwnedSlice());
|
||||
try define.dots.put(tail, list.toOwnedSlice());
|
||||
}
|
||||
} else {
|
||||
// IS_BROWSER
|
||||
@@ -154,6 +260,35 @@ pub const Define = struct {
|
||||
}
|
||||
};
|
||||
|
||||
test "defines" {
|
||||
|
||||
const expect = std.testing.expect;
|
||||
test "UserDefines" {
|
||||
try alloc.setup(std.heap.page_allocator);
|
||||
var orig = RawDefines.init(alloc.dynamic);
|
||||
try orig.put("process.env.NODE_ENV", "\"development\"");
|
||||
var log = logger.Log.init(alloc.dynamic);
|
||||
var data = try DefineData.from_input(orig, &log, alloc.dynamic);
|
||||
|
||||
expect(data.contains("process.env.NODE_ENV"));
|
||||
const val = data.get("process.env.NODE_ENV");
|
||||
expect(val != null);
|
||||
expect(strings.utf16EqlString(val.?.value.e_string.value, "development"));
|
||||
}
|
||||
|
||||
test "Defines" {
|
||||
try alloc.setup(std.heap.page_allocator);
|
||||
var orig = RawDefines.init(alloc.dynamic);
|
||||
try orig.put("process.env.NODE_ENV", "\"development\"");
|
||||
var log = logger.Log.init(alloc.dynamic);
|
||||
var data = try DefineData.from_input(orig, &log, alloc.dynamic);
|
||||
|
||||
var defines = try Define.init(alloc.dynamic, data);
|
||||
const node_env_dots = defines.dots.get("NODE_ENV");
|
||||
expect(node_env_dots != null);
|
||||
expect(node_env_dots.?.len > 0);
|
||||
const node_env = node_env_dots.?[0];
|
||||
std.testing.expectEqual(node_env.parts.len, 2);
|
||||
std.testing.expectEqualStrings("process", node_env.parts[0]);
|
||||
std.testing.expectEqualStrings("env", node_env.parts[1]);
|
||||
expect(node_env.data.original_name == null);
|
||||
expect(strings.utf16EqlString(node_env.data.value.e_string.value, "development"));
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ pub const Lexer = struct {
|
||||
|
||||
fn nextCodepointSlice(it: *@This()) callconv(.Inline) ?[]const u8 {
|
||||
if (it.current >= it.source.contents.len) {
|
||||
// without this line, strings cut off one before the last characte
|
||||
it.end = it.current;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ pub fn eqlUtf16(comptime self: string, other: JavascriptString) bool {
|
||||
return std.mem.eql(u16, std.unicode.utf8ToUtf16LeStringLiteral(self), other);
|
||||
}
|
||||
|
||||
// Check utf16 string equals utf8 string without allocating extra memory
|
||||
pub fn utf16EqlString(text: []u16, str: string) bool {
|
||||
if (text.len > str.len) {
|
||||
// Strings can't be equal if UTF-16 encoding is longer than UTF-8 encoding
|
||||
|
||||
Reference in New Issue
Block a user