mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-05-31 21:35:57 +03:00
c5517102e7
these will work better as standalone tests that exercise the std.Build API.
611 lines
22 KiB
Zig
611 lines
22 KiB
Zig
const ConfigHeader = @This();
|
|
|
|
const std = @import("std");
|
|
const Io = std.Io;
|
|
const Configuration = std.Build.Configuration;
|
|
const Writer = std.Io.Writer;
|
|
const Path = std.Build.Cache.Path;
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const Step = @import("../Step.zig");
|
|
const Maker = @import("../../Maker.zig");
|
|
|
|
const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
|
|
const c_generated_line = "/* " ++ header_text ++ " */\n";
|
|
const asm_generated_line = "; " ++ header_text ++ "\n";
|
|
|
|
/// Table value is whether the value is used.
|
|
const ValueMap = std.array_hash_map.String(bool);
|
|
const Value = Configuration.Step.ConfigHeader.Value;
|
|
|
|
pub fn make(
|
|
config_header: *ConfigHeader,
|
|
step_index: Configuration.Step.Index,
|
|
maker: *Maker,
|
|
progress_node: std.Progress.Node,
|
|
) Step.ExtendedMakeError!void {
|
|
_ = config_header;
|
|
_ = progress_node;
|
|
const graph = maker.graph;
|
|
const step = maker.stepByIndex(step_index);
|
|
const io = graph.io;
|
|
const arena = graph.arena; // TODO don't leak into the process arena
|
|
const conf = &maker.scanned_config.configuration;
|
|
const conf_step = step_index.ptr(conf);
|
|
const conf_ch = conf_step.extended.get(conf.extra).config_header;
|
|
const cache_root = graph.local_cache_root;
|
|
|
|
const input_size_limit: Io.Limit = if (conf_ch.input_size_limit.value) |x| .limited64(x) else .unlimited;
|
|
const include_guard_override: ?[]const u8 = if (conf_ch.include_guard.value) |s| s.slice(conf) else null;
|
|
const include_path: []const u8 = conf_ch.include_path.slice(conf);
|
|
const template_file = if (conf_ch.template_file.value) |lp|
|
|
try maker.resolveLazyPathIndex(arena, lp, step_index)
|
|
else
|
|
null;
|
|
const value_pairs = conf_ch.values.slice;
|
|
|
|
if (conf_ch.template_file.value) |lp| try step.singleUnchangingWatchInput(maker, arena, lp.get(conf));
|
|
|
|
var value_map: ValueMap = .empty;
|
|
try value_map.ensureTotalCapacity(arena, value_pairs.len);
|
|
for (value_pairs) |pair| value_map.putAssumeCapacityNoClobber(pair.key.slice(conf), false);
|
|
|
|
var man = graph.cache.obtain();
|
|
defer man.deinit();
|
|
|
|
// Random bytes to make ConfigHeader unique. Refresh this with new
|
|
// random bytes when ConfigHeader implementation is modified in a
|
|
// non-backwards-compatible way.
|
|
man.hash.add(@as(u32, 0xdef08d23));
|
|
man.hash.add(@as(u32, @bitCast(conf_ch.flags)));
|
|
man.hash.addBytes(include_path);
|
|
man.hash.addOptionalBytes(include_guard_override);
|
|
|
|
var aw: Writer.Allocating = .init(arena);
|
|
defer aw.deinit();
|
|
|
|
switch (conf_ch.flags.style) {
|
|
.autoconf_undef => {
|
|
const tf = template_file.?;
|
|
const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
|
|
return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
|
|
renderAutoConfUndef(maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
|
|
error.WriteFailed => return error.OutOfMemory,
|
|
else => |e| return e,
|
|
};
|
|
},
|
|
.autoconf_at => {
|
|
const tf = template_file.?;
|
|
const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
|
|
return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
|
|
renderAutoconfAt(maker, step, contents, &aw, value_pairs, &value_map, tf) catch |err| switch (err) {
|
|
error.WriteFailed => return error.OutOfMemory,
|
|
else => |e| return e,
|
|
};
|
|
},
|
|
.cmake => {
|
|
const tf = template_file.?;
|
|
const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
|
|
return step.fail(maker, "unable to read cmake input file {f}: {t}", .{ tf, err });
|
|
renderCmake(arena, maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
|
|
error.WriteFailed => return error.OutOfMemory,
|
|
else => |e| return e,
|
|
};
|
|
},
|
|
.blank => {
|
|
renderBlank(conf, &aw.writer, value_pairs, &value_map, include_path, include_guard_override) catch |err| switch (err) {
|
|
error.WriteFailed => return error.OutOfMemory,
|
|
else => |e| return e,
|
|
};
|
|
},
|
|
.nasm => {
|
|
renderNasm(conf, &aw.writer, value_pairs, &value_map) catch |err| switch (err) {
|
|
error.WriteFailed => return error.OutOfMemory,
|
|
else => |e| return e,
|
|
};
|
|
},
|
|
}
|
|
|
|
const output = aw.written();
|
|
man.hash.addBytes(output);
|
|
|
|
if (try step.cacheHit(maker, &man)) {
|
|
const digest = man.final();
|
|
maker.generatedPath(conf_ch.generated_dir).* = .{
|
|
.root_dir = cache_root,
|
|
.sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
|
|
};
|
|
return;
|
|
}
|
|
|
|
const digest = man.final();
|
|
|
|
// If output_path has directory parts, deal with them. Example:
|
|
// output_dir is zig-cache/o/HASH
|
|
// output_path is libavutil/avconfig.h
|
|
// We want to open directory zig-cache/o/HASH/libavutil/
|
|
// but keep output_dir as zig-cache/o/HASH for -I include
|
|
const out_path: Path = .{
|
|
.root_dir = cache_root,
|
|
.sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, conf_ch.include_path.slice(conf) }),
|
|
};
|
|
const out_path_dirname = out_path.dirname().?;
|
|
|
|
out_path_dirname.root_dir.handle.createDirPath(io, out_path_dirname.sub_path) catch |err|
|
|
return step.fail(maker, "unable to make path {f}: {t}", .{ out_path_dirname, err });
|
|
|
|
out_path.root_dir.handle.writeFile(io, .{ .sub_path = out_path.sub_path, .data = output }) catch |err|
|
|
return step.fail(maker, "unable to write file {f}: {t}", .{ out_path, err });
|
|
|
|
maker.generatedPath(conf_ch.generated_dir).* = .{
|
|
.root_dir = cache_root,
|
|
.sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
|
|
};
|
|
|
|
try step.writeManifest(maker, &man);
|
|
}
|
|
|
|
fn ensureAllValuesUsed(
|
|
maker: *Maker,
|
|
step: *Step,
|
|
value_map: *const ValueMap,
|
|
src_path: Path,
|
|
) Step.ExtendedMakeError!void {
|
|
var any_errors = false;
|
|
for (value_map.keys(), value_map.values()) |name, used| {
|
|
if (used) continue;
|
|
try step.addError(maker, "{f}: config header value unused: {s}", .{ src_path, name });
|
|
any_errors = true;
|
|
}
|
|
if (any_errors) return error.MakeFailed;
|
|
}
|
|
|
|
fn renderAutoConfUndef(
|
|
maker: *Maker,
|
|
step: *Step,
|
|
contents: []const u8,
|
|
w: *Writer,
|
|
value_pairs: []const Value.Pair,
|
|
value_map: *ValueMap,
|
|
src_path: Path,
|
|
) !void {
|
|
const conf = &maker.scanned_config.configuration;
|
|
|
|
try w.writeAll(c_generated_line);
|
|
|
|
var any_errors = false;
|
|
var line_index: u32 = 0;
|
|
var line_it = std.mem.splitScalar(u8, contents, '\n');
|
|
while (line_it.next()) |line| : (line_index += 1) {
|
|
if (!std.mem.startsWith(u8, line, "#")) {
|
|
try w.writeAll(line);
|
|
try w.writeByte('\n');
|
|
continue;
|
|
}
|
|
var it = std.mem.tokenizeAny(u8, line[1..], " \t\r");
|
|
const undef = it.next().?;
|
|
if (!std.mem.eql(u8, undef, "undef")) {
|
|
try w.writeAll(line);
|
|
try w.writeByte('\n');
|
|
continue;
|
|
}
|
|
const name = it.next().?;
|
|
const index = value_map.getIndex(name) orelse {
|
|
try step.addError(maker, "{f}:{d}: unspecified config header value: {s}", .{
|
|
src_path, line_index + 1, name,
|
|
});
|
|
any_errors = true;
|
|
continue;
|
|
};
|
|
value_map.values()[index] = true; // Set to used.
|
|
try renderValueC(conf, w, name, value_pairs[index].index);
|
|
}
|
|
|
|
try ensureAllValuesUsed(maker, step, value_map, src_path);
|
|
if (any_errors) return error.MakeFailed;
|
|
}
|
|
|
|
fn renderAutoconfAt(
|
|
maker: *Maker,
|
|
step: *Step,
|
|
contents: []const u8,
|
|
aw: *Writer.Allocating,
|
|
value_pairs: []const Value.Pair,
|
|
value_map: *const ValueMap,
|
|
src_path: Path,
|
|
) !void {
|
|
const w = &aw.writer;
|
|
const conf = &maker.scanned_config.configuration;
|
|
|
|
try w.writeAll(c_generated_line);
|
|
|
|
var any_errors = false;
|
|
var line_index: u32 = 0;
|
|
var line_it = std.mem.splitScalar(u8, contents, '\n');
|
|
while (line_it.next()) |line| : (line_index += 1) {
|
|
const last_line = line_it.index == line_it.buffer.len;
|
|
|
|
const old_len = aw.written().len;
|
|
expandVariablesAutoconfAt(w, line, conf, value_pairs, value_map) catch |err| switch (err) {
|
|
error.MissingValue => {
|
|
const name = aw.written()[old_len..];
|
|
defer aw.shrinkRetainingCapacity(old_len);
|
|
try step.addError(maker, "{f}:{d}: error: unspecified config header value: {s}", .{
|
|
src_path, line_index + 1, name,
|
|
});
|
|
any_errors = true;
|
|
continue;
|
|
},
|
|
else => {
|
|
try step.addError(maker, "{f}:{d}: unable to substitute variable: error: {t}", .{
|
|
src_path, line_index + 1, err,
|
|
});
|
|
any_errors = true;
|
|
continue;
|
|
},
|
|
};
|
|
if (!last_line) try w.writeByte('\n');
|
|
}
|
|
|
|
try ensureAllValuesUsed(maker, step, value_map, src_path);
|
|
if (any_errors) return error.MakeFailed;
|
|
}
|
|
|
|
fn renderCmake(
|
|
arena: Allocator,
|
|
maker: *Maker,
|
|
step: *Step,
|
|
contents: []const u8,
|
|
w: *Writer,
|
|
value_pairs: []const Value.Pair,
|
|
value_map: *ValueMap,
|
|
src_path: Path,
|
|
) !void {
|
|
const conf = &maker.scanned_config.configuration;
|
|
|
|
try w.writeAll(c_generated_line);
|
|
|
|
var any_errors = false;
|
|
var line_index: u32 = 0;
|
|
var line_it = std.mem.splitScalar(u8, contents, '\n');
|
|
while (line_it.next()) |raw_line| : (line_index += 1) {
|
|
const last_line = line_it.index == line_it.buffer.len;
|
|
|
|
const line = expandVariablesCmake(arena, raw_line, conf, value_pairs, value_map) catch |err| switch (err) {
|
|
error.InvalidCharacter => {
|
|
try step.addError(maker, "{f}:{d}: invalid character in a variable name", .{
|
|
src_path, line_index + 1,
|
|
});
|
|
any_errors = true;
|
|
continue;
|
|
},
|
|
else => {
|
|
try step.addError(maker, "{f}:{d}: failed substituting variable: {t}", .{
|
|
src_path, line_index + 1, err,
|
|
});
|
|
any_errors = true;
|
|
continue;
|
|
},
|
|
};
|
|
|
|
const line_start = std.mem.findNone(u8, line, " \t\r") orelse {
|
|
try w.writeAll(line);
|
|
if (!last_line) try w.writeByte('\n');
|
|
continue;
|
|
};
|
|
const whitespace_prefix = line[0..line_start];
|
|
const trimmed_line = line[line_start..];
|
|
|
|
if (!std.mem.startsWith(u8, trimmed_line, "#")) {
|
|
try w.writeAll(line);
|
|
if (!last_line) try w.writeByte('\n');
|
|
continue;
|
|
}
|
|
|
|
var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r");
|
|
const cmakedefine = it.next().?;
|
|
|
|
const booldefine = if (std.mem.eql(u8, cmakedefine, "cmakedefine01"))
|
|
true
|
|
else if (std.mem.eql(u8, cmakedefine, "cmakedefine"))
|
|
false
|
|
else {
|
|
try w.writeAll(line);
|
|
if (!last_line) try w.writeByte('\n');
|
|
continue;
|
|
};
|
|
|
|
const name = it.next() orelse {
|
|
try step.addError(maker, "{f}:{d}: error: missing define name", .{ src_path, line_index + 1 });
|
|
any_errors = true;
|
|
continue;
|
|
};
|
|
const orig_value: Value.Index = v: {
|
|
const index = value_map.getIndex(name) orelse break :v if (booldefine) .int_0 else .undef;
|
|
value_map.values()[index] = true; // Mark as used.
|
|
break :v value_pairs[index].index;
|
|
};
|
|
const value = switch (orig_value.unpack(conf)) {
|
|
.bool => |b| if (!b) .undef else orig_value,
|
|
inline .i64, .u64 => |i| if (i == 0) .undef else orig_value,
|
|
.string => |s| if (s.len == 0) .undef else orig_value,
|
|
else => orig_value,
|
|
};
|
|
|
|
try w.writeAll(whitespace_prefix);
|
|
|
|
if (booldefine) {
|
|
try renderValueCBool(w, name, switch (value.unpack(conf)) {
|
|
.undef, .defined => false,
|
|
.bool => |b| b,
|
|
inline .u64, .i64 => |i| i != 0,
|
|
.string => |s| s.len != 0,
|
|
.ident => false,
|
|
});
|
|
} else if (value != .undef) {
|
|
try renderValueCIdent(w, name, it.rest());
|
|
} else {
|
|
try renderValueC(conf, w, name, value);
|
|
}
|
|
}
|
|
|
|
try ensureAllValuesUsed(maker, step, value_map, src_path);
|
|
if (any_errors) return error.MakeFailed;
|
|
}
|
|
|
|
fn renderBlank(
|
|
conf: *const Configuration,
|
|
w: *Writer,
|
|
value_pairs: []const Value.Pair,
|
|
value_map: *const ValueMap,
|
|
include_path: []const u8,
|
|
include_guard_override: ?[]const u8,
|
|
) !void {
|
|
try w.writeAll(c_generated_line);
|
|
|
|
const include_guard_fmt: IncludeGuardFmt = .{
|
|
.include_path = include_path,
|
|
.override = include_guard_override,
|
|
};
|
|
|
|
try w.print(
|
|
\\#ifndef {[0]f}
|
|
\\#define {[0]f}
|
|
\\
|
|
, .{include_guard_fmt});
|
|
|
|
for (value_map.keys(), value_pairs) |name, pair| try renderValueC(conf, w, name, pair.index);
|
|
|
|
try w.print(
|
|
\\#endif /* {f} */
|
|
\\
|
|
, .{include_guard_fmt});
|
|
}
|
|
|
|
const IncludeGuardFmt = struct {
|
|
include_path: []const u8,
|
|
override: ?[]const u8,
|
|
|
|
pub fn format(this: @This(), w: *Writer) Writer.Error!void {
|
|
if (this.override) |s| return w.writeAll(s);
|
|
for (this.include_path) |byte| switch (byte) {
|
|
'a'...'z' => try w.writeByte(byte - 'a' + 'A'),
|
|
'A'...'Z', '0'...'9' => continue,
|
|
else => try w.writeByte('_'),
|
|
};
|
|
}
|
|
};
|
|
|
|
fn renderNasm(
|
|
conf: *const Configuration,
|
|
w: *Writer,
|
|
value_pairs: []const Value.Pair,
|
|
value_map: *const ValueMap,
|
|
) !void {
|
|
try w.writeAll(asm_generated_line);
|
|
for (value_map.keys(), value_pairs) |name, pair| try renderValueNasm(conf, w, name, pair.index);
|
|
}
|
|
|
|
fn renderValueC(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void {
|
|
switch (value.unpack(conf)) {
|
|
.undef => try w.print("/* #undef {s} */\n", .{name}),
|
|
.defined => try w.print("#define {s}\n", .{name}),
|
|
.bool => |b| return renderValueCBool(w, name, b),
|
|
inline .u64, .i64 => |i| try w.print("#define {s} {d}\n", .{ name, i }),
|
|
.ident => |ident| return renderValueCIdent(w, name, ident),
|
|
.string => |string| try w.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
|
|
}
|
|
}
|
|
|
|
fn renderValueCIdent(w: *Writer, name: []const u8, ident: []const u8) Writer.Error!void {
|
|
return w.print("#define {s} {s}\n", .{ name, ident });
|
|
}
|
|
|
|
fn renderValueCBool(w: *Writer, name: []const u8, b: bool) Writer.Error!void {
|
|
return w.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) });
|
|
}
|
|
|
|
fn renderValueNasm(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void {
|
|
switch (value.unpack(conf)) {
|
|
.undef => try w.print("; %undef {s}\n", .{name}),
|
|
.defined => try w.print("%define {s}\n", .{name}),
|
|
.bool => |b| try w.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
|
|
inline .u64, .i64 => |i| try w.print("%define {s} {d}\n", .{ name, i }),
|
|
.ident => |ident| try w.print("%define {s} {s}\n", .{ name, ident }),
|
|
.string => |string| try w.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
|
|
}
|
|
}
|
|
|
|
fn expandVariablesAutoconfAt(
|
|
w: *Writer,
|
|
contents: []const u8,
|
|
conf: *const Configuration,
|
|
value_pairs: []const Value.Pair,
|
|
value_map: *const ValueMap,
|
|
) !void {
|
|
const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
|
|
|
|
var curr: usize = 0;
|
|
var source_offset: usize = 0;
|
|
while (curr < contents.len) : (curr += 1) {
|
|
if (contents[curr] != '@') continue;
|
|
if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
|
|
if (close_pos == curr + 1) {
|
|
// closed immediately, preserve as a literal
|
|
continue;
|
|
}
|
|
const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
|
|
if (valid_varname_end != close_pos) {
|
|
// contains invalid characters, preserve as a literal
|
|
continue;
|
|
}
|
|
|
|
const key = contents[curr + 1 .. close_pos];
|
|
const index = value_map.getIndex(key) orelse {
|
|
// Report the missing key to the caller.
|
|
try w.writeAll(key);
|
|
return error.MissingValue;
|
|
};
|
|
const value = value_pairs[index].index;
|
|
value_map.values()[index] = true; // Mark as used.
|
|
try w.writeAll(contents[source_offset..curr]);
|
|
switch (value.unpack(conf)) {
|
|
.undef, .defined => {},
|
|
.bool => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)),
|
|
inline .u64, .i64 => |i| try w.print("{d}", .{i}),
|
|
.ident, .string => |s| try w.writeAll(s),
|
|
}
|
|
|
|
curr = close_pos;
|
|
source_offset = close_pos + 1;
|
|
}
|
|
}
|
|
|
|
try w.writeAll(contents[source_offset..]);
|
|
}
|
|
|
|
fn expandVariablesCmake(
|
|
arena: Allocator,
|
|
contents: []const u8,
|
|
conf: *const Configuration,
|
|
value_pairs: []const Value.Pair,
|
|
value_map: *const ValueMap,
|
|
) ![]const u8 {
|
|
var result: std.ArrayList(u8) = .empty;
|
|
|
|
const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";
|
|
const open_var = "${";
|
|
|
|
var curr: usize = 0;
|
|
var source_offset: usize = 0;
|
|
const Position = struct {
|
|
source: usize,
|
|
target: usize,
|
|
};
|
|
var var_stack: std.ArrayList(Position) = .empty;
|
|
loop: while (curr < contents.len) : (curr += 1) {
|
|
switch (contents[curr]) {
|
|
'@' => blk: {
|
|
if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
|
|
if (close_pos == curr + 1) {
|
|
// closed immediately, preserve as a literal
|
|
break :blk;
|
|
}
|
|
const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
|
|
if (valid_varname_end != close_pos) {
|
|
// contains invalid characters, preserve as a literal
|
|
break :blk;
|
|
}
|
|
|
|
const key = contents[curr + 1 .. close_pos];
|
|
const index = value_map.getIndex(key) orelse return error.MissingValue;
|
|
value_map.values()[index] = true; // Mark as used.
|
|
const value = value_pairs[index].index;
|
|
const missing = contents[source_offset..curr];
|
|
try result.appendSlice(arena, missing);
|
|
switch (value.unpack(conf)) {
|
|
.undef, .defined => {},
|
|
.bool => |b| try result.append(arena, if (b) '1' else '0'),
|
|
inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
|
|
.ident, .string => |s| try result.appendSlice(arena, s),
|
|
}
|
|
|
|
curr = close_pos;
|
|
source_offset = close_pos + 1;
|
|
|
|
continue :loop;
|
|
}
|
|
},
|
|
'$' => blk: {
|
|
const next = curr + 1;
|
|
if (next == contents.len or contents[next] != '{') {
|
|
// no open bracket detected, preserve as a literal
|
|
break :blk;
|
|
}
|
|
const missing = contents[source_offset..curr];
|
|
try result.appendSlice(arena, missing);
|
|
try result.appendSlice(arena, open_var);
|
|
|
|
source_offset = curr + open_var.len;
|
|
curr = next;
|
|
try var_stack.append(arena, .{
|
|
.source = curr,
|
|
.target = result.items.len - open_var.len,
|
|
});
|
|
|
|
continue :loop;
|
|
},
|
|
'}' => blk: {
|
|
if (var_stack.items.len == 0) {
|
|
// no open bracket, preserve as a literal
|
|
break :blk;
|
|
}
|
|
const open_pos = var_stack.pop().?;
|
|
if (source_offset == open_pos.source) {
|
|
source_offset += open_var.len;
|
|
}
|
|
const missing = contents[source_offset..curr];
|
|
try result.appendSlice(arena, missing);
|
|
|
|
const key_start = open_pos.target + open_var.len;
|
|
const key = result.items[key_start..];
|
|
if (key.len == 0) {
|
|
return error.MissingKey;
|
|
}
|
|
const index = value_map.getIndex(key) orelse return error.MissingValue;
|
|
value_map.values()[index] = true; // Mark as used.
|
|
const value = value_pairs[index].index;
|
|
result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len);
|
|
switch (value.unpack(conf)) {
|
|
.undef, .defined => {},
|
|
.bool => |b| try result.append(arena, if (b) '1' else '0'),
|
|
inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
|
|
.ident, .string => |s| try result.appendSlice(arena, s),
|
|
}
|
|
|
|
source_offset = curr + 1;
|
|
|
|
continue :loop;
|
|
},
|
|
'\\' => {
|
|
// backslash is not considered a special character
|
|
continue :loop;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
if (var_stack.items.len > 0 and std.mem.findScalar(u8, valid_varname_chars, contents[curr]) == null) {
|
|
return error.InvalidCharacter;
|
|
}
|
|
}
|
|
|
|
if (source_offset != contents.len) {
|
|
const missing = contents[source_offset..];
|
|
try result.appendSlice(arena, missing);
|
|
}
|
|
|
|
try result.shrinkToLen(arena);
|
|
|
|
return result.toOwnedSliceAssert();
|
|
}
|