mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 07:28:53 +00:00
Compare commits
7 Commits
dylan/pyth
...
jarred/yam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1c6df54bb | ||
|
|
43abc3837b | ||
|
|
d2bbf36c04 | ||
|
|
e5efada28b | ||
|
|
9e01a916e4 | ||
|
|
d48f345309 | ||
|
|
6b578ad033 |
@@ -343,6 +343,7 @@ pub const Api = struct {
|
||||
text,
|
||||
sqlite,
|
||||
html,
|
||||
yaml,
|
||||
_,
|
||||
|
||||
pub fn jsonStringify(self: @This(), writer: anytype) !void {
|
||||
|
||||
@@ -58,6 +58,7 @@ pub const BunObject = struct {
|
||||
pub const SHA512 = toJSGetter(Crypto.SHA512.getter);
|
||||
pub const SHA512_256 = toJSGetter(Crypto.SHA512_256.getter);
|
||||
pub const TOML = toJSGetter(Bun.getTOMLObject);
|
||||
pub const YAML = toJSGetter(Bun.getYAMLObject);
|
||||
pub const Transpiler = toJSGetter(Bun.getTranspilerConstructor);
|
||||
pub const argv = toJSGetter(Bun.getArgv);
|
||||
pub const cwd = toJSGetter(Bun.getCWD);
|
||||
@@ -117,6 +118,7 @@ pub const BunObject = struct {
|
||||
@export(BunObject.SHA512_256, .{ .name = getterName("SHA512_256") });
|
||||
|
||||
@export(BunObject.TOML, .{ .name = getterName("TOML") });
|
||||
@export(BunObject.YAML, .{ .name = getterName("YAML") });
|
||||
@export(BunObject.Glob, .{ .name = getterName("Glob") });
|
||||
@export(BunObject.Transpiler, .{ .name = getterName("Transpiler") });
|
||||
@export(BunObject.argv, .{ .name = getterName("argv") });
|
||||
@@ -3394,6 +3396,10 @@ pub fn getTOMLObject(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSVa
|
||||
return TOMLObject.create(globalThis);
|
||||
}
|
||||
|
||||
pub fn getYAMLObject(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
||||
return YAMLObject.create(globalThis);
|
||||
}
|
||||
|
||||
pub fn getGlobConstructor(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
||||
return JSC.API.Glob.getConstructor(globalThis);
|
||||
}
|
||||
@@ -3500,61 +3506,73 @@ const UnsafeObject = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const TOMLObject = struct {
|
||||
const TOMLParser = @import("../../toml/toml_parser.zig").TOML;
|
||||
fn TOMLLikeParser(comptime Parser: anytype, comptime default_name: []const u8) type {
|
||||
return struct {
|
||||
const ThisParser = Parser;
|
||||
const name = default_name;
|
||||
|
||||
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const object = JSValue.createEmptyObject(globalThis, 1);
|
||||
object.put(
|
||||
globalThis,
|
||||
ZigString.static("parse"),
|
||||
JSC.createCallback(
|
||||
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const object = JSValue.createEmptyObject(globalThis, 1);
|
||||
object.put(
|
||||
globalThis,
|
||||
ZigString.static("parse"),
|
||||
1,
|
||||
parse,
|
||||
),
|
||||
);
|
||||
JSC.createCallback(
|
||||
globalThis,
|
||||
ZigString.static("parse"),
|
||||
1,
|
||||
parse,
|
||||
),
|
||||
);
|
||||
|
||||
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", .{});
|
||||
return object;
|
||||
}
|
||||
|
||||
var input_slice = arguments[0].toSlice(globalThis, bun.default_allocator);
|
||||
defer input_slice.deinit();
|
||||
var source = logger.Source.initPathString("input.toml", input_slice.slice());
|
||||
const parse_result = TOMLParser.parse(&source, &log, allocator, false) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to parse toml"));
|
||||
};
|
||||
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);
|
||||
defer log.deinit();
|
||||
const arguments = callframe.arguments_old(1).slice();
|
||||
if (arguments.len == 0 or arguments[0].isEmptyOrUndefinedOrNull()) {
|
||||
return globalThis.throwInvalidArguments("Expected a string to parse", .{});
|
||||
}
|
||||
|
||||
// for now...
|
||||
const buffer_writer = js_printer.BufferWriter.init(allocator) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
|
||||
};
|
||||
var writer = js_printer.BufferPrinter.init(buffer_writer);
|
||||
_ = js_printer.printJSON(*js_printer.BufferPrinter, &writer, parse_result, &source, .{}) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
|
||||
};
|
||||
var input_slice: JSC.Node.StringOrBuffer = JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, arguments[0]) orelse {
|
||||
if (!globalThis.hasException()) {
|
||||
return globalThis.throwInvalidArguments("Expected a string to parse", .{});
|
||||
}
|
||||
return error.JSError;
|
||||
};
|
||||
defer input_slice.deinit();
|
||||
var source = logger.Source.initPathString(name, input_slice.slice());
|
||||
const parse_result = ThisParser.parse(&source, &log, allocator, false) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to parse toml"));
|
||||
};
|
||||
|
||||
const slice = writer.ctx.buffer.slice();
|
||||
var out = bun.String.fromUTF8(slice);
|
||||
defer out.deref();
|
||||
// for now...
|
||||
const buffer_writer = js_printer.BufferWriter.init(allocator) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
|
||||
};
|
||||
var writer = js_printer.BufferPrinter.init(buffer_writer);
|
||||
_ = js_printer.printJSON(*js_printer.BufferPrinter, &writer, parse_result, &source, .{}) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
|
||||
};
|
||||
|
||||
return out.toJSByParseJSON(globalThis);
|
||||
}
|
||||
};
|
||||
const slice = writer.ctx.buffer.slice();
|
||||
var out = bun.String.fromUTF8(slice);
|
||||
defer out.deref();
|
||||
|
||||
return out.toJSByParseJSON(globalThis);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const TOMLObject = TOMLLikeParser(@import("../../toml/toml_parser.zig").TOML, "input.toml");
|
||||
pub const YAMLObject = TOMLLikeParser(@import("../../yaml/yaml_parser.zig").YAML, "input.yaml");
|
||||
|
||||
const Debugger = JSC.Debugger;
|
||||
|
||||
|
||||
@@ -17,20 +17,21 @@
|
||||
macro(SHA512_256) \
|
||||
macro(TOML) \
|
||||
macro(Transpiler) \
|
||||
macro(YAML) \
|
||||
macro(argv) \
|
||||
macro(assetPrefix) \
|
||||
macro(cwd) \
|
||||
macro(embeddedFiles) \
|
||||
macro(enableANSIColors) \
|
||||
macro(hash) \
|
||||
macro(inspect) \
|
||||
macro(main) \
|
||||
macro(origin) \
|
||||
macro(semver) \
|
||||
macro(stderr) \
|
||||
macro(stdin) \
|
||||
macro(stdout) \
|
||||
macro(unsafe) \
|
||||
macro(semver) \
|
||||
macro(embeddedFiles) \
|
||||
|
||||
// --- Callbacks ---
|
||||
#define FOR_EACH_CALLBACK(macro) \
|
||||
|
||||
@@ -628,6 +628,7 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
|
||||
SHA512 BunObject_getter_wrap_SHA512 DontDelete|PropertyCallback
|
||||
SHA512_256 BunObject_getter_wrap_SHA512_256 DontDelete|PropertyCallback
|
||||
TOML BunObject_getter_wrap_TOML DontDelete|PropertyCallback
|
||||
YAML BunObject_getter_wrap_YAML DontDelete|PropertyCallback
|
||||
Transpiler BunObject_getter_wrap_Transpiler DontDelete|PropertyCallback
|
||||
embeddedFiles BunObject_getter_wrap_embeddedFiles DontDelete|PropertyCallback
|
||||
allocUnsafe BunObject_callback_allocUnsafe DontDelete|Function 1
|
||||
|
||||
@@ -245,13 +245,15 @@ OnLoadResult handleOnLoadResultNotPromise(Zig::GlobalObject* globalObject, JSC::
|
||||
loader = BunLoaderTypeJSON;
|
||||
} else if (loaderString == "toml"_s) {
|
||||
loader = BunLoaderTypeTOML;
|
||||
} else if (loaderString == "yaml"_s) {
|
||||
loader = BunLoaderTypeYAML;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UNLIKELY(loader == BunLoaderTypeNone)) {
|
||||
throwException(globalObject, scope, createError(globalObject, "Expected loader to be one of \"js\", \"jsx\", \"object\", \"ts\", \"tsx\", \"toml\", or \"json\""_s));
|
||||
throwException(globalObject, scope, createError(globalObject, "Expected loader to be one of \"js\", \"jsx\", \"object\", \"ts\", \"tsx\", \"toml\", \"yaml\", or \"json\""_s));
|
||||
result.value.error = scope.exception();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -220,6 +220,7 @@ const BunLoaderType BunLoaderTypeJSON = 6;
|
||||
const BunLoaderType BunLoaderTypeTOML = 7;
|
||||
const BunLoaderType BunLoaderTypeWASM = 8;
|
||||
const BunLoaderType BunLoaderTypeNAPI = 9;
|
||||
const BunLoaderType BunLoaderTypeYAML = 10;
|
||||
|
||||
#pragma mark - Stream
|
||||
|
||||
|
||||
@@ -1519,7 +1519,7 @@ pub const ModuleLoader = struct {
|
||||
}
|
||||
|
||||
switch (loader) {
|
||||
.js, .jsx, .ts, .tsx, .json, .toml, .text => {
|
||||
.js, .jsx, .ts, .tsx, .json, .toml, .yaml, .text => {
|
||||
jsc_vm.transpiled_count += 1;
|
||||
jsc_vm.transpiler.resetStore();
|
||||
const hash = JSC.GenericWatcher.getHash(path.text);
|
||||
@@ -1796,7 +1796,7 @@ pub const ModuleLoader = struct {
|
||||
.already_bundled = true,
|
||||
.hash = 0,
|
||||
.bytecode_cache = if (bytecode_slice.len > 0) bytecode_slice.ptr else null,
|
||||
.bytecode_cache_size = if (bytecode_slice.len > 0) bytecode_slice.len else 0,
|
||||
.bytecode_cache_size = bytecode_slice.len,
|
||||
.is_commonjs_module = parse_result.already_bundled.isCommonJS(),
|
||||
};
|
||||
}
|
||||
@@ -2327,6 +2327,8 @@ pub const ModuleLoader = struct {
|
||||
loader = .ts;
|
||||
} else if (attribute.eqlComptime("tsx")) {
|
||||
loader = .tsx;
|
||||
} else if (attribute.eqlComptime("yaml")) {
|
||||
loader = .yaml;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ const URL = @import("../url.zig").URL;
|
||||
const Linker = linker.Linker;
|
||||
const Resolver = _resolver.Resolver;
|
||||
const TOML = @import("../toml/toml_parser.zig").TOML;
|
||||
const YAML = @import("../yaml/yaml_parser.zig").YAML;
|
||||
const EntryPoints = @import("./entry_points.zig");
|
||||
const Dependency = js_ast.Dependency;
|
||||
const JSAst = js_ast.BundledAst;
|
||||
@@ -3667,6 +3668,12 @@ pub const ParseTask = struct {
|
||||
const root = try TOML.parse(&source, log, allocator, false);
|
||||
return JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, &source, "")).?);
|
||||
},
|
||||
.yaml => {
|
||||
const trace = tracer(@src(), "ParseYAML");
|
||||
defer trace.end();
|
||||
const root = try YAML.parse(&source, log, allocator, false);
|
||||
return JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, &source, "")).?);
|
||||
},
|
||||
.text => {
|
||||
const root = Expr.init(E.String, E.String{
|
||||
.data = source.contents,
|
||||
|
||||
@@ -1917,6 +1917,10 @@ pub const E = struct {
|
||||
return if (asProperty(self, key)) |query| query.expr else @as(?Expr, null);
|
||||
}
|
||||
|
||||
pub fn getPtr(self: *const Object, key: string) ?*Expr {
|
||||
return if (asProperty(self, key)) |query| &self.properties.slice()[query.i].value.? else null;
|
||||
}
|
||||
|
||||
pub fn toJS(this: *Object, allocator: std.mem.Allocator, globalObject: *JSC.JSGlobalObject) ToJSError!JSC.JSValue {
|
||||
var obj = JSC.JSValue.createEmptyObject(globalObject, this.properties.len);
|
||||
obj.protect();
|
||||
@@ -1965,7 +1969,7 @@ pub const E = struct {
|
||||
|
||||
// this is terribly, shamefully slow
|
||||
pub fn setRope(self: *Object, rope: *const Rope, allocator: std.mem.Allocator, value: Expr) SetError!void {
|
||||
if (self.get(rope.head.data.e_string.data)) |existing| {
|
||||
if (self.getPtr(rope.head.data.e_string.data)) |existing| {
|
||||
switch (existing.data) {
|
||||
.e_array => |array| {
|
||||
if (rope.next == null) {
|
||||
@@ -1993,6 +1997,18 @@ pub const E = struct {
|
||||
|
||||
return error.Clobber;
|
||||
},
|
||||
.e_null => {
|
||||
// null can replace.
|
||||
if (rope.next) |next| {
|
||||
var obj = Expr.init(E.Object, E.Object{ .properties = .{} }, rope.head.loc);
|
||||
existing.* = obj;
|
||||
try obj.data.e_object.setRope(next, allocator, value);
|
||||
return;
|
||||
}
|
||||
|
||||
existing.* = value;
|
||||
return;
|
||||
},
|
||||
else => {
|
||||
return error.Clobber;
|
||||
},
|
||||
@@ -3398,6 +3414,10 @@ pub const Expr = struct {
|
||||
return if (asProperty(expr, name)) |query| query.expr else null;
|
||||
}
|
||||
|
||||
pub fn getPtr(expr: *const Expr, name: string) ?*Expr {
|
||||
return if (asProperty(expr, name)) |*query| &expr.data.e_object.properties.slice()[query.i] else null;
|
||||
}
|
||||
|
||||
/// Don't use this if you care about performance.
|
||||
///
|
||||
/// Sets the value of a property, creating it if it doesn't exist.
|
||||
|
||||
@@ -645,6 +645,7 @@ pub const Loader = enum(u8) {
|
||||
sqlite,
|
||||
sqlite_embedded,
|
||||
html,
|
||||
yaml,
|
||||
|
||||
pub fn disableHTML(this: Loader) Loader {
|
||||
return switch (this) {
|
||||
@@ -684,7 +685,8 @@ pub const Loader = enum(u8) {
|
||||
return switch (this) {
|
||||
.jsx, .js, .ts, .tsx => bun.http.MimeType.javascript,
|
||||
.css => bun.http.MimeType.css,
|
||||
.toml, .json => bun.http.MimeType.json,
|
||||
.yaml, .toml, .json => bun.http.MimeType.json,
|
||||
|
||||
.wasm => bun.http.MimeType.wasm,
|
||||
.html => bun.http.MimeType.html,
|
||||
else => bun.http.MimeType.other,
|
||||
@@ -723,6 +725,7 @@ pub const Loader = enum(u8) {
|
||||
map.set(.text, "input.txt");
|
||||
map.set(.bunsh, "input.sh");
|
||||
map.set(.html, "input.html");
|
||||
map.set(.yaml, "input.yaml");
|
||||
break :brk map;
|
||||
};
|
||||
|
||||
@@ -769,6 +772,7 @@ pub const Loader = enum(u8) {
|
||||
.{ "sqlite", .sqlite },
|
||||
.{ "sqlite_embedded", .sqlite_embedded },
|
||||
.{ "html", .html },
|
||||
.{ "yaml", .yaml },
|
||||
});
|
||||
|
||||
pub const api_names = bun.ComptimeStringMap(Api.Loader, .{
|
||||
@@ -793,6 +797,7 @@ pub const Loader = enum(u8) {
|
||||
.{ "sh", .file },
|
||||
.{ "sqlite", .sqlite },
|
||||
.{ "html", .html },
|
||||
.{ "yaml", .yaml },
|
||||
});
|
||||
|
||||
pub fn fromString(slice_: string) ?Loader {
|
||||
@@ -822,6 +827,7 @@ pub const Loader = enum(u8) {
|
||||
.file, .bunsh => .file,
|
||||
.json => .json,
|
||||
.toml => .toml,
|
||||
.yaml => .yaml,
|
||||
.wasm => .wasm,
|
||||
.napi => .napi,
|
||||
.base64 => .base64,
|
||||
@@ -842,6 +848,7 @@ pub const Loader = enum(u8) {
|
||||
.file => .file,
|
||||
.json => .json,
|
||||
.toml => .toml,
|
||||
.yaml => .yaml,
|
||||
.wasm => .wasm,
|
||||
.napi => .napi,
|
||||
.base64 => .base64,
|
||||
@@ -872,8 +879,8 @@ pub const Loader = enum(u8) {
|
||||
return switch (loader) {
|
||||
.jsx, .js, .ts, .tsx, .json => true,
|
||||
|
||||
// toml is included because we can serialize to the same AST as JSON
|
||||
.toml => true,
|
||||
// toml and yaml are included because we can serialize to the same AST as JSON
|
||||
.toml, .yaml => true,
|
||||
|
||||
else => false,
|
||||
};
|
||||
@@ -903,6 +910,8 @@ const default_loaders_posix = .{
|
||||
.{ ".cts", .ts },
|
||||
|
||||
.{ ".toml", .toml },
|
||||
.{ ".yaml", .yaml },
|
||||
.{ ".yml", .yaml },
|
||||
.{ ".wasm", .wasm },
|
||||
.{ ".node", .napi },
|
||||
.{ ".txt", .text },
|
||||
@@ -1294,7 +1303,8 @@ const default_loader_ext = [_]string{
|
||||
".ts", ".tsx",
|
||||
".mts", ".cts",
|
||||
|
||||
".toml", ".wasm",
|
||||
".toml", ".yaml",
|
||||
".yml", ".wasm",
|
||||
".txt", ".text",
|
||||
};
|
||||
|
||||
@@ -1320,6 +1330,8 @@ const node_modules_default_loader_ext = [_]string{
|
||||
".wasm",
|
||||
".text",
|
||||
".html",
|
||||
".yaml",
|
||||
".yml",
|
||||
};
|
||||
|
||||
pub const ResolveFileExtensions = struct {
|
||||
|
||||
@@ -46,36 +46,6 @@ const locModuleScope = logger.Loc.Empty;
|
||||
const LEXER_DEBUGGER_WORKAROUND = false;
|
||||
const IdentityContext = @import("../identity_context.zig").IdentityContext;
|
||||
|
||||
const HashMapPool = struct {
|
||||
const HashMap = std.HashMap(u64, void, IdentityContext, 80);
|
||||
const LinkedList = std.SinglyLinkedList(HashMap);
|
||||
threadlocal var list: LinkedList = undefined;
|
||||
threadlocal var loaded: bool = false;
|
||||
|
||||
pub fn get(_: std.mem.Allocator) *LinkedList.Node {
|
||||
if (loaded) {
|
||||
if (list.popFirst()) |node| {
|
||||
node.data.clearRetainingCapacity();
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
const new_node = default_allocator.create(LinkedList.Node) catch unreachable;
|
||||
new_node.* = LinkedList.Node{ .data = HashMap.initContext(default_allocator, IdentityContext{}) };
|
||||
return new_node;
|
||||
}
|
||||
|
||||
pub fn release(node: *LinkedList.Node) void {
|
||||
if (loaded) {
|
||||
list.prepend(node);
|
||||
return;
|
||||
}
|
||||
|
||||
list = LinkedList{ .first = node };
|
||||
loaded = true;
|
||||
}
|
||||
};
|
||||
|
||||
pub const TOML = struct {
|
||||
lexer: Lexer,
|
||||
log: *logger.Log,
|
||||
|
||||
@@ -52,7 +52,7 @@ const TOML = @import("./toml/toml_parser.zig").TOML;
|
||||
const JSC = bun.JSC;
|
||||
const PackageManager = @import("./install/install.zig").PackageManager;
|
||||
const DataURL = @import("./resolver/data_url.zig").DataURL;
|
||||
|
||||
const YAML = @import("./yaml/yaml_parser.zig").YAML;
|
||||
pub fn MacroJSValueType_() type {
|
||||
if (comptime JSC.is_bindgen) {
|
||||
return struct {
|
||||
@@ -866,7 +866,7 @@ pub const Transpiler = struct {
|
||||
};
|
||||
|
||||
switch (loader) {
|
||||
.jsx, .tsx, .js, .ts, .json, .toml, .text => {
|
||||
.jsx, .tsx, .js, .ts, .json, .toml, .yaml, .text => {
|
||||
var result = transpiler.parse(
|
||||
ParseOptions{
|
||||
.allocator = transpiler.allocator,
|
||||
@@ -1452,8 +1452,7 @@ pub const Transpiler = struct {
|
||||
},
|
||||
};
|
||||
},
|
||||
// TODO: use lazy export AST
|
||||
inline .toml, .json => |kind| {
|
||||
inline .toml, .json, .yaml => |kind| {
|
||||
var expr = if (kind == .json)
|
||||
// We allow importing tsconfig.*.json or jsconfig.*.json with comments
|
||||
// These files implicitly become JSONC files, which aligns with the behavior of text editors.
|
||||
@@ -1463,6 +1462,8 @@ pub const Transpiler = struct {
|
||||
JSON.parse(&source, transpiler.log, allocator, false) catch return null
|
||||
else if (kind == .toml)
|
||||
TOML.parse(&source, transpiler.log, allocator, false) catch return null
|
||||
else if (kind == .yaml)
|
||||
YAML.parse(&source, transpiler.log, allocator, false) catch return null
|
||||
else
|
||||
@compileError("unreachable");
|
||||
|
||||
@@ -1717,7 +1718,7 @@ pub const Transpiler = struct {
|
||||
),
|
||||
};
|
||||
},
|
||||
.toml, .json => {
|
||||
.toml, .json, .yaml => {
|
||||
return ServeResult{
|
||||
.file = options.OutputFile.initPending(loader, resolved),
|
||||
.mime_type = MimeType.transpiled_json,
|
||||
|
||||
1622
src/yaml/yaml_lexer.zig
Normal file
1622
src/yaml/yaml_lexer.zig
Normal file
File diff suppressed because it is too large
Load Diff
555
src/yaml/yaml_parser.zig
Normal file
555
src/yaml/yaml_parser.zig
Normal file
@@ -0,0 +1,555 @@
|
||||
const std = @import("std");
|
||||
const logger = bun.logger;
|
||||
const js_lexer = bun.js_lexer;
|
||||
const js_ast = bun.JSAst;
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const Output = bun.Output;
|
||||
const Global = bun.Global;
|
||||
const Environment = bun.Environment;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const C = bun.C;
|
||||
const expect = std.testing.expect;
|
||||
|
||||
const BindingNodeIndex = js_ast.BindingNodeIndex;
|
||||
|
||||
const StmtNodeIndex = js_ast.StmtNodeIndex;
|
||||
const ExprNodeIndex = js_ast.ExprNodeIndex;
|
||||
const ExprNodeList = js_ast.ExprNodeList;
|
||||
const StmtNodeList = js_ast.StmtNodeList;
|
||||
const BindingNodeList = js_ast.BindingNodeList;
|
||||
const assert = bun.assert;
|
||||
|
||||
const LocRef = js_ast.LocRef;
|
||||
const S = js_ast.S;
|
||||
const B = js_ast.B;
|
||||
const G = js_ast.G;
|
||||
const T = js_lexer.T;
|
||||
const E = js_ast.E;
|
||||
const Stmt = js_ast.Stmt;
|
||||
pub const Expr = js_ast.Expr;
|
||||
const Binding = js_ast.Binding;
|
||||
const Symbol = js_ast.Symbol;
|
||||
const Level = js_ast.Op.Level;
|
||||
const Op = js_ast.Op;
|
||||
const Scope = js_ast.Scope;
|
||||
const locModuleScope = logger.Loc.Empty;
|
||||
|
||||
const Lexer = @import("./yaml_lexer.zig").Lexer;
|
||||
pub const YAML = struct {
|
||||
lexer: Lexer,
|
||||
log: *logger.Log,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, source_: logger.Source, log: *logger.Log, redact_logs: bool) !YAML {
|
||||
return YAML{
|
||||
.lexer = try Lexer.init(log, source_, allocator, redact_logs),
|
||||
.allocator = allocator,
|
||||
.log = log,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn source(p: *const YAML) *const logger.Source {
|
||||
return &p.lexer.source;
|
||||
}
|
||||
|
||||
pub fn e(_: *YAML, t: anytype, loc: logger.Loc) Expr {
|
||||
const Type = @TypeOf(t);
|
||||
if (@typeInfo(Type) == .Pointer) {
|
||||
return Expr.init(std.meta.Child(Type), t.*, loc);
|
||||
} else {
|
||||
return Expr.init(Type, t, loc);
|
||||
}
|
||||
}
|
||||
|
||||
const Rope = js_ast.E.Object.Rope;
|
||||
|
||||
pub fn parseKeySegment(p: *YAML) anyerror!?Expr {
|
||||
const loc = p.lexer.loc();
|
||||
|
||||
switch (p.lexer.token) {
|
||||
.t_string_literal => {
|
||||
const str = p.lexer.toString(loc);
|
||||
try p.lexer.next();
|
||||
return str;
|
||||
},
|
||||
.t_identifier => {
|
||||
const str = E.String{ .data = p.lexer.identifier };
|
||||
try p.lexer.next();
|
||||
return p.e(str, loc);
|
||||
},
|
||||
.t_false => {
|
||||
try p.lexer.next();
|
||||
return p.e(
|
||||
E.String{
|
||||
.data = "false",
|
||||
},
|
||||
loc,
|
||||
);
|
||||
},
|
||||
.t_true => {
|
||||
try p.lexer.next();
|
||||
return p.e(
|
||||
E.String{
|
||||
.data = "true",
|
||||
},
|
||||
loc,
|
||||
);
|
||||
},
|
||||
.t_numeric_literal => {
|
||||
const literal = p.lexer.raw();
|
||||
try p.lexer.next();
|
||||
return p.e(E.String{ .data = literal }, loc);
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parseKey(p: *YAML, allocator: std.mem.Allocator) anyerror!*Rope {
|
||||
var rope = try allocator.create(Rope);
|
||||
const head = rope;
|
||||
rope.* = .{
|
||||
.head = (try p.parseKeySegment()) orelse {
|
||||
try p.lexer.expectedString("key");
|
||||
return error.SyntaxError;
|
||||
},
|
||||
.next = null,
|
||||
};
|
||||
|
||||
while (p.lexer.token == .t_dot) {
|
||||
try p.lexer.next();
|
||||
rope = try rope.append((try p.parseKeySegment()) orelse break, allocator);
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
pub fn parse(source_: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator, redact_logs: bool) !Expr {
|
||||
switch (source_.contents.len) {
|
||||
// This is to be consistent with how disabled JS files are handled
|
||||
0 => {
|
||||
return Expr{ .loc = logger.Loc{ .start = 0 }, .data = Expr.init(E.Object, E.Object{}, logger.Loc.Empty).data };
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
var parser = try YAML.init(allocator, source_.*, log, redact_logs);
|
||||
return try parser.runParser();
|
||||
}
|
||||
|
||||
fn runParser(p: *YAML) anyerror!Expr {
|
||||
var root_expr: ?Expr = null;
|
||||
|
||||
var stack = std.heap.stackFallback(@sizeOf(Rope) * 6, p.allocator);
|
||||
const key_allocator = stack.get();
|
||||
|
||||
while (true) {
|
||||
switch (p.lexer.token) {
|
||||
.t_end_of_file => {
|
||||
return root_expr orelse p.e(E.Object{}, p.lexer.loc());
|
||||
},
|
||||
.t_document_start => {
|
||||
try p.lexer.next();
|
||||
continue;
|
||||
},
|
||||
.t_document_end => {
|
||||
try p.lexer.next();
|
||||
continue;
|
||||
},
|
||||
.t_dash => {
|
||||
// Start of sequence item
|
||||
if (root_expr == null) {
|
||||
// First dash, create the sequence
|
||||
root_expr = p.e(E.Array{
|
||||
.items = .{},
|
||||
.is_single_line = false,
|
||||
}, p.lexer.loc());
|
||||
}
|
||||
if (root_expr != null and root_expr.?.data != .e_array) {
|
||||
try p.lexer.addDefaultError("Top-level sequence must be an array or object");
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
const array = root_expr.?.data.e_array;
|
||||
const value = try p.parseValue();
|
||||
try array.push(p.allocator, value);
|
||||
continue;
|
||||
},
|
||||
.t_newline => {
|
||||
try p.lexer.next();
|
||||
continue;
|
||||
},
|
||||
.t_identifier, .t_string_literal => {
|
||||
const initial_indent = p.lexer.current_indent;
|
||||
// Create root object if needed
|
||||
if (root_expr == null) {
|
||||
root_expr = p.e(E.Object{}, p.lexer.loc());
|
||||
}
|
||||
if (root_expr.?.data != .e_object) {
|
||||
try p.lexer.addDefaultError("Top-level sequence must be an array or object");
|
||||
return error.SyntaxError;
|
||||
}
|
||||
const head = root_expr.?.data.e_object;
|
||||
|
||||
// Key-value pair
|
||||
const key = try key_allocator.create(Rope);
|
||||
key.* = .{
|
||||
.head = p.lexer.toPropertyKey(p.lexer.loc()),
|
||||
.next = null,
|
||||
};
|
||||
try p.lexer.next();
|
||||
try p.lexer.expect(.t_colon);
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
const value = value: {
|
||||
const new_indent = p.lexer.current_indent;
|
||||
if (new_indent > initial_indent) {
|
||||
const value = try p.parseObjectOrArraySequence(p.lexer.loc(), new_indent);
|
||||
break :value value;
|
||||
} else if (p.lexer.token == .t_dash) {
|
||||
try p.lexer.addDefaultError("An array cannot be nested inside an object");
|
||||
return error.SyntaxError;
|
||||
} else if (p.lexer.token == .t_end_of_file) {
|
||||
break :value p.e(E.Null{}, p.lexer.loc());
|
||||
}
|
||||
|
||||
break :value try p.parseValue();
|
||||
};
|
||||
|
||||
head.setRope(key, p.allocator, value) catch |err| {
|
||||
switch (err) {
|
||||
error.Clobber => {
|
||||
try p.lexer.addDefaultError("Cannot redefine key");
|
||||
return error.SyntaxError;
|
||||
},
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
|
||||
// Handle any trailing newlines after the value
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
},
|
||||
else => {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return root_expr orelse p.e(E.Object{}, p.lexer.loc());
|
||||
}
|
||||
|
||||
fn parseObjectOrArraySequence(p: *YAML, loc: logger.Loc, indent: u16) anyerror!Expr {
|
||||
// Check what follows to determine if it's an array or object
|
||||
if (p.lexer.token == .t_dash) {
|
||||
// The start of an array sequence
|
||||
const array = p.e(E.Array{
|
||||
.items = .{},
|
||||
}, loc);
|
||||
|
||||
while (p.lexer.token == .t_dash) {
|
||||
try p.lexer.next();
|
||||
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
if (p.lexer.token == .t_end_of_file or p.lexer.current_indent < indent) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p.lexer.current_indent > indent) {
|
||||
try array.data.e_array.push(p.allocator, try p.runParser());
|
||||
} else {
|
||||
try array.data.e_array.push(p.allocator, try p.parseValue());
|
||||
}
|
||||
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
} else {
|
||||
var root: ?Expr = null;
|
||||
|
||||
// Parse key-value pairs at this indentation level
|
||||
while (true) {
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
if (p.lexer.token == .t_end_of_file or p.lexer.current_indent < indent) {
|
||||
break;
|
||||
}
|
||||
|
||||
const key = try p.parseKey(p.allocator);
|
||||
|
||||
try p.lexer.expect(.t_colon);
|
||||
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
// A single object with { [key]: null }
|
||||
if (p.lexer.token == .t_end_of_file or p.lexer.current_indent < indent) {
|
||||
if (root == null) {
|
||||
root = p.e(E.Object{}, loc);
|
||||
}
|
||||
|
||||
root.?.data.e_object.setRope(key, p.allocator, p.e(E.Null{}, loc)) catch {
|
||||
try p.lexer.addDefaultError("Cannot redefine key");
|
||||
return error.SyntaxError;
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle potential indent after the colon
|
||||
const value = if (p.lexer.current_indent > indent)
|
||||
try p.runParser()
|
||||
else
|
||||
try p.parseValue();
|
||||
|
||||
if (root == null) {
|
||||
root = p.e(E.Object{}, loc);
|
||||
}
|
||||
|
||||
root.?.data.e_object.setRope(key, p.allocator, value) catch |err| {
|
||||
switch (err) {
|
||||
error.Clobber => {
|
||||
try p.lexer.addDefaultError("Cannot redefine key");
|
||||
return error.SyntaxError;
|
||||
},
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return root orelse p.e(E.Null{}, loc);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parseValue(p: *YAML) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
|
||||
// Handle tags - type annotations like !!str, !!int, etc.
|
||||
// Example: !!int "123" -> converts to number 123
|
||||
if (p.lexer.token == .t_tag) {
|
||||
try p.lexer.next();
|
||||
p.lexer.current_tag = p.lexer.raw();
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
// Handle anchors - define reusable nodes
|
||||
// Example: &anchor_name value
|
||||
if (p.lexer.token == .t_anchor) {
|
||||
try p.lexer.next();
|
||||
p.lexer.current_anchor = p.lexer.raw();
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
var value = switch (p.lexer.token) {
|
||||
// Handle aliases - reference previously anchored nodes
|
||||
// Example: *anchor_name
|
||||
.t_alias => brk: {
|
||||
try p.lexer.next();
|
||||
const alias_name = p.lexer.raw();
|
||||
try p.lexer.next();
|
||||
break :brk p.lexer.anchors.get(alias_name) orelse {
|
||||
try p.lexer.addDefaultError("Undefined alias");
|
||||
return error.SyntaxError;
|
||||
};
|
||||
},
|
||||
|
||||
// Handle scalar values
|
||||
.t_false => brk: {
|
||||
try p.lexer.next();
|
||||
break :brk p.e(E.Boolean{ .value = false }, loc);
|
||||
},
|
||||
.t_true => brk: {
|
||||
try p.lexer.next();
|
||||
break :brk p.e(E.Boolean{ .value = true }, loc);
|
||||
},
|
||||
.t_null => brk: {
|
||||
try p.lexer.next();
|
||||
break :brk p.e(E.Null{}, loc);
|
||||
},
|
||||
// Handle quoted strings: "quoted" or 'quoted'
|
||||
.t_string_literal => brk: {
|
||||
const str_loc = p.lexer.loc();
|
||||
const str = p.lexer.toString(str_loc);
|
||||
try p.lexer.next();
|
||||
|
||||
break :brk str;
|
||||
},
|
||||
// Handle unquoted scalars: plain_text
|
||||
.t_identifier => brk: {
|
||||
const str = E.String{ .data = p.lexer.identifier };
|
||||
try p.lexer.next();
|
||||
break :brk p.e(str, loc);
|
||||
},
|
||||
// Handle numbers: 123, 3.14, -17
|
||||
.t_numeric_literal => brk: {
|
||||
const value = p.lexer.number;
|
||||
try p.lexer.next();
|
||||
break :brk p.e(E.Number{ .value = value }, loc);
|
||||
},
|
||||
|
||||
.t_dash => {
|
||||
p.lexer.addError(loc.toUsize(), "Unexpected array element. Try either adding an indentation, or wrapping in quotes", .{});
|
||||
return error.SyntaxError;
|
||||
},
|
||||
|
||||
// Handle flow sequences (bracket-based)
|
||||
// Example: [item1, item2, item3]
|
||||
.t_open_bracket => brk: {
|
||||
try p.lexer.next();
|
||||
var items = std.ArrayList(Expr).init(p.allocator);
|
||||
errdefer items.deinit();
|
||||
|
||||
while (p.lexer.token != .t_close_bracket) {
|
||||
if (items.items.len > 0) {
|
||||
if (p.lexer.token != .t_comma) break;
|
||||
try p.lexer.next();
|
||||
// Handle newlines after commas
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
}
|
||||
try items.append(try p.parseValue());
|
||||
}
|
||||
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_close_bracket);
|
||||
break :brk p.e(E.Array{
|
||||
.items = ExprNodeList.fromList(items),
|
||||
.is_single_line = true,
|
||||
}, loc);
|
||||
},
|
||||
|
||||
// Handle flow mappings (brace-based)
|
||||
// Example: {key1: value1, key2: value2}
|
||||
.t_open_brace => brk: {
|
||||
try p.lexer.next();
|
||||
|
||||
// Handle newlines before the first key
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
const expr = p.e(E.Object{}, loc);
|
||||
const obj = expr.data.e_object;
|
||||
while (p.lexer.token != .t_close_brace) {
|
||||
if (obj.properties.len > 0) {
|
||||
if (p.lexer.token != .t_comma) break;
|
||||
try p.lexer.next();
|
||||
// Handle newlines after commas
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
const key = try p.parseKey(p.allocator);
|
||||
const key_loc = p.lexer.loc();
|
||||
try p.lexer.expect(.t_colon);
|
||||
const value = try p.parseValue();
|
||||
|
||||
obj.setRope(key, p.allocator, value) catch |err| {
|
||||
switch (err) {
|
||||
error.Clobber => {
|
||||
// TODO: add key name.
|
||||
p.lexer.addError(key_loc.toUsize(), "Cannot redefine key", .{});
|
||||
return error.SyntaxError;
|
||||
},
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
|
||||
// Handle newlines after values
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_close_brace);
|
||||
break :brk expr;
|
||||
},
|
||||
else => {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
},
|
||||
};
|
||||
|
||||
// Process anchors - store the value for later reference
|
||||
// Example: &anchor value -> stores value under name "anchor"
|
||||
if (p.lexer.current_anchor) |anchor_name| {
|
||||
p.lexer.current_anchor = null;
|
||||
try p.lexer.anchors.put(anchor_name, value);
|
||||
}
|
||||
|
||||
// Process tags - convert values based on type tags
|
||||
// Examples:
|
||||
// !!str "123" -> string "123"
|
||||
// !!int "123" -> number 123
|
||||
// !!bool "true" -> boolean true
|
||||
// !!null "" -> null
|
||||
if (p.lexer.current_tag) |tag| {
|
||||
if (strings.eqlComptime(tag, "!!str")) {
|
||||
// Already a string, no conversion needed
|
||||
} else if (strings.eqlComptime(tag, "!!int")) {
|
||||
if (value.data == .e_string) {
|
||||
const int_val = std.fmt.parseInt(i64, value.data.e_string.data, 10) catch {
|
||||
try p.lexer.addDefaultError("Invalid integer value");
|
||||
return error.SyntaxError;
|
||||
};
|
||||
value = p.e(E.Number{ .value = @as(f64, @floatFromInt(int_val)) }, loc);
|
||||
}
|
||||
} else if (strings.eqlComptime(tag, "!!float")) {
|
||||
if (value.data == .e_string) {
|
||||
const float_val = std.fmt.parseFloat(f64, value.data.e_string.data) catch {
|
||||
try p.lexer.addDefaultError("Invalid float value");
|
||||
return error.SyntaxError;
|
||||
};
|
||||
value = p.e(E.Number{ .value = float_val }, loc);
|
||||
}
|
||||
} else if (strings.eqlComptime(tag, "!!bool")) {
|
||||
if (value.data == .e_string) {
|
||||
const bool_val = if (strings.eqlComptime(value.data.e_string.data, "true"))
|
||||
true
|
||||
else if (strings.eqlComptime(value.data.e_string.data, "false"))
|
||||
false
|
||||
else {
|
||||
try p.lexer.addDefaultError("Invalid boolean value");
|
||||
return error.SyntaxError;
|
||||
};
|
||||
value = p.e(E.Boolean{ .value = bool_val }, loc);
|
||||
}
|
||||
} else if (strings.eqlComptime(tag, "!!null")) {
|
||||
value = p.e(E.Null{}, loc);
|
||||
}
|
||||
|
||||
p.lexer.current_tag = null;
|
||||
}
|
||||
|
||||
// Handle any trailing newlines after the value
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user