mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 21:01:52 +00:00
a bit further
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -37,8 +37,6 @@ pub const T = enum {
|
||||
t_null, // null
|
||||
|
||||
// YAML specific
|
||||
t_indent, // Increased indentation level
|
||||
t_dedent, // Decreased indentation level
|
||||
t_newline, // Line break
|
||||
t_pipe, // | - Literal block scalar
|
||||
t_gt, // > - Folded block scalar
|
||||
@@ -72,7 +70,7 @@ pub const ComplexKey = struct {
|
||||
|
||||
pub const BlockScalarHeader = struct {
|
||||
chomping: enum { clip, strip, keep } = .clip,
|
||||
indent: ?u8 = null,
|
||||
indent: ?u16 = null,
|
||||
style: enum { literal, folded },
|
||||
};
|
||||
|
||||
@@ -99,11 +97,8 @@ pub const Lexer = struct {
|
||||
should_redact_logs: bool,
|
||||
|
||||
// Indentation tracking
|
||||
indent_stack: std.ArrayList(usize),
|
||||
current_indent: usize = 0,
|
||||
indent_width: ?usize = null, // Will be set on first indent
|
||||
current_indent: u16 = 0,
|
||||
at_line_start: bool = true,
|
||||
pending_dedents: usize = 0,
|
||||
|
||||
// Anchor/Alias resolution
|
||||
anchors: AnchorMap,
|
||||
@@ -141,6 +136,20 @@ pub const Lexer = struct {
|
||||
block_scalar_header: ?BlockScalarHeader = null,
|
||||
block_scalar_indent: ?usize = null,
|
||||
|
||||
is_ascii: ?bool = null,
|
||||
|
||||
pub fn remaining(self: *const Lexer) []const u8 {
|
||||
return self.source.contents[@min(self.current, self.source.contents.len)..];
|
||||
}
|
||||
|
||||
pub fn isASCII(self: *Lexer) bool {
|
||||
if (self.is_ascii == null) {
|
||||
self.is_ascii = strings.isAllASCII(self.remaining());
|
||||
}
|
||||
|
||||
return self.is_ascii.?;
|
||||
}
|
||||
|
||||
pub const FlowCommaState = packed struct(u32) {
|
||||
level: u15,
|
||||
has_comma: bool = false,
|
||||
@@ -293,31 +302,26 @@ pub const Lexer = struct {
|
||||
pub fn next(lexer: *Lexer) !void {
|
||||
lexer.has_newline_before = lexer.end == 0;
|
||||
|
||||
// Handle pending dedents
|
||||
if (lexer.pending_dedents > 0) {
|
||||
lexer.pending_dedents -= 1;
|
||||
lexer.token = T.t_dedent;
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
lexer.start = lexer.end;
|
||||
lexer.token = T.t_end_of_file;
|
||||
|
||||
switch (lexer.code_point) {
|
||||
-1 => {
|
||||
// Generate dedents for any remaining indentation levels
|
||||
if (lexer.indent_stack.items.len > 1) {
|
||||
lexer.pending_dedents = lexer.indent_stack.items.len - 1;
|
||||
lexer.indent_stack.shrinkRetainingCapacity(1);
|
||||
lexer.token = T.t_dedent;
|
||||
return;
|
||||
}
|
||||
lexer.token = T.t_end_of_file;
|
||||
return;
|
||||
},
|
||||
|
||||
'\r', '\n', 0x2028, 0x2029 => {
|
||||
lexer.step();
|
||||
'\r', '\n', 0x2028, 0x2029 => |cp| {
|
||||
if (cp == '\r') {
|
||||
try lexer.step();
|
||||
if (lexer.code_point != '\n') {
|
||||
try lexer.addDefaultError("Unexpected \r carriage return. Carriage returns should be followed by a newline character.");
|
||||
return error.SyntaxError;
|
||||
}
|
||||
}
|
||||
|
||||
try lexer.step();
|
||||
lexer.has_newline_before = true;
|
||||
lexer.at_line_start = true;
|
||||
lexer.current_indent = 0;
|
||||
@@ -335,7 +339,7 @@ pub const Lexer = struct {
|
||||
|
||||
'\t' => {
|
||||
if (lexer.at_line_start) {
|
||||
lexer.current_indent += 8;
|
||||
lexer.current_indent += 1;
|
||||
}
|
||||
lexer.step();
|
||||
continue;
|
||||
@@ -453,28 +457,6 @@ pub const Lexer = struct {
|
||||
|
||||
// Handle indentation after processing the token
|
||||
if (lexer.at_line_start) {
|
||||
const last_indent = lexer.indent_stack.items[lexer.indent_stack.items.len - 1];
|
||||
if (lexer.current_indent > last_indent) {
|
||||
// This is an indent
|
||||
try lexer.indent_stack.append(lexer.current_indent);
|
||||
lexer.token = T.t_indent;
|
||||
} else if (lexer.current_indent < last_indent) {
|
||||
// This is one or more dedents
|
||||
var dedent_count: usize = 0;
|
||||
while (lexer.indent_stack.items.len > 0 and lexer.current_indent < lexer.indent_stack.items[lexer.indent_stack.items.len - 1]) {
|
||||
_ = lexer.indent_stack.pop();
|
||||
dedent_count += 1;
|
||||
}
|
||||
|
||||
if (lexer.current_indent != lexer.indent_stack.items[lexer.indent_stack.items.len - 1]) {
|
||||
try lexer.addDefaultError("Invalid indentation");
|
||||
}
|
||||
|
||||
if (dedent_count > 1) {
|
||||
lexer.pending_dedents = dedent_count - 1;
|
||||
}
|
||||
lexer.token = T.t_dedent;
|
||||
}
|
||||
lexer.at_line_start = false;
|
||||
}
|
||||
|
||||
@@ -555,7 +537,7 @@ pub const Lexer = struct {
|
||||
.prev_error_loc = logger.Loc.Empty,
|
||||
.allocator = allocator,
|
||||
.should_redact_logs = redact_logs,
|
||||
.indent_stack = std.ArrayList(usize).init(allocator),
|
||||
|
||||
.anchors = AnchorMap.init(allocator),
|
||||
.tag_library = TagMap.init(allocator),
|
||||
.tag_handles = std.ArrayList(TagHandle).init(allocator),
|
||||
@@ -566,20 +548,21 @@ pub const Lexer = struct {
|
||||
.merge_key_stack = std.ArrayList(js_ast.Expr).init(allocator),
|
||||
};
|
||||
|
||||
// Initialize with base indent level
|
||||
try lex.indent_stack.append(0);
|
||||
|
||||
// Add default tag handles
|
||||
try lex.tag_handles.append(.{ .handle = "!", .prefix = "!" });
|
||||
try lex.tag_handles.append(.{ .handle = "!!", .prefix = "tag:yaml.org,2002:" });
|
||||
|
||||
lex.step();
|
||||
try lex.next();
|
||||
|
||||
return lex;
|
||||
}
|
||||
|
||||
pub inline fn toString(lexer: *Lexer, loc_: logger.Loc) js_ast.Expr {
|
||||
pub fn toPropertyKey(lexer: *const Lexer, loc_: logger.Loc) js_ast.Expr {
|
||||
if (lexer.token == .t_identifier) {
|
||||
return js_ast.Expr.init(js_ast.E.String, js_ast.E.String{ .data = lexer.identifier }, loc_);
|
||||
}
|
||||
bun.debugAssert(lexer.token == .t_string_literal);
|
||||
return js_ast.Expr.init(js_ast.E.String, js_ast.E.String{ .data = lexer.string_literal_slice }, loc_);
|
||||
}
|
||||
|
||||
pub fn toString(lexer: *const Lexer, loc_: logger.Loc) js_ast.Expr {
|
||||
if (lexer.string_literal_is_ascii) {
|
||||
return js_ast.Expr.init(js_ast.E.String, js_ast.E.String{ .data = lexer.string_literal_slice }, loc_);
|
||||
}
|
||||
@@ -712,12 +695,9 @@ pub const Lexer = struct {
|
||||
line_start = true;
|
||||
current_indent = 0;
|
||||
},
|
||||
' ' => {
|
||||
'\t', ' ' => {
|
||||
if (line_start) current_indent += 1;
|
||||
},
|
||||
'\t' => {
|
||||
if (line_start) current_indent += 8;
|
||||
},
|
||||
else => {
|
||||
if (line_start and c != '\n' and c != '\r') {
|
||||
if (min_indent == null or current_indent < min_indent.?) {
|
||||
@@ -1248,7 +1228,6 @@ pub const Lexer = struct {
|
||||
|
||||
fn parsePlainScalar(lexer: *Lexer) !void {
|
||||
var result = std.ArrayList(u8).init(lexer.allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
var first = true;
|
||||
var spaces: usize = 0;
|
||||
|
||||
@@ -141,8 +141,6 @@ pub const YAML = struct {
|
||||
}
|
||||
|
||||
fn runParser(p: *YAML) anyerror!Expr {
|
||||
var current_sequence: ?*E.Array = null;
|
||||
var is_top_level_sequence = true;
|
||||
var root_expr: ?Expr = null;
|
||||
|
||||
var stack = std.heap.stackFallback(@sizeOf(Rope) * 6, p.allocator);
|
||||
@@ -163,75 +161,66 @@ pub const YAML = struct {
|
||||
},
|
||||
.t_dash => {
|
||||
// Start of sequence item
|
||||
try p.lexer.next();
|
||||
|
||||
// Create a new sequence if we're not already in one
|
||||
if (current_sequence == null) {
|
||||
const array_expr = p.e(E.Array{}, p.lexer.loc());
|
||||
current_sequence = array_expr.data.e_array;
|
||||
|
||||
if (is_top_level_sequence) {
|
||||
// This is a top-level sequence, make it the root
|
||||
root_expr = array_expr;
|
||||
is_top_level_sequence = false;
|
||||
} else {
|
||||
// If we're in a mapping context, we need a key for this sequence
|
||||
if (root_expr == null) {
|
||||
root_expr = p.e(E.Object{}, p.lexer.loc());
|
||||
}
|
||||
const head = root_expr.?.data.e_object;
|
||||
|
||||
const key = try p.parseKey(key_allocator);
|
||||
try p.lexer.expect(.t_colon);
|
||||
head.setRope(key, p.allocator, array_expr) catch |err| {
|
||||
switch (err) {
|
||||
error.Clobber => {
|
||||
try p.lexer.addDefaultError("Cannot redefine key");
|
||||
return error.SyntaxError;
|
||||
},
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// Parse the sequence item
|
||||
const item = try p.parseValue();
|
||||
try current_sequence.?.push(p.allocator, item);
|
||||
|
||||
// Handle indentation and newlines
|
||||
while (p.lexer.token == .t_newline) {
|
||||
try p.lexer.next();
|
||||
if (p.lexer.token != .t_dash) {
|
||||
current_sequence = null; // End of sequence
|
||||
break;
|
||||
}
|
||||
try p.lexer.next();
|
||||
}
|
||||
},
|
||||
.t_indent => {
|
||||
try p.lexer.next();
|
||||
const array = root_expr.?.data.e_array;
|
||||
const value = try p.parseValue();
|
||||
try array.push(p.allocator, value);
|
||||
continue;
|
||||
},
|
||||
.t_dedent => {
|
||||
.t_newline => {
|
||||
try p.lexer.next();
|
||||
current_sequence = null; // End of sequence on dedent
|
||||
continue;
|
||||
},
|
||||
.t_identifier, .t_string_literal => {
|
||||
// As soon as we see a key-value pair, we're no longer in a top-level sequence context
|
||||
is_top_level_sequence = false;
|
||||
|
||||
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 p.parseKey(key_allocator);
|
||||
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);
|
||||
const value = try p.parseValue();
|
||||
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 => {
|
||||
@@ -241,6 +230,11 @@ pub const YAML = struct {
|
||||
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();
|
||||
@@ -248,6 +242,98 @@ pub const YAML = struct {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -297,9 +383,11 @@ pub const YAML = struct {
|
||||
},
|
||||
// Handle quoted strings: "quoted" or 'quoted'
|
||||
.t_string_literal => brk: {
|
||||
const result = p.lexer.toString(loc);
|
||||
const str_loc = p.lexer.loc();
|
||||
const str = p.lexer.toString(str_loc);
|
||||
try p.lexer.next();
|
||||
break :brk result;
|
||||
|
||||
break :brk str;
|
||||
},
|
||||
// Handle unquoted scalars: plain_text
|
||||
.t_identifier => brk: {
|
||||
@@ -314,26 +402,9 @@ pub const YAML = struct {
|
||||
break :brk p.e(E.Number{ .value = value }, loc);
|
||||
},
|
||||
|
||||
// Handle block sequences (indentation-based)
|
||||
// Example:
|
||||
// - item1
|
||||
// - item2
|
||||
.t_dash => brk: {
|
||||
try p.lexer.next();
|
||||
var items = std.ArrayList(Expr).init(p.allocator);
|
||||
errdefer items.deinit();
|
||||
while (true) {
|
||||
if (p.lexer.token != .t_dash) break;
|
||||
try p.lexer.next();
|
||||
try items.append(try p.parseValue());
|
||||
if (p.lexer.token != .t_newline) break;
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
break :brk p.e(E.Array{
|
||||
.items = ExprNodeList.fromList(items),
|
||||
.is_single_line = false,
|
||||
}, 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)
|
||||
@@ -347,10 +418,18 @@ pub const YAML = struct {
|
||||
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),
|
||||
@@ -363,12 +442,21 @@ pub const YAML = struct {
|
||||
.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);
|
||||
@@ -386,6 +474,15 @@ pub const YAML = struct {
|
||||
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);
|
||||
@@ -448,6 +545,11 @@ pub const YAML = struct {
|
||||
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