Compare commits

..

2 Commits

Author SHA1 Message Date
Jarred Sumner
8e840eb7a9 Update env_loader.zig 2025-10-26 18:33:10 -07:00
Jarred Sumner
5b63fbff01 Update env_loader.zig 2025-10-26 18:27:46 -07:00
11 changed files with 53 additions and 608 deletions

View File

@@ -944,7 +944,7 @@ if(NOT WIN32)
if (NOT ABI STREQUAL "musl")
target_compile_options(${bun} PUBLIC
-fsanitize=null
-fno-sanitize-recover=all
-fsanitize-recover=all
-fsanitize=bounds
-fsanitize=return
-fsanitize=nullability-arg
@@ -999,20 +999,6 @@ if(NOT WIN32)
)
if(ENABLE_ASAN)
target_compile_options(${bun} PUBLIC
-fsanitize=null
-fno-sanitize-recover=all
-fsanitize=bounds
-fsanitize=return
-fsanitize=nullability-arg
-fsanitize=nullability-assign
-fsanitize=nullability-return
-fsanitize=returns-nonnull-attribute
-fsanitize=unreachable
)
target_link_libraries(${bun} PRIVATE
-fsanitize=null
)
target_compile_options(${bun} PUBLIC -fsanitize=address)
target_link_libraries(${bun} PUBLIC -fsanitize=address)
endif()

View File

@@ -87,8 +87,6 @@ pub const Features = struct {
pub var yarn_migration: usize = 0;
pub var pnpm_migration: usize = 0;
pub var yaml_parse: usize = 0;
pub var toon_parse: usize = 0;
pub var toon_stringify: usize = 0;
comptime {
@export(&napi_module_register, .{ .name = "Bun__napi_module_register_count" });

View File

@@ -27,7 +27,6 @@ pub const Subprocess = @import("./api/bun/subprocess.zig");
pub const HashObject = @import("./api/HashObject.zig");
pub const UnsafeObject = @import("./api/UnsafeObject.zig");
pub const TOMLObject = @import("./api/TOMLObject.zig");
pub const TOONObject = @import("./api/TOONObject.zig");
pub const YAMLObject = @import("./api/YAMLObject.zig");
pub const Timer = @import("./api/Timer.zig");
pub const FFIObject = @import("./api/FFIObject.zig");

View File

@@ -62,7 +62,6 @@ pub const BunObject = struct {
pub const SHA512 = toJSLazyPropertyCallback(Crypto.SHA512.getter);
pub const SHA512_256 = toJSLazyPropertyCallback(Crypto.SHA512_256.getter);
pub const TOML = toJSLazyPropertyCallback(Bun.getTOMLObject);
pub const TOON = toJSLazyPropertyCallback(Bun.getTOONObject);
pub const YAML = toJSLazyPropertyCallback(Bun.getYAMLObject);
pub const Transpiler = toJSLazyPropertyCallback(Bun.getTranspilerConstructor);
pub const argv = toJSLazyPropertyCallback(Bun.getArgv);
@@ -128,7 +127,6 @@ pub const BunObject = struct {
@export(&BunObject.SHA512_256, .{ .name = lazyPropertyCallbackName("SHA512_256") });
@export(&BunObject.TOML, .{ .name = lazyPropertyCallbackName("TOML") });
@export(&BunObject.TOON, .{ .name = lazyPropertyCallbackName("TOON") });
@export(&BunObject.YAML, .{ .name = lazyPropertyCallbackName("YAML") });
@export(&BunObject.Glob, .{ .name = lazyPropertyCallbackName("Glob") });
@export(&BunObject.Transpiler, .{ .name = lazyPropertyCallbackName("Transpiler") });
@@ -1271,10 +1269,6 @@ pub fn getTOMLObject(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSVa
return TOMLObject.create(globalThis);
}
pub fn getTOONObject(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
return TOONObject.create(globalThis);
}
pub fn getYAMLObject(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
return YAMLObject.create(globalThis);
}
@@ -2065,7 +2059,6 @@ const api = bun.api;
const FFIObject = bun.api.FFIObject;
const HashObject = bun.api.HashObject;
const TOMLObject = bun.api.TOMLObject;
const TOONObject = bun.api.TOONObject;
const UnsafeObject = bun.api.UnsafeObject;
const YAMLObject = bun.api.YAMLObject;
const node = bun.api.node;

View File

@@ -1,107 +0,0 @@
pub fn create(globalThis: *jsc.JSGlobalObject) jsc.JSValue {
const object = JSValue.createEmptyObject(globalThis, 2);
object.put(
globalThis,
ZigString.static("parse"),
jsc.createCallback(
globalThis,
ZigString.static("parse"),
1,
parse,
),
);
object.put(
globalThis,
ZigString.static("stringify"),
jsc.createCallback(
globalThis,
ZigString.static("stringify"),
3,
stringify,
),
);
return object;
}
pub fn parse(
globalThis: *jsc.JSGlobalObject,
callframe: *jsc.CallFrame,
) bun.JSError!jsc.JSValue {
var arena = bun.ArenaAllocator.init(globalThis.allocator());
const allocator = arena.allocator();
defer arena.deinit();
var log = logger.Log.init(default_allocator);
const arguments = callframe.arguments_old(1).slice();
if (arguments.len == 0 or arguments[0].isEmptyOrUndefinedOrNull()) {
return globalThis.throwInvalidArguments("Expected a string to parse", .{});
}
var input_slice = try arguments[0].toSlice(globalThis, bun.default_allocator);
defer input_slice.deinit();
const source = &logger.Source.initPathString("input.toon", input_slice.slice());
const parse_result = TOON.parse(source, &log, allocator) catch {
return globalThis.throwValue(try log.toJS(globalThis, default_allocator, "Failed to parse toon"));
};
// Convert parsed result to JSON
const buffer_writer = js_printer.BufferWriter.init(allocator);
var writer = js_printer.BufferPrinter.init(buffer_writer);
_ = js_printer.printJSON(
*js_printer.BufferPrinter,
&writer,
parse_result,
source,
.{
.mangled_props = null,
},
) catch {
return globalThis.throwValue(try log.toJS(globalThis, default_allocator, "Failed to print toon"));
};
const slice = writer.ctx.buffer.slice();
var out = bun.String.borrowUTF8(slice);
defer out.deref();
return out.toJSByParseJSON(globalThis);
}
pub fn stringify(
globalThis: *jsc.JSGlobalObject,
callframe: *jsc.CallFrame,
) bun.JSError!jsc.JSValue {
const value, const replacer, const space_value = callframe.argumentsAsArray(3);
value.ensureStillAlive();
if (value.isUndefined() or value.isSymbol() or value.isFunction()) {
return .js_undefined;
}
if (!replacer.isUndefinedOrNull()) {
return globalThis.throw("TOON.stringify does not support the replacer argument", .{});
}
var scope: bun.AllocationScope = .init(bun.default_allocator);
defer scope.deinit();
var stringifier = TOON.stringify(scope.allocator(), globalThis, value, space_value) catch |err| return switch (err) {
error.OutOfMemory => error.JSError,
error.JSError, error.JSTerminated => |js_err| js_err,
error.StackOverflow => globalThis.throwStackOverflow(),
};
defer stringifier.deinit();
return stringifier.toString(globalThis);
}
const bun = @import("bun");
const default_allocator = bun.default_allocator;
const js_printer = bun.js_printer;
const logger = bun.logger;
const TOON = bun.interchange.toon.TOON;
const jsc = bun.jsc;
const JSGlobalObject = jsc.JSGlobalObject;
const JSValue = jsc.JSValue;
const ZigString = jsc.ZigString;

View File

@@ -18,7 +18,6 @@
macro(SHA512) \
macro(SHA512_256) \
macro(TOML) \
macro(TOON) \
macro(YAML) \
macro(Transpiler) \
macro(ValkeyClient) \

View File

@@ -722,7 +722,6 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
SHA512 BunObject_lazyPropCb_wrap_SHA512 DontDelete|PropertyCallback
SHA512_256 BunObject_lazyPropCb_wrap_SHA512_256 DontDelete|PropertyCallback
TOML BunObject_lazyPropCb_wrap_TOML DontDelete|PropertyCallback
TOON BunObject_lazyPropCb_wrap_TOON DontDelete|PropertyCallback
YAML BunObject_lazyPropCb_wrap_YAML DontDelete|PropertyCallback
Transpiler BunObject_lazyPropCb_wrap_Transpiler DontDelete|PropertyCallback
embeddedFiles BunObject_lazyPropCb_wrap_embeddedFiles DontDelete|PropertyCallback

View File

@@ -709,74 +709,36 @@ pub const Loader = struct {
return;
}
var file = dir.openFile(base, .{ .mode = .read_only }) catch |err| {
switch (err) {
error.IsDir, error.FileNotFound => {
// prevent retrying
@field(this, base) = logger.Source.initPathString(base, "");
return;
},
error.Unexpected, error.FileBusy, error.DeviceBusy, error.AccessDenied => {
if (!this.quiet) {
Output.prettyErrorln("<r><red>{s}<r> error loading {s} file", .{ @errorName(err), base });
}
// prevent retrying
@field(this, base) = logger.Source.initPathString(base, "");
return;
},
else => {
return err;
},
}
};
defer file.close();
const end = brk: {
if (comptime Environment.isWindows) {
const pos = try file.getEndPos();
if (pos == 0) {
@field(this, base) = logger.Source.initPathString(base, "");
return;
const source: logger.Source = switch (bun.sys.File.toSourceAt(dir.fd, base, this.allocator, .{ .convert_bom = true })) {
.err => |err| brk: {
switch (err.getErrno()) {
.ISDIR, .NOENT, .PERM, .NOTDIR => {
// prevent retrying
break :brk logger.Source.initPathString(base, "");
},
.BUSY, .AGAIN, .ACCES, .PIPE => {
if (!this.quiet) {
Output.errGeneric("loading {s} file: {}", .{ base, err });
}
break :brk logger.Source.initPathString(base, "");
},
else => {
@field(this, base) = logger.Source.initEmptyFile(base);
return err.toZigErr();
},
}
break :brk pos;
}
const stat = try file.stat();
if (stat.size == 0 or stat.kind != .file) {
@field(this, base) = logger.Source.initPathString(base, "");
return;
}
break :brk stat.size;
},
.result => |source| source,
};
var buf = try this.allocator.alloc(u8, end + 1);
errdefer this.allocator.free(buf);
const amount_read = file.readAll(buf[0..end]) catch |err| switch (err) {
error.Unexpected, error.SystemResources, error.OperationAborted, error.BrokenPipe, error.AccessDenied, error.IsDir => {
if (!this.quiet) {
Output.prettyErrorln("<r><red>{s}<r> error loading {s} file", .{ @errorName(err), base });
}
@field(this, base) = source;
// prevent retrying
@field(this, base) = logger.Source.initPathString(base, "");
return;
},
else => {
return err;
},
};
// The null byte here is mostly for debugging purposes.
buf[end] = 0;
const source = &logger.Source.initPathString(base, buf[0..amount_read]);
if (source.contents.len == 0) {
return;
}
try Parser.parse(
source,
&@field(this, base).?,
this.allocator,
this.map,
value_buffer,
@@ -784,8 +746,6 @@ pub const Loader = struct {
false,
true,
);
@field(this, base) = source.*;
}
pub fn loadEnvFileDynamic(
@@ -794,62 +754,40 @@ pub const Loader = struct {
comptime override: bool,
value_buffer: *std.ArrayList(u8),
) !void {
if (this.custom_files_loaded.contains(file_path)) {
const gpe = try this.custom_files_loaded.getOrPut(file_path);
if (gpe.found_existing) {
return;
}
var file = bun.openFile(file_path, .{ .mode = .read_only }) catch {
// prevent retrying
try this.custom_files_loaded.put(file_path, logger.Source.initPathString(file_path, ""));
const source: logger.Source = switch (bun.sys.File.toSource(file_path, this.allocator, .{ .convert_bom = true })) {
.err => |err| brk: {
switch (err.getErrno()) {
.AGAIN, .ISDIR, .NOENT, .PERM, .NOTDIR => {
// prevent retrying
break :brk logger.Source.initPathString(file_path, "");
},
.BUSY, .ACCES, .PIPE => {
if (!this.quiet) {
Output.errGeneric("loading {s} file: {}", .{ file_path, err });
}
break :brk logger.Source.initPathString(file_path, "");
},
else => {
return err.toZigErr();
},
}
},
.result => |source| source,
};
gpe.value_ptr.* = source;
if (source.contents.len == 0) {
return;
};
defer file.close();
const end = brk: {
if (comptime Environment.isWindows) {
const pos = try file.getEndPos();
if (pos == 0) {
try this.custom_files_loaded.put(file_path, logger.Source.initPathString(file_path, ""));
return;
}
break :brk pos;
}
const stat = try file.stat();
if (stat.size == 0 or stat.kind != .file) {
try this.custom_files_loaded.put(file_path, logger.Source.initPathString(file_path, ""));
return;
}
break :brk stat.size;
};
var buf = try this.allocator.alloc(u8, end + 1);
errdefer this.allocator.free(buf);
const amount_read = file.readAll(buf[0..end]) catch |err| switch (err) {
error.Unexpected, error.SystemResources, error.OperationAborted, error.BrokenPipe, error.AccessDenied, error.IsDir => {
if (!this.quiet) {
Output.prettyErrorln("<r><red>{s}<r> error loading {s} file", .{ @errorName(err), file_path });
}
// prevent retrying
try this.custom_files_loaded.put(file_path, logger.Source.initPathString(file_path, ""));
return;
},
else => {
return err;
},
};
// The null byte here is mostly for debugging purposes.
buf[end] = 0;
const source = &logger.Source.initPathString(file_path, buf[0..amount_read]);
}
try Parser.parse(
source,
&source,
this.allocator,
this.map,
value_buffer,
@@ -857,8 +795,6 @@ pub const Loader = struct {
false,
true,
);
try this.custom_files_loaded.put(file_path, source.*);
}
};

View File

@@ -1,4 +1,3 @@
pub const json = @import("./interchange/json.zig");
pub const toml = @import("./interchange/toml.zig");
pub const toon = @import("./interchange/toon.zig");
pub const yaml = @import("./interchange/yaml.zig");

View File

@@ -1,258 +0,0 @@
const std = @import("std");
const bun = @import("bun");
const logger = bun.logger;
const JSC = bun.jsc;
const ast = bun.ast;
const wtf = bun.jsc.wtf;
const Expr = ast.Expr;
const E = ast.E;
const G = ast.G;
const OOM = bun.OOM;
const JSError = bun.JSError;
/// Token-Oriented Object Notation (TOON) parser and stringifier
/// TOON is a compact, human-readable format designed for passing structured data
/// to Large Language Models with significantly reduced token usage.
pub const TOON = struct {
/// Parse TOON text into a JavaScript AST Expr
pub fn parse(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator) (OOM || error{SyntaxError})!Expr {
bun.analytics.Features.toon_parse += 1;
var parser = Parser.init(allocator, source.contents);
const result = parser.parseValue() catch |err| {
if (err == error.SyntaxError) {
try log.addErrorFmt(
source,
logger.Loc{ .start = @as(i32, @intCast(parser.pos)) },
allocator,
"Syntax error parsing TOON: {s}",
.{parser.error_msg orelse "unexpected input"},
);
}
return err;
};
return result;
}
/// Stringify a JavaScript value to TOON format
/// Returns a Stringifier that owns the string builder
pub fn stringify(
allocator: std.mem.Allocator,
globalThis: *JSC.JSGlobalObject,
value: JSC.JSValue,
space_value: JSC.JSValue,
) (OOM || error{ JSError, JSTerminated, StackOverflow })!Stringifier {
bun.analytics.Features.toon_stringify += 1;
var stringifier = try Stringifier.init(allocator, globalThis, space_value);
errdefer stringifier.deinit();
try stringifier.stringify(globalThis, value, 0);
return stringifier;
}
};
const Parser = struct {
allocator: std.mem.Allocator,
input: []const u8,
pos: usize = 0,
error_msg: ?[]const u8 = null,
fn init(allocator: std.mem.Allocator, input: []const u8) Parser {
return .{
.allocator = allocator,
.input = input,
};
}
fn parseValue(self: *Parser) (OOM || error{SyntaxError})!Expr {
self.skipWhitespace();
if (self.pos >= self.input.len) {
return Expr.init(E.Null, .{}, .Empty);
}
// For now, return a placeholder
// Full implementation would parse the TOON format here
self.error_msg = "TOON parsing not fully implemented yet";
return error.SyntaxError;
}
fn skipWhitespace(self: *Parser) void {
while (self.pos < self.input.len) {
switch (self.input[self.pos]) {
' ', '\t', '\r', '\n' => self.pos += 1,
else => break,
}
}
}
};
pub const Stringifier = struct {
allocator: std.mem.Allocator,
builder: wtf.StringBuilder,
indent: usize,
space: Space,
known_collections: std.AutoHashMap(JSC.JSValue, void),
const Space = union(enum) {
none,
spaces: u8,
string: []const u8,
};
pub fn toString(this: *Stringifier, global: *JSC.JSGlobalObject) JSError!JSC.JSValue {
return this.builder.toString(global);
}
fn init(
allocator: std.mem.Allocator,
globalThis: *JSC.JSGlobalObject,
space_value: JSC.JSValue,
) (OOM || error{ JSError, JSTerminated })!Stringifier {
const space = if (space_value.isNumber()) blk: {
const num = space_value.toInt32();
const clamped: u8 = @intCast(@max(0, @min(num, 10)));
if (clamped == 0) {
break :blk Space.none;
}
break :blk Space{ .spaces = clamped };
} else if (space_value.isString()) blk: {
const str = try space_value.toBunString(globalThis);
defer str.deref();
if (str.length() == 0) {
break :blk Space.none;
}
const str_utf8 = str.toUTF8(allocator);
defer str_utf8.deinit();
const str_slice = try allocator.dupe(u8, str_utf8.slice());
break :blk Space{ .string = str_slice };
} else Space.none;
return .{
.allocator = allocator,
.builder = wtf.StringBuilder.init(),
.indent = 0,
.space = space,
.known_collections = std.AutoHashMap(JSC.JSValue, void).init(allocator),
};
}
pub fn deinit(self: *Stringifier) void {
self.builder.deinit();
self.known_collections.deinit();
if (self.space == .string) {
self.allocator.free(self.space.string);
}
}
fn stringify(
self: *Stringifier,
globalThis: *JSC.JSGlobalObject,
value: JSC.JSValue,
depth: usize,
) (OOM || error{ JSError, JSTerminated, StackOverflow })!void {
_ = depth;
// Check for circular references
if (value.isObject()) {
const gop = try self.known_collections.getOrPut(value);
if (gop.found_existing) {
// Circular reference - for now just write null
self.builder.append(.latin1, "null");
return;
}
}
defer {
if (value.isObject()) {
_ = self.known_collections.remove(value);
}
}
if (value.isNull()) {
self.builder.append(.latin1, "null");
} else if (value.isUndefinedOrNull()) {
self.builder.append(.latin1, "null");
} else if (value.isBoolean()) {
if (value.asBoolean()) {
self.builder.append(.latin1, "true");
} else {
self.builder.append(.latin1, "false");
}
} else if (value.isNumber()) {
const num = value.asNumber();
if (std.math.isNan(num) or std.math.isInf(num)) {
self.builder.append(.latin1, "null");
} else {
self.builder.append(.double, num);
}
} else if (value.isString()) {
const str = try value.toBunString(globalThis);
defer str.deref();
const slice = str.toUTF8(self.allocator);
defer slice.deinit();
try self.writeString(slice.slice());
} else if (value.jsType().isArray()) {
// Placeholder for array handling
self.builder.append(.latin1, "[]");
} else if (value.isObject()) {
// Placeholder for object handling
self.builder.append(.latin1, "{}");
} else {
self.builder.append(.latin1, "null");
}
}
fn writeString(self: *Stringifier, str: []const u8) OOM!void {
// Check if quoting is needed
const needs_quotes = needsQuotes(str);
if (needs_quotes) {
self.builder.append(.lchar, '"');
for (str) |c| {
switch (c) {
'"' => self.builder.append(.latin1, "\\\""),
'\\' => self.builder.append(.latin1, "\\\\"),
'\n' => self.builder.append(.latin1, "\\n"),
'\r' => self.builder.append(.latin1, "\\r"),
'\t' => self.builder.append(.latin1, "\\t"),
else => self.builder.append(.lchar, c),
}
}
self.builder.append(.lchar, '"');
} else {
self.builder.append(.latin1, str);
}
}
fn needsQuotes(str: []const u8) bool {
if (str.len == 0) return true;
// Check for leading/trailing spaces
if (str[0] == ' ' or str[str.len - 1] == ' ') return true;
// Check for special characters or keywords
if (std.mem.eql(u8, str, "true") or
std.mem.eql(u8, str, "false") or
std.mem.eql(u8, str, "null")) return true;
// Check if it looks like a number
if (str[0] >= '0' and str[0] <= '9') return true;
if (str[0] == '-' and str.len > 1 and str[1] >= '0' and str[1] <= '9') return true;
// Check for characters that need quoting
for (str) |c| {
switch (c) {
':', ',', '"', '\\', '\n', '\r', '\t', '[', ']', '{', '}' => return true,
else => {},
}
}
return false;
}
};

View File

@@ -1,99 +0,0 @@
import { TOON } from "bun";
import { describe, expect, test } from "bun:test";
describe("Bun.TOON", () => {
test("TOON object exists", () => {
expect(TOON).toBeDefined();
expect(TOON.parse).toBeDefined();
expect(TOON.stringify).toBeDefined();
});
describe("TOON.stringify", () => {
test("stringify null", () => {
expect(TOON.stringify(null)).toBe("null");
});
test("stringify boolean", () => {
expect(TOON.stringify(true)).toBe("true");
expect(TOON.stringify(false)).toBe("false");
});
test("stringify number", () => {
expect(TOON.stringify(42)).toBe("42");
expect(TOON.stringify(3.14)).toBe("3.14");
expect(TOON.stringify(0)).toBe("0");
});
test("stringify string", () => {
expect(TOON.stringify("hello")).toBe("hello");
expect(TOON.stringify("hello world")).toBe("hello world");
});
test("stringify string with special characters", () => {
expect(TOON.stringify("hello, world")).toBe('"hello, world"');
expect(TOON.stringify("true")).toBe('"true"');
expect(TOON.stringify("false")).toBe('"false"');
expect(TOON.stringify("123")).toBe('"123"');
});
test("stringify empty string", () => {
expect(TOON.stringify("")).toBe('""');
});
test("stringify simple object", () => {
const result = TOON.stringify({ name: "Alice", age: 30 });
// For now, just check it doesn't crash
expect(result).toBeDefined();
});
test("stringify array", () => {
const result = TOON.stringify(["a", "b", "c"]);
// For now, just check it doesn't crash
expect(result).toBeDefined();
});
});
describe("TOON.parse", () => {
test.todo("parse null", () => {
expect(TOON.parse("null")).toBe(null);
});
test.todo("parse boolean", () => {
expect(TOON.parse("true")).toBe(true);
expect(TOON.parse("false")).toBe(false);
});
test.todo("parse number", () => {
expect(TOON.parse("42")).toBe(42);
expect(TOON.parse("3.14")).toBe(3.14);
});
test.todo("parse string", () => {
expect(TOON.parse("hello")).toBe("hello");
expect(TOON.parse('"hello, world"')).toBe("hello, world");
});
test.todo("parse simple object", () => {
const result = TOON.parse("name: Alice\nage: 30");
expect(result).toEqual({ name: "Alice", age: 30 });
});
test.todo("parse array", () => {
const result = TOON.parse("items[3]: a,b,c");
expect(result).toEqual({ items: ["a", "b", "c"] });
});
});
describe("round-trip", () => {
test.todo("null round-trip", () => {
const value = null;
expect(TOON.parse(TOON.stringify(value))).toEqual(value);
});
test.todo("simple values round-trip", () => {
expect(TOON.parse(TOON.stringify(true))).toBe(true);
expect(TOON.parse(TOON.stringify(42))).toBe(42);
expect(TOON.parse(TOON.stringify("hello"))).toBe("hello");
});
});
});